https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/221390
>From 95877d45240fc177e5eecf2173e2ca3b89e95fb3 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 5 Sep 2026 10:28:31 +0530 Subject: [PATCH 1/2] [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 a49971adef86f..bd178fecea7ad 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -676,6 +676,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 1a310411c447b9cd3343ab957f1bf10885ff3fd6 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 9 Sep 2026 09:36:20 +0530 Subject: [PATCH 2/2] [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 bd178fecea7ad..6107fbbdfa1c3 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -679,7 +679,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 c93efeb928c56..4652d73c78753 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' +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
