Skip to content

Commit 136c9da

Browse files
authored
[CIR] Implement global array dtor support (#169070)
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.
1 parent 677fbf8 commit 136c9da

File tree

3 files changed

+231
-24
lines changed

3 files changed

+231
-24
lines changed

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,27 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
135135
// call right here.
136136
auto gd = GlobalDecl(dtor, Dtor_Complete);
137137
fnOp = cgm.getAddrAndTypeOfCXXStructor(gd).second;
138-
cgf.getBuilder().createCallOp(
139-
cgf.getLoc(vd->getSourceRange()),
140-
mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
141-
mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
138+
builder.createCallOp(cgf.getLoc(vd->getSourceRange()),
139+
mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
140+
mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
141+
assert(fnOp && "expected cir.func");
142+
// TODO(cir): This doesn't do anything but check for unhandled conditions.
143+
// What it is meant to do should really be happening in LoweringPrepare.
144+
cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr);
142145
} else {
143-
cgm.errorNYI(vd->getSourceRange(), "array destructor");
146+
// Otherwise, a custom destroyed is needed. Classic codegen creates a helper
147+
// function here and emits the destroy into the helper function, which is
148+
// called from __cxa_atexit.
149+
// In CIR, we just emit the destroy into the dtor region. It will be moved
150+
// into a separate function during the LoweringPrepare pass.
151+
// FIXME(cir): We should create a new operation here to explicitly get the
152+
// address of the global into whose dtor region we are emiiting the destroy.
153+
// The same applies to code above where it is calling getAddrOfGlobalVar.
154+
mlir::Value globalVal = builder.createGetGlobal(addr);
155+
CharUnits alignment = cgf.getContext().getDeclAlign(vd);
156+
Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
157+
cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
144158
}
145-
assert(fnOp && "expected cir.func");
146-
cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr);
147159

148160
builder.setInsertionPointToEnd(block);
149161
if (block->empty()) {

clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ struct LoweringPreparePass
7878
/// Build the function that initializes the specified global
7979
cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
8080

81+
/// Handle the dtor region by registering destructor with __cxa_atexit
82+
cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op,
83+
mlir::Region &dtorRegion,
84+
cir::CallOp &dtorCall);
85+
8186
/// Build a module init function that calls all the dynamic initializers.
8287
void buildCXXGlobalInitFunc();
8388

@@ -693,6 +698,101 @@ void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) {
693698
op.erase();
694699
}
695700

