Skip to content

Commit 35dfc77

Browse files
andykaylorsvkeerthy
authored andcommitted
[CIR] Add support for global destructors (#162532)
This adds support for generating destructor calls for global variables that have a destructor. This doesn't handle functions that are marked as global destructors with `__attribute__((destructor))`. That will be handled later.
1 parent 9b1f32c commit 35dfc77

File tree

8 files changed

+298
-29
lines changed

8 files changed

+298
-29
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,12 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
374374
resOperands, attrs);
375375
}
376376

377+
cir::CallOp createCallOp(mlir::Location loc, mlir::SymbolRefAttr callee,
378+
mlir::ValueRange operands = mlir::ValueRange(),
379+
llvm::ArrayRef<mlir::NamedAttribute> attrs = {}) {
380+
return createCallOp(loc, callee, cir::VoidType(), operands, attrs);
381+
}
382+
377383
cir::CallOp createTryCallOp(
378384
mlir::Location loc, mlir::SymbolRefAttr callee = mlir::SymbolRefAttr(),
379385
mlir::Type returnType = cir::VoidType(),

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ struct MissingFeatures {
3838
static bool opGlobalPartition() { return false; }
3939
static bool opGlobalUsedOrCompilerUsed() { return false; }
4040
static bool opGlobalAnnotations() { return false; }
41-
static bool opGlobalDtorLowering() { return false; }
4241
static bool opGlobalCtorPriority() { return false; }
43-
static bool opGlobalCtorList() { return false; }
42+
static bool opGlobalDtorList() { return false; }
4443
static bool setDSOLocal() { return false; }
4544
static bool setComdat() { return false; }
4645

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "CIRGenCXXABI.h"
1314
#include "CIRGenFunction.h"
1415
#include "CIRGenModule.h"
1516

@@ -95,7 +96,63 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
9596
return;
9697
}
9798

98-
cgf.cgm.errorNYI(vd->getSourceRange(), "global with destructor");
99+
// If not constant storage we'll emit this regardless of NeedsDtor value.
100+
CIRGenBuilderTy &builder = cgf.getBuilder();
101+
102+
// Prepare the dtor region.
103+
mlir::OpBuilder::InsertionGuard guard(builder);
104+
mlir::Block *block = builder.createBlock(&addr.getDtorRegion());
105+
CIRGenFunction::LexicalScope lexScope{cgf, addr.getLoc(),
106+
builder.getInsertionBlock()};
107+
lexScope.setAsGlobalInit();
108+
builder.setInsertionPointToStart(block);
109+
110+
CIRGenModule &cgm = cgf.cgm;
111+
QualType type = vd->getType();
112+
113+
// Special-case non-array C++ destructors, if they have the right signature.
114+
// Under some ABIs, destructors return this instead of void, and cannot be
115+
// passed directly to __cxa_atexit if the target does not allow this
116+
// mismatch.
117+
const CXXRecordDecl *record = type->getAsCXXRecordDecl();
118+
bool canRegisterDestructor =
119+
record && (!cgm.getCXXABI().hasThisReturn(
120+
GlobalDecl(record->getDestructor(), Dtor_Complete)) ||
121+
cgm.getCXXABI().canCallMismatchedFunctionType());
122+
123+
// If __cxa_atexit is disabled via a flag, a different helper function is
124+
// generated elsewhere which uses atexit instead, and it takes the destructor
125+
// directly.
126+
cir::FuncOp fnOp;
127+
if (record && (canRegisterDestructor || cgm.getCodeGenOpts().CXAAtExit)) {
128+
if (vd->getTLSKind())
129+
cgm.errorNYI(vd->getSourceRange(), "TLS destructor");
130+
assert(!record->hasTrivialDestructor());
131+
assert(!cir::MissingFeatures::openCL());
132+
CXXDestructorDecl *dtor = record->getDestructor();
133+
// In LLVM OG codegen this is done in registerGlobalDtor, but CIRGen
134+
// relies on LoweringPrepare for further decoupling, so build the
135+
// call right here.
136+
auto gd = GlobalDecl(dtor, Dtor_Complete);
137+
fnOp = cgm.getAddrAndTypeOfCXXStructor(gd).second;
138+
cgf.getBuilder().createCallOp(
139+
cgf.getLoc(vd->getSourceRange()),
140+
mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
141+
mlir::ValueRange{cgm.getAddrOfGlobalVar(vd)});
142+
} else {
143+
cgm.errorNYI(vd->getSourceRange(), "array destructor");
144+
}
145+
assert(fnOp && "expected cir.func");
146+
cgm.getCXXABI().registerGlobalDtor(vd, fnOp, nullptr);
147+
148+
builder.setInsertionPointToEnd(block);
149+
if (block->empty()) {
150+
block->erase();
151+
// Don't confuse lexical cleanup.
152+
builder.clearInsertionPoint();
153+
} else {
154+
builder.create<cir::YieldOp>(addr.getLoc());
155+
}
99156
}
100157

