Skip to content

Commit 17badb3

Browse files
andykaylorgithub-actions[bot]
authored andcommitted
Automerge: [CIR] Add support for destructing class members (#162196)
This adds the necessary cleanup handling to get class destructors to call the destructor for fields that require it.
2 parents 7cd607d + 81c6f53 commit 17badb3

File tree

4 files changed

+177
-4
lines changed

4 files changed

+177
-4
lines changed

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,109 @@ void CIRGenFunction::destroyCXXObject(CIRGenFunction &cgf, Address addr,
870870
/*delegating=*/false, addr, type);
871871
}
872872

873+
namespace {
874+
class DestroyField final : public EHScopeStack::Cleanup {
875+
const FieldDecl *field;
876+
CIRGenFunction::Destroyer *destroyer;
877+
878+
public:
879+
DestroyField(const FieldDecl *field, CIRGenFunction::Destroyer *destroyer)
880+
: field(field), destroyer(destroyer) {}
881+
882+
void emit(CIRGenFunction &cgf) override {
883+
// Find the address of the field.
884+
Address thisValue = cgf.loadCXXThisAddress();
885+
CanQualType recordTy =
886+
cgf.getContext().getCanonicalTagType(field->getParent());
887+
LValue thisLV = cgf.makeAddrLValue(thisValue, recordTy);
888+
LValue lv = cgf.emitLValueForField(thisLV, field);
889+
assert(lv.isSimple());
890+
891+
assert(!cir::MissingFeatures::ehCleanupFlags());
892+
cgf.emitDestroy(lv.getAddress(), field->getType(), destroyer);
893+
}
894+
895+
// This is a placeholder until EHCleanupScope is implemented.
896+
size_t getSize() const override {
897+
assert(!cir::MissingFeatures::ehCleanupScope());
898+
return sizeof(DestroyField);
899+
}
900+
};
901+
} // namespace
902+
903+
/// Emit all code that comes at the end of class's destructor. This is to call
904+
/// destructors on members and base classes in reverse order of their
905+
/// construction.
906+
///
907+
/// For a deleting destructor, this also handles the case where a destroying
908+
/// operator delete completely overrides the definition.
909+
void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
910+
CXXDtorType dtorType) {
911+
assert((!dd->isTrivial() || dd->hasAttr<DLLExportAttr>()) &&
912+
"Should not emit dtor epilogue for non-exported trivial dtor!");
913+
914+
// The deleting-destructor phase just needs to call the appropriate
915+
// operator delete that Sema picked up.
916+
if (dtorType == Dtor_Deleting) {
917+
cgm.errorNYI(dd->getSourceRange(), "deleting destructor cleanups");
918+
return;
919+
}
920+
921+
const CXXRecordDecl *classDecl = dd->getParent();
922+
923+
// Unions have no bases and do not call field destructors.
924+
if (classDecl->isUnion())
925+
return;
926+
927+
// The complete-destructor phase just destructs all the virtual bases.
928+
if (dtorType == Dtor_Complete) {
929+
assert(!cir::MissingFeatures::sanitizers());
930+
931+
if (classDecl->getNumVBases())
932+
cgm.errorNYI(dd->getSourceRange(), "virtual base destructor cleanups");
933+
934+
return;
935+
}
936+
937+
assert(dtorType == Dtor_Base);
938+
assert(!cir::MissingFeatures::sanitizers());
939+
940+
// Destroy non-virtual bases.
941+
for (const CXXBaseSpecifier &base : classDecl->bases()) {
942+
// Ignore virtual bases.
943+
if (base.isVirtual())
944+
continue;
945+
946+
CXXRecordDecl *baseClassDecl = base.getType()->getAsCXXRecordDecl();
947+
948+
if (baseClassDecl->hasTrivialDestructor())
949+
assert(!cir::MissingFeatures::sanitizers());
950+
else
951+
cgm.errorNYI(dd->getSourceRange(),
952+
"non-trivial base destructor cleanups");
953+
}
954+
955+
assert(!cir::MissingFeatures::sanitizers());
956+
957+
// Destroy direct fields.
958+
for (const FieldDecl *field : classDecl->fields()) {
959+
QualType type = field->getType();
960+
QualType::DestructionKind dtorKind = type.isDestructedType();
961+
if (!dtorKind)
962+
continue;
963+
964+
// Anonymous union members do not have their destructors called.
965+
const RecordType *rt = type->getAsUnionType();
966+
if (rt && rt->getOriginalDecl()->isAnonymousStructOrUnion())
967+
continue;
968+
969+
CleanupKind cleanupKind = getCleanupKind(dtorKind);
970+
assert(!cir::MissingFeatures::ehCleanupFlags());
971+
ehStack.pushCleanup<DestroyField>(cleanupKind, field,
972+
getDestroyer(dtorKind));
973+
}
974+
}
975+
873976
void CIRGenFunction::emitDelegatingCXXConstructorCall(
874977
const CXXConstructorDecl *ctor, const FunctionArgList &args) {
875978
assert(ctor->isDelegatingConstructor());

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,9 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
689689
cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor");
690690

691691
assert(!cir::MissingFeatures::sanitizers());
692-
assert(!cir::MissingFeatures::dtorCleanups());
692+
693+
// Enter the epilogue cleanups.
694+
RunCleanupsScope dtorEpilogue(*this);
693695

694696
// If this is the complete variant, just invoke the base variant;
695697
// the epilogue will destruct the virtual bases. But we can't do
@@ -708,7 +710,8 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
708710
assert((body || getTarget().getCXXABI().isMicrosoft()) &&
709711
"can't emit a dtor without a body for non-Microsoft ABIs");
710712

711-
assert(!cir::MissingFeatures::dtorCleanups());
713+
// Enter the cleanup scopes for virtual bases.
714+
enterDtorCleanups(dtor, Dtor_Complete);
712715

713716
if (!isTryBody) {
714717
QualType thisTy = dtor->getFunctionObjectParameterType();
@@ -723,7 +726,9 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
723726
case Dtor_Base:
724727
assert(body);
725728

726-
assert(!cir::MissingFeatures::dtorCleanups());
729+
// Enter the cleanup scopes for fields and non-virtual bases.
730+
enterDtorCleanups(dtor, Dtor_Base);
731+
727732
assert(!cir::MissingFeatures::vtableInitialization());
728733

729734
if (isTryBody) {
@@ -741,7 +746,8 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
741746
break;
742747
}
743748

744-
assert(!cir::MissingFeatures::dtorCleanups());
749+
// Jump out through the epilogue cleanups.
750+
dtorEpilogue.forceCleanup();
745751

746752
// Exit the try if applicable.
747753
if (isTryBody)

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,33 @@ class CIRGenFunction : public CIRGenTypeCache {
556556
cir::GlobalOp gv,
557557
cir::GetGlobalOp gvAddr);
558558

559+
/// Enter the cleanups necessary to complete the given phase of destruction
560+
/// for a destructor. The end result should call destructors on members and
561+
/// base classes in reverse order of their construction.
562+
void enterDtorCleanups(const CXXDestructorDecl *dtor, CXXDtorType type);
563+
564+
/// Determines whether an EH cleanup is required to destroy a type
565+
/// with the given destruction kind.
566+
/// TODO(cir): could be shared with Clang LLVM codegen
567+
bool needsEHCleanup(QualType::DestructionKind kind) {
568+
switch (kind) {
569+
case QualType::DK_none:
570+
return false;
571+
case QualType::DK_cxx_destructor:
572+
case QualType::DK_objc_weak_lifetime:
573+
case QualType::DK_nontrivial_c_struct:
574+
return getLangOpts().Exceptions;
575+
case QualType::DK_objc_strong_lifetime:
576+
return getLangOpts().Exceptions &&
577+
cgm.getCodeGenOpts().ObjCAutoRefCountExceptions;
578+
}
579+
llvm_unreachable("bad destruction kind");
580+
}
581+
582+
CleanupKind getCleanupKind(QualType::DestructionKind kind) {
583+
return needsEHCleanup(kind) ? NormalAndEHCleanup : NormalCleanup;
584+
}
585+
559586
/// Set the address of a local variable.
560587
void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
561588
assert(!localDeclMap.count(vd) && "Decl already exists in LocalDeclMap!");

clang/test/CIR/CodeGen/dtors.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,40 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); }
171171
// OGCG: br label %[[CLEANUP_DONE]]
172172
// OGCG: [[CLEANUP_DONE]]:
173173
// OGCG: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]])
174+
175+
struct C {
176+
~C();
177+
};
178+
179+
struct D {
180+
int n;
181+
C c;
182+
~D() {}
183+
};
184+
185+
// CIR: cir.func {{.*}} @_ZN1DD2Ev
186+
// CIR: %[[C:.*]] = cir.get_member %{{.*}}[1] {name = "c"}
187+
// CIR: cir.call @_ZN1CD1Ev(%[[C]])
188+
189+
// LLVM: define {{.*}} void @_ZN1DD2Ev
190+
// LLVM: %[[C:.*]] = getelementptr %struct.D, ptr %{{.*}}, i32 0, i32 1
191+
// LLVM: call void @_ZN1CD1Ev(ptr %[[C]])
192+
193+
// This destructor is defined after the calling function in OGCG.
194+
195+
void test_nested_dtor() {
196+
D d;
197+
}
198+
199+
// CIR: cir.func{{.*}} @_Z16test_nested_dtorv()
200+
// CIR: cir.call @_ZN1DD2Ev(%{{.*}})
201+
202+
// LLVM: define {{.*}} void @_Z16test_nested_dtorv()
203+
// LLVM: call void @_ZN1DD2Ev(ptr %{{.*}})
204+
205+
// OGCG: define {{.*}} void @_Z16test_nested_dtorv()
206+
// OGCG: call void @_ZN1DD2Ev(ptr {{.*}} %{{.*}})
207+
208+
// OGCG: define {{.*}} void @_ZN1DD2Ev
209+
// OGCG: %[[C:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i64 4
210+
// OGCG: call void @_ZN1CD1Ev(ptr {{.*}} %[[C]])

0 commit comments

Comments
 (0)