diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 89b519e96a93e..11ad61fdb8065 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -374,6 +374,12 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { resOperands, attrs); } + cir::CallOp createCallOp(mlir::Location loc, mlir::SymbolRefAttr callee, + mlir::ValueRange operands = mlir::ValueRange(), + llvm::ArrayRef attrs = {}) { + return createCallOp(loc, callee, cir::VoidType(), operands, attrs); + } + cir::CallOp createTryCallOp( mlir::Location loc, mlir::SymbolRefAttr callee = mlir::SymbolRefAttr(), mlir::Type returnType = cir::VoidType(), diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 3b7b130ebc973..fcce668947f53 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -38,9 +38,8 @@ struct MissingFeatures { static bool opGlobalPartition() { return false; } static bool opGlobalUsedOrCompilerUsed() { return false; } static bool opGlobalAnnotations() { return false; } - static bool opGlobalDtorLowering() { return false; } static bool opGlobalCtorPriority() { return false; } - static bool opGlobalCtorList() { return false; } + static bool opGlobalDtorList() { return false; } static bool setDSOLocal() { return false; } static bool setComdat() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index d5b35c25c83ba..274d11b8c7629 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "CIRGenCXXABI.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" @@ -95,7 +96,63 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd, return; } - cgf.cgm.errorNYI(vd->getSourceRange(), "global with destructor"); + // If not constant storage we'll emit this regardless of NeedsDtor value. + CIRGenBuilderTy &builder = cgf.getBuilder(); + + // Prepare the dtor region. + mlir::OpBuilder::InsertionGuard guard(builder); + mlir::Block *block = builder.createBlock(&addr.getDtorRegion()); + CIRGenFunction::LexicalScope lexScope{cgf, addr.getLoc(), + builder.getInsertionBlock()}; + lexScope.setAsGlobalInit(); + builder.setInsertionPointToStart(block); + + CIRGenModule &cgm = cgf.cgm; + QualType type = vd->getType(); + + // Special-case non-array C++ destructors, if they have the right signature. + // Under some ABIs, destructors return this instead of void, and cannot be + // passed directly to __cxa_atexit if the target does not allow this + // mismatch. + const CXXRecordDecl *record = type->getAsCXXRecordDecl(); + bool canRegisterDestructor = + record && (!cgm.getCXXABI().hasThisReturn( + GlobalDecl(record->getDestructor(), Dtor_Complete)) || + cgm.getCXXABI().canCallMismatchedFunctionType()); + + // If __cxa_atexit is disabled via a flag, a different helper function is + // generated elsewhere which uses atexit instead, and it takes the destructor + // directly. + cir::FuncOp fnOp; + if (record && (canRegisterDestructor || cgm.getCodeGenOpts().CXAAtExit)) { + if (vd->getTLSKind()) + cgm.errorNYI(vd->getSourceRange(), "TLS destructor"); + assert(!record->hasTrivialDestructor()); + assert(!cir::MissingFeatures::openCL()); + CXXDestructorDecl *dtor = record->getDestructor(); + // In LLVM OG codegen this is done in registerGlobalDtor, but CIRGen + // relies on LoweringPrepare for further decoupling, so build the + // 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)}); + } else { + cgm.errorNYI(vd->getSourceRange(), "array destructor"); + } + assert(fnOp && "expected cir.func"); + cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr); + + builder.setInsertionPointToEnd(block); + if (block->empty()) { + block->erase(); + // Don't confuse lexical cleanup. + builder.clearInsertionPoint(); + } else { + builder.create(addr.getLoc()); + } } cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) { diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index 2465a68b068bf..26da7ce94e0ef 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -160,6 +160,14 @@ class CIRGenCXXABI { bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) = 0; + /// Emit code to force the execution of a destructor during global + /// teardown. The default implementation of this uses atexit. + /// + /// \param dtor - a function taking a single pointer argument + /// \param addr - a pointer to pass to the destructor function. + virtual void registerGlobalDtor(const VarDecl *vd, cir::FuncOp dtor, + mlir::Value addr) = 0; + /// Checks if ABI requires extra virtual offset for vtable field. virtual bool isVirtualOffsetNeededForVTableField(CIRGenFunction &cgf, @@ -233,6 +241,16 @@ class CIRGenCXXABI { return false; } + /// Returns true if the target allows calling a function through a pointer + /// with a different signature than the actual function (or equivalently, + /// bitcasting a function or function pointer to a different function type). + /// In principle in the most general case this could depend on the target, the + /// calling convention, and the actual types of the arguments and return + /// value. Here it just means whether the signature mismatch could *ever* be + /// allowed; in other words, does the target do strict checking of signatures + /// for all calls. + virtual bool canCallMismatchedFunctionType() const { return true; } + /// Gets the mangle context. clang::MangleContext &getMangleContext() { return *mangleContext; } diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 04181740ccf6e..5622a68f608a0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -68,6 +68,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { CXXDtorType type, bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) override; + void registerGlobalDtor(const VarDecl *vd, cir::FuncOp dtor, + mlir::Value addr) override; void emitRethrow(CIRGenFunction &cgf, bool isNoReturn) override; void emitThrow(CIRGenFunction &cgf, const CXXThrowExpr *e) override; @@ -1507,6 +1509,27 @@ void CIRGenItaniumCXXABI::emitDestructorCall( vttTy, nullptr); } +void CIRGenItaniumCXXABI::registerGlobalDtor(const VarDecl *vd, + cir::FuncOp dtor, + mlir::Value addr) { + if (vd->isNoDestroy(cgm.getASTContext())) + return; + + if (vd->getTLSKind()) { + cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: TLS"); + return; + } + + // HLSL doesn't support atexit. + if (cgm.getLangOpts().HLSL) { + cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: HLSL"); + return; + } + + // The default behavior is to use atexit. This is handled in lowering + // prepare. Nothing to be done for CIR here. +} + // The idea here is creating a separate block for the throw with an // `UnreachableOp` as the terminator. So, we branch from the current block // to the throw block and create a block for the remaining operations. diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index bc917d0604668..706e54f064aa6 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -41,6 +41,16 @@ static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) { return fileName; } +/// Return the FuncOp called by `callOp`. +static cir::FuncOp getCalledFunction(cir::CallOp callOp) { + mlir::SymbolRefAttr sym = llvm::dyn_cast_if_present( + callOp.getCallableForCallee()); + if (!sym) + return nullptr; + return dyn_cast_or_null( + mlir::SymbolTable::lookupNearestSymbolFrom(callOp, sym)); +} + namespace { struct LoweringPreparePass : public LoweringPrepareBase { LoweringPreparePass() = default; @@ -69,6 +79,12 @@ struct LoweringPreparePass : public LoweringPrepareBase { cir::FuncType type, cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage); + cir::GlobalOp buildRuntimeVariable( + mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, + mlir::Type type, + cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage, + cir::VisibilityKind visibility = cir::VisibilityKind::Default); + /// /// AST related /// ----------- @@ -90,6 +106,25 @@ struct LoweringPreparePass : public LoweringPrepareBase { } // namespace +cir::GlobalOp LoweringPreparePass::buildRuntimeVariable( + mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, + mlir::Type type, cir::GlobalLinkageKind linkage, + cir::VisibilityKind visibility) { + cir::GlobalOp g = dyn_cast_or_null( + mlir::SymbolTable::lookupNearestSymbolFrom( + mlirModule, mlir::StringAttr::get(mlirModule->getContext(), name))); + if (!g) { + g = cir::GlobalOp::create(builder, loc, name, type); + g.setLinkageAttr( + cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); + mlir::SymbolTable::setSymbolVisibility( + g, mlir::SymbolTable::Visibility::Private); + g.setGlobalVisibilityAttr( + cir::VisibilityAttr::get(builder.getContext(), visibility)); + } + return g; +} + cir::FuncOp LoweringPreparePass::buildRuntimeFunction( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, cir::FuncType type, cir::GlobalLinkageKind linkage) { @@ -640,7 +675,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { // Create a variable initialization function. CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); - auto fnType = cir::FuncType::get({}, builder.getVoidTy()); + cir::VoidType voidTy = builder.getVoidTy(); + auto fnType = cir::FuncType::get({}, voidTy); FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, cir::GlobalLinkageKind::InternalLinkage); @@ -655,8 +691,57 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { // Register the destructor call with __cxa_atexit mlir::Region &dtorRegion = op.getDtorRegion(); if (!dtorRegion.empty()) { - assert(!cir::MissingFeatures::opGlobalDtorLowering()); - llvm_unreachable("dtor region lowering is NYI"); + 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(); + 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"); + + // Create a runtime helper function: + // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); + auto voidPtrTy = cir::PointerType::get(voidTy); + auto voidFnTy = cir::FuncType::get({voidPtrTy}, voidTy); + auto voidFnPtrTy = cir::PointerType::get(voidFnTy); + auto handlePtrTy = cir::PointerType::get(handle.getSymType()); + auto fnAtExitType = + cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, voidTy); + const char *nameAtExit = "__cxa_atexit"; + cir::FuncOp fnAtExit = + buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType); + + // Replace the dtor 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()); + // dtorPtrTy + 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); + dtorCall->erase(); + entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(), + dtorBlock.begin(), + std::prev(dtorBlock.end())); } // Replace cir.yield with cir.return @@ -666,11 +751,12 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { mlir::Block &block = op.getCtorRegion().front(); yieldOp = &block.getOperations().back(); } else { - assert(!cir::MissingFeatures::opGlobalDtorLowering()); - llvm_unreachable("dtor region lowering is NYI"); + assert(!dtorRegion.empty()); + mlir::Block &block = dtorRegion.front(); + yieldOp = &block.getOperations().back(); } - assert(isa(*yieldOp)); + assert(isa(*yieldOp)); cir::ReturnOp::create(builder, yieldOp->getLoc()); return f; } @@ -715,7 +801,10 @@ void LoweringPreparePass::buildGlobalCtorDtorList() { mlir::ArrayAttr::get(&getContext(), globalCtors)); } - assert(!cir::MissingFeatures::opGlobalDtorLowering()); + // We will eventual need to populate a global_dtor list, but that's not + // needed for globals with destructors. It will only be needed for functions + // that are marked as global destructors with an attribute. + assert(!cir::MissingFeatures::opGlobalDtorList()); } void LoweringPreparePass::buildCXXGlobalInitFunc() { diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index a80a2959deb8d..a1ecfc7a70909 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1771,9 +1771,13 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( } // Rewrite op. - rewriter.replaceOpWithNewOp( + auto newOp = rewriter.replaceOpWithNewOp( op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()), alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr, attributes); + newOp.setVisibility_Attr(mlir::LLVM::VisibilityAttr::get( + getContext(), lowerCIRVisibilityToLLVMVisibility( + op.getGlobalVisibilityAttr().getValue()))); + return mlir::success(); } @@ -2594,6 +2598,7 @@ void ConvertCIRToLLVMPass::runOnOperation() { return std::make_pair(ctorAttr.getName(), ctorAttr.getPriority()); }); + assert(!cir::MissingFeatures::opGlobalDtorList()); } mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite( diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp index 2afb5a5af01a0..0aab69536241a 100644 --- a/clang/test/CIR/CodeGen/global-init.cpp +++ b/clang/test/CIR/CodeGen/global-init.cpp @@ -6,6 +6,23 @@ // 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-prefix=OGCG +// Declarations that appear before global-specific definitions + +// CIR: module @{{.*}} attributes { +// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"_GLOBAL__sub_I_[[FILENAME:.*]]", 65535>] + +// LLVM: @__dso_handle = external hidden global i8 +// 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: @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: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }] + struct NeedsCtor { NeedsCtor(); }; @@ -16,34 +33,89 @@ NeedsCtor needsCtor; // CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr // CIR-BEFORE-LPP: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () -// CIR: module @{{.*}} attributes { -// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"_GLOBAL__sub_I_[[FILENAME:.*]]", 65535>] - // CIR: cir.global external @needsCtor = #cir.zero : !rec_NeedsCtor // CIR: cir.func internal private @__cxx_global_var_init() { // CIR: %0 = cir.get_global @needsCtor : !cir.ptr // CIR: cir.call @_ZN9NeedsCtorC1Ev(%0) : (!cir.ptr) -> () -// CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() { -// CIR: cir.call @__cxx_global_var_init() : () -> () -// CIR: cir.return -// CIR: } - -// LLVM: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1 -// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }] -// LLVM: declare void @_ZN9NeedsCtorC1Ev(ptr) - // LLVM: define internal void @__cxx_global_var_init() // LLVM: call void @_ZN9NeedsCtorC1Ev(ptr @needsCtor) -// LLVM: define void @_GLOBAL__sub_I_[[FILENAME]]() -// LLVM: call void @__cxx_global_var_init() - -// OGCG: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1 -// OGCG: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }] - // OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { // OGCG: call void @_ZN9NeedsCtorC1Ev(ptr noundef nonnull align 1 dereferenceable(1) @needsCtor) + +struct NeedsDtor { + ~NeedsDtor(); +}; + +NeedsDtor needsDtor; + +// CIR-BEFORE-LPP: cir.global external @needsDtor = #cir.zero : !rec_NeedsDtor dtor { +// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsDtor : !cir.ptr +// CIR-BEFORE-LPP: cir.call @_ZN9NeedsDtorD1Ev(%[[THIS]]) : (!cir.ptr) -> () + +// CIR: cir.global external @needsDtor = #cir.zero : !rec_NeedsDtor +// CIR: cir.func internal private @__cxx_global_var_init.1() { +// CIR: %[[OBJ:.*]] = cir.get_global @needsDtor : !cir.ptr +// CIR: %[[DTOR:.*]] = cir.get_global @_ZN9NeedsDtorD1Ev : !cir.ptr)>> +// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr)>> -> !cir.ptr)>> +// CIR: %[[OBJ_CAST:.*]] = cir.cast bitcast %[[OBJ]] : !cir.ptr -> !cir.ptr +// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr +// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[OBJ_CAST]], %[[HANDLE]]) : (!cir.ptr)>>, !cir.ptr, !cir.ptr) -> () + +// LLVM: define internal void @__cxx_global_var_init.1() { +// LLVM: call void @__cxa_atexit(ptr @_ZN9NeedsDtorD1Ev, ptr @needsDtor, ptr @__dso_handle) + +// OGCG: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { +// OGCG: %{{.*}} = call i32 @__cxa_atexit(ptr @_ZN9NeedsDtorD1Ev, ptr @needsDtor, ptr @__dso_handle) + +struct NeedsCtorDtor { + NeedsCtorDtor(); + ~NeedsCtorDtor(); +}; + +NeedsCtorDtor needsCtorDtor; + +// CIR-BEFORE-LPP: cir.global external @needsCtorDtor = ctor : !rec_NeedsCtorDtor { +// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtorDtor : !cir.ptr +// CIR-BEFORE-LPP: cir.call @_ZN13NeedsCtorDtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () +// CIR-BEFORE-LPP: } dtor { +// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtorDtor : !cir.ptr +// CIR-BEFORE-LPP: cir.call @_ZN13NeedsCtorDtorD1Ev(%[[THIS]]) : (!cir.ptr) -> () + +// CIR: cir.global external @needsCtorDtor = #cir.zero : !rec_NeedsCtorDtor +// CIR: cir.func internal private @__cxx_global_var_init.2() { +// CIR: %[[OBJ:.*]] = cir.get_global @needsCtorDtor : !cir.ptr +// CIR: cir.call @_ZN13NeedsCtorDtorC1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// CIR: %[[OBJ:.*]] = cir.get_global @needsCtorDtor : !cir.ptr +// CIR: %[[DTOR:.*]] = cir.get_global @_ZN13NeedsCtorDtorD1Ev : !cir.ptr)>> +// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr)>> -> !cir.ptr)>> +// CIR: %[[OBJ_CAST:.*]] = cir.cast bitcast %[[OBJ]] : !cir.ptr -> !cir.ptr +// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr +// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[OBJ_CAST]], %[[HANDLE]]) : (!cir.ptr)>>, !cir.ptr, !cir.ptr) -> () + +// LLVM: define internal void @__cxx_global_var_init.2() { +// LLVM: call void @_ZN13NeedsCtorDtorC1Ev(ptr @needsCtorDtor) +// LLVM: call void @__cxa_atexit(ptr @_ZN13NeedsCtorDtorD1Ev, ptr @needsCtorDtor, ptr @__dso_handle) + +// OGCG: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN13NeedsCtorDtorC1Ev(ptr noundef nonnull align 1 dereferenceable(1) @needsCtorDtor) +// OGCG: %{{.*}} = call i32 @__cxa_atexit(ptr @_ZN13NeedsCtorDtorD1Ev, ptr @needsCtorDtor, ptr @__dso_handle) + +// Common init function for all globals with default priority + +// CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() { +// CIR: cir.call @__cxx_global_var_init() : () -> () +// CIR: cir.call @__cxx_global_var_init.1() : () -> () +// CIR: cir.call @__cxx_global_var_init.2() : () -> () + +// LLVM: define void @_GLOBAL__sub_I_[[FILENAME]]() +// LLVM: call void @__cxx_global_var_init() +// LLVM: call void @__cxx_global_var_init.1() +// LLVM: call void @__cxx_global_var_init.2() + // OGCG: define internal void @_GLOBAL__sub_I_[[FILENAME]]() {{.*}} section ".text.startup" { // OGCG: call void @__cxx_global_var_init() +// OGCG: call void @__cxx_global_var_init.1() +// OGCG: call void @__cxx_global_var_init.2()