[clang] [CIR] Set the static_local attribute if needed when initializing (PR #194094)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 27 10:47:44 PDT 2026


https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/194094

>From c5bf9009f0f9cffb84e19332565098f5e542cc5a Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 24 Apr 2026 16:07:39 -0700
Subject: [PATCH 1/3] [CIR] Set the static_local attribute if needed when
 initializing

There was a case where we were creating a GetGlobalOp when initializing
a static local variable that required a guard variable but failing
to set the static_local attribute on the GetGlobalOp. This led to
a CIR verificaiton error. This change sets the attribute when it is
needed.

Assisted-by: Cursor / claude-4.7-opus-high
---
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp     |  4 +++
 clang/test/CIR/CodeGen/static-local.cpp | 45 +++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index 7ce8fb1f5903f..8ceb080ceabe1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -296,6 +296,10 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
   scope.setAsGlobalInit();
   builder.setInsertionPointToStart(block);
   mlir::Value getGlobal = builder.createGetGlobal(addr);
+  // If we're initializing a static local with a guard variable, set the flag
+  // that indicates that.
+  mlir::cast<cir::GetGlobalOp>(getGlobal.getDefiningOp())
+      .setStaticLocal(addr.getStaticLocalGuard().has_value());
 
   Address declAddr(getGlobal, getASTContext().getDeclAlign(varDecl));
   assert(performInit && "cannot have a constant initializer which needs "
diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp
index 83245488632b9..f4adb1e29e38a 100644
--- a/clang/test/CIR/CodeGen/static-local.cpp
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -24,6 +24,8 @@
 // LLVM-DAG: @_ZGVZ1fvE1a = internal global i64 0
 // CIR-DAG: cir.global "private" linkonce_odr comdat @_ZGVZ10getInlineAvE1a = #cir.int<0> : !s64i
 // LLVM-DAG: @_ZGVZ10getInlineAvE1a = linkonce_odr global i64 0, comdat
+// CIR-DAG: cir.global "private" internal dso_local @_ZGVZ8ref_initvE1y = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVZ8ref_initvE1y = internal global i64 0
 
 // CIR-BOTH-DAG: cir.global linkonce_odr comdat static_local_guard<"_ZGVZ10getInlineAvE1a"> @_ZZ10getInlineAvE1a = #cir.zero : !rec_A
 // LLVM-DAG: @_ZZ10getInlineAvE1a = linkonce_odr global %class.A zeroinitializer, comdat
@@ -45,6 +47,8 @@
 // LLVM-DAG: @_ZZ29references_param_and_previousiE12magic_static = internal global i32 0
 // CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ1fvE1a"> @_ZZ1fvE1a = #cir.zero : !rec_A
 // LLVM-DAG: @_ZZ1fvE1a = internal global %class.A zeroinitializer
+// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ8ref_initvE1y"> @_ZZ8ref_initvE1y = #cir.ptr<null> : !cir.ptr<!s32i>
+// LLVM-DAG: @_ZZ8ref_initvE1y = internal global ptr null
 // CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ15use_static_declvE1p = #cir.global_view<@_ZZ15use_static_declvE1x> : !cir.ptr<!s32i>
 // LLVM-DAG: @_ZZ15use_static_declvE1p = internal global ptr @_ZZ15use_static_declvE1x
 // CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ15use_static_declvE1x = #cir.int<42> : !s32i
@@ -610,3 +614,44 @@ void test_ctor_dtor() {
 //
 // LLVM:   ret void
 }
+
+// Reference-typed static local with a non-constant initializer. The
+// cir.get_global emitted inside the initializer region for the reference
+// must carry the static_local marker so that it matches the
+// static_local_guard attribute on the corresponding cir.global. Otherwise
+// the cir.get_global verifier rejects the IR with
+// "static_local attribute mismatch".
+int g = 5;
+int &source();
+int &ref_init() {
+  static int &y = source();
+  return y;
+// CIR-BOTH-LABEL: cir.func no_inline dso_local @_Z8ref_initv()
+// CIR-BOTH:    %[[GET_REF:.*]] = cir.get_global static_local @_ZZ8ref_initvE1y : !cir.ptr<!cir.ptr<!s32i>>
+//
+// CIR-BEFORE-LPP:    cir.local_init static_local @_ZZ8ref_initvE1y ctor {
+// CIR-BEFORE-LPP:      %[[GET_REF_INIT:.*]] = cir.get_global static_local @_ZZ8ref_initvE1y : !cir.ptr<!cir.ptr<!s32i>>
+// CIR-BEFORE-LPP:      %[[CALL_SOURCE:.*]] = cir.call @_Z6sourcev() : () -> (!cir.ptr<!s32i>{{.*}})
+// CIR-BEFORE-LPP:      cir.store {{.*}}%[[CALL_SOURCE]], %[[GET_REF_INIT]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
+// CIR-BEFORE-LPP:      cir.yield
+// CIR-BEFORE-LPP:    }
+//
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVZ8ref_initvE1y : !cir.ptr<!s64i>
+// CIR: cir.if
+// CIR:   cir.call @__cxa_guard_acquire(%[[GET_GUARD]])
+// CIR:   cir.if
+// CIR:     %[[GET_REF_INIT2:.*]] = cir.get_global static_local @_ZZ8ref_initvE1y : !cir.ptr<!cir.ptr<!s32i>>
+// CIR:     %[[CALL_SOURCE2:.*]] = cir.call @_Z6sourcev() : () -> (!cir.ptr<!s32i>{{.*}})
+// CIR:     cir.store {{.*}}%[[CALL_SOURCE2]], %[[GET_REF_INIT2]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
+// CIR:     cir.call @__cxa_guard_release(%[[GET_GUARD]])
+// CIR:   }
+// CIR: }
+// CIR-BOTH:    cir.return
+//
+// LLVM-LABEL: define dso_local {{.*}} @_Z8ref_initv()
+// LLVM:  %[[GET_GUARD:.*]] = load atomic i8, ptr @_ZGVZ8ref_initvE1y acquire
+// LLVM:  call i32 @__cxa_guard_acquire(ptr @_ZGVZ8ref_initvE1y)
+// LLVM:  %[[CALL_SOURCE:.*]] = call {{.*}}ptr @_Z6sourcev()
+// LLVM:  store ptr %[[CALL_SOURCE]], ptr @_ZZ8ref_initvE1y
+// LLVM:  call void @__cxa_guard_release(ptr @_ZGVZ8ref_initvE1y)
+}

>From 2af2b0a1b6c6e54f6f8395b1051179ea9e948e46 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 27 Apr 2026 10:27:16 -0700
Subject: [PATCH 2/3] Add handling for static local arrays

---
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp           |  2 ++
 clang/test/CIR/CodeGen/static-local.cpp       | 34 +++++++++++++++++++
 .../cast-bitcast-funcptr-roundtrip-fold.cir   | 16 ---------
 3 files changed, 36 insertions(+), 16 deletions(-)
 delete mode 100644 clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir

diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index 8ceb080ceabe1..401f05f0b18ab 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -182,6 +182,8 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
     // address of the global into whose dtor region we are emiiting the destroy.
     // The same applies to code above where it is calling getAddrOfGlobalVar.
     mlir::Value globalVal = builder.createGetGlobal(addr);
+    mlir::cast<cir::GetGlobalOp>(globalVal.getDefiningOp())
+        .setStaticLocal(addr.getStaticLocalGuard().has_value());
     CharUnits alignment = cgf.getContext().getDeclAlign(vd);
     Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
     cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp
index f4adb1e29e38a..d17b475c4e07d 100644
--- a/clang/test/CIR/CodeGen/static-local.cpp
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -26,6 +26,8 @@
 // LLVM-DAG: @_ZGVZ10getInlineAvE1a = linkonce_odr global i64 0, comdat
 // CIR-DAG: cir.global "private" internal dso_local @_ZGVZ8ref_initvE1y = #cir.int<0> : !s64i
 // LLVM-DAG: @_ZGVZ8ref_initvE1y = internal global i64 0
+// CIR-DAG: cir.global "private" internal dso_local @_ZGVZ23array_static_local_dtorvE2sm = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVZ23array_static_local_dtorvE2sm = internal global i64 0
 
 // CIR-BOTH-DAG: cir.global linkonce_odr comdat static_local_guard<"_ZGVZ10getInlineAvE1a"> @_ZZ10getInlineAvE1a = #cir.zero : !rec_A
 // LLVM-DAG: @_ZZ10getInlineAvE1a = linkonce_odr global %class.A zeroinitializer, comdat
@@ -49,6 +51,8 @@
 // LLVM-DAG: @_ZZ1fvE1a = internal global %class.A zeroinitializer
 // CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ8ref_initvE1y"> @_ZZ8ref_initvE1y = #cir.ptr<null> : !cir.ptr<!s32i>
 // LLVM-DAG: @_ZZ8ref_initvE1y = internal global ptr null
+// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ23array_static_local_dtorvE2sm"> @_ZZ23array_static_local_dtorvE2sm = #cir.zero : !cir.array<!rec_HasCtorDtor x 2>
+// LLVM-DAG: @_ZZ23array_static_local_dtorvE2sm = internal global [2 x %struct.HasCtorDtor] zeroinitializer
 // CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ15use_static_declvE1p = #cir.global_view<@_ZZ15use_static_declvE1x> : !cir.ptr<!s32i>
 // LLVM-DAG: @_ZZ15use_static_declvE1p = internal global ptr @_ZZ15use_static_declvE1x
 // CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ15use_static_declvE1x = #cir.int<42> : !s32i
@@ -655,3 +659,33 @@ int &ref_init() {
 // LLVM:  store ptr %[[CALL_SOURCE]], ptr @_ZZ8ref_initvE1y
 // LLVM:  call void @__cxa_guard_release(ptr @_ZGVZ8ref_initvE1y)
 }
+
+// Static local array with a non-trivial destructor. The cir.get_global
+// emitted inside the dtor region for the array must carry the static_local
+// marker so that it matches the static_local_guard attribute on the
+// corresponding cir.global. Otherwise the cir.get_global verifier rejects
+// the IR with "static_local attribute mismatch".
+void array_static_local_dtor() {
+  static HasCtorDtor sm[2];
+// CIR-BOTH-LABEL: cir.func no_inline dso_local @_Z23array_static_local_dtorv()
+// CIR-BOTH:    %[[GET_ARR:.*]] = cir.get_global static_local @_ZZ23array_static_local_dtorvE2sm : !cir.ptr<!cir.array<!rec_HasCtorDtor x 2>>
+//
+// CIR-BEFORE-LPP:    cir.local_init static_local @_ZZ23array_static_local_dtorvE2sm ctor {
+// CIR-BEFORE-LPP:    } dtor {
+// CIR-BEFORE-LPP:      %[[GET_ARR_DTOR:.*]] = cir.get_global static_local @_ZZ23array_static_local_dtorvE2sm : !cir.ptr<!cir.array<!rec_HasCtorDtor x 2>>
+// CIR-BEFORE-LPP:      cir.array.dtor %[[GET_ARR_DTOR]]
+// CIR-BEFORE-LPP:        cir.call @_ZN11HasCtorDtorD1Ev
+// CIR-BEFORE-LPP:      cir.yield
+// CIR-BEFORE-LPP:    }
+//
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVZ23array_static_local_dtorvE2sm : !cir.ptr<!s64i>
+// CIR: cir.if
+// CIR:   cir.call @__cxa_guard_acquire(%[[GET_GUARD]])
+// CIR:   cir.if
+// CIR:     %[[GET_ARR_INIT:.*]] = cir.get_global static_local @_ZZ23array_static_local_dtorvE2sm : !cir.ptr<!cir.array<!rec_HasCtorDtor x 2>>
+// CIR:     cir.call @__cxa_atexit
+// CIR:     cir.call @__cxa_guard_release(%[[GET_GUARD]])
+// CIR:   }
+// CIR: }
+// CIR-BOTH:    cir.return
+}
diff --git a/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir b/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir
deleted file mode 100644
index a8979f6adfe71..0000000000000
--- a/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir
+++ /dev/null
@@ -1,16 +0,0 @@
-// RUN: cir-opt %s -cir-canonicalize -o - | FileCheck %s
-
-!s32i = !cir.int<s, 32>
-!fnp = !cir.func<(!s32i) -> !s32i>
-!fnnp = !cir.func<(...) -> !s32i>
-!pfnp = !cir.ptr<!fnp>
-!pfnnp = !cir.ptr<!fnnp>
-
-// Two function-pointer bitcasts that round-trip fold to the original value.
-// CHECK-LABEL: cir.func @round_trip
-// CHECK-NEXT:    cir.return %arg0
-cir.func @round_trip(%arg0: !pfnp) -> !pfnp {
-  %0 = cir.cast bitcast %arg0 : !pfnp -> !pfnnp
-  %1 = cir.cast bitcast %0 : !pfnnp -> !pfnp
-  cir.return %1 : !pfnp
-}

>From f2e34694d7293e2f940eecb2869ad37f92b0d617 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 27 Apr 2026 10:47:03 -0700
Subject: [PATCH 3/3] Address review feedback

---
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index 401f05f0b18ab..f84a0037c3252 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -182,8 +182,8 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
     // address of the global into whose dtor region we are emiiting the destroy.
     // The same applies to code above where it is calling getAddrOfGlobalVar.
     mlir::Value globalVal = builder.createGetGlobal(addr);
-    mlir::cast<cir::GetGlobalOp>(globalVal.getDefiningOp())
-        .setStaticLocal(addr.getStaticLocalGuard().has_value());
+    globalVal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
+        addr.getStaticLocalGuard().has_value());
     CharUnits alignment = cgf.getContext().getDeclAlign(vd);
     Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
     cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
@@ -300,8 +300,8 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
   mlir::Value getGlobal = builder.createGetGlobal(addr);
   // If we're initializing a static local with a guard variable, set the flag
   // that indicates that.
-  mlir::cast<cir::GetGlobalOp>(getGlobal.getDefiningOp())
-      .setStaticLocal(addr.getStaticLocalGuard().has_value());
+  getGlobal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
+      addr.getStaticLocalGuard().has_value());
 
   Address declAddr(getGlobal, getASTContext().getDeclAlign(varDecl));
   assert(performInit && "cannot have a constant initializer which needs "



More information about the cfe-commits mailing list