https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/229896
>From 65b2ab9be84fb14c79f33165e042b69c2bed90e6 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 7 Oct 2026 13:16:07 -0700 Subject: [PATCH 1/2] [CIR] Register a static's extended temporaries where they are built Temporaries whose lifetime a static extends were all destroyed together, in one batch registered after the static's whole initializer ran. Now each one is registered for destruction right after it is built, the way classic codegen does it. That fixes a verifier failure when a static extends more than one temporary, a variable's own destructor going missing, objects destroyed in the wrong order, and a temporary destroyed even when it was never built. Assisted-by: Claude Code / claude-opus-5-5 --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 49 ++- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 5 - clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 28 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 8 - clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 46 +++ .../Dialect/Transforms/LoweringPrepare.cpp | 202 +++++++---- clang/test/CIR/CodeGen/global-temp-dtor.cpp | 336 +++++++++++------- .../CIR/CodeGen/local-static-temp-dtor.cpp | 260 ++++++++++++-- .../CIR/IR/invalid-register-exit-dtor.cir | 197 ++++++++++ clang/test/CIR/IR/register-exit-dtor.cir | 75 ++++ 10 files changed, 950 insertions(+), 256 deletions(-) create mode 100644 clang/test/CIR/IR/invalid-register-exit-dtor.cir create mode 100644 clang/test/CIR/IR/register-exit-dtor.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 985cc3712c581..f4c61afb43aef 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1162,8 +1162,8 @@ def CIR_ConditionOp : CIR_Op<"condition", [ defvar CIR_YieldableScopes = [ "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "CleanupScopeOp", "CoroutineOp", - "DoWhileOp", "ForOp", "GlobalOp", "IfOp", "LocalInitOp", "ScopeOp", "SwitchOp", - "TernaryOp", "TryOp", "WhileOp" + "DoWhileOp", "ForOp", "GlobalOp", "IfOp", "LocalInitOp", "RegisterExitDtorOp", + "ScopeOp", "SwitchOp", "TernaryOp", "TryOp", "WhileOp" ]; def CIR_YieldOp : CIR_Op<"yield", [ @@ -4511,6 +4511,51 @@ def CIR_LocalInitOp : CIR_Op<"local_init", [ let hasLLVMLowering = false; } +//===----------------------------------------------------------------------===// +// RegisterExitDtorOp +//===----------------------------------------------------------------------===// + +def CIR_RegisterExitDtorOp : CIR_Op<"register_exit_dtor", [ + DeclareOpInterfaceMethods<SymbolUserOpInterface>, IsolatedFromAbove, + NoRegionArguments, SingleBlockImplicitTerminator<"cir::YieldOp"> +]> { + let summary = + "Register the destruction of a global at program or thread exit"; + let description = [{ + The `cir.register_exit_dtor` operation registers its body to run when the + storage duration of the global `object` ends: at program exit, or at + thread exit when `object` is thread local. The registration happens when + the operation executes, so objects registered later are destroyed earlier. + + It must be nested in the ctor region of a `cir.global` or + `cir.local_init`, but not inside another `cir.register_exit_dtor`, + typically right after the construction of a temporary whose lifetime that + variable extends. `object` must have no ctor or dtor region of its own. + The body destroys `object`, which it reaches through `cir.get_global`. + + Example: + + ``` + cir.global external @r = ctor : !rec_Refs { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpC1Ei(%0, %1) : (!cir.ptr<!rec_Tmp>, !s32i) -> () + cir.register_exit_dtor @_ZGR1r_ { + %2 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%2) : (!cir.ptr<!rec_Tmp>) -> () + } + ... + } + ``` + }]; + + let arguments = (ins FlatSymbolRefAttr:$object); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "$object $body attr-dict"; + + let hasVerifier = 1; + let hasLLVMLowering = false; +} + //===----------------------------------------------------------------------===// // LLVMIntrinsicCallOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index ebb89a89f000e..2c6e4e3093077 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -278,11 +278,6 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl, assert(curCGF && "Special var init only available inside of a function"); CIRGenFunction &cgf = *curCGF; - // A temporary whose lifetime this initializer extends is destroyed alongside - // the variable itself, so pushTemporaryCleanup needs to know where that is. - llvm::SaveAndRestore<mlir::Region *> savedDtorRegion( - cgf.curStaticVarDtorRegion, &dtorRegion); - // TODO: handle address space // The address space of a static local variable (addr) may be different // from the address space of the "this" argument of the constructor. In that diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 51533f9dc0f9c..0ba03dae448bb 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -1989,37 +1989,20 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf, if (!referenceTemporaryDtor) return; - // Classic codegen calls registerGlobalDtor here, passing either the - // destructor or a generated array-destroy helper. CIR instead emits the - // destruction into the dtor region of whatever destroys the variable that - // extended the temporary: the cir.global's own region at namespace scope, - // and the enclosing cir.local_init's for a function-local static, which the - // verifier requires to be destroyed in-function under its guard. CIRGenModule &cgm = cgf.cgm; auto globalOp = mlir::cast<cir::GlobalOp>(cgm.getAddrOfGlobalTemporary(m, e)); - mlir::Region *dtorRegion = cgf.curStaticVarDtorRegion; - assert(dtorRegion && "temporary extended outside a static initializer"); - CIRGenBuilderTy &builder = cgm.getBuilder(); - mlir::OpBuilder::InsertionGuard guard(builder); mlir::Location loc = cgm.getLoc(m->getSourceRange()); - - // Temporaries are destroyed in reverse order of construction, so each one - // goes in front of those registered before it. - if (dtorRegion->empty()) { - builder.setInsertionPointToStart(builder.createBlock(dtorRegion)); - cir::YieldOp::create(builder, loc); - } - builder.setInsertionPointToStart(&dtorRegion->front()); + auto registerOp = cir::RegisterExitDtorOp::create( + builder, loc, globalOp.getSymNameAttr().getValue()); + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart( + builder.createBlock(®isterOp.getBody())); mlir::Value tempAddr = builder.createGetGlobal(globalOp); - if (e->getType()->isArrayType()) { - // emitDestroy will produce a cir.array.dtor here. LoweringPrepare's - // getOrCreateDtorFunc recognizes the non-trivial dtor region and - // hoists it into a __cxx_global_array_dtor helper. Address addr{tempAddr, cgf.convertTypeForMem(e->getType()), referenceTemporary.getAlignment()}; cgf.emitDestroy(addr, e->getType(), CIRGenFunction::destroyCXXObject); @@ -2028,6 +2011,7 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf, cir::FuncOp dtorFn = cgm.getAddrAndTypeOfCXXStructor(gd).second; builder.createCallOp(loc, dtorFn, mlir::ValueRange{tempAddr}); } + cir::YieldOp::create(builder, loc); break; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index c4963806c815b..aa02c79e85aa3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -292,14 +292,6 @@ class CIRGenFunction : public CIRGenTypeCache { /// global initializers. mlir::Operation *curFn = nullptr; - /// While the initializer of a variable with static storage duration is being - /// emitted, the region that destructors registered by that initializer belong - /// in: the cir.global's own dtor region for a namespace-scope variable, and - /// the enclosing cir.local_init's for a function-local static, which has to - /// be destroyed in-function under its guard. Null outside such an - /// initializer. - mlir::Region *curStaticVarDtorRegion = nullptr; - /// Save Parameter Decl for coroutine. llvm::SmallVector<const ParmVarDecl *> fnArgs; diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 2a5cb9a908a1d..5ec1dbe3a8168 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -679,6 +679,52 @@ cir::LocalInitOp::verifySymbolUses(SymbolTableCollection &symbolTable) { return success(); } +//===----------------------------------------------------------------------===// +// RegisterExitDtorOp +//===----------------------------------------------------------------------===// + +/// Returns true if \p op is nested in the ctor region of a cir.global or +/// cir.local_init without crossing a cir.func or a cir.register_exit_dtor. +static bool isInVarCtorRegion(mlir::Operation *op) { + for (mlir::Operation *parent = op->getParentOp(); parent; + parent = parent->getParentOp()) { + mlir::Region *ctorRegion = nullptr; + if (auto global = mlir::dyn_cast<cir::GlobalOp>(parent)) + ctorRegion = &global.getCtorRegion(); + else if (auto localInit = mlir::dyn_cast<cir::LocalInitOp>(parent)) + ctorRegion = &localInit.getCtorRegion(); + else if (mlir::isa<cir::FuncOp, cir::RegisterExitDtorOp>(parent)) + return false; + if (ctorRegion) + return ctorRegion->isAncestor(op->getParentRegion()); + } + return false; +} + +LogicalResult cir::RegisterExitDtorOp::verify() { + mlir::Block &body = getBody().front(); + if (body.without_terminator().empty()) + return emitOpError("body must destroy the object"); + if (body.getTerminator()->getNumOperands()) + return emitOpError("body must not yield a value"); + if (!isInVarCtorRegion(*this)) + return emitOpError("must be in the ctor region of a cir.global or " + "cir.local_init"); + return success(); +} + +LogicalResult +cir::RegisterExitDtorOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + auto global = symbolTable.lookupNearestSymbolFrom<cir::GlobalOp>( + *this, getObjectAttr()); + if (!global) + return emitOpError("'") + << getObject() << "' does not reference a valid cir.global"; + if (!global.getCtorRegion().empty() || !global.getDtorRegion().empty()) + return emitOpError("object must not have ctor or dtor regions"); + return success(); +} + //===----------------------------------------------------------------------===// // ConditionOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 23fb81e2f706d..e7127c6026900 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -96,6 +96,7 @@ struct LoweringPreparePass void lowerTrivialCopyCall(cir::CallOp op); void lowerStoreOfConstAggregate(cir::StoreOp op); void lowerLocalInitOp(cir::LocalInitOp op); + void lowerRegisterExitDtorOp(cir::RegisterExitDtorOp op); void lowerStdOp(cir::StdOpInterface op); /// Return the FuncOp called by `callOp`. Uses the cached `symbolTables` @@ -137,6 +138,10 @@ struct LoweringPreparePass cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, mlir::Region &dtorRegion, cir::CallOp &dtorCall); + /// Create a `void __cxx_global_array_dtor[.N](void *)` helper after `op`, + /// with an empty entry block. + cir::FuncOp buildGlobalArrayDtorFunc(CIRBaseBuilderTy &builder, + mlir::Operation *op); /// Build a function named `fnName` with the given linkage that calls each /// of `initializers` in order, then returns, and register it in @@ -383,16 +388,56 @@ struct LoweringPreparePass } } + /// Register `dtorFunc` to be called with `arg` when the program exits, or + /// when the current thread exits if `tls` is set. + cir::CallOp emitAtExitCall(CIRBaseBuilderTy &builder, mlir::Location loc, + cir::FuncOp dtorFunc, mlir::Value arg, bool tls) { + cir::GlobalOp handle; + cir::FuncOp fnAtExit; + cir::PointerType voidPtrTy = builder.getVoidPtrTy(); + cir::PointerType voidFnPtrTy = builder.getVoidFnPtrTy({voidPtrTy}); + { + mlir::OpBuilder::InsertionGuard guard(builder); + // Create a variable that binds the atexit to this shared object. + builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front()); + handle = getOrCreateRuntimeVariable( + builder, "__dso_handle", loc, builder.getUIntNTy(8), + cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden); + + // Create a runtime helper function: + // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); + cir::PointerType handlePtrTy = builder.getPointerTo(handle.getSymType()); + IntType intTy = builder.getSIntNTy(32); + auto fnAtExitType = + cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, intTy); + + llvm::StringLiteral nameAtExit = "__cxa_atexit"; + if (tls) + nameAtExit = getTargetInfo().getTriple().isOSDarwin() + ? llvm::StringLiteral("_tlv_atexit") + : llvm::StringLiteral("__cxa_thread_atexit"); + + fnAtExit = buildRuntimeFunction(builder, nameAtExit, loc, fnAtExitType); + } + + mlir::Value args[3]; + auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType()); + args[0] = cir::GetGlobalOp::create(builder, loc, dtorPtrTy, + dtorFunc.getSymName()); + args[0] = cir::CastOp::create(builder, loc, voidFnPtrTy, + cir::CastKind::bitcast, args[0]); + args[1] = cir::CastOp::create(builder, loc, voidPtrTy, + cir::CastKind::bitcast, arg); + args[2] = cir::GetGlobalOp::create( + builder, loc, builder.getPointerTo(handle.getSymType()), + handle.getSymName()); + return builder.createCallOp(loc, fnAtExit, args); + } + void emitGlobalGuardedDtorRegion(CIRBaseBuilderTy &builder, cir::GlobalOp global, mlir::Region &dtorRegion, bool tls, mlir::Block &entryBB) { - // Create a variable that binds the atexit to this shared object. - builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front()); - cir::GlobalOp handle = getOrCreateRuntimeVariable( - builder, "__dso_handle", global.getLoc(), builder.getUIntNTy(8), - cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden); - // 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 @@ -401,39 +446,11 @@ struct LoweringPreparePass cir::FuncOp dtorFunc = getOrCreateDtorFunc(builder, global, dtorRegion, dtorCall); - // Create a runtime helper function: - // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); - cir::PointerType voidPtrTy = builder.getVoidPtrTy(); - cir::PointerType voidFnPtrTy = builder.getVoidFnPtrTy({voidPtrTy}); - cir::PointerType handlePtrTy = builder.getPointerTo(handle.getSymType()); - IntType intTy = builder.getSIntNTy(32); - auto fnAtExitType = - cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, intTy); - - llvm::StringLiteral nameAtExit = "__cxa_atexit"; - if (tls) - nameAtExit = getTargetInfo().getTriple().isOSDarwin() - ? llvm::StringLiteral("_tlv_atexit") - : llvm::StringLiteral("__cxa_thread_atexit"); - - cir::FuncOp fnAtExit = buildRuntimeFunction(builder, nameAtExit, - global.getLoc(), fnAtExitType); - // 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()); - args[0] = cir::GetGlobalOp::create(builder, dtorCall.getLoc(), dtorPtrTy, - dtorFunc.getSymName()); - args[0] = cir::CastOp::create(builder, dtorCall.getLoc(), voidFnPtrTy, - cir::CastKind::bitcast, args[0]); - args[1] = - cir::CastOp::create(builder, dtorCall.getLoc(), voidPtrTy, - cir::CastKind::bitcast, dtorCall.getArgOperand(0)); - args[2] = cir::GetGlobalOp::create(builder, handle.getLoc(), handlePtrTy, - handle.getSymName()); - builder.createCallOp(dtorCall.getLoc(), fnAtExit, args); + emitAtExitCall(builder, dtorCall.getLoc(), dtorFunc, + dtorCall.getArgOperand(0), tls); dtorCall->erase(); mlir::Block &dtorBlock = dtorRegion.front(); entryBB.getOperations().splice(entryBB.end(), dtorBlock.getOperations(), @@ -1118,6 +1135,84 @@ void LoweringPreparePass::lowerComplexConjOp(cir::ComplexConjOp op) { op->erase(); } +cir::FuncOp +LoweringPreparePass::buildGlobalArrayDtorFunc(CIRBaseBuilderTy &builder, + mlir::Operation *op) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointAfter(op); + SmallString<256> fnName("__cxx_global_array_dtor"); + uint32_t cnt = dynamicInitializerNames[fnName]++; + if (cnt) + fnName += "." + std::to_string(cnt); + + auto fnType = + cir::FuncType::get({builder.getVoidPtrTy()}, builder.getVoidTy()); + cir::FuncOp dtorFunc = + buildRuntimeFunction(builder, fnName, op->getLoc(), fnType, + cir::GlobalLinkageKind::InternalLinkage); + + SmallVector<mlir::NamedAttribute> paramAttrs; + paramAttrs.push_back( + builder.getNamedAttr("llvm.noundef", builder.getUnitAttr())); + SmallVector<mlir::Attribute> argAttrDicts; + argAttrDicts.push_back( + mlir::DictionaryAttr::get(builder.getContext(), paramAttrs)); + dtorFunc.setArgAttrsAttr( + mlir::ArrayAttr::get(builder.getContext(), argAttrDicts)); + + dtorFunc.addEntryBlock(); + return dtorFunc; +} + +void LoweringPreparePass::lowerRegisterExitDtorOp(cir::RegisterExitDtorOp op) { + CIRBaseBuilderTy builder(getContext()); + mlir::Location loc = op.getLoc(); + auto object = symbolTables.lookupNearestSymbolFrom<cir::GlobalOp>( + op, op.getObjectAttr()); + assert(object && "register_exit_dtor must reference a cir.global"); + mlir::Block &body = op.getBody().front(); + builder.setInsertionPoint(op); + + // When the body is a single call on the object's address, register the + // callee directly with that address. + cir::FuncOp dtorFunc; + mlir::Value arg; + if (body.getOperations().size() == 3) { + auto getGlobal = mlir::dyn_cast<cir::GetGlobalOp>(body.front()); + auto call = mlir::dyn_cast<cir::CallOp>(*std::next(body.begin())); + if (getGlobal && call && getGlobal.getName() == op.getObject() && + call.getNumArgOperands() == 1 && + call.getArgOperand(0) == getGlobal.getResult()) { + dtorFunc = getCalledFunction(call); + if (dtorFunc) + arg = builder.clone(*getGlobal)->getResult(0); + } + } + + // Otherwise the body moves into a helper that ignores its argument, and the + // helper is registered with a null argument. + if (!dtorFunc) { + dtorFunc = buildGlobalArrayDtorFunc(builder, object); + mlir::Block *entryBB = &dtorFunc.getBody().front(); + entryBB->getOperations().splice(entryBB->end(), body.getOperations()); + auto yieldOp = mlir::cast<cir::YieldOp>(entryBB->getTerminator()); + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPoint(yieldOp); + cir::ReturnOp::create(builder, yieldOp.getLoc()); + } + yieldOp->erase(); + arg = builder.getNullPtr(builder.getVoidPtrTy(), loc); + } + + cir::CallOp atExitCall = emitAtExitCall(builder, loc, dtorFunc, arg, + object.getTlsModel().has_value()); + // The atexit functions do not throw, and marking the call so keeps it out of + // an invoke inside the initializer. + atExitCall.setNothrowAttr(builder.getUnitAttr()); + op->erase(); +} + cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, mlir::Region &dtorRegion, @@ -1125,9 +1220,6 @@ cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder, mlir::OpBuilder::InsertionGuard guard(builder); assert(!cir::MissingFeatures::astVarDeclInterface()); - cir::VoidType voidTy = builder.getVoidTy(); - auto voidPtrTy = cir::PointerType::get(voidTy); - // Look for operations in dtorBlock mlir::Block &dtorBlock = dtorRegion.front(); @@ -1156,30 +1248,8 @@ cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder, } // 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); - - SmallVector<mlir::NamedAttribute> paramAttrs; - paramAttrs.push_back( - builder.getNamedAttr("llvm.noundef", builder.getUnitAttr())); - SmallVector<mlir::Attribute> argAttrDicts; - argAttrDicts.push_back( - mlir::DictionaryAttr::get(builder.getContext(), paramAttrs)); - dtorFunc.setArgAttrsAttr( - mlir::ArrayAttr::get(builder.getContext(), argAttrDicts)); - - mlir::Block *entryBB = dtorFunc.addEntryBlock(); + cir::FuncOp dtorFunc = buildGlobalArrayDtorFunc(builder, op); + mlir::Block *entryBB = &dtorFunc.getBody().front(); // Move everything from the dtor region into the helper function. entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(), @@ -2439,6 +2509,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) { lowerArrayCtor(arrayCtor); } else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) { lowerArrayDtor(arrayDtor); + } else if (auto registerOp = dyn_cast<cir::RegisterExitDtorOp>(op)) { + lowerRegisterExitDtorOp(registerOp); } else if (auto stdOp = mlir::dyn_cast<cir::StdOpInterface>(op)) { lowerStdOp(stdOp); } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) { @@ -3072,8 +3144,8 @@ void LoweringPreparePass::runOnOperation() { cir::ComplexConjOp, cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp, cir::FuncOp, cir::CallOp, cir::GetGlobalOp, cir::GlobalOp, cir::StoreOp, - cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdOpInterface>( - op)) + cir::CmpThreeWayOp, cir::LocalInitOp, cir::RegisterExitDtorOp, + cir::StdOpInterface>(op)) opsToTransform.push_back(op); }); diff --git a/clang/test/CIR/CodeGen/global-temp-dtor.cpp b/clang/test/CIR/CodeGen/global-temp-dtor.cpp index 7a4781838ff9c..43a5bb49fc97a 100644 --- a/clang/test/CIR/CodeGen/global-temp-dtor.cpp +++ b/clang/test/CIR/CodeGen/global-temp-dtor.cpp @@ -5,6 +5,10 @@ // RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,LLVMCIR // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-llvm %s -o %t-cir-eh.ll +// RUN: FileCheck --input-file=%t-cir-eh.ll %s --check-prefix=LLVM-EH +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm %s -o %t-eh.ll +// RUN: FileCheck --input-file=%t-eh.ll %s --check-prefix=LLVM-EH // Exercises lifetime-extended reference temporaries with non-trivial // destructors where the extending declaration has static or thread storage @@ -28,10 +32,11 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // CIR-BEFORE: %[[STATIC_REF:.*]] = cir.get_global @static_ref // CIR-BEFORE: %[[REF_TEMP:.*]] = cir.get_global @_ZGR10static_ref_ // CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[REF_TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR10static_ref_ { +// CIR-BEFORE: %[[REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR10static_ref_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[REF_TEMP_DTOR]]) +// CIR-BEFORE: } // CIR-BEFORE: cir.store{{.*}} %[[REF_TEMP]], %[[STATIC_REF]] -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR10static_ref_ -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[REF_TEMP_DTOR]]) // CIR-BEFORE: } // CIR-BEFORE: cir.global "private" internal @_ZGR10static_ref_ = #cir.zero : !rec_NonTrivial @@ -40,21 +45,22 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // CIR: cir.get_global @static_ref // CIR: cir.get_global @_ZGR10static_ref_ // CIR: cir.call @_ZN10NonTrivialC1Ev -// CIR: cir.store // CIR: cir.get_global @_ZGR10static_ref_ // CIR: cir.get_global @_ZN10NonTrivialD1Ev // CIR: cir.get_global @__dso_handle -// CIR: cir.call @__cxa_atexit +// CIR: cir.call @__cxa_atexit({{.*}}) nothrow +// CIR: cir.store // CIR: cir.global "private" internal @_ZGR10static_ref_ = #cir.zero : !rec_NonTrivial // CIR-BEFORE: cir.global external tls_model = tls_dyn tls_refs = <"_ZTW10thread_ref", "_ZTH10thread_ref"> @thread_ref = ctor : !cir.ptr<!rec_NonTrivial> { // CIR-BEFORE: %[[THREAD_REF:.*]] = cir.get_global thread_local @thread_ref // CIR-BEFORE: %[[REF_TEMP:.*]] = cir.get_global @_ZGR10thread_ref_ // CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[REF_TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR10thread_ref_ { +// CIR-BEFORE: %[[REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR10thread_ref_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[REF_TEMP_DTOR]]) +// CIR-BEFORE: } // CIR-BEFORE: cir.store{{.*}} %[[REF_TEMP]], %[[THREAD_REF]] -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR10thread_ref_ -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[REF_TEMP_DTOR]]) // CIR-BEFORE: } // CIR-BEFORE: cir.global "private" internal tls_model = tls_dyn @_ZGR10thread_ref_ = #cir.zero : !rec_NonTrivial @@ -63,11 +69,11 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // CIR: cir.get_global thread_local @thread_ref // CIR: cir.get_global @_ZGR10thread_ref_ // CIR: cir.call @_ZN10NonTrivialC1Ev -// CIR: cir.store // CIR: cir.get_global @_ZGR10thread_ref_ // CIR: cir.get_global @_ZN10NonTrivialD1Ev // CIR: cir.get_global @__dso_handle -// CIR: cir.call @__cxa_thread_atexit +// CIR: cir.call @__cxa_thread_atexit({{.*}}) nothrow +// CIR: cir.store // CIR: cir.global "private" internal tls_model = tls_dyn @_ZGR10thread_ref_ = #cir.zero : !rec_NonTrivial // CIR-BEFORE: cir.global external @static_arr_ref = ctor : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { @@ -83,30 +89,31 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // CIR-BEFORE: } while { // CIR-BEFORE: cir.condition // CIR-BEFORE: } -// CIR-BEFORE: cir.store{{.*}} %[[STATIC_ARR_REF_TEMP]], %[[STATIC_ARR_REF]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_NonTrivial x 2>>> -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[STATIC_ARR_REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR14static_arr_ref_ -// CIR-BEFORE: cir.array.dtor %[[STATIC_ARR_REF_TEMP_DTOR]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { -// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR14static_arr_ref_ { +// CIR-BEFORE: %[[STATIC_ARR_REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR14static_arr_ref_ +// CIR-BEFORE: cir.array.dtor %[[STATIC_ARR_REF_TEMP_DTOR]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { +// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: } // CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[STATIC_ARR_REF_TEMP]], %[[STATIC_ARR_REF]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_NonTrivial x 2>>> // CIR-BEFORE: } // CIR-BEFORE: cir.global "private" internal @_ZGR14static_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // CIR: cir.global external @static_arr_ref = #cir.ptr<null> : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> +// CIR: cir.func internal private @__cxx_global_var_init.2() +// CIR: cir.call @_ZN10NonTrivialC1Ev +// CIR: cir.get_global @__cxx_global_array_dtor +// CIR: cir.get_global @__dso_handle +// CIR: cir.call @__cxa_atexit({{.*}}) nothrow +// CIR: cir.store +// CIR: cir.global "private" internal @_ZGR14static_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // CIR: cir.func internal private @__cxx_global_array_dtor( // CIR: cir.do { // CIR: cir.call @_ZN10NonTrivialD1Ev // CIR: } while { // CIR: cir.condition // CIR: } -// CIR: cir.func internal private @__cxx_global_var_init.2() -// CIR: cir.call @_ZN10NonTrivialC1Ev -// CIR: cir.store -// CIR: cir.get_global @__cxx_global_array_dtor -// CIR: cir.get_global @__dso_handle -// CIR: cir.call @__cxa_atexit -// CIR: cir.global "private" internal @_ZGR14static_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // CIR-BEFORE: cir.global external tls_model = tls_dyn tls_refs = <"_ZTW14thread_arr_ref", "_ZTH14thread_arr_ref"> @thread_arr_ref = ctor : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { // CIR-BEFORE: %[[ARRAY_INIT_TEMP:.*]] = cir.alloca {{.*}}"arrayinit.temp" @@ -121,30 +128,31 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // CIR-BEFORE: } while { // CIR-BEFORE: cir.condition // CIR-BEFORE: } -// CIR-BEFORE: cir.store{{.*}} %[[THREAD_ARR_REF_TEMP]], %[[THREAD_ARR_REF]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_NonTrivial x 2>>> -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[THREAD_ARR_REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR14thread_arr_ref_ -// CIR-BEFORE: cir.array.dtor %[[THREAD_ARR_REF_TEMP_DTOR]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { -// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR14thread_arr_ref_ { +// CIR-BEFORE: %[[THREAD_ARR_REF_TEMP_DTOR:.*]] = cir.get_global @_ZGR14thread_arr_ref_ +// CIR-BEFORE: cir.array.dtor %[[THREAD_ARR_REF_TEMP_DTOR]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { +// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: } // CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[THREAD_ARR_REF_TEMP]], %[[THREAD_ARR_REF]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_NonTrivial x 2>>> // CIR-BEFORE: } // CIR-BEFORE: cir.global "private" internal tls_model = tls_dyn @_ZGR14thread_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // CIR: cir.global external tls_model = tls_dyn tls_refs = <"_ZTW14thread_arr_ref", "_ZTH14thread_arr_ref"> @thread_arr_ref = #cir.ptr<null> : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> +// CIR: cir.func internal private @__cxx_global_var_init.3() +// CIR: cir.call @_ZN10NonTrivialC1Ev +// CIR: cir.get_global @__cxx_global_array_dtor.1 +// CIR: cir.get_global @__dso_handle +// CIR: cir.call @__cxa_thread_atexit({{.*}}) nothrow +// CIR: cir.store +// CIR: cir.global "private" internal tls_model = tls_dyn @_ZGR14thread_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // CIR: cir.func internal private @__cxx_global_array_dtor.1( // CIR: cir.do { // CIR: cir.call @_ZN10NonTrivialD1Ev // CIR: } while { // CIR: cir.condition // CIR: } -// CIR: cir.func internal private @__cxx_global_var_init.3() -// CIR: cir.call @_ZN10NonTrivialC1Ev -// CIR: cir.store -// CIR: cir.get_global @__cxx_global_array_dtor.1 -// CIR: cir.get_global @__dso_handle -// CIR: cir.call @__cxa_thread_atexit -// CIR: cir.global "private" internal tls_model = tls_dyn @_ZGR14thread_arr_ref_ = #cir.zero : !cir.array<!rec_NonTrivial x 2> // LLVM-DAG: @static_ref = global ptr null // LLVM-DAG: @_ZGR10static_ref_ = internal global %struct.NonTrivial zeroinitializer @@ -158,79 +166,47 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // Static, non-array. // LLVM-LABEL: define internal void @__cxx_global_var_init() -// LLVM: call void @_ZN10NonTrivialC1Ev(ptr {{.*}} @_ZGR10static_ref_) -// LLVMCIR: store ptr @_ZGR10static_ref_, ptr @static_ref +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR10static_ref_) // LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR10static_ref_, ptr @__dso_handle) -// OGCG: store ptr @_ZGR10static_ref_, ptr @static_ref - -// Thread, non-array. +// LLVM: store ptr @_ZGR10static_ref_, ptr @static_ref -// LLVMCIR-LABEL: define internal void @__cxx_global_var_init.1() -// LLVMCIR: %[[TLS_ADDR:.*]] = call {{.*}}ptr @llvm.threadlocal.address.p0(ptr {{.*}}@thread_ref) -// LLVMCIR: call void @_ZN10NonTrivialC1Ev(ptr {{.*}} @_ZGR10thread_ref_) -// LLVMCIR: store ptr @_ZGR10thread_ref_, ptr %[[TLS_ADDR]] -// LLVMCIR: call i32 @__cxa_thread_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR10thread_ref_, ptr @__dso_handle) +// Thread, non-array. CIR takes the variable's thread-local address before +// running the initializer, and without classic's align 8 on the call. -// OGCG-LABEL: define internal void @__cxx_global_var_init.1() -// OGCG: call void @_ZN10NonTrivialC1Ev(ptr {{.*}} @_ZGR10thread_ref_) -// OGCG: call i32 @__cxa_thread_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR10thread_ref_, ptr @__dso_handle) -// OGCG: %[[TLS_ADDR:.*]] = call {{.*}}ptr @llvm.threadlocal.address.p0(ptr {{.*}}@thread_ref) -// OGCG: store ptr @_ZGR10thread_ref_, ptr %[[TLS_ADDR]] +// LLVM-LABEL: define internal void @__cxx_global_var_init.1() +// LLVMCIR: %[[TLS_ADDR:.*]] = call ptr @llvm.threadlocal.address.p0(ptr @thread_ref) +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR10thread_ref_) +// LLVM: call i32 @__cxa_thread_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR10thread_ref_, ptr @__dso_handle) +// OGCG: %[[TLS_ADDR:.*]] = call align 8 ptr @llvm.threadlocal.address.p0(ptr align 8 @thread_ref) +// LLVM: store ptr @_ZGR10thread_ref_, ptr %[[TLS_ADDR]] -// Static, array. +// Static, array. The two pipelines build the construction loop differently. -// LLVMCIR-LABEL: define internal void @__cxx_global_array_dtor(ptr {{.*}}) -// LLVMCIR: [[LOOP_CONDITION_BLOCK:.*]]: -// LLVMCIR: %[[DONE:.*]] = icmp ne ptr -// LLVMCIR: br i1 %[[DONE]], label %[[LOOP_BODY_BLOCK:.*]], label %[[LOOP_EXIT_BLOCK:.*]] -// LLVMCIR: [[LOOP_BODY_BLOCK]]: -// LLVMCIR: call void @_ZN10NonTrivialD1Ev(ptr -// LLVMCIR: br label %[[LOOP_CONDITION_BLOCK]] -// LLVMCIR: [[LOOP_EXIT_BLOCK]]: - -// LLVMCIR-LABEL: define internal void @__cxx_global_var_init.2() +// LLVM-LABEL: define internal void @__cxx_global_var_init.2() // LLVMCIR: [[LOOP_CONDITION_BLOCK:.*]]: // LLVMCIR: %[[DONE:.*]] = icmp ne ptr // LLVMCIR: br i1 %[[DONE]], label %[[LOOP_BODY_BLOCK:.*]], label %[[LOOP_EXIT_BLOCK:.*]] // LLVMCIR: [[LOOP_BODY_BLOCK]]: // LLVMCIR: call void @_ZN10NonTrivialC1Ev // LLVMCIR: br label %[[LOOP_CONDITION_BLOCK]] -// LLVMCIR: [[LOOP_EXIT_BLOCK]]: -// LLVMCIR: store ptr @_ZGR14static_arr_ref_, ptr @static_arr_ref -// LLVMCIR: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr @_ZGR14static_arr_ref_, ptr @__dso_handle) - -// OGCG-LABEL: define internal void @__cxx_global_var_init.2() -// OGCG: [[ENTRY:.*]]: -// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] -// OGCG: [[LOOP_BODY_BLOCK]]: -// OGCG: call void @_ZN10NonTrivialC1Ev -// OGCG: %[[DONE:.*]] = icmp eq ptr -// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] -// OGCG: [[LOOP_EXIT_BLOCK]]: -// OGCG: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle) -// OGCG: store ptr @_ZGR14static_arr_ref_, ptr @static_arr_ref - -// OGCG-LABEL: define internal void @__cxx_global_array_dtor(ptr {{.*}}) -// OGCG: [[ENTRY:.*]]: -// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] -// OGCG: [[LOOP_BODY_BLOCK:.*]]: -// OGCG: call void @_ZN10NonTrivialD1Ev(ptr -// OGCG: %[[DONE:.*]] = icmp eq ptr -// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] -// OGCG: [[LOOP_EXIT_BLOCK]]: - -// Thread, array. - -// LLVMCIR-LABEL: define internal void @__cxx_global_array_dtor.1(ptr {{.*}}) -// LLVMCIR: [[LOOP_CONDITION_BLOCK:.*]]: -// LLVMCIR: %[[DONE:.*]] = icmp ne ptr -// LLVMCIR: br i1 %[[DONE]], label %[[LOOP_BODY_BLOCK:.*]], label %[[LOOP_EXIT_BLOCK:.*]] -// LLVMCIR: [[LOOP_BODY_BLOCK]]: -// LLVMCIR: call void @_ZN10NonTrivialD1Ev(ptr -// LLVMCIR: br label %[[LOOP_CONDITION_BLOCK]] -// LLVMCIR: [[LOOP_EXIT_BLOCK]]: +// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] +// OGCG: [[LOOP_BODY_BLOCK]]: +// OGCG: call void @_ZN10NonTrivialC1Ev +// OGCG: %[[DONE:.*]] = icmp eq ptr +// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] +// LLVM: [[LOOP_EXIT_BLOCK]]: +// LLVM: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle) +// LLVM: store ptr @_ZGR14static_arr_ref_, ptr @static_arr_ref + +// The helper destroys the array through the global, not its argument. +// LLVM-LABEL: define internal void @__cxx_global_array_dtor(ptr noundef %{{.+}}) +// LLVM: getelementptr inbounds nuw (i8, ptr @_ZGR14static_arr_ref_, i64 8) +// LLVM: call void @_ZN10NonTrivialD1Ev(ptr -// LLVMCIR-LABEL: define internal void @__cxx_global_var_init.3() +// Thread, array. CIR also reaches the variable through its thread wrapper, +// and the two pipelines number the helper differently. + +// LLVM-LABEL: define internal void @__cxx_global_var_init.3() // LLVMCIR: %[[THREAD_ARR_REF:.*]] = call ptr @_ZTW14thread_arr_ref() // LLVMCIR: [[LOOP_CONDITION_BLOCK:.*]]: // LLVMCIR: %[[DONE:.*]] = icmp ne ptr @@ -238,27 +214,147 @@ thread_local const NonTrivialArr &thread_arr_ref = NonTrivialArr{}; // LLVMCIR: [[LOOP_BODY_BLOCK]]: // LLVMCIR: call void @_ZN10NonTrivialC1Ev // LLVMCIR: br label %[[LOOP_CONDITION_BLOCK]] -// LLVMCIR: [[LOOP_EXIT_BLOCK]]: +// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] +// OGCG: [[LOOP_BODY_BLOCK]]: +// OGCG: call void @_ZN10NonTrivialC1Ev +// OGCG: %[[DONE:.*]] = icmp eq ptr +// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] +// LLVM: [[LOOP_EXIT_BLOCK]]: +// LLVMCIR: call i32 @__cxa_thread_atexit(ptr @__cxx_global_array_dtor.1, ptr null, ptr @__dso_handle) // LLVMCIR: store ptr @_ZGR14thread_arr_ref_, ptr %[[THREAD_ARR_REF]] -// LLVMCIR: call i32 @__cxa_thread_atexit(ptr @__cxx_global_array_dtor.1, ptr @_ZGR14thread_arr_ref_, ptr @__dso_handle) - -// OGCG-LABEL: define internal void @__cxx_global_var_init.3() -// OGCG: [[ENTRY:.*]]: -// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] -// OGCG: [[LOOP_BODY_BLOCK]]: -// OGCG: call void @_ZN10NonTrivialC1Ev -// OGCG: %[[DONE:.*]] = icmp eq ptr -// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] -// OGCG: [[LOOP_EXIT_BLOCK]]: -// OGCG: call i32 @__cxa_thread_atexit(ptr @__cxx_global_array_dtor.4, ptr null, ptr @__dso_handle) -// OGCG: %[[THREAD_ARR_ADDR:.*]] = call {{.*}}ptr @llvm.threadlocal.address.p0(ptr {{.*}}@thread_arr_ref) -// OGCG: store ptr @_ZGR14thread_arr_ref_, ptr %[[THREAD_ARR_ADDR]] - -// OGCG-LABEL: define internal void @__cxx_global_array_dtor.4(ptr {{.*}}) -// OGCG: [[ENTRY:.*]]: -// OGCG: br label %[[LOOP_BODY_BLOCK:.*]] -// OGCG: [[LOOP_BODY_BLOCK:.*]]: -// OGCG: call void @_ZN10NonTrivialD1Ev(ptr -// OGCG: %[[DONE:.*]] = icmp eq ptr -// OGCG: br i1 %[[DONE]], label %[[LOOP_EXIT_BLOCK:.*]], label %[[LOOP_BODY_BLOCK]] -// OGCG: [[LOOP_EXIT_BLOCK]]: +// OGCG: call i32 @__cxa_thread_atexit(ptr @__cxx_global_array_dtor.4, ptr null, ptr @__dso_handle) +// OGCG: %[[THREAD_ARR_ADDR:.*]] = call align 8 ptr @llvm.threadlocal.address.p0(ptr align 8 @thread_arr_ref) +// OGCG: store ptr @_ZGR14thread_arr_ref_, ptr %[[THREAD_ARR_ADDR]] + +// LLVMCIR-LABEL: define internal void @__cxx_global_array_dtor.1(ptr noundef %{{.+}}) +// OGCG-LABEL: define internal void @__cxx_global_array_dtor.4(ptr noundef %{{.+}}) +// LLVM: getelementptr inbounds nuw (i8, ptr @_ZGR14thread_arr_ref_, i64 8) +// LLVM: call void @_ZN10NonTrivialD1Ev(ptr + +struct TwoRefs { + const NonTrivial &a, &b; +}; + +TwoRefs two_refs{NonTrivial(), NonTrivial()}; + +struct Holder { + const NonTrivial &r; + ~Holder(); +}; + +Holder holder{NonTrivial()}; + +// CIR-BEFORE: cir.global external @two_refs = ctor : !rec_TwoRefs { +// CIR-BEFORE: %[[A:.*]] = cir.get_global @_ZGR8two_refs_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[A]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR8two_refs_ { +// CIR-BEFORE: %[[A_DTOR:.*]] = cir.get_global @_ZGR8two_refs_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[A_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[A]] +// CIR-BEFORE: %[[B:.*]] = cir.get_global @_ZGR8two_refs0_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[B]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR8two_refs0_ { +// CIR-BEFORE: %[[B_DTOR:.*]] = cir.get_global @_ZGR8two_refs0_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[B_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[B]] +// CIR-BEFORE: } + +// CIR-BEFORE: cir.global external @holder = ctor : !rec_Holder { +// CIR-BEFORE: %[[T:.*]] = cir.get_global @_ZGR6holder_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[T]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR6holder_ { +// CIR-BEFORE: %[[T_DTOR:.*]] = cir.get_global @_ZGR6holder_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[T_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[T]] +// CIR-BEFORE: } dtor { +// CIR-BEFORE: %[[HOLDER:.*]] = cir.get_global @holder +// CIR-BEFORE: cir.call @_ZN6HolderD1Ev(%[[HOLDER]]) +// CIR-BEFORE: } + +// Two temporaries extended by one variable. +// LLVM-LABEL: define internal void @__cxx_global_var_init.{{[0-9]+}}() +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR8two_refs_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR8two_refs_, ptr @__dso_handle) +// LLVM: store ptr @_ZGR8two_refs_, ptr @two_refs +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR8two_refs0_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR8two_refs0_, ptr @__dso_handle) +// LLVM: store ptr @_ZGR8two_refs0_, ptr getelementptr inbounds nuw (i8, ptr @two_refs, i64 8) + +// An extending declaration with its own destructor. +// LLVM-LABEL: define internal void @__cxx_global_var_init.{{[0-9]+}}() +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR6holder_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR6holder_, ptr @__dso_handle) +// LLVM: store ptr @_ZGR6holder_, ptr @holder +// LLVM: call i32 @__cxa_atexit(ptr @_ZN6HolderD1Ev, ptr @holder, ptr @__dso_handle) + +bool pick(); + +const NonTrivial &cond_ref = + pick() ? static_cast<const NonTrivial &>(NonTrivial()) : static_ref; + +// CIR-BEFORE: cir.global external @cond_ref = ctor : !cir.ptr<!rec_NonTrivial> { +// CIR-BEFORE: cir.ternary(%{{.+}}, true { +// CIR-BEFORE: %[[TEMP:.*]] = cir.get_global @_ZGR8cond_ref_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGR8cond_ref_ { +// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGR8cond_ref_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[TEMP_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.yield %[[TEMP]] +// CIR-BEFORE: }, false { +// CIR-BEFORE: cir.get_global @static_ref +// CIR-BEFORE-NOT: cir.register_exit_dtor +// CIR-BEFORE: }) + +// Only the arm that builds the temporary registers its destruction. +// LLVM-LABEL: define internal void @__cxx_global_var_init.{{[0-9]+}}() +// LLVM: call noundef zeroext i1 @_Z4pickv() +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGR8cond_ref_) +// LLVM-NEXT: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGR8cond_ref_, ptr @__dso_handle) +// LLVM-NEXT: br label +// LLVM-NOT: __cxa_atexit +// LLVM: phi ptr +// LLVM-NOT: __cxa_atexit +// LLVM: store ptr %{{.+}}, ptr @cond_ref +// LLVM-NOT: __cxa_atexit +// LLVM: ret void + +struct Arg { + Arg(); + ~Arg(); +}; + +struct Built { + Built(const Arg &); + ~Built(); +}; + +const Built &built_ref = Built(Arg()); + +// CIR-BEFORE: cir.global external @built_ref = ctor : !cir.ptr<!rec_Built> { +// CIR-BEFORE: cir.call @_ZN3ArgC1Ev +// CIR-BEFORE: cir.cleanup.scope { +// CIR-BEFORE: cir.call @_ZN5BuiltC1ERK3Arg +// CIR-BEFORE: cir.register_exit_dtor @_ZGR9built_ref_ { +// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGR9built_ref_ +// CIR-BEFORE: cir.call @_ZN5BuiltD1Ev(%[[TEMP_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: } cleanup normal { +// CIR-BEFORE: cir.call @_ZN3ArgD1Ev + +// The extended temporary is registered before the full-expression temporary +// is destroyed. +// LLVM-LABEL: define internal void @__cxx_global_var_init.{{[0-9]+}}() +// LLVM: call void @_ZN3ArgC1Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[ARG:.+]]) +// LLVM: call void @_ZN5BuiltC1ERK3Arg(ptr noundef nonnull align 1 dereferenceable(1) @_ZGR9built_ref_, ptr noundef nonnull align 1 dereferenceable(1) %[[ARG]]) +// LLVM-NEXT: call i32 @__cxa_atexit(ptr @_ZN5BuiltD1Ev, ptr @_ZGR9built_ref_, ptr @__dso_handle) +// LLVM: call void @_ZN3ArgD1Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[ARG]]) + +// With exceptions enabled, the registration is still a call, not an invoke. +// LLVM-EH: invoke void @_ZN5BuiltC1ERK3Arg( +// LLVM-EH-NEXT: to label %{{.+}} unwind label %{{.+}} +// LLVM-EH: call i32 @__cxa_atexit(ptr @_ZN5BuiltD1Ev, ptr @_ZGR9built_ref_, ptr @__dso_handle) +// LLVM-EH: call void @_ZN3ArgD1Ev( diff --git a/clang/test/CIR/CodeGen/local-static-temp-dtor.cpp b/clang/test/CIR/CodeGen/local-static-temp-dtor.cpp index 8e06adef0f312..a459aa8773033 100644 --- a/clang/test/CIR/CodeGen/local-static-temp-dtor.cpp +++ b/clang/test/CIR/CodeGen/local-static-temp-dtor.cpp @@ -1,14 +1,20 @@ // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before.cir // RUN: FileCheck --input-file=%t-before.cir %s --check-prefix=CIR-BEFORE +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll -// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,LLVMCIR +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG - -// The same lifetime-extended reference temporaries as global-temp-dtor.cpp, but -// extended by a function-local static. Those are initialized and destroyed -// in-function under their guard variable, so the temporary's destructor belongs -// in the cir.local_init's dtor region rather than on the cir.global. +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=HELPER +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=HELPER +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-llvm %s -o %t-cir-eh.ll +// RUN: FileCheck --input-file=%t-cir-eh.ll %s --check-prefix=LLVM-EH +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm %s -o %t-eh.ll +// RUN: FileCheck --input-file=%t-eh.ll %s --check-prefix=LLVM-EH +// RUN: %clang_cc1 -std=c++17 -triple arm64-apple-macosx14.0.0 -fclangir -emit-llvm %s -o %t-cir-darwin.ll +// RUN: FileCheck --input-file=%t-cir-darwin.ll %s --check-prefixes=DARWIN,DARWIN-CIR +// RUN: %clang_cc1 -std=c++17 -triple arm64-apple-macosx14.0.0 -emit-llvm %s -o %t-darwin.ll +// RUN: FileCheck --input-file=%t-darwin.ll %s --check-prefixes=DARWIN,DARWIN-OGCG struct NonTrivial { NonTrivial(); @@ -30,21 +36,39 @@ void ref() { // CIR-BEFORE: %[[VAR:.*]] = cir.get_global static_local @_ZZ3refvE1r // CIR-BEFORE: %[[TEMP:.*]] = cir.get_global @_ZGRZ3refvE1r_ // CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ3refvE1r_ { +// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGRZ3refvE1r_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[TEMP_DTOR]]) +// CIR-BEFORE: } // CIR-BEFORE: cir.store{{.*}} %[[TEMP]], %[[VAR]] -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGRZ3refvE1r_ -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[TEMP_DTOR]]) // CIR-BEFORE: } // LLVM-DAG: @_ZGRZ3refvE1r_ = internal global %struct.NonTrivial zeroinitializer // LLVM-DAG: @_ZGRZ3arrvE1a_ = internal global [2 x %struct.NonTrivial] zeroinitializer +// The array helper is emitted ahead of the functions. +// CIR-LABEL: cir.func internal private @__cxx_global_array_dtor(%{{.+}}: !cir.ptr<!void> {llvm.noundef} +// CIR-NEXT: cir.get_global @_ZGRZ3arrvE1a_ : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> +// CIR: cir.call @_ZN10NonTrivialD1Ev + +// CIR-LABEL: cir.func {{.*}}@_Z3refv +// CIR: cir.call @__cxa_guard_acquire +// CIR: %[[TEMP:.*]] = cir.get_global @_ZGRZ3refvE1r_ +// CIR: cir.call @_ZN10NonTrivialC1Ev(%[[TEMP]]) +// CIR: %[[OBJ:.*]] = cir.get_global @_ZGRZ3refvE1r_ +// CIR: %[[DTOR:.*]] = cir.get_global @_ZN10NonTrivialD1Ev +// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] +// CIR: %[[OBJ_CAST:.*]] = cir.cast bitcast %[[OBJ]] : !cir.ptr<!rec_NonTrivial> -> !cir.ptr<!void> +// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle +// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[OBJ_CAST]], %[[HANDLE]]) nothrow +// CIR: cir.store{{.*}} %[[TEMP]] +// CIR: cir.call @__cxa_guard_release + // LLVM-LABEL: define dso_local void @_Z3refv() // LLVM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ3refvE1r) -// LLVMCIR: call void @_ZN10NonTrivialC1Ev(ptr {{.*}} @_ZGRZ3refvE1r_) -// LLVMCIR: store ptr @_ZGRZ3refvE1r_, ptr @_ZZ3refvE1r +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3refvE1r_) // LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3refvE1r_, ptr @__dso_handle) -// OGCG: store ptr @_ZGRZ3refvE1r_, ptr @_ZZ3refvE1r +// LLVM: store ptr @_ZGRZ3refvE1r_, ptr @_ZZ3refvE1r // LLVM: call void @__cxa_guard_release(ptr @_ZGVZ3refvE1r) void arr() { @@ -54,31 +78,199 @@ void arr() { // CIR-BEFORE-LABEL: cir.func {{.*}}@_Z3arrv // CIR-BEFORE: cir.local_init static_local @_ZZ3arrvE1a ctor { -// CIR-BEFORE: } dtor { -// CIR-BEFORE: %[[ARR_TEMP:.*]] = cir.get_global @_ZGRZ3arrvE1a_ -// CIR-BEFORE: cir.array.dtor %[[ARR_TEMP]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { -// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): -// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: cir.do { +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev +// CIR-BEFORE: } while { +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ3arrvE1a_ { +// CIR-BEFORE: %[[ARR_TEMP:.*]] = cir.get_global @_ZGRZ3arrvE1a_ +// CIR-BEFORE: cir.array.dtor %[[ARR_TEMP]] : !cir.ptr<!cir.array<!rec_NonTrivial x 2>> { +// CIR-BEFORE: ^bb0(%[[ELEMENT:.*]]: !cir.ptr<!rec_NonTrivial>): +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[ELEMENT]]) +// CIR-BEFORE: } // CIR-BEFORE: } +// CIR-BEFORE: cir.store // CIR-BEFORE: } -// The array destroyer is hoisted into a helper either way, and the two arms -// differ in what they hand it: classic codegen bakes the array's address into -// the helper and ignores the incoming parameter, so it registers a null -// __cxa_atexit argument, while CIR's helper destroys whatever it is given and -// so is registered with the address. Both are correct today, and this is not -// specific to a static local -- CIR emits a namespace-scope array temporary -// the same way. -// -// TODO(cir): we want the classic shape here. Besides being the output these -// tests are written against, it settles a duplication CIR currently pays for -// and does not use: its helper is parameterized, so it is the same function -// for every array of the same element type and count, yet one is still emitted -// per global (@__cxx_global_array_dtor, @__cxx_global_array_dtor.1, ...). -// Closing over the array instead would let these two checks become one. +// CIR-LABEL: cir.func {{.*}}@_Z3arrv +// CIR: cir.call @_ZN10NonTrivialC1Ev +// CIR: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CIR: %[[ARR_DTOR:.*]] = cir.get_global @__cxx_global_array_dtor +// CIR: %[[ARR_DTOR_CAST:.*]] = cir.cast bitcast %[[ARR_DTOR]] +// CIR: %[[NULL_CAST:.*]] = cir.cast bitcast %[[NULL]] +// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle +// CIR: cir.call @__cxa_atexit(%[[ARR_DTOR_CAST]], %[[NULL_CAST]], %[[HANDLE]]) nothrow + +// The helper destroys the array through the global, not its argument. +// HELPER-LABEL: define internal void @__cxx_global_array_dtor(ptr noundef %{{.+}}) +// HELPER: getelementptr inbounds nuw (i8, ptr @_ZGRZ3arrvE1a_, i64 8) +// HELPER: call void @_ZN10NonTrivialD1Ev(ptr // LLVM-LABEL: define dso_local void @_Z3arrv() // LLVM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ3arrvE1a) -// LLVMCIR: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr @_ZGRZ3arrvE1a_, ptr @__dso_handle) -// OGCG: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle) +// LLVM: call void @_ZN10NonTrivialC1Ev +// LLVM: call i32 @__cxa_atexit(ptr @__cxx_global_array_dtor, ptr null, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ3arrvE1a_, ptr @_ZZ3arrvE1a // LLVM: call void @__cxa_guard_release(ptr @_ZGVZ3arrvE1a) + +struct TwoRefs { + const NonTrivial &a, &b; +}; + +void two() { + static TwoRefs t{NonTrivial(), NonTrivial()}; + use(t.a); +} + +struct Holder { + const NonTrivial &r; + ~Holder(); +}; + +void holder() { + static Holder h{NonTrivial()}; + use(h.r); +} + +// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z3twov +// CIR-BEFORE: cir.local_init static_local @_ZZ3twovE1t ctor { +// CIR-BEFORE: %[[A:.*]] = cir.get_global @_ZGRZ3twovE1t_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[A]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ3twovE1t_ { +// CIR-BEFORE: %[[A_DTOR:.*]] = cir.get_global @_ZGRZ3twovE1t_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[A_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[A]] +// CIR-BEFORE: %[[B:.*]] = cir.get_global @_ZGRZ3twovE1t0_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[B]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ3twovE1t0_ { +// CIR-BEFORE: %[[B_DTOR:.*]] = cir.get_global @_ZGRZ3twovE1t0_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[B_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[B]] +// CIR-BEFORE: } + +// LLVM-LABEL: define dso_local void @_Z3twov() +// LLVM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ3twovE1t) +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3twovE1t_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3twovE1t_, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ3twovE1t_, ptr @_ZZ3twovE1t +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3twovE1t0_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3twovE1t0_, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ3twovE1t0_, ptr getelementptr inbounds nuw (i8, ptr @_ZZ3twovE1t, i64 8) +// LLVM: call void @__cxa_guard_release(ptr @_ZGVZ3twovE1t) + +// LLVM-EH-LABEL: define dso_local void @_Z3twov() +// LLVM-EH: call i32 @__cxa_guard_acquire(ptr @_ZGVZ3twovE1t) +// LLVM-EH: invoke void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3twovE1t_) +// LLVM-EH-NEXT: to label %{{.+}} unwind label %[[LPAD:.+]] +// LLVM-EH: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3twovE1t_, ptr @__dso_handle) +// LLVM-EH: invoke void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3twovE1t0_) +// LLVM-EH-NEXT: to label %{{.+}} unwind label %[[LPAD]] +// LLVM-EH: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3twovE1t0_, ptr @__dso_handle) +// LLVM-EH: [[LPAD]]: +// LLVM-EH: landingpad +// LLVM-EH: call void @__cxa_guard_abort(ptr @_ZGVZ3twovE1t) + +// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z6holderv +// CIR-BEFORE: cir.local_init static_local @_ZZ6holdervE1h ctor { +// CIR-BEFORE: %[[T:.*]] = cir.get_global @_ZGRZ6holdervE1h_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[T]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ6holdervE1h_ { +// CIR-BEFORE: %[[T_DTOR:.*]] = cir.get_global @_ZGRZ6holdervE1h_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[T_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.store{{.*}} %[[T]] +// CIR-BEFORE: } dtor { +// CIR-BEFORE: %[[H:.*]] = cir.get_global static_local @_ZZ6holdervE1h +// CIR-BEFORE: cir.call @_ZN6HolderD1Ev(%[[H]]) +// CIR-BEFORE: } + +// LLVM-LABEL: define dso_local void @_Z6holderv() +// LLVM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ6holdervE1h) +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ6holdervE1h_) +// LLVM: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ6holdervE1h_, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ6holdervE1h_, ptr @_ZZ6holdervE1h +// LLVM: call i32 @__cxa_atexit(ptr @_ZN6HolderD1Ev, ptr @_ZZ6holdervE1h, ptr @__dso_handle) +// LLVM: call void @__cxa_guard_release(ptr @_ZGVZ6holdervE1h) + +void tls() { + thread_local const NonTrivial &r = NonTrivial(); + use(r); +} + +void tls_arr() { + thread_local const NonTrivialArr &a = NonTrivialArr{}; + use(a[0]); +} + +extern NonTrivial nt; + +void cond(bool c) { + static const NonTrivial &r = + c ? static_cast<const NonTrivial &>(NonTrivial()) : nt; + use(r); +} + +// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z3tlsv +// CIR-BEFORE: cir.local_init thread_local @_ZZ3tlsvE1r ctor { +// CIR-BEFORE: %[[TEMP:.*]] = cir.get_global @_ZGRZ3tlsvE1r_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ3tlsvE1r_ { +// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGRZ3tlsvE1r_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[TEMP_DTOR]]) +// CIR-BEFORE: } + +// LLVM-LABEL: define dso_local void @_Z3tlsv() +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3tlsvE1r_) +// LLVM: call i32 @__cxa_thread_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3tlsvE1r_, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ3tlsvE1r_, ptr %{{.+}} + +// DARWIN-LABEL: define void @_Z3tlsv() +// Apple arm64 constructors return this in classic but not in CIR. +// DARWIN-CIR: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3tlsvE1r_) +// DARWIN-OGCG: %{{.+}} = call noundef ptr @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ3tlsvE1r_) +// DARWIN-NEXT: call i32 @_tlv_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ3tlsvE1r_, ptr @__dso_handle) +// DARWIN: store ptr @_ZGRZ3tlsvE1r_, ptr %{{.+}} + +// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z7tls_arrv +// CIR-BEFORE: cir.local_init thread_local @_ZZ7tls_arrvE1a ctor { +// CIR-BEFORE: cir.do { +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev +// CIR-BEFORE: } while { +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ7tls_arrvE1a_ { +// CIR-BEFORE: cir.array.dtor + +// HELPER-LABEL: define internal void @__cxx_global_array_dtor.1(ptr noundef %{{.+}}) +// HELPER: getelementptr inbounds nuw (i8, ptr @_ZGRZ7tls_arrvE1a_, i64 8) +// HELPER: call void @_ZN10NonTrivialD1Ev(ptr + +// LLVM-LABEL: define dso_local void @_Z7tls_arrv() +// LLVM: call void @_ZN10NonTrivialC1Ev +// LLVM: call i32 @__cxa_thread_atexit(ptr @__cxx_global_array_dtor.1, ptr null, ptr @__dso_handle) +// LLVM: store ptr @_ZGRZ7tls_arrvE1a_, ptr %{{.+}} + +// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z4condb +// CIR-BEFORE: cir.local_init static_local @_ZZ4condbE1r ctor { +// CIR-BEFORE: cir.ternary(%{{.+}}, true { +// CIR-BEFORE: %[[TEMP:.*]] = cir.get_global @_ZGRZ4condbE1r_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialC1Ev(%[[TEMP]]) +// CIR-BEFORE: cir.register_exit_dtor @_ZGRZ4condbE1r_ { +// CIR-BEFORE: %[[TEMP_DTOR:.*]] = cir.get_global @_ZGRZ4condbE1r_ +// CIR-BEFORE: cir.call @_ZN10NonTrivialD1Ev(%[[TEMP_DTOR]]) +// CIR-BEFORE: } +// CIR-BEFORE: cir.yield %[[TEMP]] +// CIR-BEFORE: }, false { +// CIR-BEFORE: cir.get_global @nt +// CIR-BEFORE-NOT: cir.register_exit_dtor +// CIR-BEFORE: }) + +// Only the arm that builds the temporary registers its destruction. +// LLVM-LABEL: define dso_local void @_Z4condb(i1 noundef zeroext %{{.+}}) +// LLVM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ4condbE1r) +// LLVM: call void @_ZN10NonTrivialC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZGRZ4condbE1r_) +// LLVM-NEXT: call i32 @__cxa_atexit(ptr @_ZN10NonTrivialD1Ev, ptr @_ZGRZ4condbE1r_, ptr @__dso_handle) +// LLVM-NEXT: br label +// LLVM-NOT: __cxa_atexit +// LLVM: phi ptr +// LLVM-NOT: __cxa_atexit +// LLVM: call void @__cxa_guard_release(ptr @_ZGVZ4condbE1r) diff --git a/clang/test/CIR/IR/invalid-register-exit-dtor.cir b/clang/test/CIR/IR/invalid-register-exit-dtor.cir new file mode 100644 index 0000000000000..f7c5ef64b9829 --- /dev/null +++ b/clang/test/CIR/IR/invalid-register-exit-dtor.cir @@ -0,0 +1,197 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.func @in_function() { + // expected-error @below {{must be in the ctor region of a cir.global or cir.local_init}} + cir.register_exit_dtor @_ZGR1r_ { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + cir.return + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.global external @r = #cir.zero : !rec_Tmp dtor { + // expected-error @below {{must be in the ctor region of a cir.global or cir.local_init}} + cir.register_exit_dtor @_ZGR1r_ { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{body must destroy the object}} + cir.register_exit_dtor @_ZGR1r_ { + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{'missing' does not reference a valid cir.global}} + cir.register_exit_dtor @missing { + %0 = cir.get_global @r : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpC1Ev(!cir.ptr<!rec_Tmp>) + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global external @other = ctor : !rec_Tmp { + %0 = cir.get_global @other : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpC1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{object must not have ctor or dtor regions}} + cir.register_exit_dtor @other { + %0 = cir.get_global @other : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.global external @r = ctor : !rec_Tmp { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + // expected-note @below {{required by region isolation constraints}} + cir.register_exit_dtor @_ZGR1r_ { + // expected-error @below {{using value defined outside the region}} + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global external @other = #cir.zero : !rec_Tmp dtor { + %0 = cir.get_global @other : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{object must not have ctor or dtor regions}} + cir.register_exit_dtor @other { + %0 = cir.get_global @other : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!s8i = !cir.int<s, 8> +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGRZ1fvE1r_ = #cir.zero : !rec_Tmp + cir.global "private" internal static_local_guard<"_ZGVZ1fvE1r"> @_ZZ1fvE1r = #cir.zero : !rec_Tmp + static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared> + + cir.func @f() { + cir.local_init static_local @_ZZ1fvE1r dtor { + // expected-error @below {{must be in the ctor region of a cir.global or cir.local_init}} + cir.register_exit_dtor @_ZGRZ1fvE1r_ { + %0 = cir.get_global @_ZGRZ1fvE1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + cir.yield + } + cir.return + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + cir.global "private" internal @_ZGR1r0_ = #cir.zero : !rec_Tmp + + cir.global external @r = ctor : !rec_Tmp { + cir.register_exit_dtor @_ZGR1r_ { + // expected-error @below {{must be in the ctor region of a cir.global or cir.local_init}} + cir.register_exit_dtor @_ZGR1r0_ { + %0 = cir.get_global @_ZGR1r0_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{body must not yield a value}} + cir.register_exit_dtor @_ZGR1r_ { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + cir.yield %0 : !cir.ptr<!rec_Tmp> + } + } +} diff --git a/clang/test/CIR/IR/register-exit-dtor.cir b/clang/test/CIR/IR/register-exit-dtor.cir new file mode 100644 index 0000000000000..aa3da52a8110a --- /dev/null +++ b/clang/test/CIR/IR/register-exit-dtor.cir @@ -0,0 +1,75 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +!u8i = !cir.int<u, 8> + +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> +!rec_Ref = !cir.struct<"Ref" {data !cir.ptr<!rec_Tmp>}> + +module attributes {cir.triple = "x86_64-unknown-linux-gnu"} { + cir.func private @_ZN3TmpC1Ev(!cir.ptr<!rec_Tmp>) + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + + cir.global "private" internal @_ZGR3ref_ = #cir.zero : !rec_Tmp + cir.global external @ref = ctor : !rec_Ref { + %0 = cir.get_global @_ZGR3ref_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpC1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + cir.register_exit_dtor @_ZGR3ref_ { + %1 = cir.get_global @_ZGR3ref_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%1) : (!cir.ptr<!rec_Tmp>) -> () + } + } + // CHECK: cir.global external @ref = ctor : !rec_Ref { + // CHECK: %[[TMP:.*]] = cir.get_global @_ZGR3ref_ : !cir.ptr<!rec_Tmp> + // CHECK: cir.call @_ZN3TmpC1Ev(%[[TMP]]) : (!cir.ptr<!rec_Tmp>) -> () + // CHECK: cir.register_exit_dtor @_ZGR3ref_ { + // CHECK: %[[TMP_DTOR:.*]] = cir.get_global @_ZGR3ref_ : !cir.ptr<!rec_Tmp> + // CHECK: cir.call @_ZN3TmpD1Ev(%[[TMP_DTOR]]) : (!cir.ptr<!rec_Tmp>) -> () + // CHECK: } + // CHECK: } + + cir.global "private" internal @_ZGR4cond_ = #cir.zero : !rec_Tmp + cir.global external @cond = ctor : !rec_Ref { + %0 = cir.const #cir.bool<true> : !cir.bool + cir.if %0 { + %1 = cir.get_global @_ZGR4cond_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpC1Ev(%1) : (!cir.ptr<!rec_Tmp>) -> () + cir.register_exit_dtor @_ZGR4cond_ { + %2 = cir.get_global @_ZGR4cond_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%2) : (!cir.ptr<!rec_Tmp>) -> () + } + } + } + // CHECK: cir.global external @cond = ctor : !rec_Ref { + // CHECK: cir.if %{{.+}} { + // CHECK: cir.call @_ZN3TmpC1Ev + // CHECK: cir.register_exit_dtor @_ZGR4cond_ { + // CHECK: %[[TMP_DTOR:.*]] = cir.get_global @_ZGR4cond_ : !cir.ptr<!rec_Tmp> + // CHECK: cir.call @_ZN3TmpD1Ev(%[[TMP_DTOR]]) : (!cir.ptr<!rec_Tmp>) -> () + // CHECK: } + // CHECK: } + // CHECK: } + + cir.global "private" internal @_ZGRZ1fvE1r_ = #cir.zero : !rec_Tmp + cir.global "private" internal static_local_guard<"_ZGVZ1fvE1r"> @_ZZ1fvE1r = #cir.zero : !rec_Ref + static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared> + cir.func @f() { + cir.local_init static_local @_ZZ1fvE1r ctor { + %0 = cir.get_global @_ZGRZ1fvE1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpC1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + cir.register_exit_dtor @_ZGRZ1fvE1r_ { + %1 = cir.get_global @_ZGRZ1fvE1r_ : !cir.ptr<!rec_Tmp> + cir.call @_ZN3TmpD1Ev(%1) : (!cir.ptr<!rec_Tmp>) -> () + } + cir.yield + } + cir.return + } + // CHECK: cir.func @f() { + // CHECK: cir.local_init static_local @_ZZ1fvE1r ctor { + // CHECK: cir.call @_ZN3TmpC1Ev + // CHECK: cir.register_exit_dtor @_ZGRZ1fvE1r_ { + // CHECK: %[[TMP_DTOR:.*]] = cir.get_global @_ZGRZ1fvE1r_ : !cir.ptr<!rec_Tmp> + // CHECK: cir.call @_ZN3TmpD1Ev(%[[TMP_DTOR]]) : (!cir.ptr<!rec_Tmp>) -> () + // CHECK: } + // CHECK: } +} >From 15c983a78e68ebfb3cd02899ed4bb6762e4edf16 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 7 Oct 2026 17:10:09 -0700 Subject: [PATCH 2/2] [CIR] Address reviewer feedback Assisted-by: Cursor / claude-opus-5-5 --- .../CIR/IR/invalid-register-exit-dtor.cir | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clang/test/CIR/IR/invalid-register-exit-dtor.cir b/clang/test/CIR/IR/invalid-register-exit-dtor.cir index f7c5ef64b9829..487a0e0359602 100644 --- a/clang/test/CIR/IR/invalid-register-exit-dtor.cir +++ b/clang/test/CIR/IR/invalid-register-exit-dtor.cir @@ -55,6 +55,27 @@ module { !u8i = !cir.int<u, 8> !rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> +module { + cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) + cir.global "private" internal @_ZGR1r_ = #cir.zero : !rec_Tmp + + cir.global external @r = ctor : !rec_Tmp { + // expected-error @below {{expects region #0 to have 0 or 1 blocks}} + cir.register_exit_dtor @_ZGR1r_ { + %0 = cir.get_global @_ZGR1r_ : !cir.ptr<!rec_Tmp> + cir.br ^bb1 + ^bb1: + cir.call @_ZN3TmpD1Ev(%0) : (!cir.ptr<!rec_Tmp>) -> () + cir.yield + } + } +} + +// ----- + +!u8i = !cir.int<u, 8> +!rec_Tmp = !cir.struct<"Tmp" {pad !u8i}> + module { cir.func private @_ZN3TmpD1Ev(!cir.ptr<!rec_Tmp>) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
