[clang] [clang][CodeGen] Fix assertion failure with #embed in array new-expression initializers (PR #218262)
Akash Manna via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 23 09:40:57 PDT 2026
https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/218262
Fixes #128985
An `EmbedExpr` in a semantic initializer list can represent many array elements at once, and every consumer of such lists has to expand it. The array `new` emitter, `EmitNewArrayInitializer`, never did: it counted the `EmbedExpr` as one element and emitted it through the scalar path, hitting `assert(E->getDataElementCount() == 1)`. The undercount also made the runtime minimum allocation check too lax and miscomputed the trailing zero-fill size.
`Codegen` now emits one store per embed data element, converted to the element type, and counts initializers with `getNumInitsWithEmbedExpanded()` in both places. That helper also learned to look through implicit casts, since Sema wraps a multi-element `EmbedExpr` in a conversion when the element type isn't `int` — the other embed consumers already strip casts before checking.
LLM tools were used for this contribution. I've reviewed, built, and tested the change myself before pushing to GitHub.
>From 615dfb8496b61dbdd80cd32254d46c320beaac52 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Sun, 23 Aug 2026 22:04:43 +0530
Subject: [PATCH] [clang][CodeGen] Fix assertion failure with #embed in array
new-expression initializers
An EmbedExpr in a semantic initializer list can represent many array
elements, but EmitNewArrayInitializer counted it as one and emitted it
through the scalar path, hitting assert(E->getDataElementCount() == 1).
Emit one store per embed data element instead, count initializers with
getNumInitsWithEmbedExpanded() (also for the minimum-allocation check),
and make that helper look through implicit casts the way the other
embed consumers already do.
Fixes #128985
---
clang/docs/ReleaseNotes.md | 4 ++
clang/include/clang/AST/Expr.h | 2 +-
clang/lib/CodeGen/CGExprCXX.cpp | 34 +++++++++---
clang/test/CodeGenCXX/GH128985.cpp | 89 ++++++++++++++++++++++++++++++
4 files changed, 120 insertions(+), 9 deletions(-)
create mode 100644 clang/test/CodeGenCXX/GH128985.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3c6694f510952..a973b143e6e2e 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -531,6 +531,10 @@ features cannot lower the translation-unit ABI level;
parameter that follows a parameter pack (e.g.
`template <typename... T> S::S(T..., int = 10) {}`). (#GH216211)
+- Fixed an assertion failure when `#embed` was used in the braced initializer
+ of an array new-expression; codegen now expands the embedded data into the
+ individual array elements. (#GH128985)
+
#### Bug Fixes to AST Handling
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 72762c668f26a..93b5a8aea6614 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -5365,7 +5365,7 @@ class InitListExpr : public Expr {
unsigned getNumInitsWithEmbedExpanded() const {
unsigned Sum = InitExprs.size();
for (auto *IE : InitExprs)
- if (auto *EE = dyn_cast<EmbedExpr>(IE))
+ if (auto *EE = dyn_cast<EmbedExpr>(cast<Expr>(IE)->IgnoreParenImpCasts()))
Sum += EE->getDataElementCount() - 1;
return Sum;
}
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index e400a5c5a49c5..39c0e94b488f4 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1103,7 +1103,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
ArrayRef<const Expr *> InitExprs =
ILE ? ILE->inits() : CPLIE->getInitExprs();
- InitListElements = InitExprs.size();
+ InitListElements =
+ ILE ? ILE->getNumInitsWithEmbedExpanded() : InitExprs.size();
// If this is a multi-dimensional array new, we will initialize multiple
// elements with each init list element.
@@ -1138,6 +1139,14 @@ void CodeGenFunction::EmitNewArrayInitializer(
CharUnits StartAlign = CurPtr.getAlignment();
unsigned i = 0;
+ auto AdvanceToNextElement = [&]() {
+ CurPtr = Address(Builder.CreateInBoundsGEP(CurPtr.getElementType(),
+ CurPtr.emitRawPointer(*this),
+ Builder.getSize(1),
+ "array.exp.next"),
+ CurPtr.getElementType(),
+ StartAlign.alignmentAtOffset((++i) * ElementSize));
+ };
for (const Expr *IE : InitExprs) {
// Tell the cleanup that it needs to destroy up to this
// element. TODO: some of these stores can be trivially
@@ -1145,17 +1154,25 @@ void CodeGenFunction::EmitNewArrayInitializer(
if (EndOfInit.isValid()) {
Builder.CreateStore(CurPtr.emitRawPointer(*this), EndOfInit);
}
+ // An EmbedExpr can initialize more than one array element.
+ if (const auto *EmbedS = dyn_cast<EmbedExpr>(IE->IgnoreParenImpCasts())) {
+ for (const IntegerLiteral *DataElement :
+ EmbedS->underlying_data_elements()) {
+ llvm::Value *Val = EmitScalarConversion(
+ Builder.getInt(DataElement->getValue()), DataElement->getType(),
+ ElementType, DataElement->getExprLoc());
+ EmitStoreOfScalar(Val, MakeAddrLValue(CurPtr, ElementType),
+ /*isInit=*/true);
+ AdvanceToNextElement();
+ }
+ continue;
+ }
// FIXME: If the last initializer is an incomplete initializer list for
// an array, and we have an array filler, we can fold together the two
// initialization loops.
StoreAnyExprIntoOneUnit(*this, IE, IE->getType(), CurPtr,
AggValueSlot::DoesNotOverlap);
- CurPtr = Address(Builder.CreateInBoundsGEP(CurPtr.getElementType(),
- CurPtr.emitRawPointer(*this),
- Builder.getSize(1),
- "array.exp.next"),
- CurPtr.getElementType(),
- StartAlign.alignmentAtOffset((++i) * ElementSize));
+ AdvanceToNextElement();
}
// The remaining elements are filled with the array filler expression.
@@ -1591,7 +1608,8 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) {
cast<ConstantArrayType>(Init->getType()->getAsArrayTypeUnsafe())
->getZExtSize();
} else if (ILE || CPLIE) {
- minElements = ILE ? ILE->getNumInits() : CPLIE->getInitExprs().size();
+ minElements = ILE ? ILE->getNumInitsWithEmbedExpanded()
+ : CPLIE->getInitExprs().size();
}
}
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
new file mode 100644
index 0000000000000..7b703af005687
--- /dev/null
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 %s -triple x86_64 -emit-llvm -o - | FileCheck %s
+
+// GH128985: #embed in the braced initializer of an array new-expression
+// asserted in codegen.
+// The first four bytes of this file are '/', '/', ' ', 'R' (47, 47, 32, 82).
+
+// CHECK-LABEL: define {{.*}}void @_Z2f1i(
+// CHECK: icmp ult i64 %{{.*}}, 4
+// CHECK: %[[A1:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 47, ptr %[[A1]]
+// CHECK: %[[F1E1:.*]] = getelementptr inbounds i32, ptr %[[A1]], i64 1
+// CHECK: store i32 47, ptr %[[F1E1]]
+// CHECK: %[[F1E2:.*]] = getelementptr inbounds i32, ptr %[[F1E1]], i64 1
+// CHECK: store i32 32, ptr %[[F1E2]]
+// CHECK: %[[F1E3:.*]] = getelementptr inbounds i32, ptr %[[F1E2]], i64 1
+// CHECK: store i32 82, ptr %[[F1E3]]
+// CHECK: %[[F1REST:.*]] = sub i64 %{{.*}}, 16
+// CHECK: call void @llvm.memset.p0.i64(ptr align 4 %{{.*}}, i8 0, i64 %[[F1REST]], i1 false)
+void f1(int x) {
+ int *p = new int[x]{
+#embed __FILE__ limit(4)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z2f2i(
+// CHECK: icmp ult i64 %{{.*}}, 4
+// CHECK: %[[A2:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 500, ptr %[[A2]]
+// CHECK: %[[F2E1:.*]] = getelementptr inbounds i32, ptr %[[A2]], i64 1
+// CHECK: store i32 47, ptr %[[F2E1]]
+// CHECK: %[[F2E2:.*]] = getelementptr inbounds i32, ptr %[[F2E1]], i64 1
+// CHECK: store i32 47, ptr %[[F2E2]]
+// CHECK: %[[F2E3:.*]] = getelementptr inbounds i32, ptr %[[F2E2]], i64 1
+// CHECK: store i32 600, ptr %[[F2E3]]
+// CHECK: %[[F2REST:.*]] = sub i64 %{{.*}}, 16
+// CHECK: call void @llvm.memset.p0.i64(ptr align 4 %{{.*}}, i8 0, i64 %[[F2REST]], i1 false)
+void f2(int x) {
+ int *p = new int[x]{
+ 500,
+#embed __FILE__ limit(2) suffix(, 600)
+ };
+}
+
+// char arrays are initialized from the embed data via the string literal
+// initialization path.
+// CHECK-LABEL: define {{.*}}void @_Z2f3i(
+// CHECK: icmp ult i64 %{{.*}}, 4
+// CHECK: %[[A3:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %[[A3]], ptr align 1 @{{.*}}, i64 4, i1 false)
+// CHECK: %[[F3END:.*]] = getelementptr inbounds i8, ptr %[[A3]], i64 4
+// CHECK: %[[F3REST:.*]] = sub i64 %{{.*}}, 4
+// CHECK: call void @llvm.memset.p0.i64(ptr align 1 %[[F3END]], i8 0, i64 %[[F3REST]], i1 false)
+void f3(int x) {
+ char *p = new char[x]{
+#embed __FILE__ limit(4)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z2f4i(
+// CHECK: icmp ult i64 %{{.*}}, 2
+// CHECK: %[[A4:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 900, ptr %[[A4]]
+// CHECK: %[[F4E1:.*]] = getelementptr inbounds i32, ptr %[[A4]], i64 1
+// CHECK: store i32 47, ptr %[[F4E1]]
+// CHECK: %[[F4REST:.*]] = sub i64 %{{.*}}, 8
+// CHECK: call void @llvm.memset.p0.i64(ptr align 4 %{{.*}}, i8 0, i64 %[[F4REST]], i1 false)
+void f4(int x) {
+ int *p = new int[x]{
+#embed __FILE__ limit(1) prefix(900, )
+ };
+}
+
+// Constant size fully covered by the embed data: no trailing fill.
+// CHECK-LABEL: define {{.*}}void @_Z2f5v(
+// CHECK: %[[A5:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 47, ptr %[[A5]]
+// CHECK: %[[F5E1:.*]] = getelementptr inbounds i32, ptr %[[A5]], i64 1
+// CHECK: store i32 47, ptr %[[F5E1]]
+// CHECK: %[[F5E2:.*]] = getelementptr inbounds i32, ptr %[[F5E1]], i64 1
+// CHECK: store i32 32, ptr %[[F5E2]]
+// CHECK: %[[F5E3:.*]] = getelementptr inbounds i32, ptr %[[F5E2]], i64 1
+// CHECK: store i32 82, ptr %[[F5E3]]
+// CHECK-NOT: call void @llvm.memset
+// CHECK: ret void
+void f5() {
+ int *p = new int[4]{
+#embed __FILE__ limit(4)
+ };
+}
More information about the cfe-commits
mailing list