Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/lib/CIR/CodeGen/Address.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "mlir/IR/Value.h"
#include "clang/AST/CharUnits.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/PointerIntPair.h"

namespace clang::CIRGen {
Expand Down Expand Up @@ -90,6 +91,13 @@ class Address {
return getPointer();
}

/// Return the pointer contained in this class after authenticating it and
/// adding offset to it if necessary.
mlir::Value emitRawPointer() const {
assert(!cir::MissingFeatures::addressPointerAuthInfo());
return getBasePointer();
}

mlir::Type getType() const {
assert(mlir::cast<cir::PointerType>(
pointerAndKnownNonNull.getPointer().getType())
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ class CIRGenCXXABI {
virtual void emitVTableDefinitions(CIRGenVTables &cgvt,
const CXXRecordDecl *rd) = 0;

using DeleteOrMemberCallExpr =
llvm::PointerUnion<const CXXDeleteExpr *, const CXXMemberCallExpr *>;

virtual mlir::Value emitVirtualDestructorCall(CIRGenFunction &cgf,
const CXXDestructorDecl *dtor,
CXXDtorType dtorType,
Address thisAddr,
DeleteOrMemberCallExpr e) = 0;

/// Emit any tables needed to implement virtual inheritance. For Itanium,
/// this emits virtual table tables.
virtual void emitVirtualInheritanceTables(const CXXRecordDecl *rd) = 0;
Expand Down
33 changes: 32 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,26 @@ void CIRGenFunction::destroyCXXObject(CIRGenFunction &cgf, Address addr,
}

namespace {
mlir::Value loadThisForDtorDelete(CIRGenFunction &cgf,
const CXXDestructorDecl *dd) {
if (Expr *thisArg = dd->getOperatorDeleteThisArg())
return cgf.emitScalarExpr(thisArg);
return cgf.loadCXXThis();
}

/// Call the operator delete associated with the current destructor.
struct CallDtorDelete final : EHScopeStack::Cleanup {
CallDtorDelete() {}

void emit(CIRGenFunction &cgf) override {
const CXXDestructorDecl *dtor = cast<CXXDestructorDecl>(cgf.curFuncDecl);
const CXXRecordDecl *classDecl = dtor->getParent();
cgf.emitDeleteCall(dtor->getOperatorDelete(),
loadThisForDtorDelete(cgf, dtor),
cgf.getContext().getCanonicalTagType(classDecl));
}
};

class DestroyField final : public EHScopeStack::Cleanup {
const FieldDecl *field;
CIRGenFunction::Destroyer *destroyer;
Expand Down Expand Up @@ -932,7 +952,18 @@ void CIRGenFunction::enterDtorCleanups(const CXXDestructorDecl *dd,
// 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");
assert(dd->getOperatorDelete() &&
"operator delete missing - EnterDtorCleanups");
if (cxxStructorImplicitParamValue) {
cgm.errorNYI(dd->getSourceRange(), "deleting destructor with vtt");
} else {
if (dd->getOperatorDelete()->isDestroyingOperatorDelete()) {
cgm.errorNYI(dd->getSourceRange(),
"deleting destructor with destroying operator delete");
} else {
ehStack.pushCleanup<CallDtorDelete>(NormalAndEHCleanup);
}
}
return;
}

Expand Down
43 changes: 33 additions & 10 deletions clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,11 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
const CXXMethodDecl *calleeDecl =
devirtualizedMethod ? devirtualizedMethod : md;
const CIRGenFunctionInfo *fInfo = nullptr;
if (isa<CXXDestructorDecl>(calleeDecl)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
return RValue::get(nullptr);
}

fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl))
fInfo = &cgm.getTypes().arrangeCXXStructorDeclaration(
GlobalDecl(dtor, Dtor_Complete));
else
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);

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

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

if (isa<CXXDestructorDecl>(calleeDecl)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl)) {
assert(ce->arg_begin() == ce->arg_end() &&
"Destructor shouldn't have explicit parameters");
assert(returnValue.isNull() && "Destructor shouldn't have return value");
if (useVirtualCall) {
cgm.getCXXABI().emitVirtualDestructorCall(*this, dtor, Dtor_Complete,
thisPtr.getAddress(),
cast<CXXMemberCallExpr>(ce));
} else {
GlobalDecl globalDecl(dtor, Dtor_Complete);
CIRGenCallee callee;
assert(!cir::MissingFeatures::appleKext());
if (!devirtualizedMethod) {
callee = CIRGenCallee::forDirect(
cgm.getAddrOfCXXStructor(globalDecl, fInfo, ty), globalDecl);
} else {
cgm.errorNYI(ce->getSourceRange(), "devirtualized destructor call");
return RValue::get(nullptr);
}

QualType thisTy =
isArrow ? base->getType()->getPointeeType() : base->getType();
// CIRGen does not pass CallOrInvoke here (different from OG LLVM codegen)
// because in practice it always null even in OG.
emitCXXDestructorCall(globalDecl, callee, thisPtr.getPointer(), thisTy,
/*implicitParam=*/nullptr,
/*implicitParamTy=*/QualType(), ce);
}
return RValue::get(nullptr);
}

Expand Down
8 changes: 7 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,13 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) {
// possible to delegate the destructor body to the complete
// destructor. Do so.
if (dtorType == Dtor_Deleting) {
cgm.errorNYI(dtor->getSourceRange(), "deleting destructor");
RunCleanupsScope dtorEpilogue(*this);
enterDtorCleanups(dtor, Dtor_Deleting);
if (haveInsertPoint()) {
QualType thisTy = dtor->getFunctionObjectParameterType();
emitCXXDestructorCall(dtor, Dtor_Complete, /*forVirtualBase=*/false,
/*delegating=*/false, loadCXXThisAddress(), thisTy);
}
return;
}

Expand Down
28 changes: 27 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
clang::GlobalDecl gd, Address thisAddr,
mlir::Type ty,
SourceLocation loc) override;

mlir::Value emitVirtualDestructorCall(CIRGenFunction &cgf,
const CXXDestructorDecl *dtor,
CXXDtorType dtorType, Address thisAddr,
DeleteOrMemberCallExpr e) override;
mlir::Value getVTableAddressPoint(BaseSubobject base,
const CXXRecordDecl *vtableClass) override;
mlir::Value getVTableAddressPointInStructorWithVTT(
Expand Down Expand Up @@ -465,6 +468,29 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
}
}