101158
cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) {

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ class CIRGenCXXABI {
160160
bool forVirtualBase, bool delegating,
161161
Address thisAddr, QualType thisTy) = 0;
162162

163+
/// Emit code to force the execution of a destructor during global
164+
/// teardown. The default implementation of this uses atexit.
165+
///
166+
/// \param dtor - a function taking a single pointer argument
167+
/// \param addr - a pointer to pass to the destructor function.
168+
virtual void registerGlobalDtor(const VarDecl *vd, cir::FuncOp dtor,
169+
mlir::Value addr) = 0;
170+
163171
/// Checks if ABI requires extra virtual offset for vtable field.
164172
virtual bool
165173
isVirtualOffsetNeededForVTableField(CIRGenFunction &cgf,
@@ -233,6 +241,16 @@ class CIRGenCXXABI {
233241
return false;
234242
}
235243

244+
/// Returns true if the target allows calling a function through a pointer
245+
/// with a different signature than the actual function (or equivalently,
246+
/// bitcasting a function or function pointer to a different function type).
247+
/// In principle in the most general case this could depend on the target, the
248+
/// calling convention, and the actual types of the arguments and return
249+
/// value. Here it just means whether the signature mismatch could *ever* be
250+
/// allowed; in other words, does the target do strict checking of signatures
251+
/// for all calls.
252+
virtual bool canCallMismatchedFunctionType() const { return true; }
253+
236254
/// Gets the mangle context.
237255
clang::MangleContext &getMangleContext() { return *mangleContext; }
238256

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
6868
CXXDtorType type, bool forVirtualBase,
6969
bool delegating, Address thisAddr,
7070
QualType thisTy) override;
71+
void registerGlobalDtor(const VarDecl *vd, cir::FuncOp dtor,
72+
mlir::Value addr) override;
7173

7274
void emitRethrow(CIRGenFunction &cgf, bool isNoReturn) override;
7375
void emitThrow(CIRGenFunction &cgf, const CXXThrowExpr *e) override;
@@ -1507,6 +1509,27 @@ void CIRGenItaniumCXXABI::emitDestructorCall(
15071509
vttTy, nullptr);
15081510
}
15091511

1512+
void CIRGenItaniumCXXABI::registerGlobalDtor(const VarDecl *vd,
1513+
cir::FuncOp dtor,
1514+
mlir::Value addr) {
1515+
if (vd->isNoDestroy(cgm.getASTContext()))
1516+
return;
1517+
1518+
if (vd->getTLSKind()) {
1519+
cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: TLS");
1520+
return;
1521+
}
1522+
1523+
// HLSL doesn't support atexit.
1524+
if (cgm.getLangOpts().HLSL) {
1525+
cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: HLSL");
1526+
return;
1527+
}
1528+
1529+
// The default behavior is to use atexit. This is handled in lowering
1530+
// prepare. Nothing to be done for CIR here.
1531+
}
1532+
15101533
// The idea here is creating a separate block for the throw with an
15111534
// `UnreachableOp` as the terminator. So, we branch from the current block
15121535
// to the throw block and create a block for the remaining operations.

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

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) {
4141
return fileName;
4242
}
4343

