llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Kamil Jakubus (jkbz64)

<details>
<summary>Changes</summary>

## Problem

Consider a sparse lookup-table row:

```c
static const unsigned short table[8] = {
    [2] = 7,
    [7] = 42,
};
```

Clang's semantic initializer list contains eight entries. The six omitted 
elements refer to a shared `ImplicitValueInitExpr` representing zero 
initialization. Several AST and Sema traversals revisit these entries 
redundantly, adding work for each omitted element across large tables.

## Motivation

See #<!-- -->227592 but this improves compile speed only.

## C Changes

- `Expr::isConstantInitializer` accepts `ImplicitValueInitExpr` directly 
instead of calling the full checker for each occurrence.
- Conversion and integer-overflow diagnostic walks, and the 
unsequenced-operation visitor, skip implicit value-initialization nodes before 
queuing or visiting them.
- `Expr::HasSideEffects` checks the array filler once, then skips entries 
pointing to that same expression. Explicit initializers still receive their 
normal checks.

These loops still inspect every initializer slot. The savings come from fewer 
expression checks, worklist entries and visitor calls; the semantic initializer 
representation stays the same.

## C++ Changes

The shared constant-initializer, side-effect, conversion and overflow changes 
also apply to C++. Nontrivial fillers still receive their normal side-effect 
check. The sequencing shortcut applies to C++98/03; C++11 and later retain 
`SequenceExpressionsInOrder`.

## Additional changes

Refactored a few for-loops with `llvm::{all_of,any_of,copy_if}` where it 
improved readability, let me know if that's not desirable, there was no 
performance difference and it looks better (?).

Added a few test cases that might not be perfect but were uncovered, doesn't 
hurt I guess?

## Measured impact

Unpatched Clang `082fe79e68b81352290afcd8661874bed7e61074` versus this patch. 
Both are Release builds with assertions disabled, running on AArch64 (Apple M1 
Pro), and compile identical numeric `parser.c` inputs generated by upstream 
Tree-sitter 0.27.0.

Times are medians of five alternating runs per compiler.

The measurements below are independent of the allocation changes in #<!-- 
-->227592.