mlir::Value CIRGenItaniumCXXABI::emitVirtualDestructorCall(
CIRGenFunction &cgf, const CXXDestructorDecl *dtor, CXXDtorType dtorType,
Address thisAddr, DeleteOrMemberCallExpr expr) {
auto *callExpr = dyn_cast<const CXXMemberCallExpr *>(expr);
auto *delExpr = dyn_cast<const CXXDeleteExpr *>(expr);
assert((callExpr != nullptr) ^ (delExpr != nullptr));
assert(callExpr == nullptr || callExpr->arg_begin() == callExpr->arg_end());
assert(dtorType == Dtor_Deleting || dtorType == Dtor_Complete);

GlobalDecl globalDecl(dtor, dtorType);
const CIRGenFunctionInfo *fnInfo =
&cgm.getTypes().arrangeCXXStructorDeclaration(globalDecl);
const cir::FuncType &fnTy = cgm.getTypes().getFunctionType(*fnInfo);
auto callee = CIRGenCallee::forVirtual(callExpr, globalDecl, thisAddr, fnTy);

QualType thisTy =
callExpr ? callExpr->getObjectType() : delExpr->getDestroyedType();

cgf.emitCXXDestructorCall(globalDecl, callee, thisAddr.emitRawPointer(),
thisTy, nullptr, QualType(), nullptr);
return nullptr;
}

void CIRGenItaniumCXXABI::emitVirtualInheritanceTables(
const CXXRecordDecl *rd) {
CIRGenVTables &vtables = cgm.getVTables();
Expand Down
6 changes: 2 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,10 +619,8 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl gd) {
const auto *fd = cast<FunctionDecl>(gd.getDecl());

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

return arrangeFunctionDeclaration(fd);
}
Expand Down
10 changes: 3 additions & 7 deletions clang/lib/CIR/CodeGen/CIRGenVTables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,6 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
assert(!cir::MissingFeatures::vtableRelativeLayout());

switch (component.getKind()) {
case VTableComponent::CK_CompleteDtorPointer:
cgm.errorNYI("getVTableComponent: CompleteDtorPointer");
return mlir::Attribute();
case VTableComponent::CK_DeletingDtorPointer:
cgm.errorNYI("getVTableComponent: DeletingDtorPointer");
return mlir::Attribute();
case VTableComponent::CK_UnusedFunctionPointer:
cgm.errorNYI("getVTableComponent: UnusedFunctionPointer");
return mlir::Attribute();
Expand All @@ -148,7 +142,9 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
"expected GlobalViewAttr or ConstPtrAttr");
return rtti;

