llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Kamil Jakubus (jkbz64)

<details>
<summary>Changes</summary>

## Problem

Consider a row in a generated lookup table:

```c
static const unsigned short table[10000] = {
    [100] = 1,
    [5000] = 2,
};
```

Although only two entries are explicitly initialized, the semantic initializer 
list needs slots for the intervening elements. Repeating this pattern across 
many rows amplifies two allocation costs:

- Verification can construct a temporary structured initializer list before the 
performing pass constructs the final list.
- Incremental growth of a structured initializer list allocates replacement 
buffers. The AST arena retains the old buffers, increasing peak memory.

The goal is to avoid this redundant allocation while preserving initialization 
checks, diagnostics, and the final semantic representation.

## Motivation

This change is helpful in the [tree-sitter 
ecosystem](https://github.com/tree-sitter/tree-sitter), where generated C 
parsers contain large sparse lookup tables. Reducing Clang’s temporary 
allocations lowers the peak memory needed to build these parsers without 
changing their generated source. See [#<!-- 
-->5974](https://github.com/tree-sitter/tree-sitter/issues/5974).

The same optimization may benefit other projects that generate C code 
programmatically, particularly those emitting large sparse arrays with 
designated initializers. The applicability depends on the generated initializer 
structure; benefits outside the measured workloads have not been established.

## C Changes

For fixed-size C integer arrays, verification skips constructing the temporary 
structured list. Omitted integer elements can be zero-initialized, and 
overlapping designators do not affect initialization viability. The performing 
pass still constructs the semantic list and diagnoses overrides.

For fixed-size scalar arrays whose initializer entries each contain a single 
array designator, a preliminary scan determines the required extent and calls 
the existing `reserveInits` helper once. The scan uses `llvm::all_of` and falls 
back to the existing allocation behavior for unsupported forms, dependent 
indices, or invalid bounds.

The example above reserves 5,001 slots, leaving the trailing zeros implicit. 
The patch does not allocate storage for the entire declared bound.

## C++ Changes

C++ verification retains the existing behavior. The reservation optimization 
can also apply to qualifying scalar arrays where Clang accepts array 
designators as a C++ extension. 

## Measured impact

These measurements compare agains unpatched Clang 
(`b7458ff8580ffc6844b3e4f5035e7f3b2a212816`). Both compile the same `parser.c` 
files generated by upstream Tree-sitter 0.27.0.

| Parser | Compile before | Compile after | Time reduction | Peak memory before 
| Peak memory after | Memory reduction |
|---|---:|---:|---:|---:|---:|---:|
| [ABL](https://github.com/usagi-coffee/tree-sitter-abl) | 8.24 s | 8.02 s | 
2.7% | 2,705.88 MiB | 1,287.22 MiB | 52.4% |
| [Swift](https://github.com/alex-pinkus/tree-sitter-swift) | 1.12 s | 1.10 s | 
1.8% | 300.98 MiB | 253.27 MiB | 15.9% |
| [F#](https://github.com/ionide/tree-sitter-fsharp) | 2.49 s | 2.43 s | 2.4% | 
829.72 MiB | 573.72 MiB | 30.9% |
| [Scala](https://github.com/tree-sitter/tree-sitter-scala) | 1.29 s | 1.27 s | 
1.6% | 383.72 MiB | 320.61 MiB | 16.4% |
| [Kotlin](https://github.com/tree-sitter-grammars/tree-sitter-kotlin) | 1.04 s 
| 1.02 s | 1.9% | 308.56 MiB | 265.20 MiB | 14.1% |
| [Julia](https://github.com/tree-sitter/tree-sitter-julia) | 0.94 s | 0.94 s | 
0.0% | 294.41 MiB | 252.64 MiB | 14.2% |
| [C#](https://github.com/tree-sitter/tree-sitter-c-sharp) | 1.33 s | 1.30 s | 
2.3% | 457.17 MiB | 329.64 MiB | 27.9% |
| [C++](https://github.com/tree-sitter/tree-sitter-cpp) | 1.18 s | 1.17 s | 
0.8% | 356.69 MiB | 293.69 MiB | 17.7% |
| [Vim](https://github.com/tree-sitter-grammars/tree-sitter-vim) | 0.72 s | 
0.72 s | 0.0% | 207.27 MiB | 207.25 MiB | 0.0% |

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

The main result is reduced compiler memory. Avoiding repeated allocations and 
copying may also reduce compile time, but the measured timing differences are 
too small to establish a consistent speedup.

Assisted-by: OpenAI Codex

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


2 Files Affected:

- (modified) clang/lib/Sema/SemaInit.cpp (+45-1) 
- (modified) clang/test/Sema/designated-initializers.c (+11) 


``````````diff
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 3f30e94aa8976..b9828b61c28b2 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -33,11 +33,13 @@
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/PointerIntPair.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include <limits>
 
 using namespace clang;
 
@@ -1087,7 +1089,16 @@ InitListChecker::InitListChecker(
       TreatUnavailableAsInvalid(TreatUnavailableAsInvalid),
       InOverloadResolution(InOverloadResolution),
       AggrDeductionCandidateParamTypes(AggrDeductionCandidateParamTypes) {
-  if (!VerifyOnly || hasAnyDesignatedInits(IL)) {
+  // In C, omitted integer array elements can always be zero-initialized and
+  // overlapping designators do not affect initialization viability. There is
+  // no need to build a dense semantic list merely to verify such an array.
+  // The performing pass still builds the list and diagnoses overrides. Keep
+  // the existing C++ path, where overrides can affect overload resolution.
+  bool NeedsStructuredList = true;
+  if (VerifyOnly && !SemaRef.getLangOpts().CPlusPlus)
+    if (const auto *CAT = SemaRef.Context.getAsConstantArrayType(T))
+      NeedsStructuredList = !CAT->getElementType()->isIntegerType();
+  if (!VerifyOnly || (NeedsStructuredList && hasAnyDesignatedInits(IL))) {
     FullyStructuredList = createInitListExpr(
         T, IL->getSourceRange(), IL->getNumInits(), IL->isExplicit());
 
@@ -2156,6 +2167,39 @@ void InitListChecker::CheckArrayType(const 
InitializedEntity &Entity,
     IList->setInit(0, Embed->getDataStringLiteral());
   }
 
+  // A sparse list of array designators can touch most of an array even when
+  // it contains few explicit initializers. Growing its semantic initializer
+  // incrementally retains every old buffer in the AST arena. Reserve the
+  // required extent once, without allocating space for trailing zeroes.
+  if (StructuredList && Index == 0 && StructuredIndex == 0 &&
+      IList->getNumInits() > 1 && arrayType->getElementType()->isScalarType()) 
{
+    if (const auto *CAT = dyn_cast<ConstantArrayType>(arrayType)) {
+      uint64_t Extent = 0;
+      const uint64_t Bound = CAT->getZExtSize();
+      if (llvm::all_of(IList->inits(), [&](const Expr *Init) {
+            const auto *DIE = dyn_cast<DesignatedInitExpr>(Init);
+            if (!DIE || DIE->size() != 1 ||
+                !DIE->getDesignator(0)->isArrayDesignator())
+              return false;
+            const Expr *IndexExpr = DIE->getArrayIndex(*DIE->getDesignator(0));
+            if (IndexExpr->isValueDependent())
+              return false;
+            // Sema has already checked that this is a nonnegative integer
+            // constant expression. Leave invalid bounds to the usual path.
+            uint64_t ArrayIndex =
+                IndexExpr->EvaluateKnownConstInt(SemaRef.Context)
+                    .getLimitedValue();
+            if (ArrayIndex >= Bound ||
+                ArrayIndex >= std::numeric_limits<unsigned>::max())
+              return false;
+            Extent = std::max(Extent, ArrayIndex + 1);
+            return true;
+          }))
+        StructuredList->reserveInits(SemaRef.Context,
+                                     static_cast<unsigned>(Extent));
+    }
+  }
+
   // Check for the special-case of initializing an array with a string.
   if (Index < IList->getNumInits()) {
     if (IsStringInit(IList->getInit(Index), arrayType, SemaRef.Context) ==
diff --git a/clang/test/Sema/designated-initializers.c 
b/clang/test/Sema/designated-initializers.c
index 11dc3a2308dee..999757bb3a909 100644
--- a/clang/test/Sema/designated-initializers.c
+++ b/clang/test/Sema/designated-initializers.c
@@ -375,3 +375,14 @@ void gh154046(void) {
     [1] = ""  // expected-error {{incompatible pointer to integer conversion 
initializing 'const char' with an expression of type 'char[1]'}}
   }[1];
 }
+
+// Nested fixed-size integer arrays exercise verification without a temporary
+// structured list and reservation for out-of-order array designators.
+unsigned short sparse_rows[2][8] = {
+    [1] = {[7] = 0xffff, [1] = 42},
+    [0] = {[6] = 7, [2] = 3},
+};
+unsigned short sparse_duplicate[1][8] = {
+    {[3] = 1, // expected-note {{previous initialization is here}}
+     [3] = 2} // expected-warning {{initializer overrides prior initialization 
of this subobject}}
+};

``````````

</details>


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

Reply via email to