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 <[email protected]> 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) + }; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