701+
cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
702+
cir::GlobalOp op,
703+
mlir::Region &dtorRegion,
704+
cir::CallOp &dtorCall) {
705+
assert(!cir::MissingFeatures::astVarDeclInterface());
706+
assert(!cir::MissingFeatures::opGlobalThreadLocal());
707+
708+
cir::VoidType voidTy = builder.getVoidTy();
709+
auto voidPtrTy = cir::PointerType::get(voidTy);
710+
711+
// Look for operations in dtorBlock
712+
mlir::Block &dtorBlock = dtorRegion.front();
713+
714+
// The first operation should be a get_global to retrieve the address
715+
// of the global variable we're destroying.
716+
auto opIt = dtorBlock.getOperations().begin();
717+
cir::GetGlobalOp ggop = mlir::cast<cir::GetGlobalOp>(*opIt);
718+
719+
// The simple case is just a call to a destructor, like this:
720+
//
721+
// %0 = cir.get_global %globalS : !cir.ptr<!rec_S>
722+
// cir.call %_ZN1SD1Ev(%0) : (!cir.ptr<!rec_S>) -> ()
723+
// (implicit cir.yield)
724+
//
725+
// That is, if the second operation is a call that takes the get_global result
726+
// as its only operand, and the only other operation is a yield, then we can
727+
// just return the called function.
728+
if (dtorBlock.getOperations().size() == 3) {
729+
auto callOp = mlir::dyn_cast<cir::CallOp>(&*(++opIt));
730+
auto yieldOp = mlir::dyn_cast<cir::YieldOp>(&*(++opIt));
731+
if (yieldOp && callOp && callOp.getNumOperands() == 1 &&
732+
callOp.getArgOperand(0) == ggop) {
733+
dtorCall = callOp;
734+
return getCalledFunction(callOp);
735+
}
736+
}
737+
738+
// Otherwise, we need to create a helper function to replace the dtor region.
739+
// This name is kind of arbitrary, but it matches the name that classic
740+
// codegen uses, based on the expected case that gets us here.
741+
builder.setInsertionPointAfter(op);
742+
SmallString<256> fnName("__cxx_global_array_dtor");
743+
uint32_t cnt = dynamicInitializerNames[fnName]++;
744+
if (cnt)
745+
fnName += "." + std::to_string(cnt);
746+
747+
// Create the helper function.
748+
auto fnType = cir::FuncType::get({voidPtrTy}, voidTy);
749+
cir::FuncOp dtorFunc =
750+
buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
751+
cir::GlobalLinkageKind::InternalLinkage);
752+
mlir::Block *entryBB = dtorFunc.addEntryBlock();
753+
754+
// Move everything from the dtor region into the helper function.
755+
entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(),
756+
dtorBlock.begin(), dtorBlock.end());
757+
758+
// Before erasing this, clone it back into the dtor region
759+
cir::GetGlobalOp dtorGGop =
760+
mlir::cast<cir::GetGlobalOp>(entryBB->getOperations().front());
761+
builder.setInsertionPointToStart(&dtorBlock);
762+
builder.clone(*dtorGGop.getOperation());
763+
764+
// Replace all uses of the help function's get_global with the function
765+
// argument.
766+
mlir::Value dtorArg = entryBB->getArgument(0);
767+
dtorGGop.replaceAllUsesWith(dtorArg);
768+
dtorGGop.erase();
769+
770+
// Replace the yield in the final block with a return
771+
mlir::Block &finalBlock = dtorFunc.getBody().back();
772+
auto yieldOp = cast<cir::YieldOp>(finalBlock.getTerminator());
773+
builder.setInsertionPoint(yieldOp);
774+
cir::ReturnOp::create(builder, yieldOp->getLoc());
775+
yieldOp->erase();
776+
777+
// Create a call to the helper function, passing the original get_global op
778+
// as the argument.
779+
cir::GetGlobalOp origGGop =
780+
mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front());
781+
builder.setInsertionPointAfter(origGGop);
782+
mlir::Value ggopResult = origGGop.getResult();
783+
dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult);
784+
785+
// Add a yield after the call.
786+
auto finalYield = cir::YieldOp::create(builder, op.getLoc());
787+
788+
// Erase everything after the yield.
789+
dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)),
790+
dtorBlock.end());
791+
dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end());
792+
793+
return dtorFunc;
794+
}
795+
696796
cir::FuncOp
697797
LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
698798
// TODO(cir): Store this in the GlobalOp.
@@ -701,7 +801,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
701801
// Get a unique name
702802
uint32_t cnt = dynamicInitializerNames[fnName]++;
703803
if (cnt)
704-
fnName += "." + llvm::Twine(cnt).str();
804+
fnName += "." + std::to_string(cnt);
705805

706806
// Create a variable initialization function.
707807
CIRBaseBuilderTy builder(getContext());
@@ -724,22 +824,20 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
724824
if (!dtorRegion.empty()) {
725825
assert(!cir::MissingFeatures::astVarDeclInterface());
726826
assert(!cir::MissingFeatures::opGlobalThreadLocal());
827+
727828
// Create a variable that binds the atexit to this shared object.
728829
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
729830
cir::GlobalOp handle = buildRuntimeVariable(
730831
builder, "__dso_handle", op.getLoc(), builder.getI8Type(),
731832
cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden);
732833

733-
// Look for the destructor call in dtorBlock
734-
mlir::Block &dtorBlock = dtorRegion.front();
834+
// If this is a simple call to a destructor, get the called function.
835+
// Otherwise, create a helper function for the entire dtor region,
836+
// replacing the current dtor region body with a call to the helper
837+
// function.
735838
cir::CallOp dtorCall;
736-
for (auto op : reverse(dtorBlock.getOps<cir::CallOp>())) {
737-
dtorCall = op;
738-
break;
739-
}
740-
assert(dtorCall && "Expected a dtor call");
741-
cir::FuncOp dtorFunc = getCalledFunction(dtorCall);
742-
assert(dtorFunc && "Expected a dtor call");
839+
cir::FuncOp dtorFunc =
840+
getOrCreateDtorFunc(builder, op, dtorRegion, dtorCall);
743841

744842
// Create a runtime helper function:
745843
// extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
@@ -753,8 +851,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
753851
cir::FuncOp fnAtExit =
754852
buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType);
755853

