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
2 changes: 2 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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; }
Expand Down
215 changes: 215 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created #160554 to refactor this for sharing. If that's accepted before this PR lands, I'll update this here. Otherwise, I'll do that as a follow-up.

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,
Expand Down Expand Up @@ -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>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will eventually ensure that the delete call happens even if an exception occurs, but even without exceptions, this is where the delete call is emitted.

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());
Expand Down Expand Up @@ -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);
}
6 changes: 5 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
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);
Expand Down Expand Up @@ -2355,4 +2359,4 @@ mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *e,
bool isPre) {
return ScalarExprEmitter(*this, builder)
.emitScalarPrePostIncDec(e, lv, kind, isPre);
}
}
5 changes: 5 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,8 @@ class CIRGenFunction : public CIRGenTypeCache {
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);
Expand Down Expand Up @@ -1244,6 +1246,9 @@ class CIRGenFunction : public CIRGenTypeCache {
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.)
Expand Down
88 changes: 88 additions & 0 deletions clang/test/CIR/CodeGen/delete.cpp
Original file line number Diff line number Diff line change
@@ -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)