Skip to content

Commit f78d6ca

Browse files
authored
[CIR] Add Minimal Destructor Definition Support (#144719)
This patch upstreams support for writing inline and out of line C++ destructor definitions. Calling a destructor implcitly or explicitly is left for a future patch. Because of that restriction complete destructors (D2 in Itanium mangling) do not call into the base (D1) destructors yet but simply behave like a base destructor. Deleting (D0) destructor support is not part of this patch. Destructor aliases aren't supported, either. Because of this compilation with -mno-constructor-aliases may be required to avoid running into NYI errors.
1 parent 3d6aac3 commit f78d6ca

File tree

8 files changed

+226
-16
lines changed

8 files changed

+226
-16
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ struct MissingFeatures {
250250
static bool typeChecks() { return false; }
251251
static bool weakRefReference() { return false; }
252252
static bool writebacks() { return false; }
253+
static bool appleKext() { return false; }
254+
static bool dtorCleanups() { return false; }
255+
static bool completeDtors() { return false; }
256+
static bool vtableInitialization() { return false; }
253257

254258
// Missing types
255259
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
4141
assert(!cir::MissingFeatures::cxxabiThisAlignment());
4242
}
4343

44+
cir::GlobalLinkageKind CIRGenCXXABI::getCXXDestructorLinkage(
45+
GVALinkage linkage, const CXXDestructorDecl *dtor, CXXDtorType dt) const {
46+
// Delegate back to cgm by default.
47+
return cgm.getCIRLinkageForDeclarator(dtor, linkage,
48+
/*isConstantVariable=*/false);
49+
}
50+
4451
mlir::Value CIRGenCXXABI::loadIncomingCXXThis(CIRGenFunction &cgf) {
4552
ImplicitParamDecl *vd = getThisDecl(cgf);
4653
Address addr = cgf.getAddrOfLocalVar(vd);

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ class CIRGenCXXABI {
7272
/// Emit constructor variants required by this ABI.
7373
virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
7474

75+
/// Emit dtor variants required by this ABI.
76+
virtual void emitCXXDestructors(const clang::CXXDestructorDecl *d) = 0;
77+
78+
/// Returns true if the given destructor type should be emitted as a linkonce
79+
/// delegating thunk, regardless of whether the dtor is defined in this TU or
80+
/// not.
81+
virtual bool useThunkForDtorVariant(const CXXDestructorDecl *dtor,
82+
CXXDtorType dt) const = 0;
83+
84+
virtual cir::GlobalLinkageKind
85+
getCXXDestructorLinkage(GVALinkage linkage, const CXXDestructorDecl *dtor,
86+
CXXDtorType dt) const;
87+
7588
/// Returns true if the given constructor or destructor is one of the kinds
7689
/// that the ABI says returns 'this' (only applies when called non-virtually
7790
/// for destructors).

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
463463
startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());
464464

465465
if (isa<CXXDestructorDecl>(funcDecl)) {
466-
getCIRGenModule().errorNYI(bodyRange, "C++ destructor definition");
466+
emitDestructorBody(args);
467467
} else if (isa<CXXConstructorDecl>(funcDecl)) {
468468
emitConstructorBody(args);
469469
} else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
@@ -540,6 +540,96 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList &args) {
540540
}
541541
}
542542