756-
// Replace the dtor call with a call to __cxa_atexit(&dtor, &var,
757-
// &__dso_handle)
854+
// Replace the dtor (or helper) call with a call to
855+
// __cxa_atexit(&dtor, &var, &__dso_handle)
758856
builder.setInsertionPointAfter(dtorCall);
759857
mlir::Value args[3];
760858
auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
@@ -770,6 +868,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
770868
handle.getSymName());
771869
builder.createCallOp(dtorCall.getLoc(), fnAtExit, args);
772870
dtorCall->erase();
871+
mlir::Block &dtorBlock = dtorRegion.front();
773872
entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(),
774873
dtorBlock.begin(),
775874
std::prev(dtorBlock.end()));

clang/test/CIR/CodeGen/global-init.cpp

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
// LLVM: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
1616
// LLVM: @needsDtor = global %struct.NeedsDtor zeroinitializer, align 1
1717
// LLVM: @needsCtorDtor = global %struct.NeedsCtorDtor zeroinitializer, align 1
18+
// LLVM: @arrDtor = global [16 x %struct.ArrayDtor] zeroinitializer, align 16
1819
// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
1920

2021
// OGCG: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
2122
// OGCG: @needsDtor = global %struct.NeedsDtor zeroinitializer, align 1
2223
// OGCG: @__dso_handle = external hidden global i8
2324
// OGCG: @needsCtorDtor = global %struct.NeedsCtorDtor zeroinitializer, align 1
25+
// OGCG: @arrDtor = global [16 x %struct.ArrayDtor] zeroinitializer, align 16
2426
// OGCG: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
2527

