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 1/7] [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 0fb6dcf59d4c9f..0192a03c89aff3 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 d8a3aa9c1c5580..81e366ffaa7458 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 48e80910ce5777..cf2a4ff18cea69 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 f9c3bd82fd025d..838d67b2f3f176 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 00000000000000..dfdf5dd96924d4 --- /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 2/7] [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 0192a03c89aff3..e6f7c71a563766 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 81e366ffaa7458..d8a3aa9c1c5580 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 cf2a4ff18cea69..48e80910ce5777 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 a1514ebae40f18..463f14d47f2784 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 bd8f0f27af24a6..29e21798b9295c 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 dfdf5dd96924d4..db06279586c54b 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 5062729c772c72..0b456747fb6720 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 3/7] [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 daa5307c92298c..fa2508aecbccda 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 463f14d47f2784..fff3c21b3bf333 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 5c1cebf2af998f..a300750f70c0b7 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 db06279586c54b..fe657972c041f7 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 4/7] [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 fa2508aecbccda..5cc683ff77a2c2 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 fff3c21b3bf333..ca65fe828df90e 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 5/7] [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 5cc683ff77a2c2..bcd26e9bc416d7 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 ca65fe828df90e..e4e6d46274e85f 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 a300750f70c0b7..cfe12e5ca105db 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 fe657972c041f7..7ee83fc45b5502 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 6/7] [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 ff543079d15161..5f2525d87b2e77 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 7ee83fc45b5502..61adb0779929f8 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 7/7] [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 5f2525d87b2e77..cbfd9a924026ef 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 61adb0779929f8..a6b843fc49116e 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"} }; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
