https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/221390
>From cf0f5b8d5ee08efaaed03b6371f7cc84adb1a8a7 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 5 Sep 2026 10:28:31 +0530 Subject: [PATCH 01/15] [clang][CodeGen] Emit file-scope compound literals in a constant context A file-scope compound literal is a constant-initialized global, and Sema validates its initializer in a constant context. CodeGen evaluated the same initializer under whatever context the caller happened to be in, so when the literal's address was taken by a dynamic initializer the emitter was non-constant, __builtin_constant_p of a non-foldable operand refused to fold, and tryEmitGlobalCompoundLiteral hit its assertion. Set the constant context on the emitter for any file-scope literal in tryEmitGlobalCompoundLiteral itself, so both entry points evaluate under the same rules Sema used. Mirror the change in CIR. Fixes #212106 --- clang/docs/ReleaseNotes.md | 4 ++ clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 6 +++ clang/lib/CodeGen/CGExprConstant.cpp | 6 +++ .../AST/static-compound-literals-crash.cpp | 12 +---- clang/test/CodeGenCXX/GH212106.cpp | 49 +++++++++++++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 clang/test/CodeGenCXX/GH212106.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 0fb6dcf59d4c9..0192a03c89aff 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -682,6 +682,10 @@ features cannot lower the translation-unit ABI level; `this` via a member access through a dependent base class. - Fixed `DiagnoseUnguardedAvailability::TraverseIfStmt` dereferencing a nullptr on `if consteval {}`. (#GH220004) +- Fixed an assertion failure when the dynamic initializer of a global variable + takes the address of a file-scope compound literal whose initializer is only + constant under constant-evaluation rules, such as `__builtin_constant_p` of a + non-constant expression. (#GH212106) ### OpenACC Specific Changes diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index d8a3aa9c1c558..81e366ffaa745 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -962,6 +962,12 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter, if (cir::GlobalOp addr = cgm.getAddrOfConstantCompoundLiteralIfEmitted(e)) return builder.getGlobalViewAttr(addr); + // A file-scope compound literal is a constant-initialized global, so emit + // its initializer under constant-evaluation rules even when reached from a + // non-constant context. + if (e->isFileScope()) + emitter.setInConstantContext(true); + assert(!cir::MissingFeatures::addressSpace()); mlir::Attribute c = emitter.tryEmitForInitializer(e->getInitializer(), e->getType()); diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 48e80910ce577..cf2a4ff18cea6 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -1087,6 +1087,12 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter, CGM.getAddrOfConstantCompoundLiteralIfEmitted(E)) return ConstantAddress(Addr, Addr->getValueType(), Align); + // A file-scope compound literal is a constant-initialized global, so emit + // its initializer under constant-evaluation rules even when reached from a + // non-constant context such as the dynamic initializer of another global. + if (E->isFileScope()) + emitter.setInConstantContext(true); + LangAS addressSpace = E->getType().getAddressSpace(); llvm::Constant *C = emitter.tryEmitForInitializer(E->getInitializer(), addressSpace, E->getType()); diff --git a/clang/test/AST/static-compound-literals-crash.cpp b/clang/test/AST/static-compound-literals-crash.cpp index f9c3bd82fd025..838d67b2f3f17 100644 --- a/clang/test/AST/static-compound-literals-crash.cpp +++ b/clang/test/AST/static-compound-literals-crash.cpp @@ -1,5 +1,5 @@ -// FIXME: These test cases currently crash during codegen, despite initializers -// for CLEs being constant. +// FIXME: This test case currently crashes during codegen, despite the +// initializer for the CLE being constant. // RUN: not --crash %clang_cc1 -verify -std=c++20 -emit-llvm %s -o - // expected-no-diagnostics namespace case1 { @@ -7,11 +7,3 @@ struct RR { int&& r; }; struct Z { RR* x; }; constinit Z z = { (RR[1]){1} }; } - - -namespace case2 { -struct RR { int r; }; -struct Z { int x; const RR* y; int z; }; -inline int f() { return 0; } -Z z2 = { 10, (const RR[1]){__builtin_constant_p(z2.x)}, z2.y->r+f() }; -} diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp new file mode 100644 index 0000000000000..dfdf5dd96924d --- /dev/null +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -ffp-exception-behavior=strict -emit-llvm -o - %s | FileCheck %s + +// A file-scope compound literal is a constant-initialized global even when its +// address is taken from a non-constant context, so its initializer must be +// emitted under constant-evaluation rules. + +struct RR { int r; }; +struct Z { int x; const RR* y; int z; }; +inline int f() { return 0; } +Z z2 = { 10, (const RR[1]){__builtin_constant_p(z2.x)}, z2.y->r+f() }; + +// CHECK-DAG: @z2 = {{.*}}global %struct.Z zeroinitializer +// CHECK-DAG: [[Z2CL:@.compoundliteral(\.[0-9]+)?]] = internal constant [1 x %struct.RR] zeroinitializer + +namespace reduced { +struct Z { const int* y; int z; }; +int f(); +Z z2 = { (int[1]){__builtin_constant_p(z2.z)}, f() }; +} + +// CHECK-DAG: @_ZN7reduced2z2E = {{.*}}global %"struct.reduced::Z" zeroinitializer +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal global [1 x i32] zeroinitializer + +struct F { int a; const float *fp; }; +int g(); +F fl = { g(), (float[1]){0.1} }; + +// CHECK-DAG: @fl = {{.*}}global %struct.F zeroinitializer +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal global [1 x float] [float 1.000000e-01] + +const RR *p = (const RR[1]){__builtin_constant_p(1)}; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x %struct.RR] [%struct.RR { i32 1 }] +// CHECK-DAG: @p = {{.*}}global ptr @.compoundliteral{{(\.[0-9]+)?}} + +// A default member initializer at namespace scope is also a file-scope compound +// literal, reached here from a constructor emitted for a local variable. +extern int n; +struct Q { const int *m = (const int[1]){__builtin_constant_p(n)}; }; +void h() { Q q; } + +// CHECK-DAG: [[QCL:@.compoundliteral(\.[0-9]+)?]] = internal constant [1 x i32] zeroinitializer + +// CHECK-LABEL: define internal void @__cxx_global_var_init() +// CHECK: store ptr [[Z2CL]], ptr getelementptr inbounds{{.*}}(i8, ptr @z2, i64 8) + +// CHECK-LABEL: define {{.*}}void @_ZN1QC2Ev( +// CHECK: store ptr [[QCL]], ptr >From b33be0db7b8e45a0c25bdfa854fe8bace545aab8 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 9 Sep 2026 09:36:20 +0530 Subject: [PATCH 02/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper. CodeGen then emits the stored value. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/docs/ReleaseNotes.md | 3 ++- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 6 ------ clang/lib/CodeGen/CGExprConstant.cpp | 6 ------ clang/lib/Sema/SemaExpr.cpp | 17 ++++++++++++++--- .../AST/static-compound-literals-reeval.cpp | 5 ++--- clang/test/CodeGenCXX/GH212106.cpp | 3 +-- clang/test/SemaCXX/compound-literal.cpp | 13 +++++++++++++ 7 files changed, 32 insertions(+), 21 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 0192a03c89aff..e6f7c71a56376 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -685,7 +685,8 @@ features cannot lower the translation-unit ABI level; - Fixed an assertion failure when the dynamic initializer of a global variable takes the address of a file-scope compound literal whose initializer is only constant under constant-evaluation rules, such as `__builtin_constant_p` of a - non-constant expression. (#GH212106) + non-constant expression. The elements of a file-scope compound literal are now + evaluated once in Sema and the results are stored in the AST. (#GH212106) ### OpenACC Specific Changes diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 81e366ffaa745..d8a3aa9c1c558 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -962,12 +962,6 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter, if (cir::GlobalOp addr = cgm.getAddrOfConstantCompoundLiteralIfEmitted(e)) return builder.getGlobalViewAttr(addr); - // A file-scope compound literal is a constant-initialized global, so emit - // its initializer under constant-evaluation rules even when reached from a - // non-constant context. - if (e->isFileScope()) - emitter.setInConstantContext(true); - assert(!cir::MissingFeatures::addressSpace()); mlir::Attribute c = emitter.tryEmitForInitializer(e->getInitializer(), e->getType()); diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index cf2a4ff18cea6..48e80910ce577 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -1087,12 +1087,6 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter, CGM.getAddrOfConstantCompoundLiteralIfEmitted(E)) return ConstantAddress(Addr, Addr->getValueType(), Align); - // A file-scope compound literal is a constant-initialized global, so emit - // its initializer under constant-evaluation rules even when reached from a - // non-constant context such as the dynamic initializer of another global. - if (E->isFileScope()) - emitter.setInConstantContext(true); - LangAS addressSpace = E->getType().getAddressSpace(); llvm::Constant *C = emitter.tryEmitForInitializer(E->getInitializer(), addressSpace, E->getType()); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index a1514ebae40f1..463f14d47f278 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7544,14 +7544,25 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); - if (!Init->isTypeDependent() && !Init->isValueDependent() && - !Init->isConstantInitializer(Context)) { + if (Init->isTypeDependent() || Init->isValueDependent()) { + ILE->setInit(i, ConstantExpr::Create(Context, Init)); + continue; + } + if (!Init->isConstantInitializer(Context)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); return ExprError(); } - ILE->setInit(i, ConstantExpr::Create(Context, Init)); + // Store the value so CodeGen does not re-evaluate the element outside + // a constant context. + Expr::EvalResult Eval; + if (Init->isPRValue() && + Init->EvaluateAsRValue(Eval, Context, /*InConstantContext=*/true) && + !Eval.HasSideEffects && Eval.Val.hasValue()) + ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); + else + ILE->setInit(i, ConstantExpr::Create(Context, Init)); } auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, diff --git a/clang/test/AST/static-compound-literals-reeval.cpp b/clang/test/AST/static-compound-literals-reeval.cpp index bd8f0f27af24a..29e21798b9295 100644 --- a/clang/test/AST/static-compound-literals-reeval.cpp +++ b/clang/test/AST/static-compound-literals-reeval.cpp @@ -3,7 +3,6 @@ struct RR { int r; }; struct Z { int x; const RR* y; int z; }; constinit Z z = { 10, (const RR[1]){__builtin_constant_p(z.x)}, z.y->r }; -// Check that we zero-initialize z.y->r. +// Check that we zero-initialize z.y->r and that z.z sees the same value. // CHECK: @.compoundliteral = internal constant [1 x %struct.RR] zeroinitializer -// FIXME: Despite of z.y->r being 0, we evaluate z.z to 1. -// CHECK: global %struct.Z { i32 10, ptr @.compoundliteral, i32 1 } +// CHECK: global %struct.Z { i32 10, ptr @.compoundliteral, i32 0 } diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index dfdf5dd96924d..db06279586c54 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -2,8 +2,7 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -ffp-exception-behavior=strict -emit-llvm -o - %s | FileCheck %s // A file-scope compound literal is a constant-initialized global even when its -// address is taken from a non-constant context, so its initializer must be -// emitted under constant-evaluation rules. +// address is taken from a non-constant context. struct RR { int r; }; struct Z { int x; const RR* y; int z; }; diff --git a/clang/test/SemaCXX/compound-literal.cpp b/clang/test/SemaCXX/compound-literal.cpp index 5062729c772c7..0b456747fb672 100644 --- a/clang/test/SemaCXX/compound-literal.cpp +++ b/clang/test/SemaCXX/compound-literal.cpp @@ -40,8 +40,10 @@ namespace brace_initializers { // CHECK: CompoundLiteralExpr {{.*}} 'POD'{{$}} // CHECK-NEXT: InitListExpr {{.*}} 'POD' explicit{{$}} // CHECK-NEXT: ConstantExpr {{.*}} + // CHECK-NEXT: value: Int 1 // CHECK-NEXT: IntegerLiteral {{.*}} 1{{$}} // CHECK-NEXT: ConstantExpr {{.*}} + // CHECK-NEXT: value: Int 2 // CHECK-NEXT: IntegerLiteral {{.*}} 2{{$}} void test() { @@ -137,3 +139,14 @@ namespace GH147949 { const S* x = (const S[]){S{S{3}}}; } #endif + +namespace GH212106 { + // Elements of a file-scope compound literal carry their evaluated value. + struct Z { int x; const int *y; }; + Z z = { 1, (const int[1]){__builtin_constant_p(z.x)} }; + // CHECK: CompoundLiteralExpr {{.*}} 'const int[1]' lvalue + // CHECK-NEXT: InitListExpr {{.*}} 'const int[1]' + // CHECK-NEXT: ConstantExpr {{.*}} 'int' + // CHECK-NEXT: value: Int 0 + // CHECK-NEXT: CallExpr {{.*}} 'int' +} >From 2f855764ee32bf7d923f68d36250cdfd6065f552 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 9 Sep 2026 22:24:12 +0530 Subject: [PATCH 03/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, so CodeGen emits the stored value. Elements that are rebuilt at each use site, such as source_location::current() in a default argument, keep the valueless wrapper. The bytecode interpreter now visits the subexpression of a ConstantExpr whose lvalue result it cannot re-materialize, such as a pointer into a string literal or an address cast to an integer. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/AST/ByteCode/Compiler.cpp | 9 ++++++++- clang/lib/Sema/SemaExpr.cpp | 7 +++++-- clang/test/AST/ByteCode/c.c | 4 ++++ clang/test/CodeGenCXX/GH212106.cpp | 5 +++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index daa5307c92298..fa2508aecbccd 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -2708,7 +2708,14 @@ bool Compiler<Emitter>::VisitConstantExpr(const ConstantExpr *E) { // diagnostics or any double values. if (DiscardResult) return true; - return this->visitAPValue(E->getAPValueResult(), *T, E); + const APValue &Val = E->getAPValueResult(); + // visitAPValue can only re-materialize an lvalue that is null or that + // designates a declaration. + if (Val.isLValue() && !Val.isNullPointer() && + !(Val.hasLValuePath() && + Val.getLValueBase().dyn_cast<const ValueDecl *>())) + return this->delegate(E->getSubExpr()); + return this->visitAPValue(Val, *T, E); } // Fall back to the subexpr for non-primitive APValues. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 463f14d47f278..fff3c21b3bf33 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7555,9 +7555,12 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, } // Store the value so CodeGen does not re-evaluate the element outside - // a constant context. + // a constant context. Elements that are rebuilt at each use site + // cannot be cached. + ImmediateCallVisitor V(Context); + V.TraverseStmt(Init); Expr::EvalResult Eval; - if (Init->isPRValue() && + if (!V.HasImmediateCalls && Init->isPRValue() && Init->EvaluateAsRValue(Eval, Context, /*InConstantContext=*/true) && !Eval.HasSideEffects && Eval.Val.hasValue()) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); diff --git a/clang/test/AST/ByteCode/c.c b/clang/test/AST/ByteCode/c.c index 5c1cebf2af998..a300750f70c0b 100644 --- a/clang/test/AST/ByteCode/c.c +++ b/clang/test/AST/ByteCode/c.c @@ -253,6 +253,10 @@ void unaryops(void) { (void)((struct ww {float x;}){3}.x--); } +/// Elements of a file-scope compound literal carry their evaluated value. +static long *addr_as_int = (long[]){2, (long)"x"}; +static const char **into_string = (const char *[]){&"abc"[1]}; + /// This used to fail because we didn't properly mark the struct /// initialized through a CompoundLiteralExpr as initialized. struct TestStruct { diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index db06279586c54..fe657972c041f 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -41,6 +41,11 @@ void h() { Q q; } // CHECK-DAG: [[QCL:@.compoundliteral(\.[0-9]+)?]] = internal constant [1 x i32] zeroinitializer +struct SP { int a; const char *const *s; }; +SP sp = { g(), (const char *const[1]){__builtin_constant_p(n) ? "a" : "b"} }; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x ptr] [ptr @.str{{(\.[0-9]+)?}}] + // CHECK-LABEL: define internal void @__cxx_global_var_init() // CHECK: store ptr [[Z2CL]], ptr getelementptr inbounds{{.*}}(i8, ptr @z2, i64 8) >From 0a6697c93b19f0233b691781f0dc3fc9e69e0821 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 9 Sep 2026 23:41:22 +0530 Subject: [PATCH 04/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, for both prvalue and reference-binding lvalue elements. Elements that are rebuilt per use site, such as source_location::current() in a default argument, are left uncached, as is a pointer cast to an integer. Fix the bytecode interpreter's visitAPValue to re-materialize an lvalue with an expression base and a path rather than asserting. Fixes #212106 --- clang/lib/AST/ByteCode/Compiler.cpp | 32 ++++++++++++++--------------- clang/lib/Sema/SemaExpr.cpp | 19 ++++++++++++----- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index fa2508aecbccd..5cc683ff77a2c 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -2708,14 +2708,7 @@ bool Compiler<Emitter>::VisitConstantExpr(const ConstantExpr *E) { // diagnostics or any double values. if (DiscardResult) return true; - const APValue &Val = E->getAPValueResult(); - // visitAPValue can only re-materialize an lvalue that is null or that - // designates a declaration. - if (Val.isLValue() && !Val.isNullPointer() && - !(Val.hasLValuePath() && - Val.getLValueBase().dyn_cast<const ValueDecl *>())) - return this->delegate(E->getSubExpr()); - return this->visitAPValue(Val, *T, E); + return this->visitAPValue(E->getAPValueResult(), *T, E); } // Fall back to the subexpr for non-primitive APValues. @@ -5861,16 +5854,21 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, return this->emitNull(ValType, 0, nullptr, Info); APValue::LValueBase Base = Val.getLValueBase(); - ArrayRef<APValue::LValuePathEntry> Path = Val.getLValuePath(); - - if (const Expr *BaseExpr = Base.dyn_cast<const Expr *>()) - return this->visit(BaseExpr); - if (const auto *VD = Base.dyn_cast<const ValueDecl *>()) { + QualType EntryType; + if (const Expr *BaseExpr = Base.dyn_cast<const Expr *>()) { + if (!this->visit(BaseExpr)) + return false; + EntryType = BaseExpr->getType(); + } else if (const auto *VD = Base.dyn_cast<const ValueDecl *>()) { if (!this->visitDeclRef(VD, Info.asExpr())) return false; + EntryType = VD->getType(); + } else { + return false; + } - QualType EntryType = VD->getType(); - for (auto &Entry : Path) { + if (Val.hasLValuePath()) { + for (auto &Entry : Val.getLValuePath()) { if (EntryType->isArrayType()) { uint64_t Index = Entry.getAsArrayIndex(); QualType ElemType = @@ -5907,9 +5905,9 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, } } } - - return true; } + + return true; } return false; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index fff3c21b3bf33..ca65fe828df90 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7555,14 +7555,23 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, } // Store the value so CodeGen does not re-evaluate the element outside - // a constant context. Elements that are rebuilt at each use site - // cannot be cached. + // a constant context. Elements rebuilt at each use site, such as + // source_location::current() in a default argument, must not be cached. ImmediateCallVisitor V(Context); V.TraverseStmt(Init); Expr::EvalResult Eval; - if (!V.HasImmediateCalls && Init->isPRValue() && - Init->EvaluateAsRValue(Eval, Context, /*InConstantContext=*/true) && - !Eval.HasSideEffects && Eval.Val.hasValue()) + bool Evaluated = + !V.HasImmediateCalls && + (Init->isGLValue() + ? Init->EvaluateAsLValue(Eval, Context, + /*InConstantContext=*/true) + : Init->EvaluateAsRValue(Eval, Context, + /*InConstantContext=*/true)); + // Don't cache a pointer cast to an integer; the interpreter cannot + // re-materialize an lvalue stored in an integer slot. + if (Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue() && + !(Eval.Val.isLValue() && + Init->getType()->isIntegralOrEnumerationType())) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else ILE->setInit(i, ConstantExpr::Create(Context, Init)); >From ccccd14e96506e13ded301c7b15c1e36e5a548e4 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Thu, 10 Sep 2026 00:49:19 +0530 Subject: [PATCH 05/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise. In a default argument or default member initializer, an element with an immediate call or source_location is left for the use site, where the initializer is rebuilt. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/AST/ByteCode/Compiler.cpp | 38 ++++++++++++++- clang/lib/Sema/SemaExpr.cpp | 74 ++++++++++++++++++++++------- clang/test/AST/ByteCode/c.c | 1 + clang/test/CodeGenCXX/GH212106.cpp | 24 ++++++++++ 4 files changed, 118 insertions(+), 19 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 5cc683ff77a2c..bcd26e9bc416d 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5863,6 +5863,28 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, if (!this->visitDeclRef(VD, Info.asExpr())) return false; EntryType = VD->getType(); + } else if (Base.is<TypeInfoLValue>()) { + EntryType = Base.getTypeInfoType(); + if (!this->emitGetTypeid(Base.get<TypeInfoLValue>() + .getType() + ->getCanonicalTypeUnqualified() + .getTypePtr(), + EntryType.getTypePtr(), Info)) + return false; + } else if (!Base) { + // An integer cast to a pointer. + const Expr *E = Info.asExpr(); + if (!E) + return false; + QualType PtrType = E->isGLValue() + ? Ctx.getASTContext().getPointerType(E->getType()) + : E->getType(); + uint64_t Offset = Val.getLValueOffset().getQuantity(); + if (!this->emitConst(Offset, PT_Uint64, Info)) + return false; + if (!this->emitGetIntPtr(PT_Uint64, PtrType.getTypePtr(), Info)) + return false; + return true; } else { return false; } @@ -5907,7 +5929,21 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, } } - return true; + if (isPtrType(ValType)) + return true; + + // A pointer cast to an integer is stored as an lvalue; cast it the same + // way the source expression does. + if (ValType == PT_IntAP || ValType == PT_IntAPS) { + const Expr *E = Info.asExpr(); + if (!E) + return false; + uint32_t BitWidth = Ctx.getBitWidth(E->getType()); + return ValType == PT_IntAP + ? this->emitCastPointerIntegralAP(BitWidth, Info) + : this->emitCastPointerIntegralAPS(BitWidth, Info); + } + return this->emitCastPointerIntegral(ValType, Info); } return false; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ca65fe828df90..e4e6d46274e85 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7449,6 +7449,31 @@ Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty, return BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, InitExpr); } +/// Whether the \p Index-th element of the semantic form of \p ILE initializes +/// a reference member, so that its initializer is a glvalue that binds. +static bool initializesReferenceMember(const InitListExpr *ILE, + unsigned Index) { + const RecordDecl *RD = ILE->getType()->getAsRecordDecl(); + if (!RD || ILE->isTransparent()) + return false; + if (RD->isUnion()) { + const FieldDecl *FD = ILE->getInitializedFieldInUnion(); + return FD && FD->getType()->isReferenceType(); + } + unsigned ElementNo = 0; + if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) + ElementNo = CXXRD->getNumBases(); + if (Index < ElementNo) + return false; + for (const FieldDecl *FD : RD->fields()) { + if (FD->isUnnamedBitField()) + continue; + if (ElementNo++ == Index) + return FD->getType()->isReferenceType(); + } + return false; +} + ExprResult Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc, Expr *LiteralExpr) { @@ -7541,41 +7566,54 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, // "If the compound literal occurs outside the body of a function, the // initializer list shall consist of constant expressions." if (IsFileScope) - if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) + if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) { + // A default argument or default member initializer containing an + // immediate call or source_location is rebuilt at each use site, where + // its elements are evaluated (see BuildCXXDefaultArgExpr). + bool InDefaultArgOrInit = + isCheckingDefaultArgumentOrInitializer() || + InnermostDeclarationWithDelayedImmediateInvocations().has_value(); for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); + // An immediate invocation is already a ConstantExpr and receives its + // value at the end of the full-expression. + if (isa<ConstantExpr>(Init)) + continue; if (Init->isTypeDependent() || Init->isValueDependent()) { ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } - if (!Init->isConstantInitializer(Context)) { + bool IsRef = initializesReferenceMember(ILE, i); + if (!Init->isConstantInitializer(Context, IsRef)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); return ExprError(); } // Store the value so CodeGen does not re-evaluate the element outside - // a constant context. Elements rebuilt at each use site, such as - // source_location::current() in a default argument, must not be cached. - ImmediateCallVisitor V(Context); - V.TraverseStmt(Init); + // a constant context. + bool DeferToUseSite = false; + if (InDefaultArgOrInit) { + ImmediateCallVisitor V(Context); + V.TraverseStmt(Init); + DeferToUseSite = V.HasImmediateCalls; + } Expr::EvalResult Eval; - bool Evaluated = - !V.HasImmediateCalls && - (Init->isGLValue() - ? Init->EvaluateAsLValue(Eval, Context, - /*InConstantContext=*/true) - : Init->EvaluateAsRValue(Eval, Context, - /*InConstantContext=*/true)); - // Don't cache a pointer cast to an integer; the interpreter cannot - // re-materialize an lvalue stored in an integer slot. - if (Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue() && - !(Eval.Val.isLValue() && - Init->getType()->isIntegralOrEnumerationType())) + bool Evaluated = false; + if (!DeferToUseSite) { + if (IsRef) + Evaluated = Init->EvaluateAsLValue(Eval, Context, + /*InConstantContext=*/true); + else if (Init->isPRValue()) + Evaluated = Init->EvaluateAsRValue(Eval, Context, + /*InConstantContext=*/true); + } + if (Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue()) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else ILE->setInit(i, ConstantExpr::Create(Context, Init)); } + } auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, LiteralExpr, IsFileScope); diff --git a/clang/test/AST/ByteCode/c.c b/clang/test/AST/ByteCode/c.c index a300750f70c0b..cfe12e5ca105d 100644 --- a/clang/test/AST/ByteCode/c.c +++ b/clang/test/AST/ByteCode/c.c @@ -256,6 +256,7 @@ void unaryops(void) { /// Elements of a file-scope compound literal carry their evaluated value. static long *addr_as_int = (long[]){2, (long)"x"}; static const char **into_string = (const char *[]){&"abc"[1]}; +static int **int_as_ptr = (int *[]){(int *)(intptr_t)16}; /// This used to fail because we didn't properly mark the struct /// initialized through a CompoundLiteralExpr as initialized. diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index fe657972c041f..7ee83fc45b550 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -46,6 +46,30 @@ SP sp = { g(), (const char *const[1]){__builtin_constant_p(n) ? "a" : "b"} }; // CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x ptr] [ptr @.str{{(\.[0-9]+)?}}] +// A reference member binds its element as an lvalue. +int gv; +struct RS { const int &r; int v; }; +RS rs = (RS){gv, __builtin_constant_p(n)}; + +// CHECK-DAG: @rs = {{.*}}global { ptr, i32 } { ptr @gv, i32 0 } + +// An immediate invocation is already a ConstantExpr. +consteval int cf() { return 3; } +const int *cp = (const int[1]){cf()}; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x i32] [i32 3] + +// A pointer cast to an integer is stored as an lvalue. +struct LI { int a; const long *l; }; +LI li = { g(), (const long[1]){(long)"x"} }; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x i64] [i64 ptrtoint (ptr @.str{{(\.[0-9]+)?}} to i64)] + +namespace std { class type_info; } +const std::type_info *const *tp = (const std::type_info *const[1]){&typeid(int)}; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x ptr] [ptr @_ZTIi] + // CHECK-LABEL: define internal void @__cxx_global_var_init() // CHECK: store ptr [[Z2CL]], ptr getelementptr inbounds{{.*}}(i8, ptr @z2, i64 8) >From 49523d40e0a6ef346db51a7f3a3b40c8b0e8085c Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 11 Sep 2026 14:48:17 +0530 Subject: [PATCH 06/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise. In a default argument or default member initializer, an element with an immediate call or source_location is left for the use site, where the initializer is rebuilt. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 30 ++++-------------------------- clang/test/CodeGenCXX/GH212106.cpp | 9 +++++++++ 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ff543079d1516..5f2525d87b2e7 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7449,31 +7449,6 @@ Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty, return BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, InitExpr); } -/// Whether the \p Index-th element of the semantic form of \p ILE initializes -/// a reference member, so that its initializer is a glvalue that binds. -static bool initializesReferenceMember(const InitListExpr *ILE, - unsigned Index) { - const RecordDecl *RD = ILE->getType()->getAsRecordDecl(); - if (!RD || ILE->isTransparent()) - return false; - if (RD->isUnion()) { - const FieldDecl *FD = ILE->getInitializedFieldInUnion(); - return FD && FD->getType()->isReferenceType(); - } - unsigned ElementNo = 0; - if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) - ElementNo = CXXRD->getNumBases(); - if (Index < ElementNo) - return false; - for (const FieldDecl *FD : RD->fields()) { - if (FD->isUnnamedBitField()) - continue; - if (ElementNo++ == Index) - return FD->getType()->isReferenceType(); - } - return false; -} - ExprResult Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc, Expr *LiteralExpr) { @@ -7583,7 +7558,10 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } - bool IsRef = initializesReferenceMember(ILE, i); + // Only a reference member is initialized by a glvalue, apart from a + // string literal initializing an array. + bool IsRef = Init->isGLValue() && + !isa<StringLiteral, ObjCEncodeExpr>(Init->IgnoreParens()); if (!Init->isConstantInitializer(Context, IsRef)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index 7ee83fc45b550..61adb0779929f 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -53,6 +53,15 @@ RS rs = (RS){gv, __builtin_constant_p(n)}; // CHECK-DAG: @rs = {{.*}}global { ptr, i32 } { ptr @gv, i32 0 } +// A string literal initializing an array is an lvalue that is not a +// reference binding. +const char *ps = (const char[4]){"abc"}; +struct CS { int a; const char (*s)[4]; }; +CS cs = { g(), (const char[2][4]){"abc", "def"} }; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [4 x i8] c"abc\00" +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [2 x [4 x i8]] {{\[}}[4 x i8] c"abc\00", [4 x i8] c"def\00"] + // An immediate invocation is already a ConstantExpr. consteval int cf() { return 3; } const int *cp = (const int[1]){cf()}; >From 10459ff63e485970d78e4e201abf72863012ccd6 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 12 Sep 2026 08:46:19 +0530 Subject: [PATCH 07/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise. In a default argument or default member initializer, an element with an immediate call or source_location is left for the use site, where the initializer is rebuilt. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 6 ++---- clang/test/CodeGenCXX/GH212106.cpp | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 5f2525d87b2e7..cbfd9a924026e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7558,10 +7558,8 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } - // Only a reference member is initialized by a glvalue, apart from a - // string literal initializing an array. - bool IsRef = Init->isGLValue() && - !isa<StringLiteral, ObjCEncodeExpr>(Init->IgnoreParens()); + // Only a reference member is initialized by a glvalue. + bool IsRef = Init->isGLValue(); if (!Init->isConstantInitializer(Context, IsRef)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index 61adb0779929f..a6b843fc49116 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -53,8 +53,7 @@ RS rs = (RS){gv, __builtin_constant_p(n)}; // CHECK-DAG: @rs = {{.*}}global { ptr, i32 } { ptr @gv, i32 0 } -// A string literal initializing an array is an lvalue that is not a -// reference binding. +// A string literal initializing an array element is a prvalue. const char *ps = (const char[4]){"abc"}; struct CS { int a; const char (*s)[4]; }; CS cs = { g(), (const char[2][4]){"abc", "def"} }; >From 053c20b94cf98d0da040495552308a8917f6999e Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Thu, 17 Sep 2026 09:18:12 +0530 Subject: [PATCH 08/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema, accept it when the evaluation succeeds without side effects, and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise. In a default argument or default member initializer, an element with an immediate call or source_location is left for the use site, where the initializer is rebuilt. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 26 +++++++++---------- .../AST/static-compound-literals-crash.cpp | 10 +++---- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index cbfd9a924026e..035afd52d1f89 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7558,9 +7558,17 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } - // Only a reference member is initialized by a glvalue. - bool IsRef = Init->isGLValue(); - if (!Init->isConstantInitializer(Context, IsRef)) { + // Only a reference member is initialized by a glvalue. Like other + // constant initializers, undefined behavior such as signed overflow + // is folded with a warning. + Expr::EvalResult Eval; + bool Evaluated = + Init->isGLValue() + ? Init->EvaluateAsLValue(Eval, Context, + /*InConstantContext=*/true) + : Init->EvaluateAsRValue(Eval, Context, + /*InConstantContext=*/true); + if (!Evaluated || Eval.HasSideEffects) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); return ExprError(); @@ -7574,17 +7582,7 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, V.TraverseStmt(Init); DeferToUseSite = V.HasImmediateCalls; } - Expr::EvalResult Eval; - bool Evaluated = false; - if (!DeferToUseSite) { - if (IsRef) - Evaluated = Init->EvaluateAsLValue(Eval, Context, - /*InConstantContext=*/true); - else if (Init->isPRValue()) - Evaluated = Init->EvaluateAsRValue(Eval, Context, - /*InConstantContext=*/true); - } - if (Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue()) + if (!DeferToUseSite && Eval.Val.hasValue()) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else ILE->setInit(i, ConstantExpr::Create(Context, Init)); diff --git a/clang/test/AST/static-compound-literals-crash.cpp b/clang/test/AST/static-compound-literals-crash.cpp index 838d67b2f3f17..977806281031d 100644 --- a/clang/test/AST/static-compound-literals-crash.cpp +++ b/clang/test/AST/static-compound-literals-crash.cpp @@ -1,9 +1,9 @@ -// FIXME: This test case currently crashes during codegen, despite the -// initializer for the CLE being constant. -// RUN: not --crash %clang_cc1 -verify -std=c++20 -emit-llvm %s -o - -// expected-no-diagnostics +// The temporary bound to the reference member is not lifetime-extended by the +// file-scope compound literal, so the element is not a constant initializer. +// FIXME: Extend the temporary's lifetime to that of the compound literal. +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s namespace case1 { struct RR { int&& r; }; struct Z { RR* x; }; -constinit Z z = { (RR[1]){1} }; +constinit Z z = { (RR[1]){1} }; // expected-error {{initializer element is not a compile-time constant}} } >From f3e49b741fec0ad2171490e88061013a2f5d3d01 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Thu, 17 Sep 2026 11:53:36 +0530 Subject: [PATCH 09/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise; the structural constant-initializer rules are consulted only when the evaluator cannot produce a value. In a default argument or default member initializer, an element with an immediate call or source_location is left for the use site, where the initializer is rebuilt. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/AST/ByteCode/Compiler.cpp | 17 ++++++------- clang/lib/Sema/SemaExpr.cpp | 24 +++++++++---------- .../AST/static-compound-literals-crash.cpp | 10 ++++---- clang/test/CodeGenCXX/GH212106.cpp | 2 ++ 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index bcd26e9bc416d..1867016fe232c 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5864,12 +5864,13 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, return false; EntryType = VD->getType(); } else if (Base.is<TypeInfoLValue>()) { + // The type_info object for the type the lvalue describes. + const Type *OperandType = Base.get<TypeInfoLValue>() + .getType() + ->getCanonicalTypeUnqualified() + .getTypePtr(); EntryType = Base.getTypeInfoType(); - if (!this->emitGetTypeid(Base.get<TypeInfoLValue>() - .getType() - ->getCanonicalTypeUnqualified() - .getTypePtr(), - EntryType.getTypePtr(), Info)) + if (!this->emitGetTypeid(OperandType, EntryType.getTypePtr(), Info)) return false; } else if (!Base) { // An integer cast to a pointer. @@ -5880,11 +5881,11 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, ? Ctx.getASTContext().getPointerType(E->getType()) : E->getType(); uint64_t Offset = Val.getLValueOffset().getQuantity(); - if (!this->emitConst(Offset, PT_Uint64, Info)) + if (!this->emitConstUint64(Offset, Info)) return false; - if (!this->emitGetIntPtr(PT_Uint64, PtrType.getTypePtr(), Info)) + if (!this->emitGetIntPtrUint64(PtrType.getTypePtr(), Info)) return false; - return true; + EntryType = PtrType->getPointeeType(); } else { return false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 035afd52d1f89..e082188f9991a 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7543,8 +7543,7 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, if (IsFileScope) if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) { // A default argument or default member initializer containing an - // immediate call or source_location is rebuilt at each use site, where - // its elements are evaluated (see BuildCXXDefaultArgExpr). + // immediate call or source_location is rebuilt at each use site. bool InDefaultArgOrInit = isCheckingDefaultArgumentOrInitializer() || InnermostDeclarationWithDelayedImmediateInvocations().has_value(); @@ -7558,17 +7557,18 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } - // Only a reference member is initialized by a glvalue. Like other - // constant initializers, undefined behavior such as signed overflow - // is folded with a warning. + // A glvalue element binds a reference member; store its address. + bool IsRef = Init->isGLValue(); Expr::EvalResult Eval; bool Evaluated = - Init->isGLValue() - ? Init->EvaluateAsLValue(Eval, Context, - /*InConstantContext=*/true) - : Init->EvaluateAsRValue(Eval, Context, - /*InConstantContext=*/true); - if (!Evaluated || Eval.HasSideEffects) { + IsRef ? Init->EvaluateAsLValue(Eval, Context, + /*InConstantContext=*/true) + : Init->EvaluateAsRValue(Eval, Context, + /*InConstantContext=*/true); + Evaluated = Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue(); + // Not every constant initializer evaluates to a value, e.g. a union + // that is non-trivial to destroy; fall back to the structural rules. + if (!Evaluated && !Init->isConstantInitializer(Context, IsRef)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); return ExprError(); @@ -7582,7 +7582,7 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, V.TraverseStmt(Init); DeferToUseSite = V.HasImmediateCalls; } - if (!DeferToUseSite && Eval.Val.hasValue()) + if (Evaluated && !DeferToUseSite) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else ILE->setInit(i, ConstantExpr::Create(Context, Init)); diff --git a/clang/test/AST/static-compound-literals-crash.cpp b/clang/test/AST/static-compound-literals-crash.cpp index 977806281031d..838d67b2f3f17 100644 --- a/clang/test/AST/static-compound-literals-crash.cpp +++ b/clang/test/AST/static-compound-literals-crash.cpp @@ -1,9 +1,9 @@ -// The temporary bound to the reference member is not lifetime-extended by the -// file-scope compound literal, so the element is not a constant initializer. -// FIXME: Extend the temporary's lifetime to that of the compound literal. -// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s +// FIXME: This test case currently crashes during codegen, despite the +// initializer for the CLE being constant. +// RUN: not --crash %clang_cc1 -verify -std=c++20 -emit-llvm %s -o - +// expected-no-diagnostics namespace case1 { struct RR { int&& r; }; struct Z { RR* x; }; -constinit Z z = { (RR[1]){1} }; // expected-error {{initializer element is not a compile-time constant}} +constinit Z z = { (RR[1]){1} }; } diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index a6b843fc49116..dd9dcb81bbf5f 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - %s | FileCheck %s // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -ffp-exception-behavior=strict -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -ffp-exception-behavior=strict -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck %s // A file-scope compound literal is a constant-initialized global even when its // address is taken from a non-constant context. >From 2a14121521e203b277e3e6b97d40a1d7020a3ce9 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 09:17:20 +0530 Subject: [PATCH 10/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise; the structural constant-initializer rules are consulted only when the evaluator cannot produce a value. An element with an immediate call or source_location in a default argument or default member initializer depends on the use site and is left alone until the initializer has been rebuilt there. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 31 +++++++++++++---------- clang/test/SemaObjC/non-trivial-c-union.m | 2 ++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index e082188f9991a..8f677362ad6b8 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7542,11 +7542,13 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, // initializer list shall consist of constant expressions." if (IsFileScope) if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) { - // A default argument or default member initializer containing an - // immediate call or source_location is rebuilt at each use site. - bool InDefaultArgOrInit = + // An element with an immediate call or source_location is left for the + // use site; a rebuild in an immediate function context is too early, as + // its default arguments still carry the definition's location. + bool DeferImmediate = isCheckingDefaultArgumentOrInitializer() || - InnermostDeclarationWithDelayedImmediateInvocations().has_value(); + (InnermostDeclarationWithDelayedImmediateInvocations().has_value() && + isImmediateFunctionContext()); for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); // An immediate invocation is already a ConstantExpr and receives its @@ -7557,6 +7559,15 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; } + if (DeferImmediate) { + ImmediateCallVisitor V(Context); + V.TraverseStmt(Init); + if (V.HasImmediateCalls) { + ILE->setInit(i, ConstantExpr::Create(Context, Init)); + continue; + } + } + // A glvalue element binds a reference member; store its address. bool IsRef = Init->isGLValue(); Expr::EvalResult Eval; @@ -7573,16 +7584,9 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, << Init->getSourceBitField(); return ExprError(); } - // Store the value so CodeGen does not re-evaluate the element outside // a constant context. - bool DeferToUseSite = false; - if (InDefaultArgOrInit) { - ImmediateCallVisitor V(Context); - V.TraverseStmt(Init); - DeferToUseSite = V.HasImmediateCalls; - } - if (Evaluated && !DeferToUseSite) + if (Evaluated) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else ILE->setInit(i, ConstantExpr::Create(Context, Init)); @@ -7592,7 +7596,8 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, LiteralExpr, IsFileScope); if (IsFileScope) { - if (!LiteralExpr->isTypeDependent() && + // The elements of an initializer list were checked above. + if (!isa<InitListExpr>(LiteralExpr) && !LiteralExpr->isTypeDependent() && !LiteralExpr->isValueDependent() && !literalType->isDependentType()) // C99 6.5.2.5p3 if (CheckForConstantInitializer(LiteralExpr)) diff --git a/clang/test/SemaObjC/non-trivial-c-union.m b/clang/test/SemaObjC/non-trivial-c-union.m index 39fbe2d33818e..06324541b734b 100644 --- a/clang/test/SemaObjC/non-trivial-c-union.m +++ b/clang/test/SemaObjC/non-trivial-c-union.m @@ -52,6 +52,8 @@ void testAssignment(void) { U0 ug2 = (U0){ .f1 = 0 }; // expected-error {{cannot copy-initialize an object of type 'U0' since it is a union that is non-trivial to copy}} S0 sg3 = (S0){ .f0 = {0}, .f1 = 0 }; // expected-error {{cannot copy-initialize an object of type 'S0' since it contains a union that is non-trivial to copy}} S0 *sg4 = &(S0){ .f1 = 0 }; // expected-error {{cannot default-initialize an object of type 'U0' since it is a union that is non-trivial to default-initialize}} +// A union element cannot be constant-evaluated, but is a constant initializer. +S0 *sg5 = &(S0){ .f0 = {0}, .f1 = 0 }; void testCompoundLiteral(void) { const U0 *t0 = &(U0){ .f0 = g0 }; // expected-error {{cannot construct an automatic compound literal of type 'U0' since it is a union that is non-trivial to destruct}} >From 768b6b0026a11bff0dca4f919fcd8d2581a36105 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 09:24:23 +0530 Subject: [PATCH 11/15] Reposition the release notes to avoid the conflicts --- clang/docs/ReleaseNotes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 36a9c35f1b78d..5a5dbc1e8b428 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -691,6 +691,11 @@ features cannot lower the translation-unit ABI level; - Fixed a crash in CTAD for type alias templates when the aggregate deduction guide could not be resolved. (#GH206994) - Fixed a crash when instantiating an invalid dependent friend destructor declaration in a class template. (#GH210234) +- Fixed an assertion failure when the dynamic initializer of a global variable + takes the address of a file-scope compound literal whose initializer is only + constant under constant-evaluation rules, such as `__builtin_constant_p` of a + non-constant expression. The elements of a file-scope compound literal are now + evaluated once in Sema and the results are stored in the AST. (#GH212106) - Fixed an assertion failure in `-extract-api` when a documentation comment contains invalid UTF-8. (#GH212393) - Fixed a crash in codegen on 32-bit targets caused by a struct too large to @@ -709,11 +714,6 @@ features cannot lower the translation-unit ABI level; - Fixed an assertion when the `dim` argument to an OpenACC `gang` clause evaluated to a value not representable by a signed integer, such as an unsigned wrap around. (#GH221418) -- Fixed an assertion failure when the dynamic initializer of a global variable - takes the address of a file-scope compound literal whose initializer is only - constant under constant-evaluation rules, such as `__builtin_constant_p` of a - non-constant expression. The elements of a file-scope compound literal are now - evaluated once in Sema and the results are stored in the AST. (#GH212106) ### OpenACC Specific Changes >From f9cca510d0b6afdf57d78aa116e9ef211ce68563 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 10:28:30 +0530 Subject: [PATCH 12/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise; the structural constant-initializer rules are consulted only when the evaluator cannot produce a value. An element with an immediate call or source_location in a default argument or default member initializer depends on the use site and is left alone until the initializer has been rebuilt there with the use-site location. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 8f677362ad6b8..ed3e2a810fa4f 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7551,10 +7551,6 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, isImmediateFunctionContext()); for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); - // An immediate invocation is already a ConstantExpr and receives its - // value at the end of the full-expression. - if (isa<ConstantExpr>(Init)) - continue; if (Init->isTypeDependent() || Init->isValueDependent()) { ILE->setInit(i, ConstantExpr::Create(Context, Init)); continue; @@ -7585,7 +7581,10 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, return ExprError(); } // Store the value so CodeGen does not re-evaluate the element outside - // a constant context. + // a constant context; an immediate invocation already is a + // ConstantExpr. + if (isa<ConstantExpr>(Init)) + continue; if (Evaluated) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); else >From 48c5012620f6be250d9769fc9ca996cee3b73bf3 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 11:37:42 +0530 Subject: [PATCH 13/15] [clang][Sema] Store the evaluated elements of file-scope compound literals A file-scope compound literal must have a constant initializer. Sema checks each element in a constant context and wraps it in a ConstantExpr, but does not store the value, so CodeGen evaluated the element again under whatever context it happened to be in. When the literal's address is taken by the dynamic initializer of another global that context is non-constant, __builtin_constant_p of a non-foldable operand refuses to fold, and tryEmitGlobalCompoundLiteral hits its assertion. Evaluate each element once in Sema and store the result in the existing ConstantExpr wrapper, as an lvalue for a reference member and an rvalue otherwise; the structural constant-initializer rules are consulted only when the evaluator cannot produce a value. An element with an immediate call or source_location in a default argument or default member initializer depends on the use site and is left alone until the initializer has been rebuilt there with the use-site location. Teach the bytecode interpreter's visitAPValue to re-materialize every lvalue the evaluator can store, including a pointer cast to an integer, instead of asserting on a missing designator. This also makes the constant evaluator and CodeGen agree on the literal's contents. Fixes #212106 --- clang/lib/Sema/SemaExpr.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 8aaebe2eb7a5b..32a80c9058235 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7645,12 +7645,12 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, if (IsFileScope) if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) { // An element with an immediate call or source_location is left for the - // use site; a rebuild in an immediate function context is too early, as - // its default arguments still carry the definition's location. + // use site, and for its rebuild too when the rebuilt default arguments + // could not be given the use-site location. bool DeferImmediate = isCheckingDefaultArgumentOrInitializer() || - (InnermostDeclarationWithDelayedImmediateInvocations().has_value() && - isImmediateFunctionContext()); + (currentEvaluationContext().DelayedDefaultInitializationContext && + !OutermostDeclarationWithDelayedImmediateInvocations()); for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); if (Init->isTypeDependent() || Init->isValueDependent()) { >From 3193bc2f4055d796c6a4b2bd45a8948926808479 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 19 Sep 2026 09:06:12 +0530 Subject: [PATCH 14/15] [clang][Sema] Evaluate file-scope compound literal elements in place Add Expr::EvaluateAsConstantInitializer, which evaluates an expression as the initializer of a static-storage object: in place, so a class or array prvalue is not materialized as a temporary whose un-run destruction counts as a side effect, and a glvalue yields the address a reference binds to. Sema::BuildCompoundLiteralExpr accepts a file-scope compound literal element only if this evaluation succeeds; the isConstantInitializer fallback is gone. A non-trivial C union element and an element of a class with a non-trivial destructor now evaluate like a variable's initializer, and a reference member bound to a temporary is diagnosed instead of crashing CodeGen. --- clang/include/clang/AST/Expr.h | 7 +++ clang/lib/AST/ExprConstant.cpp | 46 +++++++++++++++++++ clang/lib/Sema/SemaExpr.cpp | 18 +------- .../AST/static-compound-literals-crash.cpp | 9 ++-- clang/test/CodeGenCXX/GH212106.cpp | 6 +++ clang/test/SemaObjC/non-trivial-c-union.m | 1 - 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index c03c88232e13d..c212afbe1a74d 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -757,6 +757,13 @@ class Expr : public ValueStmt { EvalResult &Result, bool IsConstantInitializer) const; + /// EvaluateAsConstantInitializer - Evaluate an expression as if it were the + /// initializer of an object with static storage duration: in place, so that + /// no temporary is materialized, and with a glvalue yielding the address it + /// binds a reference to. Undefined behavior is allowed, side effects are not. + bool EvaluateAsConstantInitializer(EvalResult &Result, + const ASTContext &Ctx) const; + /// EvaluateWithSubstitution - Evaluate an expression as if from the context /// of a call to the given function with the given arguments, inside an /// unevaluated context. Returns true if the expression could be folded to a diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 9242491832841..ee091ffdb3391 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -22210,6 +22210,52 @@ bool Expr::EvaluateAsInitializer(const ASTContext &Ctx, const VarDecl *VD, CheckMemoryLeaks(Info); } +bool Expr::EvaluateAsConstantInitializer(EvalResult &Result, + const ASTContext &Ctx) const { + assert(!isValueDependent() && + "Expression evaluator can't be called on a dependent expression."); + bool IsConst; + if (FastEvaluateAsRValue(this, Result.Val, Ctx, IsConst) && + Result.Val.hasValue()) + return true; + + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsConstantInitializer"); + // Fold through undefined behavior, as isConstantInitializer allows. + EvalInfo Info(Ctx, Result, EvaluationMode::IgnoreSideEffects); + Info.InConstantContext = true; + + if (Info.EnableNewConstInterp) { + auto &InterpCtx = Info.Ctx.getInterpContext(); + // Keep a glvalue's address; do not destroy a prvalue's result object. + if (isGLValue() ? !InterpCtx.evaluate(Info, this, Result.Val, + ConstantExprKind::Normal) + : !InterpCtx.evaluateAsRValue(Info, this, Result.Val)) + return false; + return !Result.HasSideEffects && + CheckConstantExpression(Info, getExprLoc(), + getStorageType(Ctx, this), Result.Val, + ConstantExprKind::Normal); + } + + // Initialize a stand-in for the object in place, as EvaluateAsConstantExpr + // does, rather than a temporary whose destruction counts as a side effect. + MaterializeTemporaryExpr BaseMTE(getType(), const_cast<Expr *>(this), true); + APValue::LValueBase Base(&BaseMTE); + Info.setEvaluatingDecl(Base, Result.Val); + + LValue LVal; + LVal.set(Base); + FullExpressionRAII Scope(Info); + if (!::EvaluateInPlace(Result.Val, Info, LVal, this) || !Scope.destroy() || + !Info.discardCleanups() || Result.HasSideEffects) + return false; + + // A glvalue is checked as the reference it binds to. + return CheckConstantExpression(Info, getExprLoc(), getStorageType(Ctx, this), + Result.Val, ConstantExprKind::Normal) && + CheckMemoryLeaks(Info); +} + bool VarDecl::evaluateDestruction( SmallVectorImpl<PartialDiagnosticAt> &Notes) const { // This function is only meaningful for records and arrays of records. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 32a80c9058235..9f115f175ab9c 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7666,18 +7666,8 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, } } - // A glvalue element binds a reference member; store its address. - bool IsRef = Init->isGLValue(); Expr::EvalResult Eval; - bool Evaluated = - IsRef ? Init->EvaluateAsLValue(Eval, Context, - /*InConstantContext=*/true) - : Init->EvaluateAsRValue(Eval, Context, - /*InConstantContext=*/true); - Evaluated = Evaluated && !Eval.HasSideEffects && Eval.Val.hasValue(); - // Not every constant initializer evaluates to a value, e.g. a union - // that is non-trivial to destroy; fall back to the structural rules. - if (!Evaluated && !Init->isConstantInitializer(Context, IsRef)) { + if (!Init->EvaluateAsConstantInitializer(Eval, Context)) { Diag(Init->getExprLoc(), diag::err_init_element_not_constant) << Init->getSourceBitField(); return ExprError(); @@ -7685,12 +7675,8 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, // Store the value so CodeGen does not re-evaluate the element outside // a constant context; an immediate invocation already is a // ConstantExpr. - if (isa<ConstantExpr>(Init)) - continue; - if (Evaluated) + if (!isa<ConstantExpr>(Init)) ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); - else - ILE->setInit(i, ConstantExpr::Create(Context, Init)); } } diff --git a/clang/test/AST/static-compound-literals-crash.cpp b/clang/test/AST/static-compound-literals-crash.cpp index 838d67b2f3f17..965c1f6037ba0 100644 --- a/clang/test/AST/static-compound-literals-crash.cpp +++ b/clang/test/AST/static-compound-literals-crash.cpp @@ -1,9 +1,8 @@ -// FIXME: This test case currently crashes during codegen, despite the -// initializer for the CLE being constant. -// RUN: not --crash %clang_cc1 -verify -std=c++20 -emit-llvm %s -o - -// expected-no-diagnostics +// FIXME: The temporary bound to the reference member is not lifetime-extended +// like it would be for a static variable, so the initializer is rejected. +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s namespace case1 { struct RR { int&& r; }; struct Z { RR* x; }; -constinit Z z = { (RR[1]){1} }; +constinit Z z = { (RR[1]){1} }; // expected-error {{initializer element is not a compile-time constant}} } diff --git a/clang/test/CodeGenCXX/GH212106.cpp b/clang/test/CodeGenCXX/GH212106.cpp index dd9dcb81bbf5f..c187088fa7f4e 100644 --- a/clang/test/CodeGenCXX/GH212106.cpp +++ b/clang/test/CodeGenCXX/GH212106.cpp @@ -80,6 +80,12 @@ const std::type_info *const *tp = (const std::type_info *const[1]){&typeid(int)} // CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal constant [1 x ptr] [ptr @_ZTIi] +// An element of non-literal type is initialized in place, like a variable. +struct D { int a; ~D(); }; +const D *dp = (const D[1]){{5}}; + +// CHECK-DAG: @.compoundliteral{{(\.[0-9]+)?}} = internal global [1 x %struct.D] [%struct.D { i32 5 }] + // CHECK-LABEL: define internal void @__cxx_global_var_init() // CHECK: store ptr [[Z2CL]], ptr getelementptr inbounds{{.*}}(i8, ptr @z2, i64 8) diff --git a/clang/test/SemaObjC/non-trivial-c-union.m b/clang/test/SemaObjC/non-trivial-c-union.m index 06324541b734b..a9cc779368be4 100644 --- a/clang/test/SemaObjC/non-trivial-c-union.m +++ b/clang/test/SemaObjC/non-trivial-c-union.m @@ -52,7 +52,6 @@ void testAssignment(void) { U0 ug2 = (U0){ .f1 = 0 }; // expected-error {{cannot copy-initialize an object of type 'U0' since it is a union that is non-trivial to copy}} S0 sg3 = (S0){ .f0 = {0}, .f1 = 0 }; // expected-error {{cannot copy-initialize an object of type 'S0' since it contains a union that is non-trivial to copy}} S0 *sg4 = &(S0){ .f1 = 0 }; // expected-error {{cannot default-initialize an object of type 'U0' since it is a union that is non-trivial to default-initialize}} -// A union element cannot be constant-evaluated, but is a constant initializer. S0 *sg5 = &(S0){ .f0 = {0}, .f1 = 0 }; void testCompoundLiteral(void) { >From 6a3477852d5fe61756603b8fd19ed66fa48e0b7d Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 22 Sep 2026 10:20:13 +0530 Subject: [PATCH 15/15] [clang][Sema] Evaluate compound literal initializers as ConstantExprKind::Initializer Fold Expr::EvaluateAsConstantInitializer into EvaluateAsConstantExpr with a new ConstantExprKind::Initializer, which folds through undefined behavior and keeps the initialized object alive in the bytecode interpreter. A non-list initializer of a file-scope compound literal, a class with a constructor in C++, is now checked and stored like a single element, so CheckForConstantInitializer is no longer used there. --- clang/include/clang/AST/Expr.h | 11 ++-- clang/lib/AST/ByteCode/Context.cpp | 4 +- clang/lib/AST/ExprConstant.cpp | 61 +++--------------- clang/lib/Sema/SemaExpr.cpp | 84 ++++++++++++------------- clang/test/SemaCXX/compound-literal.cpp | 11 ++++ 5 files changed, 70 insertions(+), 101 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index c212afbe1a74d..8698912c76850 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -757,13 +757,6 @@ class Expr : public ValueStmt { EvalResult &Result, bool IsConstantInitializer) const; - /// EvaluateAsConstantInitializer - Evaluate an expression as if it were the - /// initializer of an object with static storage duration: in place, so that - /// no temporary is materialized, and with a glvalue yielding the address it - /// binds a reference to. Undefined behavior is allowed, side effects are not. - bool EvaluateAsConstantInitializer(EvalResult &Result, - const ASTContext &Ctx) const; - /// EvaluateWithSubstitution - Evaluate an expression as if from the context /// of a call to the given function with the given arguments, inside an /// unevaluated context. Returns true if the expression could be folded to a @@ -786,6 +779,10 @@ class Expr : public ValueStmt { /// evaluation is not part of the evaluation, but all other temporaries /// are destroyed. ImmediateInvocation, + /// The initializer of an object with static storage duration. The object + /// is not destroyed, and undefined behavior is folded through like + /// isConstantInitializer allows. + Initializer, }; /// Evaluate an expression that is required to be a constant expression. Does diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index d96bc68217576..8a9ce8be2b81a 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -107,8 +107,10 @@ bool Context::evaluate(State &Parent, const Expr *E, APValue &Result, size_t StackSizeBefore = Stk.size(); Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc); + // The object of an initializer outlives the evaluation. auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/false, - /*DestroyToplevelScope=*/true); + /*DestroyToplevelScope=*/Kind != + ConstantExprKind::Initializer); if (Res.isInvalid()) { C.cleanup(); Stk.clearTo(StackSizeBefore); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index ee091ffdb3391..bc44f8259a90a 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -158,6 +158,7 @@ namespace { case ConstantExprKind::Normal: case ConstantExprKind::ClassTemplateArgument: case ConstantExprKind::ImmediateInvocation: + case ConstantExprKind::Initializer: // Note that non-type template arguments of class type are emitted as // template parameter objects. return false; @@ -172,6 +173,7 @@ namespace { switch (Kind) { case ConstantExprKind::Normal: case ConstantExprKind::ImmediateInvocation: + case ConstantExprKind::Initializer: return false; case ConstantExprKind::ClassTemplateArgument: @@ -22088,12 +22090,15 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, return true; ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsConstantExpr"); - EvaluationMode EM = EvaluationMode::ConstantExpression; + EvaluationMode EM = Kind == ConstantExprKind::Initializer + ? EvaluationMode::IgnoreSideEffects + : EvaluationMode::ConstantExpression; EvalInfo Info(Ctx, Result, EM); Info.InConstantContext = true; if (Info.EnableNewConstInterp) { - if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, Kind)) + if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, Kind) || + Result.HasSideEffects) return false; return CheckConstantExpression(Info, getExprLoc(), getStorageType(Ctx, this), Result.Val, Kind); @@ -22118,14 +22123,14 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, // So we need to make sure temporary objects are destroyed after having // evaluating the expression (per C++23 [class.temporary]/p4). FullExpressionRAII Scope(Info); - if (!::EvaluateInPlace(Result.Val, Info, LVal, this) || - Result.HasSideEffects || !Scope.destroy()) + if (!::EvaluateInPlace(Result.Val, Info, LVal, this) || !Scope.destroy()) return false; if (!Info.discardCleanups()) llvm_unreachable("Unhandled cleanup; missing full expression marker?"); - if (!CheckConstantExpression(Info, getExprLoc(), getStorageType(Ctx, this), + if (Result.HasSideEffects || + !CheckConstantExpression(Info, getExprLoc(), getStorageType(Ctx, this), Result.Val, Kind)) return false; if (!CheckMemoryLeaks(Info)) @@ -22210,52 +22215,6 @@ bool Expr::EvaluateAsInitializer(const ASTContext &Ctx, const VarDecl *VD, CheckMemoryLeaks(Info); } -bool Expr::EvaluateAsConstantInitializer(EvalResult &Result, - const ASTContext &Ctx) const { - assert(!isValueDependent() && - "Expression evaluator can't be called on a dependent expression."); - bool IsConst; - if (FastEvaluateAsRValue(this, Result.Val, Ctx, IsConst) && - Result.Val.hasValue()) - return true; - - ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsConstantInitializer"); - // Fold through undefined behavior, as isConstantInitializer allows. - EvalInfo Info(Ctx, Result, EvaluationMode::IgnoreSideEffects); - Info.InConstantContext = true; - - if (Info.EnableNewConstInterp) { - auto &InterpCtx = Info.Ctx.getInterpContext(); - // Keep a glvalue's address; do not destroy a prvalue's result object. - if (isGLValue() ? !InterpCtx.evaluate(Info, this, Result.Val, - ConstantExprKind::Normal) - : !InterpCtx.evaluateAsRValue(Info, this, Result.Val)) - return false; - return !Result.HasSideEffects && - CheckConstantExpression(Info, getExprLoc(), - getStorageType(Ctx, this), Result.Val, - ConstantExprKind::Normal); - } - - // Initialize a stand-in for the object in place, as EvaluateAsConstantExpr - // does, rather than a temporary whose destruction counts as a side effect. - MaterializeTemporaryExpr BaseMTE(getType(), const_cast<Expr *>(this), true); - APValue::LValueBase Base(&BaseMTE); - Info.setEvaluatingDecl(Base, Result.Val); - - LValue LVal; - LVal.set(Base); - FullExpressionRAII Scope(Info); - if (!::EvaluateInPlace(Result.Val, Info, LVal, this) || !Scope.destroy() || - !Info.discardCleanups() || Result.HasSideEffects) - return false; - - // A glvalue is checked as the reference it binds to. - return CheckConstantExpression(Info, getExprLoc(), getStorageType(Ctx, this), - Result.Val, ConstantExprKind::Normal) && - CheckMemoryLeaks(Info); -} - bool VarDecl::evaluateDestruction( SmallVectorImpl<PartialDiagnosticAt> &Notes) const { // This function is only meaningful for records and arrays of records. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 9f115f175ab9c..198f50bd04669 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7642,55 +7642,55 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, // C99 6.5.2.5 // "If the compound literal occurs outside the body of a function, the // initializer list shall consist of constant expressions." - if (IsFileScope) - if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) { - // An element with an immediate call or source_location is left for the - // use site, and for its rebuild too when the rebuilt default arguments - // could not be given the use-site location. - bool DeferImmediate = - isCheckingDefaultArgumentOrInitializer() || - (currentEvaluationContext().DelayedDefaultInitializationContext && - !OutermostDeclarationWithDelayedImmediateInvocations()); + if (IsFileScope) { + // An element with an immediate call or source_location is left for the + // use site, and for its rebuild too when the rebuilt default arguments + // could not be given the use-site location. + bool DeferImmediate = + isCheckingDefaultArgumentOrInitializer() || + (currentEvaluationContext().DelayedDefaultInitializationContext && + !OutermostDeclarationWithDelayedImmediateInvocations()); + // Store the element's value so CodeGen does not re-evaluate it outside a + // constant context. + auto CheckElement = [&](Expr *Init) -> Expr * { + if (Init->isTypeDependent() || Init->isValueDependent()) + return ConstantExpr::Create(Context, Init); + if (DeferImmediate) { + ImmediateCallVisitor V(Context); + V.TraverseStmt(Init); + if (V.HasImmediateCalls) + return ConstantExpr::Create(Context, Init); + } + Expr::EvalResult Eval; + if (!Init->EvaluateAsConstantExpr(Eval, Context, + ConstantExprKind::Initializer)) { + Diag(Init->getExprLoc(), diag::err_init_element_not_constant) + << Init->getSourceBitField(); + return nullptr; + } + // An immediate invocation already is a ConstantExpr. + if (isa<ConstantExpr>(Init)) + return Init; + return ConstantExpr::Create(Context, Init, Eval.Val); + }; + if (auto *ILE = dyn_cast<InitListExpr>(LiteralExpr)) { for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { - Expr *Init = ILE->getInit(i); - if (Init->isTypeDependent() || Init->isValueDependent()) { - ILE->setInit(i, ConstantExpr::Create(Context, Init)); - continue; - } - if (DeferImmediate) { - ImmediateCallVisitor V(Context); - V.TraverseStmt(Init); - if (V.HasImmediateCalls) { - ILE->setInit(i, ConstantExpr::Create(Context, Init)); - continue; - } - } - - Expr::EvalResult Eval; - if (!Init->EvaluateAsConstantInitializer(Eval, Context)) { - Diag(Init->getExprLoc(), diag::err_init_element_not_constant) - << Init->getSourceBitField(); + Expr *Init = CheckElement(ILE->getInit(i)); + if (!Init) return ExprError(); - } - // Store the value so CodeGen does not re-evaluate the element outside - // a constant context; an immediate invocation already is a - // ConstantExpr. - if (!isa<ConstantExpr>(Init)) - ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val)); + ILE->setInit(i, Init); } + } else { + LiteralExpr = CheckElement(LiteralExpr); + if (!LiteralExpr) + return ExprError(); } + } auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, LiteralExpr, IsFileScope); - if (IsFileScope) { - // The elements of an initializer list were checked above. - if (!isa<InitListExpr>(LiteralExpr) && !LiteralExpr->isTypeDependent() && - !LiteralExpr->isValueDependent() && - !literalType->isDependentType()) // C99 6.5.2.5p3 - if (CheckForConstantInitializer(LiteralExpr)) - return ExprError(); - } else if (literalType.getAddressSpace() != LangAS::opencl_private && - literalType.getAddressSpace() != LangAS::Default) { + if (!IsFileScope && literalType.getAddressSpace() != LangAS::opencl_private && + literalType.getAddressSpace() != LangAS::Default) { // Embedded-C extensions to C99 6.5.2.5: // "If the compound literal occurs inside the body of a function, the // type name shall not be qualified by an address-space qualifier." diff --git a/clang/test/SemaCXX/compound-literal.cpp b/clang/test/SemaCXX/compound-literal.cpp index 0b456747fb672..960235555c3d9 100644 --- a/clang/test/SemaCXX/compound-literal.cpp +++ b/clang/test/SemaCXX/compound-literal.cpp @@ -149,4 +149,15 @@ namespace GH212106 { // CHECK-NEXT: ConstantExpr {{.*}} 'int' // CHECK-NEXT: value: Int 0 // CHECK-NEXT: CallExpr {{.*}} 'int' + +#if __cplusplus >= 201103L + // A class with a constructor is initialized by one expression. + struct C { constexpr C(int v) : v(v) {} int v; }; + C c = (C){__builtin_constant_p(z.x)}; + // CHECK-CXX11: CompoundLiteralExpr {{.*}} '{{(GH212106::)?}}C' + // CHECK-CXX11-NEXT: ConstantExpr {{.*}} '{{(GH212106::)?}}C' + // CHECK-CXX11-NEXT: value: Struct + // CHECK-CXX11-NEXT: field: Int 0 + // CHECK-CXX11-NEXT: CXXTemporaryObjectExpr +#endif } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
