Skip to content

Commit 2e66d6c

Browse files
committed
[CIR] Call base class destructors
This adds handling for calling virtual and non-virtual base class destructors. Non-virtual base class destructors are call from the base (D2) destructor body for derived classes. Virtual base class destructors are called only from the complete (D1) destructor.
1 parent 761be78 commit 2e66d6c

File tree

4 files changed

+150
-10
lines changed

4 files changed

+150
-10
lines changed

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ class CIRGenCXXABI {
149149
/// Loads the incoming C++ this pointer as it was passed by the caller.
150150
mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
151151

152+
/// Get the implicit (second) parameter that comes after the "this" pointer,
153+
/// or nullptr if there is isn't one.
154+
virtual mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &cgf,
155+
const CXXDestructorDecl *dd,
156+
CXXDtorType type,
157+
bool forVirtualBase,
158+
bool delegating) = 0;
159+
152160
/// Emit constructor variants required by this ABI.
153161
virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
154162

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,36 @@ static bool isInitializerOfDynamicClass(const CXXCtorInitializer *baseInit) {
126126
}
127127

128128
namespace {
129+
/// Call the destructor for a direct base class.
130+
struct CallBaseDtor final : EHScopeStack::Cleanup {
131+
const CXXRecordDecl *baseClass;
132+
bool baseIsVirtual;
133+
CallBaseDtor(const CXXRecordDecl *base, bool baseIsVirtual)
134+
: baseClass(base), baseIsVirtual(baseIsVirtual) {}
135+
136+
void emit(CIRGenFunction &cgf) override {
137+
const CXXRecordDecl *derivedClass =
138+
cast<CXXMethodDecl>(cgf.curFuncDecl)->getParent();
139+
140+
const CXXDestructorDecl *d = baseClass->getDestructor();
141+
// We are already inside a destructor, so presumably the object being
142+
// destroyed should have the expected type.
143+
QualType thisTy = d->getFunctionObjectParameterType();
144+
assert(cgf.currSrcLoc && "expected source location");
145+
Address addr = cgf.getAddressOfDirectBaseInCompleteClass(
146+
*cgf.currSrcLoc, cgf.loadCXXThisAddress(), derivedClass, baseClass,
147+
baseIsVirtual);
148+
cgf.emitCXXDestructorCall(d, Dtor_Base, baseIsVirtual,
149+
/*delegating=*/false, addr, thisTy);
150+
}
151+
152+
// This is a placeholder until EHCleanupScope is implemented.
153+
size_t getSize() const override {
154+
assert(!cir::MissingFeatures::ehCleanupScope());
155+
return sizeof(CallBaseDtor);
156+
}
157+
};
158+
129159
/// A visitor which checks whether an initializer uses 'this' in a
130160
/// way which requires the vtable to be properly set.
131161
struct DynamicThisUseChecker
@@ -928,8 +958,21 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
928958
if (dtorType == Dtor_Complete) {
929959
assert(!cir::MissingFeatures::sanitizers());
930960

931-
if (classDecl->getNumVBases())
932-
cgm.errorNYI(dd->getSourceRange(), "virtual base destructor cleanups");
961+
// We push them in the forward order so that they'll be popped in
962+
// the reverse order.
963+
for (const CXXBaseSpecifier &base : classDecl->vbases()) {
964+
auto *baseClassDecl = base.getType()->castAsCXXRecordDecl();
965+
966+
if (baseClassDecl->hasTrivialDestructor()) {
967+
// Under SanitizeMemoryUseAfterDtor, poison the trivial base class
968+
// memory. For non-trival base classes the same is done in the class
969+
// destructor.
970+
assert(!cir::MissingFeatures::sanitizers());
971+
} else {
972+
ehStack.pushCleanup<CallBaseDtor>(NormalAndEHCleanup, baseClassDecl,
973+
/*baseIsVirtual=*/true);
974+
}
975+
}
933976

934977
return;
935978
}
@@ -948,8 +991,8 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
948991
if (baseClassDecl->hasTrivialDestructor())
949992
assert(!cir::MissingFeatures::sanitizers());
950993
else
951-
cgm.errorNYI(dd->getSourceRange(),
952-
"non-trivial base destructor cleanups");
994+
ehStack.pushCleanup<CallBaseDtor>(NormalAndEHCleanup, baseClassDecl,
995+
/*baseIsVirtual=*/false);
953996
}
954997

955998
assert(!cir::MissingFeatures::sanitizers());

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
5959

6060
void addImplicitStructorParams(CIRGenFunction &cgf, QualType &resTy,
6161
FunctionArgList &params) override;
62-
62+
mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &cgf,
63+
const CXXDestructorDecl *dd,
64+
CXXDtorType type,
65+
bool forVirtualBase,
66+
bool delegating) override;
6367
void emitCXXConstructors(const clang::CXXConstructorDecl *d) override;
6468
void emitCXXDestructors(const clang::CXXDestructorDecl *d) override;
6569
void emitCXXStructor(clang::GlobalDecl gd) override;
@@ -1492,11 +1496,8 @@ void CIRGenItaniumCXXABI::emitDestructorCall(
14921496
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
14931497
bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) {
14941498
GlobalDecl gd(dd, type);
1495-
if (needsVTTParameter(gd)) {
1496-
cgm.errorNYI(dd->getSourceRange(), "emitDestructorCall: VTT");
1497-
}
1498-
1499-
mlir::Value vtt = nullptr;
1499+
mlir::Value vtt =
1500+
getCXXDestructorImplicitParam(cgf, dd, type, forVirtualBase, delegating);
15001501
ASTContext &astContext = cgm.getASTContext();
15011502
QualType vttTy = astContext.getPointerType(astContext.VoidPtrTy);
15021503
assert(!cir::MissingFeatures::appleKext());
@@ -1507,6 +1508,13 @@ void CIRGenItaniumCXXABI::emitDestructorCall(
15071508
vttTy, nullptr);
15081509
}
15091510

