Skip to content

Commit 1b627da

Browse files
authored
[CIR] Call base class destructors (llvm#162562)
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 2c3f0e5 commit 1b627da

File tree

4 files changed

+144
-10
lines changed

4 files changed

+144
-10
lines changed

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

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

158+
/// Get the implicit (second) parameter that comes after the "this" pointer,
159+
/// or nullptr if there is isn't one.
160+
virtual mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &cgf,
161+
const CXXDestructorDecl *dd,
162+
CXXDtorType type,
163+
bool forVirtualBase,
164+
bool delegating) = 0;
165+
158166
/// Emit constructor variants required by this ABI.
159167
virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
160168

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,30 @@ 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+
129153
/// A visitor which checks whether an initializer uses 'this' in a
130154
/// way which requires the vtable to be properly set.
131155
struct DynamicThisUseChecker
@@ -922,8 +946,21 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
922946
if (dtorType == Dtor_Complete) {
923947
assert(!cir::MissingFeatures::sanitizers());
924948

925-
if (classDecl->getNumVBases())
926-
cgm.errorNYI(dd->getSourceRange(), "virtual base destructor cleanups");
949+
// We push them in the forward order so that they'll be popped in
950+
// the reverse order.
951+
for (const CXXBaseSpecifier &base : classDecl->vbases()) {
952+
auto *baseClassDecl = base.getType()->castAsCXXRecordDecl();
953+
954+
if (baseClassDecl->hasTrivialDestructor()) {
955+
// Under SanitizeMemoryUseAfterDtor, poison the trivial base class
956+
// memory. For non-trival base classes the same is done in the class
957+
// destructor.
958+
assert(!cir::MissingFeatures::sanitizers());
959+
} else {
960+
ehStack.pushCleanup<CallBaseDtor>(NormalAndEHCleanup, baseClassDecl,
961+
/*baseIsVirtual=*/true);
962+
}
963+
}
927964

928965
return;
929966
}
@@ -942,8 +979,8 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
942979
if (baseClassDecl->hasTrivialDestructor())
943980
assert(!cir::MissingFeatures::sanitizers());
944981
else
945-
cgm.errorNYI(dd->getSourceRange(),
946-
"non-trivial base destructor cleanups");
982+
ehStack.pushCleanup<CallBaseDtor>(NormalAndEHCleanup, baseClassDecl,
983+
/*baseIsVirtual=*/false);
947984
}
948985

949986
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;
@@ -1504,11 +1508,8 @@ void CIRGenItaniumCXXABI::emitDestructorCall(
15041508
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
15051509
bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) {
15061510
GlobalDecl gd(dd, type);
1507-
if (needsVTTParameter(gd)) {
1508-
cgm.errorNYI(dd->getSourceRange(), "emitDestructorCall: VTT");
1509-
}
1510-
1511-
mlir::Value vtt = nullptr;
1511+
mlir::Value vtt =
1512+
getCXXDestructorImplicitParam(cgf, dd, type, forVirtualBase, delegating);
15121513
ASTContext &astContext = cgm.getASTContext();
15131514
QualType vttTy = astContext.getPointerType(astContext.VoidPtrTy);
15141515
assert(!cir::MissingFeatures::appleKext());
@@ -1540,6 +1541,13 @@ void CIRGenItaniumCXXABI::registerGlobalDtor(const VarDecl *vd,
15401541
// prepare. Nothing to be done for CIR here.
15411542
}
15421543

1544+
mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam(
1545+
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
1546+
bool forVirtualBase, bool delegating) {
1547+
GlobalDecl gd(dd, type);
1548+
return cgf.getVTTParameter(gd, forVirtualBase, delegating);
1549+
}
1550+
15431551
// The idea here is creating a separate block for the throw with an
15441552
// `UnreachableOp` as the terminator. So, we branch from the current block
15451553
// 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)