44+
/// Return the FuncOp called by `callOp`.
45+
static cir::FuncOp getCalledFunction(cir::CallOp callOp) {
46+
mlir::SymbolRefAttr sym = llvm::dyn_cast_if_present<mlir::SymbolRefAttr>(
47+
callOp.getCallableForCallee());
48+
if (!sym)
49+
return nullptr;
50+
return dyn_cast_or_null<cir::FuncOp>(
51+
mlir::SymbolTable::lookupNearestSymbolFrom(callOp, sym));
52+
}
53+
4454
namespace {
4555
struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
4656
LoweringPreparePass() = default;
@@ -69,6 +79,12 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
6979
cir::FuncType type,
7080
cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage);
7181

82+
cir::GlobalOp buildRuntimeVariable(
83+
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
84+
mlir::Type type,
85+
cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage,
86+
cir::VisibilityKind visibility = cir::VisibilityKind::Default);
87+
7288
///
7389
/// AST related
7490
/// -----------
@@ -90,6 +106,25 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
90106

91107
} // namespace
92108

109+
cir::GlobalOp LoweringPreparePass::buildRuntimeVariable(
110+
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
111+
mlir::Type type, cir::GlobalLinkageKind linkage,
112+
cir::VisibilityKind visibility) {
113+
cir::GlobalOp g = dyn_cast_or_null<cir::GlobalOp>(
114+
mlir::SymbolTable::lookupNearestSymbolFrom(
115+
mlirModule, mlir::StringAttr::get(mlirModule->getContext(), name)));
116+
if (!g) {
117+
g = cir::GlobalOp::create(builder, loc, name, type);
118+
g.setLinkageAttr(
119+
cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
120+
mlir::SymbolTable::setSymbolVisibility(
121+
g, mlir::SymbolTable::Visibility::Private);
122+
g.setGlobalVisibilityAttr(
123+
cir::VisibilityAttr::get(builder.getContext(), visibility));
124+
}
125+
return g;
126+
}
127+
93128
cir::FuncOp LoweringPreparePass::buildRuntimeFunction(
94129
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
95130
cir::FuncType type, cir::GlobalLinkageKind linkage) {
@@ -640,7 +675,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
640675
// Create a variable initialization function.
641676
CIRBaseBuilderTy builder(getContext());
642677
builder.setInsertionPointAfter(op);
643-
auto fnType = cir::FuncType::get({}, builder.getVoidTy());
678+
cir::VoidType voidTy = builder.getVoidTy();
679+
auto fnType = cir::FuncType::get({}, voidTy);
644680
FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
645681
cir::GlobalLinkageKind::InternalLinkage);
646682

@@ -655,8 +691,57 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
655691
// Register the destructor call with __cxa_atexit
656692
mlir::Region &dtorRegion = op.getDtorRegion();
657693
if (!dtorRegion.empty()) {
658-
assert(!cir::MissingFeatures::opGlobalDtorLowering());
659-
llvm_unreachable("dtor region lowering is NYI");
694+
assert(!cir::MissingFeatures::astVarDeclInterface());
695+
assert(!cir::MissingFeatures::opGlobalThreadLocal());
696+
// Create a variable that binds the atexit to this shared object.
697+
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
698+
cir::GlobalOp handle = buildRuntimeVariable(
699+
builder, "__dso_handle", op.getLoc(), builder.getI8Type(),
700+
cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden);
701+
702+
// Look for the destructor call in dtorBlock
703+
mlir::Block &dtorBlock = dtorRegion.front();
704+
cir::CallOp dtorCall;
705+
for (auto op : reverse(dtorBlock.getOps<cir::CallOp>())) {
706+
dtorCall = op;
707+
break;
708+
}
709+
assert(dtorCall && "Expected a dtor call");
710+
cir::FuncOp dtorFunc = getCalledFunction(dtorCall);
711+
assert(dtorFunc && "Expected a dtor call");
712+
713+
// Create a runtime helper function:
714+
// extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
715+
auto voidPtrTy = cir::PointerType::get(voidTy);
716+
auto voidFnTy = cir::FuncType::get({voidPtrTy}, voidTy);
717+
auto voidFnPtrTy = cir::PointerType::get(voidFnTy);
718+
auto handlePtrTy = cir::PointerType::get(handle.getSymType());
719+
auto fnAtExitType =
720+
cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, voidTy);
721+
const char *nameAtExit = "__cxa_atexit";
722+
cir::FuncOp fnAtExit =
723+
buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType);
724+
725+
// Replace the dtor call with a call to __cxa_atexit(&dtor, &var,
726+
// &__dso_handle)
727+
builder.setInsertionPointAfter(dtorCall);
728+
mlir::Value args[3];
729+
auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
730+
// dtorPtrTy
731+
args[0] = cir::GetGlobalOp::create(builder, dtorCall.getLoc(), dtorPtrTy,
732+
dtorFunc.getSymName());
733+
args[0] = cir::CastOp::create(builder, dtorCall.getLoc(), voidFnPtrTy,
734+
cir::CastKind::bitcast, args[0]);
735+
args[1] =
736+
cir::CastOp::create(builder, dtorCall.getLoc(), voidPtrTy,
737+
cir::CastKind::bitcast, dtorCall.getArgOperand(0));
738+
args[2] = cir::GetGlobalOp::create(builder, handle.getLoc(), handlePtrTy,
739+
handle.getSymName());
740+
builder.createCallOp(dtorCall.getLoc(), fnAtExit, args);
741+
dtorCall->erase();
742+
entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(),
743+
dtorBlock.begin(),
744+
std::prev(dtorBlock.end()));
660745
}
661746

