diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index a3e20817d2ca4..71568ec87a31b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -135,15 +135,27 @@ 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. + 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. + // 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)); } - 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..a9ed8879eb842 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(*opIt); + + // The simple case is just a call to a destructor, like this: + // + // %0 = cir.get_global %globalS : !cir.ptr + // cir.call %_ZN1SD1Ev(%0) : (!cir.ptr) -> () + // (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(&*(++opIt)); + auto yieldOp = mlir::dyn_cast(&*(++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 += "." + std::to_string(cnt); + + // 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(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(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(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. @@ -699,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()); @@ -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())) { - 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 -// CIR-BEFORE-LPP: %1 = cir.get_global @fp : !cir.ptr -// CIR-BEFORE-LPP: %2 = cir.load{{.*}} %1 : !cir.ptr, !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 +// CIR-BEFORE-LPP: %[[I:.*]] = cir.get_global @i : !cir.ptr +// CIR-BEFORE-LPP: %[[FP:.*]] = cir.get_global @fp : !cir.ptr +// CIR-BEFORE-LPP: %[[FP_VAL:.*]] = cir.load{{.*}} %[[FP]] : !cir.ptr, !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 // 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 +// CIR-BEFORE-LPP-SAME: dtor { +// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @arrDtor : !cir.ptr> +// CIR-BEFORE-LPP: cir.array.dtor %[[THIS]] : !cir.ptr> { +// CIR-BEFORE-LPP: ^bb0(%[[ELEM:.*]]: !cir.ptr): +// CIR-BEFORE-LPP: cir.call @_ZN9ArrayDtorD1Ev(%[[ELEM]]) nothrow : (!cir.ptr) -> () +// CIR-BEFORE-LPP: cir.yield +// CIR-BEFORE-LPP: } +// CIR-BEFORE-LPP: } + +// CIR: cir.global external @arrDtor = #cir.zero : !cir.array {alignment = 16 : i64} +// CIR: cir.func internal private @__cxx_global_array_dtor(%[[ARR_ARG:.*]]: !cir.ptr {{.*}}) { +// CIR: %[[CONST15:.*]] = cir.const #cir.int<15> : !u64i +// CIR: %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR_ARG]] : !cir.ptr -> !cir.ptr +// CIR: %[[END:.*]] = cir.ptr_stride %[[BEGIN]], %[[CONST15]] : (!cir.ptr, !u64i) -> !cir.ptr +// CIR: %[[CUR_ADDR:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["__array_idx"] +// CIR: cir.store %[[END]], %[[CUR_ADDR]] : !cir.ptr, !cir.ptr> +// CIR: cir.do { +// CIR: %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr>, !cir.ptr +// CIR: cir.call @_ZN9ArrayDtorD1Ev(%[[CUR]]) nothrow : (!cir.ptr) -> () +// CIR: %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i +// CIR: %[[NEXT:.*]] = cir.ptr_stride %[[CUR]], %[[NEG_ONE]] : (!cir.ptr, !s64i) -> !cir.ptr +// CIR: cir.store %[[NEXT]], %[[CUR_ADDR]] : !cir.ptr, !cir.ptr> +// CIR: cir.yield +// CIR: } while { +// CIR: %[[CUR:.*]] = cir.load %[[CUR_ADDR]] : !cir.ptr>, !cir.ptr +// CIR: %[[CMP:.*]] = cir.cmp(ne, %[[CUR]], %[[BEGIN]]) : !cir.ptr, !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: %[[DTOR:.*]] = cir.get_global @__cxx_global_array_dtor : !cir.ptr)>> +// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr)>> -> !cir.ptr)>> +// CIR: %[[ARR_CAST:.*]] = cir.cast bitcast %[[ARR]] : !cir.ptr> -> !cir.ptr +// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr +// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[ARR_CAST]], %[[HANDLE]]) : (!cir.ptr)>>, !cir.ptr, !cir.ptr) -> () + +// 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()