mirror of
https://github.com/intel/llvm.git
synced 2026-02-09 01:52:26 +08:00
[CIR] Add initial support for operator delete (#160574)
This adds basic operator delete handling in CIR. This does not yet handle destroying delete or array delete, which will be added later. It also does not insert non-null checks when not optimizing for size.
This commit is contained in:
@@ -208,6 +208,7 @@ struct MissingFeatures {
|
||||
static bool dataLayoutTypeAllocSize() { return false; }
|
||||
static bool dataLayoutTypeStoreSize() { return false; }
|
||||
static bool deferredCXXGlobalInit() { return false; }
|
||||
static bool deleteArray() { return false; }
|
||||
static bool devirtualizeMemberFunction() { return false; }
|
||||
static bool ehCleanupFlags() { return false; }
|
||||
static bool ehCleanupScope() { return false; }
|
||||
@@ -219,6 +220,7 @@ struct MissingFeatures {
|
||||
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
|
||||
static bool emitLifetimeMarkers() { return false; }
|
||||
static bool emitLValueAlignmentAssumption() { return false; }
|
||||
static bool emitNullCheckForDeleteCalls() { return false; }
|
||||
static bool emitNullabilityCheck() { return false; }
|
||||
static bool emitTypeCheck() { return false; }
|
||||
static bool emitTypeMetadataCodeForVCall() { return false; }
|
||||
|
||||
@@ -210,6 +210,60 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
|
||||
return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// The parameters to pass to a usual operator delete.
|
||||
struct UsualDeleteParams {
|
||||
TypeAwareAllocationMode typeAwareDelete = TypeAwareAllocationMode::No;
|
||||
bool destroyingDelete = false;
|
||||
bool size = false;
|
||||
AlignedAllocationMode alignment = AlignedAllocationMode::No;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// FIXME(cir): this should be shared with LLVM codegen
|
||||
static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *fd) {
|
||||
UsualDeleteParams params;
|
||||
|
||||
const FunctionProtoType *fpt = fd->getType()->castAs<FunctionProtoType>();
|
||||
auto ai = fpt->param_type_begin(), ae = fpt->param_type_end();
|
||||
|
||||
if (fd->isTypeAwareOperatorNewOrDelete()) {
|
||||
params.typeAwareDelete = TypeAwareAllocationMode::Yes;
|
||||
assert(ai != ae);
|
||||
++ai;
|
||||
}
|
||||
|
||||
// The first argument after the type-identity parameter (if any) is
|
||||
// always a void* (or C* for a destroying operator delete for class
|
||||
// type C).
|
||||
++ai;
|
||||
|
||||
// The next parameter may be a std::destroying_delete_t.
|
||||
if (fd->isDestroyingOperatorDelete()) {
|
||||
params.destroyingDelete = true;
|
||||
assert(ai != ae);
|
||||
++ai;
|
||||
}
|
||||
|
||||
// Figure out what other parameters we should be implicitly passing.
|
||||
if (ai != ae && (*ai)->isIntegerType()) {
|
||||
params.size = true;
|
||||
++ai;
|
||||
} else {
|
||||
assert(!isTypeAwareAllocation(params.typeAwareDelete));
|
||||
}
|
||||
|
||||
if (ai != ae && (*ai)->isAlignValT()) {
|
||||
params.alignment = AlignedAllocationMode::Yes;
|
||||
++ai;
|
||||
} else {
|
||||
assert(!isTypeAwareAllocation(params.typeAwareDelete));
|
||||
}
|
||||
|
||||
assert(ai == ae && "unexpected usual deallocation function parameter");
|
||||
return params;
|
||||
}
|
||||
|
||||
static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
|
||||
unsigned minElements,
|
||||
mlir::Value &numElements,
|
||||
@@ -332,6 +386,117 @@ static RValue emitNewDeleteCall(CIRGenFunction &cgf,
|
||||
return rv;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// Calls the given 'operator delete' on a single object.
|
||||
struct CallObjectDelete final : EHScopeStack::Cleanup {
|
||||
mlir::Value ptr;
|
||||
const FunctionDecl *operatorDelete;
|
||||
QualType elementType;
|
||||
|
||||
CallObjectDelete(mlir::Value ptr, const FunctionDecl *operatorDelete,
|
||||
QualType elementType)
|
||||
: ptr(ptr), operatorDelete(operatorDelete), elementType(elementType) {}
|
||||
|
||||
void emit(CIRGenFunction &cgf) override {
|
||||
cgf.emitDeleteCall(operatorDelete, ptr, elementType);
|
||||
}
|
||||
|
||||
// This is a placeholder until EHCleanupScope is implemented.
|
||||
size_t getSize() const override {
|
||||
assert(!cir::MissingFeatures::ehCleanupScope());
|
||||
return sizeof(CallObjectDelete);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
/// Emit the code for deleting a single object.
|
||||
static void emitObjectDelete(CIRGenFunction &cgf, const CXXDeleteExpr *de,
|
||||
Address ptr, QualType elementType) {
|
||||
// C++11 [expr.delete]p3:
|
||||
// If the static type of the object to be deleted is different from its
|
||||
// dynamic type, the static type shall be a base class of the dynamic type
|
||||
// of the object to be deleted and the static type shall have a virtual
|
||||
// destructor or the behavior is undefined.
|
||||
assert(!cir::MissingFeatures::emitTypeCheck());
|
||||
|
||||
const FunctionDecl *operatorDelete = de->getOperatorDelete();
|
||||
assert(!operatorDelete->isDestroyingOperatorDelete());
|
||||
|
||||
// Find the destructor for the type, if applicable. If the
|
||||
// destructor is virtual, we'll just emit the vcall and return.
|
||||
const CXXDestructorDecl *dtor = nullptr;
|
||||
if (const auto *rd = elementType->getAsCXXRecordDecl()) {
|
||||
if (rd->hasDefinition() && !rd->hasTrivialDestructor()) {
|
||||
dtor = rd->getDestructor();
|
||||
|
||||
if (dtor->isVirtual()) {
|
||||
cgf.cgm.errorNYI(de->getSourceRange(),
|
||||
"emitObjectDelete: virtual destructor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that we call delete even if the dtor throws.
|
||||
// This doesn't have to a conditional cleanup because we're going
|
||||
// to pop it off in a second.
|
||||
cgf.ehStack.pushCleanup<CallObjectDelete>(
|
||||
NormalAndEHCleanup, ptr.getPointer(), operatorDelete, elementType);
|
||||
|
||||
if (dtor) {
|
||||
cgf.emitCXXDestructorCall(dtor, Dtor_Complete,
|
||||
/*ForVirtualBase=*/false,
|
||||
/*Delegating=*/false, ptr, elementType);
|
||||
} else if (elementType.getObjCLifetime()) {
|
||||
assert(!cir::MissingFeatures::objCLifetime());
|
||||
cgf.cgm.errorNYI(de->getSourceRange(), "emitObjectDelete: ObjCLifetime");
|
||||
}
|
||||
|
||||
// In traditional LLVM codegen null checks are emitted to save a delete call.
|
||||
// In CIR we optimize for size by default, the null check should be added into
|
||||
// this function callers.
|
||||
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
|
||||
|
||||
cgf.popCleanupBlock();
|
||||
}
|
||||
|
||||
void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
|
||||
const Expr *arg = e->getArgument();
|
||||
Address ptr = emitPointerWithAlignment(arg);
|
||||
|
||||
// Null check the pointer.
|
||||
//
|
||||
// We could avoid this null check if we can determine that the object
|
||||
// destruction is trivial and doesn't require an array cookie; we can
|
||||
// unconditionally perform the operator delete call in that case. For now, we
|
||||
// assume that deleted pointers are null rarely enough that it's better to
|
||||
// keep the branch. This might be worth revisiting for a -O0 code size win.
|
||||
//
|
||||
// CIR note: emit the code size friendly by default for now, such as mentioned
|
||||
// in `emitObjectDelete`.
|
||||
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
|
||||
QualType deleteTy = e->getDestroyedType();
|
||||
|
||||
// A destroying operator delete overrides the entire operation of the
|
||||
// delete expression.
|
||||
if (e->getOperatorDelete()->isDestroyingOperatorDelete()) {
|
||||
cgm.errorNYI(e->getSourceRange(),
|
||||
"emitCXXDeleteExpr: destroying operator delete");
|
||||
return;
|
||||
}
|
||||
|
||||
// We might be deleting a pointer to array.
|
||||
deleteTy = getContext().getBaseElementType(deleteTy);
|
||||
ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy));
|
||||
|
||||
if (e->isArrayForm()) {
|
||||
assert(!cir::MissingFeatures::deleteArray());
|
||||
cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
|
||||
return;
|
||||
} else {
|
||||
emitObjectDelete(*this, e, ptr, deleteTy);
|
||||
}
|
||||
}
|
||||
|
||||
mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
|
||||
// The element type being allocated.
|
||||
QualType allocType = getContext().getBaseElementType(e->getAllocatedType());
|
||||
@@ -443,3 +608,53 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
|
||||
allocSizeWithoutCookie);
|
||||
return result.getPointer();
|
||||
}
|
||||
|
||||
void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD,
|
||||
mlir::Value ptr, QualType deleteTy) {
|
||||
assert(!cir::MissingFeatures::deleteArray());
|
||||
|
||||
const auto *deleteFTy = deleteFD->getType()->castAs<FunctionProtoType>();
|
||||
CallArgList deleteArgs;
|
||||
|
||||
UsualDeleteParams params = getUsualDeleteParams(deleteFD);
|
||||
auto paramTypeIt = deleteFTy->param_type_begin();
|
||||
|
||||
// Pass std::type_identity tag if present
|
||||
if (isTypeAwareAllocation(params.typeAwareDelete))
|
||||
cgm.errorNYI(deleteFD->getSourceRange(),
|
||||
"emitDeleteCall: type aware delete");
|
||||
|
||||
// Pass the pointer itself.
|
||||
QualType argTy = *paramTypeIt++;
|
||||
mlir::Value deletePtr =
|
||||
builder.createBitcast(ptr.getLoc(), ptr, convertType(argTy));
|
||||
deleteArgs.add(RValue::get(deletePtr), argTy);
|
||||
|
||||
// Pass the std::destroying_delete tag if present.
|
||||
if (params.destroyingDelete)
|
||||
cgm.errorNYI(deleteFD->getSourceRange(),
|
||||
"emitDeleteCall: destroying delete");
|
||||
|
||||
// Pass the size if the delete function has a size_t parameter.
|
||||
if (params.size) {
|
||||
QualType sizeType = *paramTypeIt++;
|
||||
CharUnits deleteTypeSize = getContext().getTypeSizeInChars(deleteTy);
|
||||
assert(mlir::isa<cir::IntType>(convertType(sizeType)) &&
|
||||
"expected cir::IntType");
|
||||
cir::ConstantOp size = builder.getConstInt(
|
||||
*currSrcLoc, convertType(sizeType), deleteTypeSize.getQuantity());
|
||||
|
||||
deleteArgs.add(RValue::get(size), sizeType);
|
||||
}
|
||||
|
||||
// Pass the alignment if the delete function has an align_val_t parameter.
|
||||
if (isAlignedAllocation(params.alignment))
|
||||
cgm.errorNYI(deleteFD->getSourceRange(),
|
||||
"emitDeleteCall: aligned allocation");
|
||||
|
||||
assert(paramTypeIt == deleteFTy->param_type_end() &&
|
||||
"unknown parameter to usual delete function");
|
||||
|
||||
// Emit the call to delete.
|
||||
emitNewDeleteCall(*this, deleteFD, deleteFTy, deleteArgs);
|
||||
}
|
||||
|
||||
@@ -687,6 +687,10 @@ public:
|
||||
mlir::Value VisitCXXNewExpr(const CXXNewExpr *e) {
|
||||
return cgf.emitCXXNewExpr(e);
|
||||
}
|
||||
mlir::Value VisitCXXDeleteExpr(const CXXDeleteExpr *e) {
|
||||
cgf.emitCXXDeleteExpr(e);
|
||||
return {};
|
||||
}
|
||||
|
||||
mlir::Value VisitCXXThrowExpr(const CXXThrowExpr *e) {
|
||||
cgf.emitCXXThrowExpr(e);
|
||||
|
||||
@@ -1197,6 +1197,8 @@ public:
|
||||
bool delegating, Address thisAddr,
|
||||
CallArgList &args, clang::SourceLocation loc);
|
||||
|
||||
void emitCXXDeleteExpr(const CXXDeleteExpr *e);
|
||||
|
||||
void emitCXXDestructorCall(const CXXDestructorDecl *dd, CXXDtorType type,
|
||||
bool forVirtualBase, bool delegating,
|
||||
Address thisAddr, QualType thisTy);
|
||||
@@ -1244,6 +1246,9 @@ public:
|
||||
void emitDelegatingCXXConstructorCall(const CXXConstructorDecl *ctor,
|
||||
const FunctionArgList &args);
|
||||
|
||||
void emitDeleteCall(const FunctionDecl *deleteFD, mlir::Value ptr,
|
||||
QualType deleteTy);
|
||||
|
||||
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
|
||||
|
||||
/// Emit an expression as an initializer for an object (variable, field, etc.)
|
||||
|
||||
88
clang/test/CIR/CodeGen/delete.cpp
Normal file
88
clang/test/CIR/CodeGen/delete.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-cir %s -o %t.cir
|
||||
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-llvm %s -o %t-cir.ll
|
||||
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -mconstructor-aliases -emit-llvm %s -o %t.ll
|
||||
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
|
||||
|
||||
typedef __typeof(sizeof(int)) size_t;
|
||||
|
||||
struct SizedDelete {
|
||||
void operator delete(void*, size_t);
|
||||
int member;
|
||||
};
|
||||
void test_sized_delete(SizedDelete *x) {
|
||||
delete x;
|
||||
}
|
||||
|
||||
// SizedDelete::operator delete(void*, unsigned long)
|
||||
// CIR: cir.func private @_ZN11SizedDeletedlEPvm(!cir.ptr<!void>, !u64i)
|
||||
// LLVM: declare void @_ZN11SizedDeletedlEPvm(ptr, i64)
|
||||
|
||||
// CIR: cir.func dso_local @_Z17test_sized_deleteP11SizedDelete
|
||||
// CIR: %[[X:.*]] = cir.load{{.*}} %{{.*}}
|
||||
// CIR: %[[X_CAST:.*]] = cir.cast(bitcast, %[[X]] : !cir.ptr<!rec_SizedDelete>), !cir.ptr<!void>
|
||||
// CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<4> : !u64i
|
||||
// CIR: cir.call @_ZN11SizedDeletedlEPvm(%[[X_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> ()
|
||||
|
||||
// LLVM: define dso_local void @_Z17test_sized_deleteP11SizedDelete
|
||||
// LLVM: %[[X:.*]] = load ptr, ptr %{{.*}}
|
||||
// LLVM: call void @_ZN11SizedDeletedlEPvm(ptr %[[X]], i64 4)
|
||||
|
||||
// OGCG: define dso_local void @_Z17test_sized_deleteP11SizedDelete
|
||||
// OGCG: %[[X:.*]] = load ptr, ptr %{{.*}}
|
||||
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[X]], null
|
||||
// OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]]
|
||||
// OGCG: [[DELETE_NOTNULL]]:
|
||||
// OGCG: call void @_ZN11SizedDeletedlEPvm(ptr noundef %[[X]], i64 noundef 4)
|
||||
|
||||
// This function is declared below the call in OGCG.
|
||||
// OGCG: declare void @_ZN11SizedDeletedlEPvm(ptr noundef, i64 noundef)
|
||||
|
||||
struct Contents {
|
||||
~Contents() {}
|
||||
};
|
||||
struct Container {
|
||||
Contents *contents;
|
||||
~Container();
|
||||
};
|
||||
Container::~Container() { delete contents; }
|
||||
|
||||
// Contents::~Contents()
|
||||
// CIR: cir.func comdat linkonce_odr @_ZN8ContentsD2Ev
|
||||
// LLVM: define linkonce_odr void @_ZN8ContentsD2Ev
|
||||
|
||||
// operator delete(void*, unsigned long)
|
||||
// CIR: cir.func private @_ZdlPvm(!cir.ptr<!void>, !u64i)
|
||||
// LLVM: declare void @_ZdlPvm(ptr, i64)
|
||||
|
||||
// Container::~Container()
|
||||
// CIR: cir.func dso_local @_ZN9ContainerD2Ev
|
||||
// CIR: %[[THIS:.*]] = cir.load %{{.*}}
|
||||
// CIR: %[[CONTENTS_PTR_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "contents"} : !cir.ptr<!rec_Container> -> !cir.ptr<!cir.ptr<!rec_Contents>>
|
||||
// CIR: %[[CONTENTS_PTR:.*]] = cir.load{{.*}} %[[CONTENTS_PTR_ADDR]]
|
||||
// CIR: cir.call @_ZN8ContentsD2Ev(%[[CONTENTS_PTR]]) nothrow : (!cir.ptr<!rec_Contents>) -> ()
|
||||
// CIR: %[[CONTENTS_CAST:.*]] = cir.cast(bitcast, %[[CONTENTS_PTR]] : !cir.ptr<!rec_Contents>), !cir.ptr<!void>
|
||||
// CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<1> : !u64i
|
||||
// CIR: cir.call @_ZdlPvm(%[[CONTENTS_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> ()
|
||||
|
||||
// LLVM: define dso_local void @_ZN9ContainerD2Ev
|
||||
// LLVM: %[[THIS:.*]] = load ptr, ptr %{{.*}}
|
||||
// LLVM: %[[CONTENTS_PTR_ADDR:.*]] = getelementptr %struct.Container, ptr %[[THIS]], i32 0, i32 0
|
||||
// LLVM: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS_PTR_ADDR]]
|
||||
// LLVM: call void @_ZN8ContentsD2Ev(ptr %[[CONTENTS_PTR]])
|
||||
// LLVM: call void @_ZdlPvm(ptr %[[CONTENTS_PTR]], i64 1)
|
||||
|
||||
// OGCG: define dso_local void @_ZN9ContainerD2Ev
|
||||
// OGCG: %[[THIS:.*]] = load ptr, ptr %{{.*}}
|
||||
// OGCG: %[[CONTENTS:.*]] = getelementptr inbounds nuw %struct.Container, ptr %[[THIS]], i32 0, i32 0
|
||||
// OGCG: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS]]
|
||||
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[CONTENTS_PTR]], null
|
||||
// OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]]
|
||||
// OGCG: [[DELETE_NOTNULL]]:
|
||||
// OGCG: call void @_ZN8ContentsD2Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[CONTENTS_PTR]])
|
||||
// OGCG: call void @_ZdlPvm(ptr noundef %[[CONTENTS_PTR]], i64 noundef 1)
|
||||
|
||||
// These functions are declared/defined below the calls in OGCG.
|
||||
// OGCG: define linkonce_odr void @_ZN8ContentsD2Ev
|
||||
// OGCG: declare void @_ZdlPvm(ptr noundef, i64 noundef)
|
||||
Reference in New Issue
Block a user