Skip to content

Commit d81e8c0

Browse files
authored
[CIR] Add support for virtual destructor calls (#162725)
This adds support for calling virtual destructors.
1 parent 92e6fa8 commit d81e8c0

File tree

9 files changed

+250
-24
lines changed

9 files changed

+250
-24
lines changed

clang/lib/CIR/CodeGen/Address.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "mlir/IR/Value.h"
1818
#include "clang/AST/CharUnits.h"
1919
#include "clang/CIR/Dialect/IR/CIRTypes.h"
20+
#include "clang/CIR/MissingFeatures.h"
2021
#include "llvm/ADT/PointerIntPair.h"
2122

2223
namespace clang::CIRGen {
@@ -90,6 +91,13 @@ class Address {
9091
return getPointer();
9192
}
9293

94+
/// Return the pointer contained in this class after authenticating it and
95+
/// adding offset to it if necessary.
96+
mlir::Value emitRawPointer() const {
97+
assert(!cir::MissingFeatures::addressPointerAuthInfo());
98+
return getBasePointer();
99+
}
100+
93101
mlir::Type getType() const {
94102
assert(mlir::cast<cir::PointerType>(
95103
pointerAndKnownNonNull.getPointer().getType())

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ class CIRGenCXXABI {
191191
virtual void emitVTableDefinitions(CIRGenVTables &cgvt,
192192
const CXXRecordDecl *rd) = 0;
193193

194+
using DeleteOrMemberCallExpr =
195+
llvm::PointerUnion<const CXXDeleteExpr *, const CXXMemberCallExpr *>;
196+
197+
virtual mlir::Value emitVirtualDestructorCall(CIRGenFunction &cgf,
198+
const CXXDestructorDecl *dtor,
199+
CXXDtorType dtorType,
200+
Address thisAddr,
201+
DeleteOrMemberCallExpr e) = 0;
202+
194203
/// Emit any tables needed to implement virtual inheritance. For Itanium,
195204
/// this emits virtual table tables.
196205
virtual void emitVirtualInheritanceTables(const CXXRecordDecl *rd) = 0;

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,26 @@ void CIRGenFunction::destroyCXXObject(CIRGenFunction &cgf, Address addr,
895895
}
896896

897897
namespace {
898+
mlir::Value loadThisForDtorDelete(CIRGenFunction &cgf,
899+
const CXXDestructorDecl *dd) {
900+
if (Expr *thisArg = dd->getOperatorDeleteThisArg())
901+
return cgf.emitScalarExpr(thisArg);
902+
return cgf.loadCXXThis();
903+
}
904+
905+
/// Call the operator delete associated with the current destructor.
906+
struct CallDtorDelete final : EHScopeStack::Cleanup {
907+
CallDtorDelete() {}
908+
909+
void emit(CIRGenFunction &cgf) override {
910+
const CXXDestructorDecl *dtor = cast<CXXDestructorDecl>(cgf.curFuncDecl);
911+
const CXXRecordDecl *classDecl = dtor->getParent();
912+
cgf.emitDeleteCall(dtor->getOperatorDelete(),
913+
loadThisForDtorDelete(cgf, dtor),
914+
cgf.getContext().getCanonicalTagType(classDecl));
915+
}
916+
};
917+
898918
class DestroyField final : public EHScopeStack::Cleanup {
899919
const FieldDecl *field;
900920
CIRGenFunction::Destroyer *destroyer;
@@ -932,7 +952,18 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
932952
// The deleting-destructor phase just needs to call the appropriate
933953
// operator delete that Sema picked up.
934954
if (dtorType == Dtor_Deleting) {
935-
cgm.errorNYI(dd->getSourceRange(), "deleting destructor cleanups");
955+
assert(dd->getOperatorDelete() &&
956+
"operator delete missing - EnterDtorCleanups");
957+
if (cxxStructorImplicitParamValue) {
958+
cgm.errorNYI(dd->getSourceRange(), "deleting destructor with vtt");
959+
} else {
960+
if (dd->getOperatorDelete()->isDestroyingOperatorDelete()) {
961+
cgm.errorNYI(dd->getSourceRange(),
962+
"deleting destructor with destroying operator delete");
963+
} else {
964+
ehStack.pushCleanup<CallDtorDelete>(NormalAndEHCleanup);
965+
}
966+
}
936967
return;
937968
}
938969

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,11 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
130130
const CXXMethodDecl *calleeDecl =
131131
devirtualizedMethod ? devirtualizedMethod : md;
132132
const CIRGenFunctionInfo *fInfo = nullptr;
133-
if (isa<CXXDestructorDecl>(calleeDecl)) {
134-
cgm.errorNYI(ce->getSourceRange(),
135-
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
136-
return RValue::get(nullptr);
137-
}
138-
139-
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
133+
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl))
134+
fInfo = &cgm.getTypes().arrangeCXXStructorDeclaration(
135+
GlobalDecl(dtor, Dtor_Complete));
136+
else
137+
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
140138

141139
cir::FuncType ty = cgm.getTypes().getFunctionType(*fInfo);
142140

@@ -151,9 +149,34 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
151149
// because then we know what the type is.
152150
bool useVirtualCall = canUseVirtualCall && !devirtualizedMethod;
153151

154-
if (isa<CXXDestructorDecl>(calleeDecl)) {
155-
cgm.errorNYI(ce->getSourceRange(),
156-
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
152+
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl)) {
153+
assert(ce->arg_begin() == ce->arg_end() &&
154+
"Destructor shouldn't have explicit parameters");
155+
assert(returnValue.isNull() && "Destructor shouldn't have return value");
156+
if (useVirtualCall) {
157+
cgm.getCXXABI().emitVirtualDestructorCall(*this, dtor, Dtor_Complete,
158+
thisPtr.getAddress(),
159+
cast<CXXMemberCallExpr>(ce));
160+
} else {
161+
GlobalDecl globalDecl(dtor, Dtor_Complete);
162+
CIRGenCallee callee;
163+
assert(!cir::MissingFeatures::appleKext());
164+
if (!devirtualizedMethod) {
165+
callee = CIRGenCallee::forDirect(
166+
cgm.getAddrOfCXXStructor(globalDecl, fInfo, ty), globalDecl);
167+
} else {
168+
cgm.errorNYI(ce->getSourceRange(), "devirtualized destructor call");
169+
return RValue::get(nullptr);
170+
}
171+
172+
QualType thisTy =
173+
isArrow ? base->getType()->getPointeeType() : base->getType();
174+
// CIRGen does not pass CallOrInvoke here (different from OG LLVM codegen)
175+
// because in practice it always null even in OG.
176+
emitCXXDestructorCall(globalDecl, callee, thisPtr.getPointer(), thisTy,
177+
/*implicitParam=*/nullptr,
178+
/*implicitParamTy=*/QualType(), ce);
179+
}
157180
return RValue::get(nullptr);
158181
}
159182

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,13 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
678678
// possible to delegate the destructor body to the complete
679679
// destructor. Do so.
680680
if (dtorType == Dtor_Deleting) {
681-
cgm.errorNYI(dtor->getSourceRange(), "deleting destructor");
681+
RunCleanupsScope dtorEpilogue(*this);
682+
enterDtorCleanups(dtor, Dtor_Deleting);
683+
if (haveInsertPoint()) {
684+
QualType thisTy = dtor->getFunctionObjectParameterType();
685+
emitCXXDestructorCall(dtor, Dtor_Complete, /*forVirtualBase=*/false,
686+
/*delegating=*/false, loadCXXThisAddress(), thisTy);
687+
}
682688
return;
683689
}
684690

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
9595
clang::GlobalDecl gd, Address thisAddr,
9696
mlir::Type ty,
9797
SourceLocation loc) override;
98-
98+
mlir::Value emitVirtualDestructorCall(CIRGenFunction &cgf,
99+
const CXXDestructorDecl *dtor,
100+
CXXDtorType dtorType, Address thisAddr,
101+
DeleteOrMemberCallExpr e) override;
99102
mlir::Value getVTableAddressPoint(BaseSubobject base,
100103
const CXXRecordDecl *vtableClass) override;
101104
mlir::Value getVTableAddressPointInStructorWithVTT(
@@ -465,6 +468,29 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
465468
}
466469
}
467470

471+
mlir::Value CIRGenItaniumCXXABI::emitVirtualDestructorCall(
472+
CIRGenFunction &cgf, const CXXDestructorDecl *dtor, CXXDtorType dtorType,
473+
Address thisAddr, DeleteOrMemberCallExpr expr) {
474+
auto *callExpr = dyn_cast<const CXXMemberCallExpr *>(expr);
475+
auto *delExpr = dyn_cast<const CXXDeleteExpr *>(expr);
476+
assert((callExpr != nullptr) ^ (delExpr != nullptr));
477+
assert(callExpr == nullptr || callExpr->arg_begin() == callExpr->arg_end());
478+
assert(dtorType == Dtor_Deleting || dtorType == Dtor_Complete);
479+
480+
GlobalDecl globalDecl(dtor, dtorType);
481+
const CIRGenFunctionInfo *fnInfo =
482+
&cgm.getTypes().arrangeCXXStructorDeclaration(globalDecl);
483+
const cir::FuncType &fnTy = cgm.getTypes().getFunctionType(*fnInfo);
484+
auto callee = CIRGenCallee::forVirtual(callExpr, globalDecl, thisAddr, fnTy);
485+
486+
QualType thisTy =
487+
callExpr ? callExpr->getObjectType() : delExpr->getDestroyedType();
488+
489+
cgf.emitCXXDestructorCall(globalDecl, callee, thisAddr.emitRawPointer(),
490+
thisTy, nullptr, QualType(), nullptr);
491+
return nullptr;
492+
}
493+
468494
void CIRGenItaniumCXXABI::emitVirtualInheritanceTables(
469495
const CXXRecordDecl *rd) {
470496
CIRGenVTables &vtables = cgm.getVTables();

clang/lib/CIR/CodeGen/CIRGenTypes.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,10 +619,8 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl gd) {
619619
const auto *fd = cast<FunctionDecl>(gd.getDecl());
620620

621621
if (isa<CXXConstructorDecl>(gd.getDecl()) ||
622-
isa<CXXDestructorDecl>(gd.getDecl())) {
623-
cgm.errorNYI(SourceLocation(),
624-
"arrangeGlobalDeclaration for C++ constructor or destructor");
625-
}
622+
isa<CXXDestructorDecl>(gd.getDecl()))
623+
return arrangeCXXStructorDeclaration(gd);
626624

627625
return arrangeFunctionDeclaration(fd);
628626
}

clang/lib/CIR/CodeGen/CIRGenVTables.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,6 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
120120
assert(!cir::MissingFeatures::vtableRelativeLayout());
121121

122122
switch (component.getKind()) {
123-
case VTableComponent::CK_CompleteDtorPointer:
124-
cgm.errorNYI("getVTableComponent: CompleteDtorPointer");
125-
return mlir::Attribute();
126-
case VTableComponent::CK_DeletingDtorPointer:
127-
cgm.errorNYI("getVTableComponent: DeletingDtorPointer");
128-
return mlir::Attribute();
129123
case VTableComponent::CK_UnusedFunctionPointer:
130124
cgm.errorNYI("getVTableComponent: UnusedFunctionPointer");
131125
return mlir::Attribute();
@@ -148,7 +142,9 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
148142
"expected GlobalViewAttr or ConstPtrAttr");
149143
return rtti;
150144

151-
case VTableComponent::CK_FunctionPointer: {
145+
case VTableComponent::CK_FunctionPointer:
146+
case VTableComponent::CK_CompleteDtorPointer:
147+
case VTableComponent::CK_DeletingDtorPointer: {
152148
GlobalDecl gd = component.getGlobalDecl();
153149

154150
assert(!cir::MissingFeatures::cudaSupport());
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
5+
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
7+
8+
// TODO(cir): Try to emit base destructor as an alias at O1 or higher.
9+
10+
// FIXME: LLVM IR dialect does not yet support function ptr globals, which precludes
11+
// a lot of the proper semantics for properly representing alias functions in LLVM
12+
// (see the note on LLVM_O1 below).
13+
14+
struct Member {
15+
~Member();
16+
};
17+
18+
struct A {
19+
virtual ~A();
20+
};
21+
22+
struct B : A {
23+
Member m;
24+
virtual ~B();
25+
};
26+
27+
B::~B() { }
28+
29+
// Aliases are inserted before the function definitions in LLVM IR
30+
// FIXME: These should have unnamed_addr set.
31+
// LLVM: @_ZN1BD1Ev = alias void (ptr), ptr @_ZN1BD2Ev
32+
// LLVM: @_ZN1CD1Ev = alias void (ptr), ptr @_ZN1CD2Ev
33+
34+
// OGCG: @_ZN1BD1Ev = unnamed_addr alias void (ptr), ptr @_ZN1BD2Ev
35+
// OGCG: @_ZN1CD1Ev = unnamed_addr alias void (ptr), ptr @_ZN1CD2Ev
36+
37+
38+
// Base (D2) dtor for B: calls A's base dtor.
39+
40+
// CIR: cir.func{{.*}} @_ZN1BD2Ev
41+
// CIR: cir.call @_ZN6MemberD1Ev
42+
// CIR: cir.call @_ZN1AD2Ev
43+
44+
// LLVM: define{{.*}} void @_ZN1BD2Ev
45+
// LLVM: call void @_ZN6MemberD1Ev
46+
// LLVM: call void @_ZN1AD2Ev
47+
48+
// OGCG: define{{.*}} @_ZN1BD2Ev
49+
// OGCG: call void @_ZN6MemberD1Ev
50+
// OGCG: call void @_ZN1AD2Ev
51+
52+
// Complete (D1) dtor for B: just an alias because there are no virtual bases.
53+
54+
// CIR: cir.func{{.*}} @_ZN1BD1Ev(!cir.ptr<!rec_B>) alias(@_ZN1BD2Ev)
55+
// This is defined above for LLVM and OGCG.
56+
57+
// Deleting (D0) dtor for B: defers to the complete dtor but also calls operator delete.
58+
59+
// CIR: cir.func{{.*}} @_ZN1BD0Ev
60+
// CIR: cir.call @_ZN1BD1Ev(%[[THIS:.*]]) nothrow : (!cir.ptr<!rec_B>) -> ()
61+
// CIR: %[[THIS_VOID:.*]] = cir.cast bitcast %[[THIS]] : !cir.ptr<!rec_B> -> !cir.ptr<!void>
62+
// CIR: %[[SIZE:.*]] = cir.const #cir.int<16>
63+
// CIR: cir.call @_ZdlPvm(%[[THIS_VOID]], %[[SIZE]])
64+
65+
// LLVM: define{{.*}} void @_ZN1BD0Ev
66+
// LLVM: call void @_ZN1BD1Ev(ptr %[[THIS:.*]])
67+
// LLVM: call void @_ZdlPvm(ptr %[[THIS]], i64 16)
68+
69+
// OGCG: define{{.*}} @_ZN1BD0Ev
70+
// OGCG: call void @_ZN1BD1Ev(ptr{{.*}} %[[THIS:.*]])
71+
// OGCG: call void @_ZdlPvm(ptr{{.*}} %[[THIS]], i64{{.*}} 16)
72+
73+
struct C : B {
74+
~C();
75+
};
76+
77+
C::~C() { }
78+
79+
// Base (D2) dtor for C: calls B's base dtor.
80+
81+
// CIR: cir.func{{.*}} @_ZN1CD2Ev
82+
// CIR: %[[B:.*]] = cir.base_class_addr %[[THIS:.*]] : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
83+
// CIR: cir.call @_ZN1BD2Ev(%[[B]])
84+
85+
// LLVM: define{{.*}} void @_ZN1CD2Ev
86+
// LLVM: call void @_ZN1BD2Ev
87+
88+
// OGCG: define{{.*}} @_ZN1CD2Ev
89+
// OGCG: call void @_ZN1BD2Ev
90+
91+
// Complete (D1) dtor for C: just an alias because there are no virtual bases.
92+
93+
// CIR: cir.func{{.*}} @_ZN1CD1Ev(!cir.ptr<!rec_C>) alias(@_ZN1CD2Ev)
94+
// This is defined above for LLVM and OGCG.
95+
96+
97+
// Deleting (D0) dtor for C: defers to the complete dtor but also calls operator delete.
98+
99+
// CIR: cir.func{{.*}} @_ZN1CD0Ev
100+
// CIR: cir.call @_ZN1CD1Ev(%[[THIS:.*]]) nothrow : (!cir.ptr<!rec_C>) -> ()
101+
// CIR: %[[THIS_VOID:.*]] = cir.cast bitcast %[[THIS]] : !cir.ptr<!rec_C> -> !cir.ptr<!void>
102+
// CIR: %[[SIZE:.*]] = cir.const #cir.int<16>
103+
// CIR: cir.call @_ZdlPvm(%[[THIS_VOID]], %[[SIZE]])
104+
105+
// LLVM: define{{.*}} void @_ZN1CD0Ev
106+
// LLVM: call void @_ZN1CD1Ev(ptr %[[THIS:.*]])
107+
// LLVM: call void @_ZdlPvm(ptr %[[THIS]], i64 16)
108+
109+
// OGCG: define{{.*}} @_ZN1CD0Ev
110+
// OGCG: call void @_ZN1CD1Ev(ptr{{.*}} %[[THIS:.*]])
111+
// OGCG: call void @_ZdlPvm(ptr{{.*}} %[[THIS]], i64{{.*}} 16)
112+
113+
namespace PR12798 {
114+
// A qualified call to a base class destructor should not undergo virtual
115+
// dispatch. Template instantiation used to lose the qualifier.
116+
struct A { virtual ~A(); };
117+
template<typename T> void f(T *p) { p->A::~A(); }
118+
119+
// CIR: cir.func{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
120+
// CIR: cir.call @_ZN7PR127981AD1Ev
121+
122+
// LLVM: define{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
123+
// LLVM: call void @_ZN7PR127981AD1Ev
124+
125+
// OGCG: define{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
126+
// OGCG: call void @_ZN7PR127981AD1Ev
127+
128+
template void f(A*);
129+
}

0 commit comments

Comments
 (0)