| Parser | Compile before | Compile after | Time reduction |
|---|---:|---:|---:|
| [ABL](https://github.com/usagi-coffee/tree-sitter-abl) | 8.374 s | 7.787 s | 
7.0% |
| 
[Swift](https://github.com/alex-pinkus/tree-sitter-swift/tree/35245fbfee2fccf16273c6f4299438fb76875970)
 | 1.126 s | 1.094 s | 2.8% |
| 
[F#](https://github.com/ionide/tree-sitter-fsharp/tree/aefd0c8741bdf3aeb827a228aa4a996a6536697e)
 | 2.520 s | 2.422 s | 3.9% |
| 
[Scala](https://github.com/tree-sitter/tree-sitter-scala/tree/db390f312a54b04b13790e1767bfac32665c17ac)
 | 1.321 s | 1.282 s | 2.9% |
| 
[Kotlin](https://github.com/tree-sitter-grammars/tree-sitter-kotlin/tree/3dea6dfa9c0129deb7c4315afbda806c85c41667)
 | 1.056 s | 1.027 s | 2.8% |
| 
[Julia](https://github.com/tree-sitter/tree-sitter-julia/tree/e0f9dcd180fdcfcfa8d79a3531e11d99e79321d3)
 | 0.954 s | 0.927 s | 2.9% |
| 
[C#](https://github.com/tree-sitter/tree-sitter-c-sharp/tree/8c0abe0b84a3681d3e6852ce2e2a1eecb4f731c5)
 | 1.343 s | 1.289 s | 4.0% |
| 
[C++](https://github.com/tree-sitter/tree-sitter-cpp/tree/c009222808634c1014f82438d4883753516a2c24)
 | 1.202 s | 1.163 s | 3.2% |
| 
[Vim](https://github.com/tree-sitter-grammars/tree-sitter-vim/tree/039c8d0aa1deae00ddeb0374dd70bcc0ec56938d)
 | 0.758 s | 0.741 s | 2.2% |

```sh
clang --gcc-triple=aarch64-redhat-linux -std=c11 -O0 \
    -fPIC -ffunction-sections -fdata-sections \
    -I src -c src/parser.c -o parser.o
```

---
Full diff: https://github.com/llvm/llvm-project/pull/227671.diff


3 Files Affected:

- (modified) clang/lib/AST/Expr.cpp (+18-12) 
- (modified) clang/lib/Sema/SemaChecking.cpp (+15-6) 
- (modified) clang/test/Sema/designated-initializers.c (+20) 


``````````diff
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 728be799c50a3..21a7794c7ec35 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -34,6 +34,7 @@
 #include "clang/Lex/Lexer.h"
 #include "clang/Lex/LiteralSupport.h"
 #include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/raw_ostream.h"
@@ -3460,12 +3461,11 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool 
IsForRef,
       return ILE->getInit(0)->isConstantInitializer(Ctx, false, Culprit);
 
     if (ILE->getType()->isArrayType()) {
-      unsigned numInits = ILE->getNumInits();
-      for (unsigned i = 0; i < numInits; i++) {
-        if (!ILE->getInit(i)->isConstantInitializer(Ctx, false, Culprit))
-          return false;
-      }
-      return true;
+      // Implicit zero initializers are constant without further evaluation.
+      return llvm::all_of(ILE->inits(), [&](const Expr *Init) {
+        return isa<ImplicitValueInitExpr>(Init) ||
+               Init->isConstantInitializer(Ctx, false, Culprit);
+      });
     }
 
     if (ILE->getType()->isRecordType()) {
@@ -3885,12 +3885,18 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
       return true;
     break;
 
-  case InitListExprClass:
-    // FIXME: The children for an InitListExpr doesn't include the array 
filler.
-    if (const Expr *E = cast<InitListExpr>(this)->getArrayFiller())
-      if (E->HasSideEffects(Ctx, IncludePossibleEffects))
-        return true;
-    break;
+  case InitListExprClass: {
+    const auto *ILE = cast<InitListExpr>(this);
+    // The filler also occupies every hole in a sparse initializer. Checking
+    // its side effects once suffices, even when it occurs many times.
+    const Expr *Filler = ILE->getArrayFiller();
+    if (Filler && Filler->HasSideEffects(Ctx, IncludePossibleEffects))
+      return true;
+    return llvm::any_of(ILE->inits(), [&](const Expr *Init) {
+      return Init && Init != Filler &&
+             Init->HasSideEffects(Ctx, IncludePossibleEffects);
+    });
+  }
 
   case GenericSelectionExprClass:
     return cast<GenericSelectionExpr>(this)->getResultExpr()->HasSideEffects(
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index c0cfc51f5b68f..825c6a8877a77 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -113,6 +113,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <functional>
+#include <iterator>
 #include <limits>
 #include <optional>
 #include <string>
@@ -14295,7 +14296,9 @@ static void AnalyzeImplicitConversions(
   bool IsLogicalAndOperator = BO && BO->getOpcode() == BO_LAnd;
   for (Stmt *SubStmt : E->children()) {
     Expr *ChildExpr = dyn_cast_or_null<Expr>(SubStmt);
-    if (!ChildExpr)
+    // Value initialization has no implicit conversions to diagnose. Sparse
+    // array initializers can contain many references to the same zero filler.
+    if (!ChildExpr || isa<ImplicitValueInitExpr>(ChildExpr))
       continue;
 
     if (auto *CSE = dyn_cast<CoroutineSuspendExpr>(E))
@@ -14668,9 +14671,11 @@ void Sema::CheckForIntOverflow (const Expr *E) {
       continue;
     }
 
-    if (const auto *InitList = dyn_cast<InitListExpr>(OriginalE))
-      Exprs.append(InitList->inits().begin(), InitList->inits().end());
-    else if (isa<ObjCBoxedExpr>(OriginalE))
+    if (const auto *InitList = dyn_cast<InitListExpr>(OriginalE)) {
+      llvm::copy_if(
+          InitList->inits(), std::back_inserter(Exprs),
+          [](const Expr *Init) { return !isa<ImplicitValueInitExpr>(Init); });
+    } else if (isa<ObjCBoxedExpr>(OriginalE))
       E->EvaluateForOverflow(Context);
     else if (const auto *Call = dyn_cast<CallExpr>(E))
       Exprs.append(Call->arg_begin(), Call->arg_end());
@@ -15528,8 +15533,12 @@ class SequenceChecker : public 
ConstEvaluatedExprVisitor<SequenceChecker> {
   }
 
   void VisitInitListExpr(const InitListExpr *ILE) {
-    if (!SemaRef.getLangOpts().CPlusPlus11)
-      return VisitExpr(ILE);
+    if (!SemaRef.getLangOpts().CPlusPlus11) {
+      for (const Expr *Init : ILE->inits())
+        if (Init && !isa<ImplicitValueInitExpr>(Init))
+          Visit(Init);
+      return;
+    }
 
     // In C++11, list initializations are sequenced.
     SequenceExpressionsInOrder(ILE->inits());
diff --git a/clang/test/Sema/designated-initializers.c 
b/clang/test/Sema/designated-initializers.c
index 11dc3a2308dee..7b0c3387de1b8 100644
--- a/clang/test/Sema/designated-initializers.c
+++ b/clang/test/Sema/designated-initializers.c
@@ -375,3 +375,23 @@ void gh154046(void) {
     [1] = ""  // expected-error {{incompatible pointer to integer conversion 
initializing 'const char' with an expression of type 'char[1]'}}
   }[1];
 }
+
+// Implicit zero elements must not hide diagnostics for explicit initializers.
+void sparse_diagnostics(int i) {
+  unsigned char conversion[128] = {
+    [127] = 256 // expected-warning {{implicit conversion from 'int' to 
'unsigned char' changes value from 256 to 0}}
+  };
+  int overflow[128] = {
+    [127] = 2147483647 + 1 // expected-warning {{overflow in expression; 
result is -2'147'483'648 with type 'int'}}
+  };
+  int unsequenced[128] = {
+    [127] = i++ + i // expected-warning {{unsequenced modification and access 
to 'i'}}
+  };
+  static int nonconstant[128] = {
+    [127] = i // expected-error {{initializer element is not a compile-time 
constant}}
+  };
+  struct { int row[128]; } overridden = {
+    .row = {[127] = i++}, // expected-note {{previous initialization with side 
effects is here (side effects will not occur at run time)}}
+    .row = {[1] = 1} // expected-warning {{initializer overrides prior 
initialization of this subobject}}
+  };
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/227671
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to