https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/221390
Fixes #212106 A file-scope compound literal is always a constant-initialized global, and Sema checks its initializer in a constant context, where `__builtin_constant_p` of something it can't fold just yields 0. CodeGen evaluated the same initializer under whatever context the *caller* was in. When the literal's address is taken by a global that needs dynamic initialization, that context is non-constant, `__builtin_constant_p` refuses to fold, and `tryEmitGlobalCompoundLiteral` asserts. The strict-FP variant of this was fixed in D131555 by propagating the caller's context, which didn't cover this entry point. The literal's constant-ness is a property of the literal, not of whoever asks for its address, so `tryEmitGlobalCompoundLiteral` now sets the constant context itself for any file-scope literal. That covers both callers and keeps the assertion in place. The same two lines are mirrored in CIR, which has the identical check. >From 4ce1af3cb393648bb95507354c85c78f46673416 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 5 Sep 2026 10:28:31 +0530 Subject: [PATCH] [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 042d7112dbe7d..18de7e72abef4 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -652,6 +652,10 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when generating fake uses for parameters of bodyless destructors with `-fextend-variable-liveness`. - Fixed an assertion failure when instantiating a block that captures `this` via a member access through a dependent base class. +- 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 4bebe053f5768..d036d73388100 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -941,6 +941,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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
