[clang] [clang][Sema] Reduce allocations for sparse integer array initializers (PR #227592)

via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 30 00:50:40 PDT 2026


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


More information about the cfe-commits mailing list