case VTableComponent::CK_FunctionPointer: {
case VTableComponent::CK_FunctionPointer:
case VTableComponent::CK_CompleteDtorPointer:
case VTableComponent::CK_DeletingDtorPointer: {
GlobalDecl gd = component.getGlobalDecl();

assert(!cir::MissingFeatures::cudaSupport());
Expand Down
129 changes: 129 additions & 0 deletions clang/test/CIR/CodeGen/virtual-destructor-calls.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s

// TODO(cir): Try to emit base destructor as an alias at O1 or higher.

// FIXME: LLVM IR dialect does not yet support function ptr globals, which precludes
// a lot of the proper semantics for properly representing alias functions in LLVM
// (see the note on LLVM_O1 below).

struct Member {
~Member();
};

struct A {
virtual ~A();
};

struct B : A {
Member m;
virtual ~B();
};

B::~B() { }

// Aliases are inserted before the function definitions in LLVM IR
// FIXME: These should have unnamed_addr set.
// LLVM: @_ZN1BD1Ev = alias void (ptr), ptr @_ZN1BD2Ev
// LLVM: @_ZN1CD1Ev = alias void (ptr), ptr @_ZN1CD2Ev

// OGCG: @_ZN1BD1Ev = unnamed_addr alias void (ptr), ptr @_ZN1BD2Ev
// OGCG: @_ZN1CD1Ev = unnamed_addr alias void (ptr), ptr @_ZN1CD2Ev


// Base (D2) dtor for B: calls A's base dtor.

// CIR: cir.func{{.*}} @_ZN1BD2Ev
// CIR: cir.call @_ZN6MemberD1Ev
// CIR: cir.call @_ZN1AD2Ev

// LLVM: define{{.*}} void @_ZN1BD2Ev
// LLVM: call void @_ZN6MemberD1Ev
// LLVM: call void @_ZN1AD2Ev

// OGCG: define{{.*}} @_ZN1BD2Ev
// OGCG: call void @_ZN6MemberD1Ev
// OGCG: call void @_ZN1AD2Ev

// Complete (D1) dtor for B: just an alias because there are no virtual bases.

// CIR: cir.func{{.*}} @_ZN1BD1Ev(!cir.ptr<!rec_B>) alias(@_ZN1BD2Ev)
// This is defined above for LLVM and OGCG.

// Deleting (D0) dtor for B: defers to the complete dtor but also calls operator delete.

// CIR: cir.func{{.*}} @_ZN1BD0Ev
// CIR: cir.call @_ZN1BD1Ev(%[[THIS:.*]]) nothrow : (!cir.ptr<!rec_B>) -> ()
// CIR: %[[THIS_VOID:.*]] = cir.cast bitcast %[[THIS]] : !cir.ptr<!rec_B> -> !cir.ptr<!void>
// CIR: %[[SIZE:.*]] = cir.const #cir.int<16>
// CIR: cir.call @_ZdlPvm(%[[THIS_VOID]], %[[SIZE]])

// LLVM: define{{.*}} void @_ZN1BD0Ev
// LLVM: call void @_ZN1BD1Ev(ptr %[[THIS:.*]])
// LLVM: call void @_ZdlPvm(ptr %[[THIS]], i64 16)

// OGCG: define{{.*}} @_ZN1BD0Ev
// OGCG: call void @_ZN1BD1Ev(ptr{{.*}} %[[THIS:.*]])
// OGCG: call void @_ZdlPvm(ptr{{.*}} %[[THIS]], i64{{.*}} 16)

struct C : B {
~C();
};

C::~C() { }

// Base (D2) dtor for C: calls B's base dtor.

// CIR: cir.func{{.*}} @_ZN1CD2Ev
// CIR: %[[B:.*]] = cir.base_class_addr %[[THIS:.*]] : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
// CIR: cir.call @_ZN1BD2Ev(%[[B]])

// LLVM: define{{.*}} void @_ZN1CD2Ev
// LLVM: call void @_ZN1BD2Ev

// OGCG: define{{.*}} @_ZN1CD2Ev
// OGCG: call void @_ZN1BD2Ev

// Complete (D1) dtor for C: just an alias because there are no virtual bases.

// CIR: cir.func{{.*}} @_ZN1CD1Ev(!cir.ptr<!rec_C>) alias(@_ZN1CD2Ev)
// This is defined above for LLVM and OGCG.


// Deleting (D0) dtor for C: defers to the complete dtor but also calls operator delete.

// CIR: cir.func{{.*}} @_ZN1CD0Ev
// CIR: cir.call @_ZN1CD1Ev(%[[THIS:.*]]) nothrow : (!cir.ptr<!rec_C>) -> ()
// CIR: %[[THIS_VOID:.*]] = cir.cast bitcast %[[THIS]] : !cir.ptr<!rec_C> -> !cir.ptr<!void>
// CIR: %[[SIZE:.*]] = cir.const #cir.int<16>
// CIR: cir.call @_ZdlPvm(%[[THIS_VOID]], %[[SIZE]])

// LLVM: define{{.*}} void @_ZN1CD0Ev
// LLVM: call void @_ZN1CD1Ev(ptr %[[THIS:.*]])
// LLVM: call void @_ZdlPvm(ptr %[[THIS]], i64 16)

// OGCG: define{{.*}} @_ZN1CD0Ev
// OGCG: call void @_ZN1CD1Ev(ptr{{.*}} %[[THIS:.*]])
// OGCG: call void @_ZdlPvm(ptr{{.*}} %[[THIS]], i64{{.*}} 16)

namespace PR12798 {
// A qualified call to a base class destructor should not undergo virtual
// dispatch. Template instantiation used to lose the qualifier.
struct A { virtual ~A(); };
template<typename T> void f(T *p) { p->A::~A(); }

// CIR: cir.func{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
// CIR: cir.call @_ZN7PR127981AD1Ev

// LLVM: define{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
// LLVM: call void @_ZN7PR127981AD1Ev

// OGCG: define{{.*}} @_ZN7PR127981fINS_1AEEEvPT_
// OGCG: call void @_ZN7PR127981AD1Ev

template void f(A*);
}