2628
struct NeedsCtor {
@@ -145,11 +147,11 @@ float fp;
145147
int i = (int)fp;
146148

147149
// CIR-BEFORE-LPP: cir.global external @i = ctor : !s32i {
148-
// CIR-BEFORE-LPP: %0 = cir.get_global @i : !cir.ptr<!s32i>
149-
// CIR-BEFORE-LPP: %1 = cir.get_global @fp : !cir.ptr<!cir.float>
150-
// CIR-BEFORE-LPP: %2 = cir.load{{.*}} %1 : !cir.ptr<!cir.float>, !cir.float
151-
// CIR-BEFORE-LPP: %3 = cir.cast float_to_int %2 : !cir.float -> !s32i
152-
// CIR-BEFORE-LPP: cir.store{{.*}} %3, %0 : !s32i, !cir.ptr<!s32i>
150+
// CIR-BEFORE-LPP: %[[I:.*]] = cir.get_global @i : !cir.ptr<!s32i>
151+
// CIR-BEFORE-LPP: %[[FP:.*]] = cir.get_global @fp : !cir.ptr<!cir.float>
152+
// CIR-BEFORE-LPP: %[[FP_VAL:.*]] = cir.load{{.*}} %[[FP]] : !cir.ptr<!cir.float>, !cir.float
153+
// CIR-BEFORE-LPP: %[[FP_I32:.*]] = cir.cast float_to_int %[[FP_VAL]] : !cir.float -> !s32i
154+
// CIR-BEFORE-LPP: cir.store{{.*}} %[[FP_I32]], %[[I]] : !s32i, !cir.ptr<!s32i>
153155
// CIR-BEFORE-LPP: }
154156

155157
// CIR: cir.func internal private @__cxx_global_var_init.4()
@@ -169,6 +171,97 @@ int i = (int)fp;
169171
// OGCG: %[[FP_I32:.*]] = fptosi float %[[TMP_FP]] to i32
170172
// OGCG: store i32 %[[FP_I32]], ptr @i, align 4
171173

174+
struct ArrayDtor {
175+
~ArrayDtor();
176+
};
177+
178+
ArrayDtor arrDtor[16];
179+
180+
// CIR-BEFORE-LPP: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16>
181+
// CIR-BEFORE-LPP-SAME: dtor {
182+
// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @arrDtor : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>>
183+
// CIR-BEFORE-LPP: cir.array.dtor %[[THIS]] : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>> {
184+
// CIR-BEFORE-LPP: ^bb0(%[[ELEM:.*]]: !cir.ptr<!rec_ArrayDtor>):
185+
// CIR-BEFORE-LPP: cir.call @_ZN9ArrayDtorD1Ev(%[[ELEM]]) nothrow : (!cir.ptr<!rec_ArrayDtor>) -> ()
186+
// CIR-BEFORE-LPP: cir.yield
187+
// CIR-BEFORE-LPP: }
188+
// CIR-BEFORE-LPP: }
189+
190+
// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16> {alignment = 16 : i64}
191+
// CIR: cir.func internal private @__cxx_global_array_dtor(%[[ARR_ARG:.*]]: !cir.ptr<!void> {{.*}}) {
192+
// CIR: %[[CONST15:.*]] = cir.const #cir.int<15> : !u64i
193+
// CIR: %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR_ARG]] : !cir.ptr<!void> -> !cir.ptr<!rec_ArrayDtor>
194+
// CIR: %[[END:.*]] = cir.ptr_stride %[[BEGIN]], %[[CONST15]] : (!cir.ptr<!rec_ArrayDtor>, !u64i) -> !cir.ptr<!rec_ArrayDtor>
195+
// CIR: %[[CUR_ADDR:.*]] = cir.alloca !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, ["__array_idx"]
196+
// CIR: cir.store %[[END]], %[[CUR_ADDR]] : !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>
197+
// CIR: cir.do {
198+
// CIR: %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, !cir.ptr<!rec_ArrayDtor>
199+
// CIR: cir.call @_ZN9ArrayDtorD1Ev(%[[CUR]]) nothrow : (!cir.ptr<!rec_ArrayDtor>) -> ()
200+
// CIR: %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i
201+
// CIR: %[[NEXT:.*]] = cir.ptr_stride %[[CUR]], %[[NEG_ONE]] : (!cir.ptr<!rec_ArrayDtor>, !s64i) -> !cir.ptr<!rec_ArrayDtor>
202+
// CIR: cir.store %[[NEXT]], %[[CUR_ADDR]] : !cir.ptr<!rec_ArrayDtor>, !cir.ptr<!cir.ptr<!rec_ArrayDtor>>
203+
// CIR: cir.yield
204+
// CIR: } while {
205+
// CIR: %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr<!cir.ptr<!rec_ArrayDtor>>, !cir.ptr<!rec_ArrayDtor>
206+
// CIR: %[[CMP:.*]] = cir.cmp(ne, %[[CUR]], %[[BEGIN]]) : !cir.ptr<!rec_ArrayDtor>, !cir.bool
207+
// CIR: cir.condition(%[[CMP]])
208+
// CIR: }
209+
// CIR: cir.return
210+
// CIR: }
211+
//
212+
// CIR: cir.func internal private @__cxx_global_var_init.5() {
213+
// CIR: %[[ARR:.*]] = cir.get_global @arrDtor : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>>
214+
// CIR: %[[DTOR:.*]] = cir.get_global @__cxx_global_array_dtor : !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
215+
// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!void>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
216+
// CIR: %[[ARR_CAST:.*]] = cir.cast bitcast %[[ARR]] : !cir.ptr<!cir.array<!rec_ArrayDtor x 16>> -> !cir.ptr<!void>
217+
// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<i8>
218+
// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[ARR_CAST]], %[[HANDLE]]) : (!cir.ptr<!cir.func<(!cir.ptr<!void>)>>, !cir.ptr<!void>, !cir.ptr<i8>) -> ()
219+
220+
// LLVM: define internal void @__cxx_global_array_dtor(ptr %[[ARR_ARG:.*]]) {
221+
// LLVM: %[[BEGIN:.*]] = getelementptr %struct.ArrayDtor, ptr %[[ARR_ARG]], i32 0
222+
// LLVM: %[[END:.*]] = getelementptr %struct.ArrayDtor, ptr %[[BEGIN]], i64 15
223+
// LLVM: %[[CUR_ADDR:.*]] = alloca ptr
224+
// LLVM: store ptr %[[END]], ptr %[[CUR_ADDR]]
225+
// LLVM: br label %[[LOOP_BODY:.*]]
226+
// LLVM: [[LOOP_COND:.*]]:
227+
// LLVM: %[[CUR:.*]] = load ptr, ptr %[[CUR_ADDR]]
228+
// LLVM: %[[CMP:.*]] = icmp ne ptr %[[CUR]], %[[BEGIN]]
229+
// LLVM: br i1 %[[CMP]], label %[[LOOP_BODY]], label %[[LOOP_END:.*]]
230+
// LLVM: [[LOOP_BODY]]:
231+
// LLVM: %[[CUR:.*]] = load ptr, ptr %[[CUR_ADDR]]
232+
// LLVM: call void @_ZN9ArrayDtorD1Ev(ptr %[[CUR]]) #0
233+
// LLVM: %[[PREV:.*]] = getelementptr %struct.ArrayDtor, ptr %[[CUR]], i64 -1
234+
// LLVM: store ptr %[[PREV]], ptr %[[CUR_ADDR]]
235+
// LLVM: br label %[[LOOP_COND]]
236+
// LLVM: [[LOOP_END]]:
237+
// LLVM: ret void
238+
// LLVM: }
239+
//
240+
// LLVM: define internal void @__cxx_global_var_init.5() {
241+
// LLVM: call void @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr @arrDtor, ptr @__dso_handle)
242+
243+
// Note: OGCG defines these functions in reverse order of CIR->LLVM.
244+
// Note also: OGCG doesn't pass the address of the array to the destructor function.
245+
// Instead, it uses the global directly in the helper function.
246+
247+
// OGCG: define internal void @__cxx_global_var_init.5() {{.*}} section ".text.startup" {
248+
// OGCG: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle)
249+
250+
// OGCG: define internal void @__cxx_global_array_dtor(ptr noundef %[[ARG:.*]]) {{.*}} section ".text.startup" {
251+
// OGCG: entry:
252+
// OGCG: %[[UNUSED_ADDR:.*]] = alloca ptr
253+
// OGCG: store ptr %[[ARG]], ptr %[[UNUSED_ADDR]]
254+
// OGCG: br label %[[LOOP_BODY:.*]]
255+
// OGCG: [[LOOP_BODY]]:
256+
// OGCG: %[[PREV:.*]] = phi ptr [ getelementptr inbounds (%struct.ArrayDtor, ptr @arrDtor, i64 16), %entry ], [ %[[CUR:.*]], %[[LOOP_BODY]] ]
257+
// OGCG: %[[CUR]] = getelementptr inbounds %struct.ArrayDtor, ptr %[[PREV]], i64 -1
258+
// OGCG: call void @_ZN9ArrayDtorD1Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[CUR]])
259+
// OGCG: %[[DONE:.*]] = icmp eq ptr %[[CUR]], @arrDtor
260+
// OGCG: br i1 %[[DONE]], label %[[LOOP_END:.*]], label %[[LOOP_BODY]]
261+
// OGCG: [[LOOP_END]]:
262+
// OGCG: ret void
263+
// OGCG: }
264+
172265
// Common init function for all globals with default priority
173266

174267
// CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() {
@@ -177,17 +270,20 @@ int i = (int)fp;
177270
// CIR: cir.call @__cxx_global_var_init.2() : () -> ()
178271
// CIR: cir.call @__cxx_global_var_init.3() : () -> ()
179272
// CIR: cir.call @__cxx_global_var_init.4() : () -> ()
273+
// CIR: cir.call @__cxx_global_var_init.5() : () -> ()
180274

181275
// LLVM: define void @_GLOBAL__sub_I_[[FILENAME]]()
182276
// LLVM: call void @__cxx_global_var_init()
183277
// LLVM: call void @__cxx_global_var_init.1()
184278
// LLVM: call void @__cxx_global_var_init.2()
185279
// LLVM: call void @__cxx_global_var_init.3()
186280
// LLVM: call void @__cxx_global_var_init.4()
281+
// LLVM: call void @__cxx_global_var_init.5()
187282

188283
// OGCG: define internal void @_GLOBAL__sub_I_[[FILENAME]]() {{.*}} section ".text.startup" {
189284
// OGCG: call void @__cxx_global_var_init()
190285
// OGCG: call void @__cxx_global_var_init.1()
191286
// OGCG: call void @__cxx_global_var_init.2()
192287
// OGCG: call void @__cxx_global_var_init.3()
193288
// OGCG: call void @__cxx_global_var_init.4()
289+
// OGCG: call void @__cxx_global_var_init.5()

0 commit comments

Comments
 (0)