543+
/// Emits the body of the current destructor.
544+
void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
545+
const CXXDestructorDecl *dtor = cast<CXXDestructorDecl>(curGD.getDecl());
546+
CXXDtorType dtorType = curGD.getDtorType();
547+
548+
// For an abstract class, non-base destructors are never used (and can't
549+
// be emitted in general, because vbase dtors may not have been validated
550+
// by Sema), but the Itanium ABI doesn't make them optional and Clang may
551+
// in fact emit references to them from other compilations, so emit them
552+
// as functions containing a trap instruction.
553+
if (dtorType != Dtor_Base && dtor->getParent()->isAbstract()) {
554+
cgm.errorNYI(dtor->getSourceRange(), "abstract base class destructors");
555+
return;
556+
}
557+
558+
Stmt *body = dtor->getBody();
559+
assert(body && !cir::MissingFeatures::incrementProfileCounter());
560+
561+
// The call to operator delete in a deleting destructor happens
562+
// outside of the function-try-block, which means it's always
563+
// possible to delegate the destructor body to the complete
564+
// destructor. Do so.
565+
if (dtorType == Dtor_Deleting) {
566+
cgm.errorNYI(dtor->getSourceRange(), "deleting destructor");
567+
return;
568+
}
569+
570+
// If the body is a function-try-block, enter the try before
571+
// anything else.
572+
const bool isTryBody = isa_and_nonnull<CXXTryStmt>(body);
573+
if (isTryBody)
574+
cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor");
575+
576+
assert(!cir::MissingFeatures::sanitizers());
577+
assert(!cir::MissingFeatures::dtorCleanups());
578+
579+
// If this is the complete variant, just invoke the base variant;
580+
// the epilogue will destruct the virtual bases. But we can't do
581+
// this optimization if the body is a function-try-block, because
582+
// we'd introduce *two* handler blocks. In the Microsoft ABI, we
583+
// always delegate because we might not have a definition in this TU.
584+
switch (dtorType) {
585+
case Dtor_Comdat:
586+
llvm_unreachable("not expecting a COMDAT");
587+
case Dtor_Deleting:
588+
llvm_unreachable("already handled deleting case");
589+
590+
case Dtor_Complete:
591+
assert((body || getTarget().getCXXABI().isMicrosoft()) &&
592+
"can't emit a dtor without a body for non-Microsoft ABIs");
593+
594+
assert(!cir::MissingFeatures::dtorCleanups());
595+
596+
// TODO(cir): A complete destructor is supposed to call the base destructor.
597+
// Since we have to emit both dtor kinds we just fall through for now and.
598+
// As long as we don't support virtual bases this should be functionally
599+
// equivalent.
600+
assert(!cir::MissingFeatures::completeDtors());
601+
602+
// Fallthrough: act like we're in the base variant.
603+
[[fallthrough]];
604+
605+
case Dtor_Base:
606+
assert(body);
607+
608+
assert(!cir::MissingFeatures::dtorCleanups());
609+
assert(!cir::MissingFeatures::vtableInitialization());
610+
611+
if (isTryBody) {
612+
cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor");
613+
} else if (body) {
614+
(void)emitStmt(body, /*useCurrentScope=*/true);
615+
} else {
616+
assert(dtor->isImplicit() && "bodyless dtor not implicit");
617+
// nothing to do besides what's in the epilogue
618+
}
619+
// -fapple-kext must inline any call to this dtor into
620+
// the caller's body.
621+
assert(!cir::MissingFeatures::appleKext());
622+
623+
break;
624+
}
625+
626+
assert(!cir::MissingFeatures::dtorCleanups());
627+
628+
// Exit the try if applicable.
629+
if (isTryBody)
630+
cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor");
631+
}
632+
543633
/// Given a value of type T* that may not be to a complete object, construct
544634
/// an l-vlaue withi the natural pointee alignment of T.
545635
LValue CIRGenFunction::makeNaturalAlignPointeeAddrLValue(mlir::Value val,

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ class CIRGenFunction : public CIRGenTypeCache {
830830
LValue emitCompoundAssignmentLValue(const clang::CompoundAssignOperator *e);
831831

832832
void emitConstructorBody(FunctionArgList &args);
833+
void emitDestructorBody(FunctionArgList &args);
833834

834835
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
835836

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,16 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
4343
CIRGenFunction &cgf) override;
4444

4545
void emitCXXConstructors(const clang::CXXConstructorDecl *d) override;
46+
void emitCXXDestructors(const clang::CXXDestructorDecl *d) override;
4647
void emitCXXStructor(clang::GlobalDecl gd) override;
48+
49+
bool useThunkForDtorVariant(const CXXDestructorDecl *dtor,
50+
CXXDtorType dt) const override {
51+
// Itanium does not emit any destructor variant as an inline thunk.
52+
// Delegating may occur as an optimization, but all variants are either
53+
// emitted with external linkage or as linkonce if they are inline and used.
54+
return false;
55+
}
4756
};
4857

