[clang] [clang][CodeGen] Emit file-scope compound literals in a constant context (PR #221390)
Akash Manna via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 8 23:06:39 PDT 2026
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 <akash.manna.mymail at gmail.com>
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 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 <akash.manna.mymail at gmail.com>
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 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'
+}
More information about the cfe-commits
mailing list