1511+
mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam(
1512+
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
1513+
bool forVirtualBase, bool delegating) {
1514+
GlobalDecl gd(dd, type);
1515+
return cgf.getVTTParameter(gd, forVirtualBase, delegating);
1516+
}
1517+
15101518
// The idea here is creating a separate block for the throw with an
15111519
// `UnreachableOp` as the terminator. So, we branch from the current block
15121520
// to the throw block and create a block for the remaining operations.

clang/test/CIR/CodeGen/dtors.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,84 @@ void test_nested_dtor() {
208208
// OGCG: define {{.*}} void @_ZN1DD2Ev
209209
// OGCG: %[[C:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i64 4
210210
// OGCG: call void @_ZN1CD1Ev(ptr {{.*}} %[[C]])
211+
212+
struct E {
213+
~E();
214+
};
215+
216+
struct F : public E {
217+
int n;
218+
~F() {}
219+
};
220+
221+
// CIR: cir.func {{.*}} @_ZN1FD2Ev
222+
// CIR: %[[BASE_E:.*]] = cir.base_class_addr %{{.*}} : !cir.ptr<!rec_F> nonnull [0] -> !cir.ptr<!rec_E>
223+
// CIR: cir.call @_ZN1ED2Ev(%[[BASE_E]]) nothrow : (!cir.ptr<!rec_E>) -> ()
224+
225+
// Because E is at offset 0 in F, there is no getelementptr needed.
226+
227+
// LLVM: define {{.*}} void @_ZN1FD2Ev
228+
// LLVM: call void @_ZN1ED2Ev(ptr %{{.*}})
229+
230+
// This destructor is defined after the calling function in OGCG.
231+
232+
void test_base_dtor_call() {
233+
F f;
234+
}
235+
236+
// CIR: cir.func {{.*}} @_Z19test_base_dtor_callv()
237+
// cir.call @_ZN1FD2Ev(%{{.*}}) nothrow : (!cir.ptr<!rec_F>) -> ()
238+
239+
// LLVM: define {{.*}} void @_Z19test_base_dtor_callv()
240+
// LLVM: call void @_ZN1FD2Ev(ptr %{{.*}})
241+
242+
// OGCG: define {{.*}} void @_Z19test_base_dtor_callv()
243+
// OGCG: call void @_ZN1FD2Ev(ptr {{.*}} %{{.*}})
244+
245+
// OGCG: define {{.*}} void @_ZN1FD2Ev
246+
// OGCG: call void @_ZN1ED2Ev(ptr {{.*}} %{{.*}})
247+
248+
struct VirtualBase {
249+
~VirtualBase();
250+
};
251+
252+
struct Derived : virtual VirtualBase {
253+
~Derived() {}
254+
};
255+
256+
void test_base_dtor_call_virtual_base() {
257+
Derived d;
258+
}
259+
260+
// Derived D2 (base) destructor -- does not call VirtualBase destructor
261+
262+
// CIR: cir.func {{.*}} @_ZN7DerivedD2Ev
263+
// CIR-NOT: cir.call{{.*}} @_ZN11VirtualBaseD2Ev
264+
// CIR: cir.return
265+
266+
// LLVM: define {{.*}} void @_ZN7DerivedD2Ev
267+
// LLVM-NOT: call{{.*}} @_ZN11VirtualBaseD2Ev
268+
// LLVM: ret
269+
270+
// Derived D1 (complete) destructor -- does call VirtualBase destructor
271+
272+
// CIR: cir.func {{.*}} @_ZN7DerivedD1Ev
273+
// CIR: %[[THIS:.*]] = cir.load %{{.*}}
274+
// CIR: %[[VTT:.*]] = cir.vtt.address_point @_ZTT7Derived, offset = 0 -> !cir.ptr<!cir.ptr<!void>>
275+
// CIR: cir.call @_ZN7DerivedD2Ev(%[[THIS]], %[[VTT]])
276+
// CIR: %[[VIRTUAL_BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_VirtualBase>
277+
// CIR: cir.call @_ZN11VirtualBaseD2Ev(%[[VIRTUAL_BASE]])
278+
279+
// LLVM: define {{.*}} void @_ZN7DerivedD1Ev
280+
// LLVM: call void @_ZN7DerivedD2Ev(ptr %{{.*}}, ptr @_ZTT7Derived)
281+
// LLVM: call void @_ZN11VirtualBaseD2Ev(ptr %{{.*}})
282+
283+
// OGCG emits these destructors in reverse order
284+
285+
// OGCG: define {{.*}} void @_ZN7DerivedD1Ev
286+
// OGCG: call void @_ZN7DerivedD2Ev(ptr {{.*}} %{{.*}}, ptr {{.*}} @_ZTT7Derived)
287+
// OGCG: call void @_ZN11VirtualBaseD2Ev(ptr {{.*}} %{{.*}})
288+
289+
// OGCG: define {{.*}} void @_ZN7DerivedD2Ev
290+
// OGCG-NOT: call{{.*}} @_ZN11VirtualBaseD2Ev
291+
// OGCG: ret

0 commit comments

Comments
 (0)