4958
} // namespace
@@ -150,17 +159,14 @@ static void emitConstructorDestructorAlias(CIRGenModule &cgm,
150159

151160
void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) {
152161
auto *md = cast<CXXMethodDecl>(gd.getDecl());
153-
auto *cd = dyn_cast<CXXConstructorDecl>(md);
154-
155162
StructorCIRGen cirGenType = getCIRGenToUse(cgm, md);
163+
const auto *cd = dyn_cast<CXXConstructorDecl>(md);
156164

157-
if (!cd) {
158-
cgm.errorNYI(md->getSourceRange(), "CXCABI emit destructor");
159-
return;
160-
}
161-
162-
if (gd.getCtorType() == Ctor_Complete) {
163-
GlobalDecl baseDecl = gd.getWithCtorType(Ctor_Base);
165+
if (cd ? gd.getCtorType() == Ctor_Complete
166+
: gd.getDtorType() == Dtor_Complete) {
167+
GlobalDecl baseDecl =
168+
cd ? gd.getWithCtorType(Ctor_Base) : gd.getWithDtorType(Dtor_Base);
169+
;
164170

165171
if (cirGenType == StructorCIRGen::Alias ||
166172
cirGenType == StructorCIRGen::COMDAT) {
@@ -197,6 +203,22 @@ void CIRGenItaniumCXXABI::emitCXXConstructors(const CXXConstructorDecl *d) {
197203
}
198204
}
199205

206+
void CIRGenItaniumCXXABI::emitCXXDestructors(const CXXDestructorDecl *d) {
207+
// The destructor used for destructing this as a base class; ignores
208+
// virtual bases.
209+
cgm.emitGlobal(GlobalDecl(d, Dtor_Base));
210+
211+
// The destructor used for destructing this as a most-derived class;
212+
// call the base destructor and then destructs any virtual bases.
213+
cgm.emitGlobal(GlobalDecl(d, Dtor_Complete));
214+
215+
// The destructor in a virtual table is always a 'deleting'
216+
// destructor, which calls the complete destructor and then uses the
217+
// appropriate operator delete.
218+
if (d->isVirtual())
219+
cgm.emitGlobal(GlobalDecl(d, Dtor_Deleting));
220+
}
221+
200222
/// Return whether the given global decl needs a VTT (virtual table table)
201223
/// parameter, which it does if it's a base constructor or destructor with
202224
/// virtual bases.

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,14 +1111,14 @@ CIRGenModule::getCIRLinkageVarDefinition(const VarDecl *vd, bool isConstant) {
11111111
}
11121112

11131113
cir::GlobalLinkageKind CIRGenModule::getFunctionLinkage(GlobalDecl gd) {
1114-
const auto *fd = cast<FunctionDecl>(gd.getDecl());
1114+
const auto *d = cast<FunctionDecl>(gd.getDecl());
11151115

1116-
GVALinkage linkage = astContext.GetGVALinkageForFunction(fd);
1116+
GVALinkage linkage = astContext.GetGVALinkageForFunction(d);
11171117

1118-
if (isa<CXXDestructorDecl>(fd))
1119-
errorNYI(fd->getSourceRange(), "getFunctionLinkage: CXXDestructorDecl");
1118+
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(d))
1119+
return getCXXABI().getCXXDestructorLinkage(linkage, dtor, gd.getDtorType());
11201120

1121-
return getCIRLinkageForDeclarator(fd, linkage, /*IsConstantVariable=*/false);
1121+
return getCIRLinkageForDeclarator(d, linkage, /*isConstantVariable=*/false);
11221122
}
11231123

11241124
static cir::GlobalOp
@@ -1274,6 +1274,9 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
12741274
case Decl::CXXConstructor:
12751275
getCXXABI().emitCXXConstructors(cast<CXXConstructorDecl>(decl));
12761276
break;
1277+
case Decl::CXXDestructor:
1278+
getCXXABI().emitCXXDestructors(cast<CXXDestructorDecl>(decl));
1279+
break;
12771280

12781281
// C++ Decls
12791282
case Decl::LinkageSpec:
@@ -1335,6 +1338,17 @@ cir::FuncOp CIRGenModule::getAddrOfFunction(clang::GlobalDecl gd,
13351338
funcType = convertType(fd->getType());
13361339
}
13371340

1341+
// Devirtualized destructor calls may come through here instead of via
1342+
// getAddrOfCXXStructor. Make sure we use the MS ABI base destructor instead
1343+
// of the complete destructor when necessary.
1344+
if (const auto *dd = dyn_cast<CXXDestructorDecl>(gd.getDecl())) {
1345+
if (getTarget().getCXXABI().isMicrosoft() &&
1346+
gd.getDtorType() == Dtor_Complete &&
1347+
dd->getParent()->getNumVBases() == 0)
1348+
errorNYI(dd->getSourceRange(),
1349+
"getAddrOfFunction: MS ABI complete destructor");
1350+
}
1351+
13381352
StringRef mangledName = getMangledName(gd);
13391353
cir::FuncOp func =
13401354
getOrCreateCIRFunction(mangledName, funcType, gd, forVTable, dontDefer,
@@ -1729,7 +1743,9 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
17291743
// All MSVC dtors other than the base dtor are linkonce_odr and delegate to
17301744
// each other bottoming out wiht the base dtor. Therefore we emit non-base
17311745
// dtors on usage, even if there is no dtor definition in the TU.
1732-
if (isa_and_nonnull<CXXDestructorDecl>(d))
1746+
if (isa_and_nonnull<CXXDestructorDecl>(d) &&
1747+
getCXXABI().useThunkForDtorVariant(cast<CXXDestructorDecl>(d),
1748+
gd.getDtorType()))
17331749
errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: dtor");
17341750

17351751
// This is the first use or definition of a mangled name. If there is a
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir -mno-constructor-aliases %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
3+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -mno-constructor-aliases -o %t-cir.ll
4+
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
5+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -mno-constructor-aliases -o %t.ll
6+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
7+
8+
void some_function() noexcept;
9+
10+
struct out_of_line_destructor {
11+
int prevent_tail_padding_reuse;
12+
~out_of_line_destructor();
13+
};
14+
15+
out_of_line_destructor::~out_of_line_destructor() {
16+
some_function();
17+
}
18+
19+
// CIR: !rec_out_of_line_destructor = !cir.record<struct "out_of_line_destructor" {!s32i}>
20+
21+
// CIR: cir.func dso_local @_ZN22out_of_line_destructorD2Ev(%{{.+}}: !cir.ptr<!rec_out_of_line_destructor>
22+
// CIR: cir.call @_Z13some_functionv() nothrow : () -> ()
23+
// CIR: cir.return
24+
25+
// LLVM: define dso_local void @_ZN22out_of_line_destructorD2Ev(ptr %{{.+}})
26+
// LLVM: call void @_Z13some_functionv()
27+
// LLVM: ret void
28+
29+
// OGCG: define dso_local void @_ZN22out_of_line_destructorD2Ev(ptr {{.*}}%{{.+}})
30+
// OGCG: call void @_Z13some_functionv()
31+
// OGCG: ret void
32+
33+
// CIR: cir.func dso_local @_ZN22out_of_line_destructorD1Ev(%{{.+}}: !cir.ptr<!rec_out_of_line_destructor>
34+
// CIR: cir.call @_Z13some_functionv() nothrow : () -> ()
35+
// CIR: cir.return
36+
37+
// LLVM: define dso_local void @_ZN22out_of_line_destructorD1Ev(ptr %{{.+}})
38+
// LLVM: call void @_Z13some_functionv()
39+
// LLVM: ret void
40+
41+
// OGCG: define dso_local void @_ZN22out_of_line_destructorD1Ev(ptr {{.*}}%{{.+}})
42+
// OGCG: call void @_ZN22out_of_line_destructorD2Ev
43+
// OGCG: ret void
44+
45+
struct inline_destructor {
46+
int prevent_tail_padding_reuse;
47+
~inline_destructor() noexcept(false) {
48+
some_function();
49+
}
50+
};
51+
52+
// This inline destructor is not odr-used in this TU.
53+
// Make sure we don't emit a definition
54+
55+
// CIR-NOT: cir.func {{.*}}inline_destructor{{.*}}
56+
// LLVM-NOT: define {{.*}}inline_destructor{{.*}}
57+
// OGCG-NOT: define {{.*}}inline_destructor{{.*}}

0 commit comments

Comments
 (0)