[clang] [CIR] Implement global array dtor support (PR #169070)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Fri Nov 21 13:41:04 PST 2025


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

>From 811ca4aa6a15406b401b7c42656a0986b18209f0 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 19 Nov 2025 14:55:12 -0800
Subject: [PATCH 1/2] [CIR] Implement global array dtor support

This implements handling to destroy global arrays that require destruction.
Unlike classic codegen, CIR emits the destructor loop into a 'dtor' region
associated with the global array variable. Later, during LoweringPrepare,
this code is moved into a helper function and a call to __cxa_atexit
arranges for it to be called during the shared object shutdown.
---
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp           |  16 ++-
 .../Dialect/Transforms/LoweringPrepare.cpp    | 121 ++++++++++++++++--
 clang/test/CIR/CodeGen/global-init.cpp        | 106 ++++++++++++++-
 3 files changed, 224 insertions(+), 19 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index a3e20817d2ca4..bfd0481073788 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -139,11 +139,21 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
         cgf.getLoc(vd->getSourceRange()),
         mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
         mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
+    assert(fnOp && "expected cir.func");
+    // TODO(cir): This doesn't do anything but check for unhandled conditions.
+    // What it is meant to do should really be happening in LoweringPrepare.
+    cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr);
   } else {
-    cgm.errorNYI(vd->getSourceRange(), "array destructor");
+    // Otherwise, a custom destroyed is needed. Classic codegen creates a helper
+    // function here and emits the destroy into the helper function, which is
+    // called from __cxa_atexit.
+    // In CIR, we just emit the destroy into the dtor region. It will be moved
+    // into a separate function during the LoweringPrepare pass.
+    mlir::Value globalVal = cgf.getBuilder().createGetGlobal(addr);
+    CharUnits alignment = cgf.getContext().getDeclAlign(vd);
+    Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
+    cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
   }
-  assert(fnOp && "expected cir.func");
-  cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr);
 
   builder.setInsertionPointToEnd(block);
   if (block->empty()) {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 29b1211d2c351..91d817e09bfbb 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -76,6 +76,11 @@ struct LoweringPreparePass
   /// Build the function that initializes the specified global
   cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
 
+  /// Handle the dtor region by registering destructor with __cxa_atexit
+  cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op,
+                                  mlir::Region &dtorRegion,
+                                  cir::CallOp &dtorCall);
+
   /// Build a module init function that calls all the dynamic initializers.
   void buildCXXGlobalInitFunc();
 
@@ -691,6 +696,101 @@ void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) {
   op.erase();
 }
 
+cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
+                                                     cir::GlobalOp op,
+                                                     mlir::Region &dtorRegion,
+                                                     cir::CallOp &dtorCall) {
+  assert(!cir::MissingFeatures::astVarDeclInterface());
+  assert(!cir::MissingFeatures::opGlobalThreadLocal());
+
+  cir::VoidType voidTy = builder.getVoidTy();
+  auto voidPtrTy = cir::PointerType::get(voidTy);
+
+  // Look for operations in dtorBlock
+  mlir::Block &dtorBlock = dtorRegion.front();
+
+  // The first operation should be a get_global to retrieve the address
+  // of the global variable we're destroying.
+  auto opIt = dtorBlock.getOperations().begin();
+  cir::GetGlobalOp ggop = mlir::cast<cir::GetGlobalOp>(*opIt);
+
+  // The simple case is just a call to a destructor, like this:
+  //
+  //   %0 = cir.get_global %globalS : !cir.ptr<!rec_S>
+  //   cir.call %_ZN1SD1Ev(%0) : (!cir.ptr<!rec_S>) -> ()
+  //   (implicit cir.yield)
+  //
+  // That is, if the second operation is a call that takes the get_global result
+  // as its only operand, and the only other operation is a yield, then we can
+  // just return the called function.
+  if (dtorBlock.getOperations().size() == 3) {
+    auto callOp = mlir::dyn_cast<cir::CallOp>(&*(++opIt));
+    auto yieldOp = mlir::dyn_cast<cir::YieldOp>(&*(++opIt));
+    if (yieldOp && callOp && callOp.getNumOperands() == 1 &&
+        callOp.getArgOperand(0) == ggop) {
+      dtorCall = callOp;
+      return getCalledFunction(callOp);
+    }
+  }
+
+  // Otherwise, we need to create a helper function to replace the dtor region.
+  // This name is kind of arbitrary, but it matches the name that classic
+  // codegen uses, based on the expected case that gets us here.
+  builder.setInsertionPointAfter(op);
+  SmallString<256> fnName("__cxx_global_array_dtor");
+  uint32_t cnt = dynamicInitializerNames[fnName]++;
+  if (cnt)
+    fnName += "." + llvm::Twine(cnt).str();
+
+  // Create the helper function.
+  auto fnType = cir::FuncType::get({voidPtrTy}, voidTy);
+  cir::FuncOp dtorFunc =
+      buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
+                           cir::GlobalLinkageKind::InternalLinkage);
+  mlir::Block *entryBB = dtorFunc.addEntryBlock();
+
+  // Move everything from the dtor region into the helper function.
+  entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(),
+                                  dtorBlock.begin(), dtorBlock.end());
+
+  // Before erasing this, clone it back into the dtor region
+  cir::GetGlobalOp dtorGGop =
+      mlir::cast<cir::GetGlobalOp>(entryBB->getOperations().front());
+  builder.setInsertionPointToStart(&dtorBlock);
+  builder.clone(*dtorGGop.getOperation());
+
+  // Replace all uses of the help function's get_global with the function
+  // argument.
+  mlir::Value dtorArg = entryBB->getArgument(0);
+  dtorGGop.replaceAllUsesWith(dtorArg);
+  dtorGGop.erase();
+
+  // Replace the yield in the final block with a return
+  mlir::Block &finalBlock = dtorFunc.getBody().back();
+  auto yieldOp = cast<cir::YieldOp>(finalBlock.getTerminator());
+  builder.setInsertionPoint(yieldOp);
+  cir::ReturnOp::create(builder, yieldOp->getLoc());
+  yieldOp->erase();
+
+  // Create a call to the helper function, passing the original get_global op
+  // as the argument.
+  cir::GetGlobalOp origGGop =
+      mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front());
+  builder.setInsertionPointAfter(origGGop);
+  mlir::Value ggopResult = origGGop.getResult();
+  dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult);
+
+  // Add a yield after the call.
+  auto finalYield = cir::YieldOp::create(builder, op.getLoc());
+
+  // Erase everything after the yield.
+  dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)),
+                                  dtorBlock.end());
+  dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end());
+
+  return dtorFunc;
+}
+
 cir::FuncOp
 LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
   // TODO(cir): Store this in the GlobalOp.
@@ -722,22 +822,20 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
   if (!dtorRegion.empty()) {
     assert(!cir::MissingFeatures::astVarDeclInterface());
     assert(!cir::MissingFeatures::opGlobalThreadLocal());
+
     // Create a variable that binds the atexit to this shared object.
     builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
     cir::GlobalOp handle = buildRuntimeVariable(
         builder, "__dso_handle", op.getLoc(), builder.getI8Type(),
         cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden);
 
-    // Look for the destructor call in dtorBlock
-    mlir::Block &dtorBlock = dtorRegion.front();
+    // If this is a simple call to a destructor, get the called function.
+    // Otherwise, create a helper function for the entire dtor region,
+    // replacing the current dtor region body with a call to the helper
+    // function.
     cir::CallOp dtorCall;
-    for (auto op : reverse(dtorBlock.getOps<cir::CallOp>())) {
-      dtorCall = op;
-      break;
-    }
-    assert(dtorCall && "Expected a dtor call");
-    cir::FuncOp dtorFunc = getCalledFunction(dtorCall);
-    assert(dtorFunc && "Expected a dtor call");
+    cir::FuncOp dtorFunc =
+        getOrCreateDtorFunc(builder, op, dtorRegion, dtorCall);
 
     // Create a runtime helper function:
     //    extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
@@ -751,8 +849,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
     cir::FuncOp fnAtExit =
         buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType);
 
-    // Replace the dtor call with a call to __cxa_atexit(&dtor, &var,
-    // &__dso_handle)
+    // Replace the dtor (or helper) call with a call to
+    //   __cxa_atexit(&dtor, &var, &__dso_handle)
     builder.setInsertionPointAfter(dtorCall);
     mlir::Value args[3];
     auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
@@ -768,6 +866,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
                                        handle.getSymName());
     builder.createCallOp(dtorCall.getLoc(), fnAtExit, args);
     dtorCall->erase();
+    mlir::Block &dtorBlock = dtorRegion.front();
     entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(),
                                     dtorBlock.begin(),
                                     std::prev(dtorBlock.end()));
diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp
index 01e2868278514..3510e3e82f4e8 100644
--- a/clang/test/CIR/CodeGen/global-init.cpp
+++ b/clang/test/CIR/CodeGen/global-init.cpp
@@ -15,12 +15,14 @@
 // LLVM: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
 // LLVM: @needsDtor = global %struct.NeedsDtor zeroinitializer, align 1
 // LLVM: @needsCtorDtor = global %struct.NeedsCtorDtor zeroinitializer, align 1
+// LLVM: @arrDtor = global [16 x %struct.ArrayDtor] zeroinitializer, align 16
 // LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
 
 // OGCG: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
 // OGCG: @needsDtor = global %struct.NeedsDtor zeroinitializer, align 1
 // OGCG: @__dso_handle = external hidden global i8
 // OGCG: @needsCtorDtor = global %struct.NeedsCtorDtor zeroinitializer, align 1
+// OGCG: @arrDtor = global [16 x %struct.ArrayDtor] zeroinitializer, align 16
 // OGCG: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
 
 struct NeedsCtor {
@@ -145,11 +147,11 @@ float fp;
 int i = (int)fp;
 
 // CIR-BEFORE-LPP: cir.global external @i = ctor : !s32i {
-// CIR-BEFORE-LPP:   %0 = cir.get_global @i : !cir.ptr<!s32i>
-// CIR-BEFORE-LPP:   %1 = cir.get_global @fp : !cir.ptr<!cir.float>
-// CIR-BEFORE-LPP:   %2 = cir.load{{.*}} %1 : !cir.ptr<!cir.float>, !cir.float
-// CIR-BEFORE-LPP:   %3 = cir.cast float_to_int %2 : !cir.float -> !s32i
-// CIR-BEFORE-LPP:   cir.store{{.*}} %3, %0 : !s32i, !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:   %[[I:.*]] = cir.get_global @i : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:   %[[FP:.*]] = cir.get_global @fp : !cir.ptr<!cir.float>
+// CIR-BEFORE-LPP:   %[[FP_VAL:.*]] = cir.load{{.*}} %[[FP]] : !cir.ptr<!cir.float>, !cir.float
+// CIR-BEFORE-LPP:   %[[FP_I32:.*]] = cir.cast float_to_int %[[FP_VAL]] : !cir.float -> !s32i
+// CIR-BEFORE-LPP:   cir.store{{.*}} %[[FP_I32]], %[[I]] : !s32i, !cir.ptr<!s32i>
 // CIR-BEFORE-LPP: }
 
 // CIR: cir.func internal private @__cxx_global_var_init.4()
@@ -169,6 +171,97 @@ int i = (int)fp;
 // OGCG:   %[[FP_I32:.*]] = fptosi float %[[TMP_FP]] to i32
 // OGCG:   store i32 %[[FP_I32]], ptr @i, align 4
 
+struct ArrayDtor {
+  ~ArrayDtor();
+};
+
+ArrayDtor arrDtor[16];
+
+// CIR-BEFORE-LPP:      cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16>
+// CIR-BEFORE-LPP-SAME:   dtor {
+// CIR-BEFORE-LPP:          %[[THIS:.*]] = cir.get_global @arrDtor : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>>
+// CIR-BEFORE-LPP:          cir.array.dtor %[[THIS]] : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>> {
+// CIR-BEFORE-LPP:          ^bb0(%[[ELEM:.*]]: !cir.ptr<!rec_ArrayDtor>):
+// CIR-BEFORE-LPP:            cir.call @_ZN9ArrayDtorD1Ev(%[[ELEM]]) nothrow : (!cir.ptr<!rec_ArrayDtor>) -> ()
+// CIR-BEFORE-LPP:            cir.yield
+// CIR-BEFORE-LPP:          }
+// CIR-BEFORE-LPP:        }
+
+// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16> {alignment = 16 : i64}
+// CIR: cir.func internal private @__cxx_global_array_dtor(%[[ARR_ARG:.*]]: !cir.ptr<!void> {{.*}}) {
+// CIR:   %[[CONST15:.*]] = cir.const #cir.int<15> : !u64i
+// CIR:   %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR_ARG]] : !cir.ptr<!void> -> !cir.ptr<!rec_ArrayDtor>
+// CIR:   %[[END:.*]] = cir.ptr_stride %[[BEGIN]], %[[CONST15]] : (!cir.ptr<!rec_ArrayDtor>, !u64i) -> !cir.ptr<!rec_ArrayDtor>
+// CIR:   %[[CUR_ADDR:.*]] = cir.alloca !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, ["__array_idx"]
+// CIR:   cir.store %[[END]], %[[CUR_ADDR]] : !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>
+// CIR:   cir.do {
+// CIR:     %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, !cir.ptr<!rec_ArrayDtor>
+// CIR:     cir.call @_ZN9ArrayDtorD1Ev(%[[CUR]]) nothrow : (!cir.ptr<!rec_ArrayDtor>) -> ()
+// CIR:     %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:     %[[NEXT:.*]] = cir.ptr_stride %[[CUR]], %[[NEG_ONE]] : (!cir.ptr<!rec_ArrayDtor>, !s64i) -> !cir.ptr<!rec_ArrayDtor>
+// CIR:     cir.store %[[NEXT]], %[[CUR_ADDR]] : !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>
+// CIR:     cir.yield
+// CIR:   } while {
+// CIR:     %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, !cir.ptr<!rec_ArrayDtor>
+// CIR:     %[[CMP:.*]] = cir.cmp(ne, %[[CUR]], %[[BEGIN]]) : !cir.ptr<!rec_ArrayDtor>, !cir.bool
+// CIR:     cir.condition(%[[CMP]])
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+//
+// CIR: cir.func internal private @__cxx_global_var_init.5() {
+// CIR:   %[[ARR:.*]] = cir.get_global @arrDtor : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>>
+// CIR:   %[[DTOR:.*]] = cir.get_global @__cxx_global_array_dtor : !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
+// CIR:   %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!void>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
+// CIR:   %[[ARR_CAST:.*]] = cir.cast bitcast %[[ARR]] : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>> -> !cir.ptr<!void>
+// CIR:   %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<i8>
+// CIR:   cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[ARR_CAST]], %[[HANDLE]]) : (!cir.ptr<!cir.func<(!cir.ptr<!void>)>>, !cir.ptr<!void>, !cir.ptr<i8>) -> ()
+
+// LLVM: define internal void @__cxx_global_array_dtor(ptr %[[ARR_ARG:.*]]) {
+// LLVM:   %[[BEGIN:.*]] = getelementptr %struct.ArrayDtor, ptr %[[ARR_ARG]], i32 0
+// LLVM:   %[[END:.*]] = getelementptr %struct.ArrayDtor, ptr %[[BEGIN]], i64 15
+// LLVM:   %[[CUR_ADDR:.*]] = alloca ptr
+// LLVM:   store ptr %[[END]], ptr %[[CUR_ADDR]]
+// LLVM:   br label %[[LOOP_BODY:.*]]
+// LLVM: [[LOOP_COND:.*]]:
+// LLVM:   %[[CUR:.*]] = load ptr, ptr %[[CUR_ADDR]]
+// LLVM:   %[[CMP:.*]] = icmp ne ptr %[[CUR]], %[[BEGIN]]
+// LLVM:   br i1 %[[CMP]], label %[[LOOP_BODY]], label %[[LOOP_END:.*]]
+// LLVM: [[LOOP_BODY]]:
+// LLVM:   %[[CUR:.*]] = load ptr, ptr %[[CUR_ADDR]]
+// LLVM:   call void @_ZN9ArrayDtorD1Ev(ptr %[[CUR]]) #0
+// LLVM:   %[[PREV:.*]] = getelementptr %struct.ArrayDtor, ptr %[[CUR]], i64 -1
+// LLVM:   store ptr %[[PREV]], ptr %[[CUR_ADDR]]
+// LLVM:   br label %[[LOOP_COND]]
+// LLVM: [[LOOP_END]]:
+// LLVM:   ret void
+// LLVM: }
+//
+// LLVM: define internal void @__cxx_global_var_init.5() {
+// LLVM:   call void @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr @arrDtor, ptr @__dso_handle)
+
+// Note: OGCG defines these functions in reverse order of CIR->LLVM.
+// Note also: OGCG doesn't pass the address of the array to the destructor function.
+//            Instead, it uses the global directly in the helper function.
+
+// OGCG: define internal void @__cxx_global_var_init.5() {{.*}} section ".text.startup" {
+// OGCG:   call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle)
+
+// OGCG: define internal void @__cxx_global_array_dtor(ptr noundef %[[ARG:.*]]) {{.*}} section ".text.startup" {
+// OGCG: entry:
+// OGCG:   %[[UNUSED_ADDR:.*]] = alloca ptr
+// OGCG:   store ptr %[[ARG]], ptr %[[UNUSED_ADDR]]
+// OGCG:   br label %[[LOOP_BODY:.*]]
+// OGCG: [[LOOP_BODY]]:
+// OGCG:   %[[PREV:.*]] = phi ptr [ getelementptr inbounds (%struct.ArrayDtor, ptr @arrDtor, i64 16), %entry ], [ %[[CUR:.*]], %[[LOOP_BODY]] ]
+// OGCG:   %[[CUR]] = getelementptr inbounds %struct.ArrayDtor, ptr %[[PREV]], i64 -1
+// OGCG:   call void @_ZN9ArrayDtorD1Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[CUR]])
+// OGCG:   %[[DONE:.*]] = icmp eq ptr %[[CUR]], @arrDtor
+// OGCG:   br i1 %[[DONE]], label %[[LOOP_END:.*]], label %[[LOOP_BODY]]
+// OGCG: [[LOOP_END]]:
+// OGCG:   ret void
+// OGCG: }
+
 // Common init function for all globals with default priority
 
 // CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() {
@@ -177,6 +270,7 @@ int i = (int)fp;
 // CIR:   cir.call @__cxx_global_var_init.2() : () -> ()
 // CIR:   cir.call @__cxx_global_var_init.3() : () -> ()
 // CIR:   cir.call @__cxx_global_var_init.4() : () -> ()
+// CIR:   cir.call @__cxx_global_var_init.5() : () -> ()
 
 // LLVM: define void @_GLOBAL__sub_I_[[FILENAME]]()
 // LLVM:   call void @__cxx_global_var_init()
@@ -184,6 +278,7 @@ int i = (int)fp;
 // LLVM:   call void @__cxx_global_var_init.2()
 // LLVM:   call void @__cxx_global_var_init.3()
 // LLVM:   call void @__cxx_global_var_init.4()
+// LLVM:   call void @__cxx_global_var_init.5()
 
 // OGCG: define internal void @_GLOBAL__sub_I_[[FILENAME]]() {{.*}} section ".text.startup" {
 // OGCG:   call void @__cxx_global_var_init()
@@ -191,3 +286,4 @@ int i = (int)fp;
 // OGCG:   call void @__cxx_global_var_init.2()
 // OGCG:   call void @__cxx_global_var_init.3()
 // OGCG:   call void @__cxx_global_var_init.4()
+// OGCG:   call void @__cxx_global_var_init.5()

>From db8857b82bc62727cb5ea321a9381a0cf3160954 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 21 Nov 2025 13:38:32 -0800
Subject: [PATCH 2/2] Address review feedback

---
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp                  | 12 +++++++-----
 clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp |  4 ++--
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index bfd0481073788..71568ec87a31b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -135,10 +135,9 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
     // call right here.
     auto gd = GlobalDecl(dtor, Dtor_Complete);
     fnOp = cgm.getAddrAndTypeOfCXXStructor(gd).second;
-    cgf.getBuilder().createCallOp(
-        cgf.getLoc(vd->getSourceRange()),
-        mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
-        mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
+    builder.createCallOp(cgf.getLoc(vd->getSourceRange()),
+                         mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
+                         mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
     assert(fnOp && "expected cir.func");
     // TODO(cir): This doesn't do anything but check for unhandled conditions.
     // What it is meant to do should really be happening in LoweringPrepare.
@@ -149,7 +148,10 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
     // called from __cxa_atexit.
     // In CIR, we just emit the destroy into the dtor region. It will be moved
     // into a separate function during the LoweringPrepare pass.
-    mlir::Value globalVal = cgf.getBuilder().createGetGlobal(addr);
+    // FIXME(cir): We should create a new operation here to explicitly get the
+    // 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);
     CharUnits alignment = cgf.getContext().getDeclAlign(vd);
     Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
     cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 91d817e09bfbb..a9ed8879eb842 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -740,7 +740,7 @@ cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
   SmallString<256> fnName("__cxx_global_array_dtor");
   uint32_t cnt = dynamicInitializerNames[fnName]++;
   if (cnt)
-    fnName += "." + llvm::Twine(cnt).str();
+    fnName += "." + std::to_string(cnt);
 
   // Create the helper function.
   auto fnType = cir::FuncType::get({voidPtrTy}, voidTy);
@@ -799,7 +799,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
   // Get a unique name
   uint32_t cnt = dynamicInitializerNames[fnName]++;
   if (cnt)
-    fnName += "." + llvm::Twine(cnt).str();
+    fnName += "." + std::to_string(cnt);
 
   // Create a variable initialization function.
   CIRBaseBuilderTy builder(getContext());



More information about the cfe-commits mailing list