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/6] [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/6] [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/6] [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/6] [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/6] [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/6] [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()};

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to