662747
// Replace cir.yield with cir.return
@@ -666,11 +751,12 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
666751
mlir::Block &block = op.getCtorRegion().front();
667752
yieldOp = &block.getOperations().back();
668753
} else {
669-
assert(!cir::MissingFeatures::opGlobalDtorLowering());
670-
llvm_unreachable("dtor region lowering is NYI");
754+
assert(!dtorRegion.empty());
755+
mlir::Block &block = dtorRegion.front();
756+
yieldOp = &block.getOperations().back();
671757
}
672758

673-
assert(isa<YieldOp>(*yieldOp));
759+
assert(isa<cir::YieldOp>(*yieldOp));
674760
cir::ReturnOp::create(builder, yieldOp->getLoc());
675761
return f;
676762
}
@@ -715,7 +801,10 @@ void LoweringPreparePass::buildGlobalCtorDtorList() {
715801
mlir::ArrayAttr::get(&getContext(), globalCtors));
716802
}
717803

718-
assert(!cir::MissingFeatures::opGlobalDtorLowering());
804+
// We will eventual need to populate a global_dtor list, but that's not
805+
// needed for globals with destructors. It will only be needed for functions
806+
// that are marked as global destructors with an attribute.
807+
assert(!cir::MissingFeatures::opGlobalDtorList());
719808
}
720809

721810
void LoweringPreparePass::buildCXXGlobalInitFunc() {

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1771,9 +1771,13 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
17711771
}
17721772

17731773
// Rewrite op.
1774-
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
1774+
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
17751775
op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()),
17761776
alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr, attributes);
1777+
newOp.setVisibility_Attr(mlir::LLVM::VisibilityAttr::get(
1778+
getContext(), lowerCIRVisibilityToLLVMVisibility(
1779+
op.getGlobalVisibilityAttr().getValue())));
1780+
17771781
return mlir::success();
17781782
}
17791783

@@ -2594,6 +2598,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
25942598
return std::make_pair(ctorAttr.getName(),
25952599
ctorAttr.getPriority());
25962600
});
2601+
assert(!cir::MissingFeatures::opGlobalDtorList());
25972602
}
25982603

25992604
mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite(

0 commit comments

Comments
 (0)