[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
Wed Sep 9 04:19:21 PDT 2026
https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/218262
>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 1/6] [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)
+ };
+}
>From df02caddd25573b2ce95f071453d00069c096607 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 24 Aug 2026 23:31:48 +0530
Subject: [PATCH 2/6] [clang][CodeGen] Address review comments for #embed in
array new
Use a dedicated input file instead of __FILE__, add codegen coverage for
non-int element types, multidimensional arrays, arrays of structs and
deduced array bounds, and add a Sema test for too many/too few embed
elements with a constant bound.
---
clang/test/CodeGenCXX/GH128985.cpp | 130 ++++++++++++++++----
clang/test/CodeGenCXX/Inputs/embed-data.txt | 1 +
clang/test/SemaCXX/GH128985.cpp | 38 ++++++
3 files changed, 147 insertions(+), 22 deletions(-)
create mode 100644 clang/test/CodeGenCXX/Inputs/embed-data.txt
create mode 100644 clang/test/SemaCXX/GH128985.cpp
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
index 7b703af005687..87ea50661b09c 100644
--- a/clang/test/CodeGenCXX/GH128985.cpp
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -1,24 +1,26 @@
-// RUN: %clang_cc1 %s -triple x86_64 -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple x86_64 --embed-dir=%S/Inputs -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).
+// embed-data.txt contains "0123456789" (48 ... 57) without a trailing newline.
+
+struct S {
+ int a, b;
+};
// CHECK-LABEL: define {{.*}}void @_Z2f1i(
// CHECK: icmp ult i64 %{{.*}}, 4
// CHECK: %[[A1:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
-// CHECK: store i32 47, ptr %[[A1]]
+// CHECK: store i32 48, ptr %[[A1]]
// CHECK: %[[F1E1:.*]] = getelementptr inbounds i32, ptr %[[A1]], i64 1
-// CHECK: store i32 47, ptr %[[F1E1]]
+// CHECK: store i32 49, ptr %[[F1E1]]
// CHECK: %[[F1E2:.*]] = getelementptr inbounds i32, ptr %[[F1E1]], i64 1
-// CHECK: store i32 32, ptr %[[F1E2]]
+// CHECK: store i32 50, ptr %[[F1E2]]
// CHECK: %[[F1E3:.*]] = getelementptr inbounds i32, ptr %[[F1E2]], i64 1
-// CHECK: store i32 82, ptr %[[F1E3]]
+// CHECK: store i32 51, 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)
+#embed <embed-data.txt> limit(4)
};
}
@@ -27,9 +29,9 @@ void f1(int x) {
// 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: store i32 48, ptr %[[F2E1]]
// CHECK: %[[F2E2:.*]] = getelementptr inbounds i32, ptr %[[F2E1]], i64 1
-// CHECK: store i32 47, ptr %[[F2E2]]
+// CHECK: store i32 49, ptr %[[F2E2]]
// CHECK: %[[F2E3:.*]] = getelementptr inbounds i32, ptr %[[F2E2]], i64 1
// CHECK: store i32 600, ptr %[[F2E3]]
// CHECK: %[[F2REST:.*]] = sub i64 %{{.*}}, 16
@@ -37,12 +39,11 @@ void f1(int x) {
void f2(int x) {
int *p = new int[x]{
500,
-#embed __FILE__ limit(2) suffix(, 600)
+#embed <embed-data.txt> limit(2) suffix(, 600)
};
}
-// char arrays are initialized from the embed data via the string literal
-// initialization path.
+// char arrays go through the string literal initialization path.
// CHECK-LABEL: define {{.*}}void @_Z2f3i(
// CHECK: icmp ult i64 %{{.*}}, 4
// CHECK: %[[A3:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
@@ -52,7 +53,7 @@ void f2(int x) {
// 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)
+#embed <embed-data.txt> limit(4)
};
}
@@ -61,29 +62,114 @@ void f3(int x) {
// 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: store i32 48, 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, )
+#embed <embed-data.txt> 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: store i32 48, ptr %[[A5]]
// CHECK: %[[F5E1:.*]] = getelementptr inbounds i32, ptr %[[A5]], i64 1
-// CHECK: store i32 47, ptr %[[F5E1]]
+// CHECK: store i32 49, ptr %[[F5E1]]
// CHECK: %[[F5E2:.*]] = getelementptr inbounds i32, ptr %[[F5E1]], i64 1
-// CHECK: store i32 32, ptr %[[F5E2]]
+// CHECK: store i32 50, ptr %[[F5E2]]
// CHECK: %[[F5E3:.*]] = getelementptr inbounds i32, ptr %[[F5E2]], i64 1
-// CHECK: store i32 82, ptr %[[F5E3]]
+// CHECK: store i32 51, ptr %[[F5E3]]
// CHECK-NOT: call void @llvm.memset
// CHECK: ret void
void f5() {
int *p = new int[4]{
-#embed __FILE__ limit(4)
+#embed <embed-data.txt> limit(4)
+ };
+}
+
+// Sema wraps the EmbedExpr in an implicit conversion to the element type.
+// CHECK-LABEL: define {{.*}}void @_Z2f6i(
+// CHECK: icmp ult i64 %{{.*}}, 4
+// CHECK: %[[A6:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i64 48, ptr %[[A6]]
+// CHECK: %[[F6E1:.*]] = getelementptr inbounds i64, ptr %[[A6]], i64 1
+// CHECK: store i64 49, ptr %[[F6E1]]
+// CHECK: %[[F6E2:.*]] = getelementptr inbounds i64, ptr %[[F6E1]], i64 1
+// CHECK: store i64 50, ptr %[[F6E2]]
+// CHECK: %[[F6E3:.*]] = getelementptr inbounds i64, ptr %[[F6E2]], i64 1
+// CHECK: store i64 51, ptr %[[F6E3]]
+// CHECK: %[[F6REST:.*]] = sub i64 %{{.*}}, 32
+// CHECK: call void @llvm.memset.p0.i64(ptr align 8 %{{.*}}, i8 0, i64 %[[F6REST]], i1 false)
+void f6(int x) {
+ long long *p = new long long[x]{
+#embed <embed-data.txt> limit(4)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z2f7i(
+// CHECK: icmp ult i64 %{{.*}}, 2
+// CHECK: call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 48, ptr
+// CHECK: store i32 49, ptr
+// CHECK: store i32 50, ptr
+// CHECK: store i32 51, ptr
+// CHECK: %[[F7REST:.*]] = sub i64 %{{.*}}, 16
+// CHECK: call void @llvm.memset.p0.i64(ptr align {{[0-9]+}} %{{.*}}, i8 0, i64 %[[F7REST]], i1 false)
+void f7(int x) {
+ int (*p)[2] = new int[x][2]{
+#embed <embed-data.txt> limit(4)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z2f8i(
+// CHECK: icmp ult i64 %{{.*}}, 2
+// CHECK: call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 48, ptr
+// CHECK: store i32 49, ptr
+// CHECK: store i32 50, ptr
+// CHECK: store i32 51, ptr
+// CHECK: %[[F8REST:.*]] = sub i64 %{{.*}}, 16
+// CHECK: call void @llvm.memset.p0.i64(ptr align {{[0-9]+}} %{{.*}}, i8 0, i64 %[[F8REST]], i1 false)
+void f8(int x) {
+ S *p = new S[x]{
+#embed <embed-data.txt> limit(4)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z2f9i(
+// CHECK: icmp ult i64 %{{.*}}, 2
+// CHECK: call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store i32 48, ptr
+// CHECK: store i32 49, ptr
+// CHECK: store i32 50, ptr
+// CHECK: store i32 51, ptr
+// CHECK: store i32 52, ptr
+// CHECK: store i32 53, ptr
+// CHECK: store i32 54, ptr
+// CHECK: store i32 55, ptr
+// CHECK: %[[F9REST:.*]] = sub i64 %{{.*}}, 32
+// CHECK: call void @llvm.memset.p0.i64(ptr align {{[0-9]+}} %{{.*}}, i8 0, i64 %[[F9REST]], i1 false)
+void f9(int x) {
+ S (*p)[2] = new S[x][2]{
+#embed <embed-data.txt> limit(8)
+ };
+}
+
+// CHECK-LABEL: define {{.*}}void @_Z3f10v(
+// CHECK: %[[A10:.*]] = call {{.*}}ptr @_Znam(i64 noundef 16)
+// CHECK: store i32 48, ptr %[[A10]]
+// CHECK: %[[F10E1:.*]] = getelementptr inbounds i32, ptr %[[A10]], i64 1
+// CHECK: store i32 49, ptr %[[F10E1]]
+// CHECK: %[[F10E2:.*]] = getelementptr inbounds i32, ptr %[[F10E1]], i64 1
+// CHECK: store i32 50, ptr %[[F10E2]]
+// CHECK: %[[F10E3:.*]] = getelementptr inbounds i32, ptr %[[F10E2]], i64 1
+// CHECK: store i32 51, ptr %[[F10E3]]
+// CHECK-NOT: call void @llvm.memset
+// CHECK: ret void
+void f10() {
+ int *p = new int[]{
+#embed <embed-data.txt> limit(4)
};
}
diff --git a/clang/test/CodeGenCXX/Inputs/embed-data.txt b/clang/test/CodeGenCXX/Inputs/embed-data.txt
new file mode 100644
index 0000000000000..ad471007bd7f5
--- /dev/null
+++ b/clang/test/CodeGenCXX/Inputs/embed-data.txt
@@ -0,0 +1 @@
+0123456789
\ No newline at end of file
diff --git a/clang/test/SemaCXX/GH128985.cpp b/clang/test/SemaCXX/GH128985.cpp
new file mode 100644
index 0000000000000..adb0d8a1fc93f
--- /dev/null
+++ b/clang/test/SemaCXX/GH128985.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wno-c23-extensions %s
+
+struct S {
+ int a, b;
+};
+
+void f(int x) {
+ int *a = new int[2]{
+#embed __FILE__ limit(4)
+ // expected-error at -1 {{excess elements in array initializer}}
+ };
+
+ int *b = new int[4]{
+#embed __FILE__ limit(4)
+ };
+
+ int *c = new int[8]{
+#embed __FILE__ limit(4)
+ };
+
+ int *d = new int[x]{
+#embed __FILE__ limit(4)
+ };
+
+ int (*e)[2] = new int[2][2]{
+#embed __FILE__ limit(5)
+ // expected-error at -1 {{excess elements in array initializer}}
+ };
+
+ S *s = new S[1]{
+#embed __FILE__ limit(3)
+ // expected-error at -1 {{excess elements in array initializer}}
+ };
+
+ S *t = new S[x]{
+#embed __FILE__ limit(3)
+ };
+}
>From 6dbf0263885b2814f41d8699e95a3c97604ffbe6 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 25 Aug 2026 21:35:55 +0530
Subject: [PATCH 3/6] [clang][CodeGen] Read embed data directly when
initializing array new
Emit the elements of an EmbedExpr from its underlying string literal
instead of iterating the fake integer literals, and add a float array
test.
---
clang/lib/CodeGen/CGExprCXX.cpp | 11 +++++++----
clang/test/CodeGenCXX/GH128985.cpp | 18 ++++++++++++++++++
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 39c0e94b488f4..3c141990dc887 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1156,11 +1156,14 @@ void CodeGenFunction::EmitNewArrayInitializer(
}
// 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()) {
+ const StringLiteral *SL = EmbedS->getDataStringLiteral();
+ llvm::Type *DataTy = ConvertType(EmbedS->getType());
+ for (unsigned I = EmbedS->getStartingElementPos(),
+ End = I + EmbedS->getDataElementCount();
+ I != End; ++I) {
llvm::Value *Val = EmitScalarConversion(
- Builder.getInt(DataElement->getValue()), DataElement->getType(),
- ElementType, DataElement->getExprLoc());
+ llvm::ConstantInt::get(DataTy, SL->getCodeUnit(I)),
+ EmbedS->getType(), ElementType, EmbedS->getLocation());
EmitStoreOfScalar(Val, MakeAddrLValue(CurPtr, ElementType),
/*isInit=*/true);
AdvanceToNextElement();
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
index 87ea50661b09c..37660d47b29b8 100644
--- a/clang/test/CodeGenCXX/GH128985.cpp
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -173,3 +173,21 @@ void f10() {
#embed <embed-data.txt> limit(4)
};
}
+
+// CHECK-LABEL: define {{.*}}void @_Z3f11i(
+// CHECK: icmp ult i64 %{{.*}}, 4
+// CHECK: %[[A11:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
+// CHECK: store float 4.800000e+01, ptr %[[A11]]
+// CHECK: %[[F11E1:.*]] = getelementptr inbounds float, ptr %[[A11]], i64 1
+// CHECK: store float 4.900000e+01, ptr %[[F11E1]]
+// CHECK: %[[F11E2:.*]] = getelementptr inbounds float, ptr %[[F11E1]], i64 1
+// CHECK: store float 5.000000e+01, ptr %[[F11E2]]
+// CHECK: %[[F11E3:.*]] = getelementptr inbounds float, ptr %[[F11E2]], i64 1
+// CHECK: store float 5.100000e+01, ptr %[[F11E3]]
+// CHECK: %[[F11REST:.*]] = sub i64 %{{.*}}, 16
+// CHECK: call void @llvm.memset.p0.i64(ptr align 4 %{{.*}}, i8 0, i64 %[[F11REST]], i1 false)
+void f11(int x) {
+ float *p = new float[x]{
+#embed <embed-data.txt> limit(4)
+ };
+}
>From 606fdb8a57dc7e13b1afdfad046dabdeb606a15d Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 25 Aug 2026 22:31:02 +0530
Subject: [PATCH 4/6] [clang][Sema] Only expand #embed over multiple elements
for scalar array elements
HandleEmbed returned a multi-element EmbedExpr for any array element,
so for a class element with a converting constructor the whole embed
became a single constructor argument. Restrict it to scalar element
types so class elements are constructed from one data element each.
---
clang/lib/Sema/SemaInit.cpp | 3 ++-
clang/test/CodeGenCXX/GH128985.cpp | 16 ++++++++++++++++
clang/test/SemaCXX/GH128985.cpp | 12 ++++++++++++
3 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 9b10ac1735c81..ef259bbb0e98c 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -563,7 +563,8 @@ class InitListChecker {
// Reference just one if we're initializing a single scalar.
uint64_t ElsCount = 1;
// Otherwise try to fill whole array with embed data.
- if (Entity.getKind() == InitializedEntity::EK_ArrayElement) {
+ if (Entity.getKind() == InitializedEntity::EK_ArrayElement &&
+ Entity.getType()->isScalarType()) {
unsigned ArrIndex = Entity.getElementIndex();
auto *AType =
SemaRef.Context.getAsArrayType(Entity.getParent()->getType());
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
index 37660d47b29b8..741d1f7ea1d80 100644
--- a/clang/test/CodeGenCXX/GH128985.cpp
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -6,6 +6,10 @@ struct S {
int a, b;
};
+struct A {
+ A(char);
+};
+
// CHECK-LABEL: define {{.*}}void @_Z2f1i(
// CHECK: icmp ult i64 %{{.*}}, 4
// CHECK: %[[A1:.*]] = call {{.*}}ptr @_Znam(i64 {{.*}})
@@ -191,3 +195,15 @@ void f11(int x) {
#embed <embed-data.txt> limit(4)
};
}
+
+// Class elements are constructed from one data element each.
+// CHECK-LABEL: define {{.*}}void @_Z3f12v(
+// CHECK: %[[A12:.*]] = call {{.*}}ptr @_Znam(i64 noundef 2)
+// CHECK: call void @_ZN1AC1Ec(ptr {{.*}}%[[A12]], i8 noundef signext 48)
+// CHECK: %[[F12E1:.*]] = getelementptr inbounds %struct.A, ptr %[[A12]], i64 1
+// CHECK: call void @_ZN1AC1Ec(ptr {{.*}}%[[F12E1]], i8 noundef signext 49)
+void f12() {
+ A *p = new A[]{
+#embed <embed-data.txt> limit(2)
+ };
+}
diff --git a/clang/test/SemaCXX/GH128985.cpp b/clang/test/SemaCXX/GH128985.cpp
index adb0d8a1fc93f..faa6eeab3a253 100644
--- a/clang/test/SemaCXX/GH128985.cpp
+++ b/clang/test/SemaCXX/GH128985.cpp
@@ -4,6 +4,10 @@ struct S {
int a, b;
};
+struct A {
+ A(char);
+};
+
void f(int x) {
int *a = new int[2]{
#embed __FILE__ limit(4)
@@ -35,4 +39,12 @@ void f(int x) {
S *t = new S[x]{
#embed __FILE__ limit(3)
};
+
+ A *u = new A[]{1, 2, 3,
+#embed __FILE__ limit(10)
+ };
+
+ A v[] = {1, 2, 3,
+#embed __FILE__ limit(10)
+ };
}
>From f391dc9cd3fffcf9e834f4f63724640e245ed688 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 25 Aug 2026 23:28:33 +0530
Subject: [PATCH 5/6] [clang][Sema] Narrow multi-element #embed expansion to
integer and floating-point arrays
Match the element types the constant evaluator and constant emitter
handle, and only take the multi-element path in array new codegen when
the embed actually covers more than one element, so single-element
embeds under other conversions (e.g. to _Complex) go through the normal
initializer path.
---
clang/docs/ReleaseNotes.md | 4 ++--
clang/include/clang/AST/Expr.h | 2 +-
clang/lib/CodeGen/CGExprCXX.cpp | 3 ++-
clang/lib/Sema/SemaInit.cpp | 3 ++-
clang/test/CodeGenCXX/GH128985.cpp | 13 +++++++++++++
clang/test/SemaCXX/GH128985.cpp | 4 ++++
6 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a973b143e6e2e..c719a8c86d0cd 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -532,8 +532,8 @@ features cannot lower the translation-unit ABI level;
`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)
+ of an array new-expression, or of an array whose elements are of class type.
+ (#GH128985)
#### Bug Fixes to AST Handling
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 93b5a8aea6614..737ec4134a449 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -5151,7 +5151,7 @@ struct EmbedDataStorage {
/// { {EE(9th and 10th element), { zeroinitializer }}}
///
/// EmbedExpr inside of a semantic initializer list and referencing more than
-/// one element can only appear for arrays of scalars.
+/// one element can only appear for arrays of integer or floating-point type.
class EmbedExpr final : public Expr {
SourceLocation EmbedKeywordLoc;
IntegerLiteral *FakeChildNode = nullptr;
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 3c141990dc887..7969aec916122 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1155,7 +1155,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
Builder.CreateStore(CurPtr.emitRawPointer(*this), EndOfInit);
}
// An EmbedExpr can initialize more than one array element.
- if (const auto *EmbedS = dyn_cast<EmbedExpr>(IE->IgnoreParenImpCasts())) {
+ const auto *EmbedS = dyn_cast<EmbedExpr>(IE->IgnoreParenImpCasts());
+ if (EmbedS && EmbedS->getDataElementCount() > 1) {
const StringLiteral *SL = EmbedS->getDataStringLiteral();
llvm::Type *DataTy = ConvertType(EmbedS->getType());
for (unsigned I = EmbedS->getStartingElementPos(),
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ef259bbb0e98c..086aed2e30d85 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -564,7 +564,8 @@ class InitListChecker {
uint64_t ElsCount = 1;
// Otherwise try to fill whole array with embed data.
if (Entity.getKind() == InitializedEntity::EK_ArrayElement &&
- Entity.getType()->isScalarType()) {
+ (Entity.getType()->isIntegerType() ||
+ Entity.getType()->isRealFloatingType())) {
unsigned ArrIndex = Entity.getElementIndex();
auto *AType =
SemaRef.Context.getAsArrayType(Entity.getParent()->getType());
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
index 741d1f7ea1d80..1eeb1d17678de 100644
--- a/clang/test/CodeGenCXX/GH128985.cpp
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -207,3 +207,16 @@ void f12() {
#embed <embed-data.txt> limit(2)
};
}
+
+// Complex elements are converted from one data element each.
+// CHECK-LABEL: define {{.*}}void @_Z3f13v(
+// CHECK: call {{.*}}ptr @_Znam(i64 noundef 32)
+// CHECK: store double 4.800000e+01, ptr
+// CHECK: store double 0.000000e+00, ptr
+// CHECK: store double 4.900000e+01, ptr
+// CHECK: store double 0.000000e+00, ptr
+void f13() {
+ _Complex double *p = new _Complex double[]{
+#embed <embed-data.txt> limit(2)
+ };
+}
diff --git a/clang/test/SemaCXX/GH128985.cpp b/clang/test/SemaCXX/GH128985.cpp
index faa6eeab3a253..0d51e3e53582f 100644
--- a/clang/test/SemaCXX/GH128985.cpp
+++ b/clang/test/SemaCXX/GH128985.cpp
@@ -45,6 +45,10 @@ void f(int x) {
};
A v[] = {1, 2, 3,
+#embed __FILE__ limit(10)
+ };
+
+ _Complex double *w = new _Complex double[]{
#embed __FILE__ limit(10)
};
}
>From 6cd6372f65a59c1437aec1b9fd00615f099be649 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Wed, 9 Sep 2026 16:48:31 +0530
Subject: [PATCH 6/6] [clang][CodeGen] Clarify the multi-element embed check
and test single-element complex embeds
A single-element embed can be wrapped in a conversion to a non-scalar
element type and must go through the regular initializer path.
---
clang/lib/CodeGen/CGExprCXX.cpp | 5 ++++-
clang/test/CodeGenCXX/GH128985.cpp | 14 +++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 7969aec916122..97bfcd7bda4e8 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1154,7 +1154,10 @@ void CodeGenFunction::EmitNewArrayInitializer(
if (EndOfInit.isValid()) {
Builder.CreateStore(CurPtr.emitRawPointer(*this), EndOfInit);
}
- // An EmbedExpr can initialize more than one array element.
+ // A multi-element EmbedExpr initializes several array elements at once.
+ // A single-element embed can be wrapped in a conversion to a non-scalar
+ // element type (e.g. _Complex) and is emitted like any other
+ // initializer.
const auto *EmbedS = dyn_cast<EmbedExpr>(IE->IgnoreParenImpCasts());
if (EmbedS && EmbedS->getDataElementCount() > 1) {
const StringLiteral *SL = EmbedS->getDataStringLiteral();
diff --git a/clang/test/CodeGenCXX/GH128985.cpp b/clang/test/CodeGenCXX/GH128985.cpp
index 1eeb1d17678de..a1d813cc43dfd 100644
--- a/clang/test/CodeGenCXX/GH128985.cpp
+++ b/clang/test/CodeGenCXX/GH128985.cpp
@@ -208,7 +208,9 @@ void f12() {
};
}
-// Complex elements are converted from one data element each.
+// Complex elements are not integer or floating-point type, so Sema slices
+// the embed into single-element EmbedExprs wrapped in an int-to-complex
+// conversion, one per element.
// CHECK-LABEL: define {{.*}}void @_Z3f13v(
// CHECK: call {{.*}}ptr @_Znam(i64 noundef 32)
// CHECK: store double 4.800000e+01, ptr
@@ -220,3 +222,13 @@ void f13() {
#embed <embed-data.txt> limit(2)
};
}
+
+// CHECK-LABEL: define {{.*}}void @_Z3f14v(
+// CHECK: %[[A14:.*]] = call {{.*}}ptr @_Znam(i64 noundef 16)
+// CHECK: store double 4.800000e+01, ptr
+// CHECK: store double 0.000000e+00, ptr
+void f14() {
+ _Complex double *p = new _Complex double[]{
+#embed <embed-data.txt> limit(1)
+ };
+}
More information about the cfe-commits
mailing list