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