-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[CIR] Add support for destructing class members #162196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
andykaylor
wants to merge
1
commit into
llvm:main
Choose a base branch
from
andykaylor:cir-dtor-members
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
+177
−4
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This adds the necessary cleanup handling to get class destructors to call the destructor for fields that require it.
@llvm/pr-subscribers-clangir @llvm/pr-subscribers-clang Author: Andy Kaylor (andykaylor) ChangesThis adds the necessary cleanup handling to get class destructors to call the destructor for fields that require it. Full diff: https://github.com/llvm/llvm-project/pull/162196.diff 4 Files Affected:
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 8f4377b435775..d9ebf19534dc4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -870,6 +870,109 @@ void CIRGenFunction::destroyCXXObject(CIRGenFunction &cgf, Address addr,
/*delegating=*/false, addr, type);
}
+namespace {
+class DestroyField final : public EHScopeStack::Cleanup {
+ const FieldDecl *field;
+ CIRGenFunction::Destroyer *destroyer;
+
+public:
+ DestroyField(const FieldDecl *field, CIRGenFunction::Destroyer *destroyer)
+ : field(field), destroyer(destroyer) {}
+
+ void emit(CIRGenFunction &cgf) override {
+ // Find the address of the field.
+ Address thisValue = cgf.loadCXXThisAddress();
+ CanQualType recordTy =
+ cgf.getContext().getCanonicalTagType(field->getParent());
+ LValue thisLV = cgf.makeAddrLValue(thisValue, recordTy);
+ LValue lv = cgf.emitLValueForField(thisLV, field);
+ assert(lv.isSimple());
+
+ assert(!cir::MissingFeatures::ehCleanupFlags());
+ cgf.emitDestroy(lv.getAddress(), field->getType(), destroyer);
+ }
+
+ // This is a placeholder until EHCleanupScope is implemented.
+ size_t getSize() const override {
+ assert(!cir::MissingFeatures::ehCleanupScope());
+ return sizeof(DestroyField);
+ }
+};
+} // namespace
+
+/// Emit all code that comes at the end of class's destructor. This is to call
+/// destructors on members and base classes in reverse order of their
+/// construction.
+///
+/// For a deleting destructor, this also handles the case where a destroying
+/// operator delete completely overrides the definition.
+void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
+ CXXDtorType dtorType) {
+ assert((!dd->isTrivial() || dd->hasAttr<DLLExportAttr>()) &&
+ "Should not emit dtor epilogue for non-exported trivial dtor!");
+
+ // The deleting-destructor phase just needs to call the appropriate
+ // operator delete that Sema picked up.
+ if (dtorType == Dtor_Deleting) {
+ cgm.errorNYI(dd->getSourceRange(), "deleting destructor cleanups");
+ return;
+ }
+
+ const CXXRecordDecl *classDecl = dd->getParent();
+
+ // Unions have no bases and do not call field destructors.
+ if (classDecl->isUnion())
+ return;
+
+ // The complete-destructor phase just destructs all the virtual bases.
+ if (dtorType == Dtor_Complete) {
+ assert(!cir::MissingFeatures::sanitizers());
+
+ if (classDecl->getNumVBases())
+ cgm.errorNYI(dd->getSourceRange(), "virtual base destructor cleanups");
+
+ return;
+ }
+
+ assert(dtorType == Dtor_Base);
+ assert(!cir::MissingFeatures::sanitizers());
+
+ // Destroy non-virtual bases.
+ for (const CXXBaseSpecifier &base : classDecl->bases()) {
+ // Ignore virtual bases.
+ if (base.isVirtual())
+ continue;
+
+ CXXRecordDecl *baseClassDecl = base.getType()->getAsCXXRecordDecl();
+
+ if (baseClassDecl->hasTrivialDestructor())
+ assert(!cir::MissingFeatures::sanitizers());
+ else
+ cgm.errorNYI(dd->getSourceRange(),
+ "non-trivial base destructor cleanups");
+ }
+
+ assert(!cir::MissingFeatures::sanitizers());
+
+ // Destroy direct fields.
+ for (const FieldDecl *field : classDecl->fields()) {
+ QualType type = field->getType();
+ QualType::DestructionKind dtorKind = type.isDestructedType();
+ if (!dtorKind)
+ continue;
+
+ // Anonymous union members do not have their destructors called.
+ const RecordType *rt = type->getAsUnionType();
+ if (rt && rt->getOriginalDecl()->isAnonymousStructOrUnion())
+ continue;
+
+ CleanupKind cleanupKind = getCleanupKind(dtorKind);
+ assert(!cir::MissingFeatures::ehCleanupFlags());
+ ehStack.pushCleanup<DestroyField>(cleanupKind, field,
+ getDestroyer(dtorKind));
+ }
+}
+
void CIRGenFunction::emitDelegatingCXXConstructorCall(
const CXXConstructorDecl *ctor, const FunctionArgList &args) {
assert(ctor->isDelegatingConstructor());
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 52fb0d758dff8..7a774e0441bbb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -689,7 +689,9 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor");
assert(!cir::MissingFeatures::sanitizers());
- assert(!cir::MissingFeatures::dtorCleanups());
+
+ // Enter the epilogue cleanups.
+ RunCleanupsScope dtorEpilogue(*this);
// If this is the complete variant, just invoke the base variant;
// the epilogue will destruct the virtual bases. But we can't do
@@ -708,7 +710,8 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
assert((body || getTarget().getCXXABI().isMicrosoft()) &&
"can't emit a dtor without a body for non-Microsoft ABIs");
- assert(!cir::MissingFeatures::dtorCleanups());
+ // Enter the cleanup scopes for virtual bases.
+ enterDtorCleanups(dtor, Dtor_Complete);
if (!isTryBody) {
QualType thisTy = dtor->getFunctionObjectParameterType();
@@ -723,7 +726,9 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
case Dtor_Base:
assert(body);
- assert(!cir::MissingFeatures::dtorCleanups());
+ // Enter the cleanup scopes for fields and non-virtual bases.
+ enterDtorCleanups(dtor, Dtor_Base);
+
assert(!cir::MissingFeatures::vtableInitialization());
if (isTryBody) {
@@ -741,7 +746,8 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
break;
}
- assert(!cir::MissingFeatures::dtorCleanups());
+ // Jump out through the epilogue cleanups.
+ dtorEpilogue.forceCleanup();
// Exit the try if applicable.
if (isTryBody)
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index cbc0f4a068d7b..9162c36aaaa9f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -556,6 +556,33 @@ class CIRGenFunction : public CIRGenTypeCache {
cir::GlobalOp gv,
cir::GetGlobalOp gvAddr);
+ /// Enter the cleanups necessary to complete the given phase of destruction
+ /// for a destructor. The end result should call destructors on members and
+ /// base classes in reverse order of their construction.
+ void enterDtorCleanups(const CXXDestructorDecl *dtor, CXXDtorType type);
+
+ /// Determines whether an EH cleanup is required to destroy a type
+ /// with the given destruction kind.
+ /// TODO(cir): could be shared with Clang LLVM codegen
+ bool needsEHCleanup(QualType::DestructionKind kind) {
+ switch (kind) {
+ case QualType::DK_none:
+ return false;
+ case QualType::DK_cxx_destructor:
+ case QualType::DK_objc_weak_lifetime:
+ case QualType::DK_nontrivial_c_struct:
+ return getLangOpts().Exceptions;
+ case QualType::DK_objc_strong_lifetime:
+ return getLangOpts().Exceptions &&
+ cgm.getCodeGenOpts().ObjCAutoRefCountExceptions;
+ }
+ llvm_unreachable("bad destruction kind");
+ }
+
+ CleanupKind getCleanupKind(QualType::DestructionKind kind) {
+ return (needsEHCleanup(kind) ? NormalAndEHCleanup : NormalCleanup);
+ }
+
/// Set the address of a local variable.
void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
assert(!localDeclMap.count(vd) && "Decl already exists in LocalDeclMap!");
diff --git a/clang/test/CIR/CodeGen/dtors.cpp b/clang/test/CIR/CodeGen/dtors.cpp
index 66554b70e1700..49952a79c2cec 100644
--- a/clang/test/CIR/CodeGen/dtors.cpp
+++ b/clang/test/CIR/CodeGen/dtors.cpp
@@ -171,3 +171,40 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); }
// OGCG: br label %[[CLEANUP_DONE]]
// OGCG: [[CLEANUP_DONE]]:
// OGCG: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]])
+
+struct C {
+ ~C();
+};
+
+struct D {
+ int n;
+ C c;
+ ~D() {}
+};
+
+// CIR: cir.func {{.*}} @_ZN1DD2Ev
+// CIR: %[[C:.*]] = cir.get_member %{{.*}}[1] {name = "c"}
+// CIR: cir.call @_ZN1CD1Ev(%[[C]])
+
+// LLVM: define {{.*}} void @_ZN1DD2Ev
+// LLVM: %[[C:.*]] = getelementptr %struct.D, ptr %{{.*}}, i32 0, i32 1
+// LLVM: call void @_ZN1CD1Ev(ptr %[[C]])
+
+// This destructor is defined after the calling function in OGCG.
+
+void test_nested_dtor() {
+ D d;
+}
+
+// CIR: cir.func{{.*}} @_Z16test_nested_dtorv()
+// CIR: cir.call @_ZN1DD2Ev(%{{.*}})
+
+// LLVM: define {{.*}} void @_Z16test_nested_dtorv()
+// LLVM: call void @_ZN1DD2Ev(ptr %{{.*}})
+
+// OGCG: define {{.*}} void @_Z16test_nested_dtorv()
+// OGCG: call void @_ZN1DD2Ev(ptr {{.*}} %{{.*}})
+
+// OGCG: define {{.*}} void @_ZN1DD2Ev
+// OGCG: %[[C:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i64 4
+// OGCG: call void @_ZN1CD1Ev(ptr {{.*}} %[[C]])
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This adds the necessary cleanup handling to get class destructors to call the destructor for fields that require it.