[clang] [clang][codegen] Prevent inline weak static locals from being placed in .rodata if not formally constant-initialized (PR #226645)

Divyansh Yadav via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 27 01:33:49 PDT 2026


https://github.com/schizophrenicmaniac updated https://github.com/llvm/llvm-project/pull/226645

>From 045a77f1533f3a4659c21565015753e3d8631ebc Mon Sep 17 00:00:00 2001
From: Divyansh <anshmcs at gmail.com>
Date: Sat, 26 Sep 2026 11:56:14 +0530
Subject: [PATCH] [clang][CodeGen] Use guarded init for weak static locals
 without constant init (#226631)

---
 clang/lib/CodeGen/CGDecl.cpp                  | 22 ++++--
 .../static-local-inline-non-constant-init.cpp | 77 +++++++++++++++++++
 2 files changed, 94 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp

diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index e1ed66ae71243..bbf94e8c4ecce 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -224,10 +224,6 @@ void CodeGenFunction::EmitVarDecl(const VarDecl &D) {
     llvm::GlobalValue::LinkageTypes Linkage =
         CGM.getLLVMLinkageVarDefinition(&D);
 
-    // FIXME: We need to force the emission/use of a guard variable for
-    // some variables even if we can constant-evaluate them because
-    // we can't guarantee every translation unit will constant-evaluate them.
-
     return EmitStaticVarDecl(D, Linkage);
   }
 
@@ -360,8 +356,24 @@ llvm::Constant *CodeGenModule::getOrCreateStaticVarDecl(
 llvm::GlobalVariable *
 CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D,
                                                llvm::GlobalVariable *GV) {
+  // A static local with vague linkage (e.g. one in an inline function) is
+  // shared by every translation unit that emits it, but only a constant
+  // initializer is guaranteed to be emitted as a constant in all of them. If
+  // we were to fold an initializer that another translation unit can't fold
+  // (say, because it reads a variable whose initializer is only visible here),
+  // that translation unit would perform a guarded initialization which could
+  // store to our copy of the variable (possibly placed in read-only memory) or
+  // race with our unguarded reads of it. Use a guarded initialization here as
+  // well, so that every translation unit agrees on how the variable is
+  // initialized.
+  bool NeedsGuardedInit =
+      getLangOpts().CPlusPlus && !getLangOpts().CUDAIsDevice &&
+      GV->isWeakForLinker() && !D.hasConstantInitialization() &&
+      !D.hasFlexibleArrayInit(getContext());
+
   ConstantEmitter emitter(*this);
-  llvm::Constant *Init = emitter.tryEmitForInitializer(D);
+  llvm::Constant *Init =
+      NeedsGuardedInit ? nullptr : emitter.tryEmitForInitializer(D);
 
   // If constant emission failed, then this should be a C++ static
   // initializer.
diff --git a/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp b/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp
new file mode 100644
index 0000000000000..ec78d2d90e360
--- /dev/null
+++ b/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -emit-llvm -o - %s | FileCheck %s --check-prefix=ITANIUM
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c++17 -emit-llvm -o - %s | FileCheck %s --check-prefix=MSVC
+
+// A static local in an inline function is shared by every translation unit
+// that uses the function. If its initializer isn't a constant initializer,
+// another translation unit might not be able to fold it (here, one that can't
+// see the initializer of kSize) and will initialize the variable dynamically,
+// behind a guard variable. So it must not be emitted as a (possibly read-only)
+// constant without a guard in this translation unit either.
+// See https://github.com/llvm/llvm-project/issues/226631.
+
+extern const int kSize;
+
+inline const int &instance() {
+  static const int meta = kSize;
+  return meta;
+}
+
+// Constant initializers are still emitted as constants, without a guard.
+inline const int &constant() {
+  static const int c = 42;
+  return c;
+}
+
+// A static local that isn't shared with other translation units can still be
+// folded, even if its initializer isn't a constant initializer.
+static const int &internal() {
+  static const int i = kSize;
+  return i;
+}
+
+extern const int kSize = 3;
+
+const int *a() { return &instance(); }
+const int *b() { return &constant(); }
+const int *c() { return &internal(); }
+
+// ITANIUM-DAG: @_ZZ8instancevE4meta = linkonce_odr global i32 0, comdat, align 4
+// ITANIUM-DAG: @_ZGVZ8instancevE4meta = linkonce_odr global i64 0, comdat, align 8
+// ITANIUM-DAG: @_ZZ8constantvE1c = linkonce_odr constant i32 42, comdat, align 4
+// ITANIUM-DAG: @_ZZL8internalvE1i = internal constant i32 3, align 4
+// ITANIUM-NOT: @_ZGVZ8constantvE1c
+// ITANIUM-NOT: @_ZGVZL8internalvE1i
+
+// ITANIUM-LABEL: define linkonce_odr {{.*}} ptr @_Z8instancev()
+// ITANIUM:         load atomic i8, ptr @_ZGVZ8instancevE4meta acquire
+// ITANIUM:         call i32 @__cxa_guard_acquire(ptr @_ZGVZ8instancevE4meta)
+// ITANIUM:         store i32 3, ptr @_ZZ8instancevE4meta
+// ITANIUM:         call void @__cxa_guard_release(ptr @_ZGVZ8instancevE4meta)
+// ITANIUM:         ret ptr @_ZZ8instancevE4meta
+
+// ITANIUM-LABEL: define linkonce_odr {{.*}} ptr @_Z8constantv()
+// ITANIUM-NEXT:  entry:
+// ITANIUM-NEXT:    ret ptr @_ZZ8constantvE1c
+
+// ITANIUM-LABEL: define internal {{.*}} ptr @_ZL8internalv()
+// ITANIUM-NEXT:  entry:
+// ITANIUM-NEXT:    ret ptr @_ZZL8internalvE1i
+
+// MSVC-DAG: @"?meta@?1??instance@@YAAEBHXZ at 4HB" = linkonce_odr dso_local global i32 0, comdat, align 4
+// MSVC-DAG: @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA" = linkonce_odr global i32 0, comdat, align 4
+// MSVC-DAG: @"?c@?1??constant@@YAAEBHXZ at 4HB" = linkonce_odr dso_local constant i32 42, comdat, align 4
+// MSVC-DAG: @"?i@?1??internal@@YAAEBHXZ at 4HB" = internal constant i32 3, align 4
+
+// MSVC-LABEL: define linkonce_odr {{.*}} ptr @"?instance@@YAAEBHXZ"()
+// MSVC:         call void @_Init_thread_header(ptr @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA")
+// MSVC:         store i32 3, ptr @"?meta@?1??instance@@YAAEBHXZ at 4HB"
+// MSVC:         call void @_Init_thread_footer(ptr @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA")
+// MSVC:         ret ptr @"?meta@?1??instance@@YAAEBHXZ at 4HB"
+
+// MSVC-LABEL: define linkonce_odr {{.*}} ptr @"?constant@@YAAEBHXZ"()
+// MSVC-NEXT:  entry:
+// MSVC-NEXT:    ret ptr @"?c@?1??constant@@YAAEBHXZ at 4HB"
+
+// MSVC-LABEL: define internal {{.*}} ptr @"?internal@@YAAEBHXZ"()
+// MSVC-NEXT:  entry:
+// MSVC-NEXT:    ret ptr @"?i@?1??internal@@YAAEBHXZ at 4HB"



More information about the cfe-commits mailing list