Skip to content

Commit 0f64aec

Browse files
committed
[CIR] Add initial support for operator delete
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.
1 parent be7444b commit 0f64aec

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ struct MissingFeatures {
208208
static bool dataLayoutTypeAllocSize() { return false; }
209209
static bool dataLayoutTypeStoreSize() { return false; }
210210
static bool deferredCXXGlobalInit() { return false; }
211+
static bool deleteArray() { return false; }
211212
static bool devirtualizeMemberFunction() { return false; }
212213
static bool ehCleanupFlags() { return false; }
213214
static bool ehCleanupScope() { return false; }
@@ -219,6 +220,7 @@ struct MissingFeatures {
219220
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
220221
static bool emitLifetimeMarkers() { return false; }
221222
static bool emitLValueAlignmentAssumption() { return false; }
223+
static bool emitNullCheckForDeleteCalls() { return false; }
222224
static bool emitNullabilityCheck() { return false; }
223225
static bool emitTypeCheck() { return false; }
224226
static bool emitTypeMetadataCodeForVCall() { return false; }

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,60 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
210210
return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
211211
}
212212

213+
namespace {
214+
/// The parameters to pass to a usual operator delete.
215+
struct UsualDeleteParams {
216+
TypeAwareAllocationMode typeAwareDelete = TypeAwareAllocationMode::No;
217+
bool destroyingDelete = false;
218+
bool size = false;
219+
AlignedAllocationMode alignment = AlignedAllocationMode::No;
220+
};
221+
} // namespace
222+
223+
// FIXME(cir): this should be shared with LLVM codegen
224+
static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *fd) {
225+
UsualDeleteParams params;
226+
227+
const FunctionProtoType *fpt = fd->getType()->castAs<FunctionProtoType>();
228+
auto ai = fpt->param_type_begin(), ae = fpt->param_type_end();
229+
230+
if (fd->isTypeAwareOperatorNewOrDelete()) {
231+
params.typeAwareDelete = TypeAwareAllocationMode::Yes;
232+
assert(ai != ae);
233+
++ai;
234+
}
235+
236+
// The first argument after the type-identity parameter (if any) is
237+
// always a void* (or C* for a destroying operator delete for class
238+
// type C).
239+
++ai;
240+
241+
// The next parameter may be a std::destroying_delete_t.
242+
if (fd->isDestroyingOperatorDelete()) {
243+
params.destroyingDelete = true;
244+
assert(ai != ae);
245+
++ai;
246+
}
247+
248+
// Figure out what other parameters we should be implicitly passing.
249+
if (ai != ae && (*ai)->isIntegerType()) {
250+
params.size = true;
251+
++ai;
252+
} else {
253+
assert(!isTypeAwareAllocation(params.typeAwareDelete));
254+
}
255+
256+
if (ai != ae && (*ai)->isAlignValT()) {
257+
params.alignment = AlignedAllocationMode::Yes;
258+
++ai;
259+
} else {
260+
assert(!isTypeAwareAllocation(params.typeAwareDelete));
261+
}
262+
263+
assert(ai == ae && "unexpected usual deallocation function parameter");
264+
return params;
265+
}
266+
213267
static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
214268
unsigned minElements,
215269
mlir::Value &numElements,
@@ -332,6 +386,117 @@ static RValue emitNewDeleteCall(CIRGenFunction &cgf,
332386
return rv;
333387
}
334388

389+
namespace {
390+
/// Calls the given 'operator delete' on a single object.
391+
struct CallObjectDelete final : EHScopeStack::Cleanup {
392+
mlir::Value ptr;
393+
const FunctionDecl *operatorDelete;
394+
QualType elementType;
395+
396+
CallObjectDelete(mlir::Value ptr, const FunctionDecl *operatorDelete,
397+
QualType elementType)
398+
: ptr(ptr), operatorDelete(operatorDelete), elementType(elementType) {}
399+
400+
void emit(CIRGenFunction &cgf) override {
401+
cgf.emitDeleteCall(operatorDelete, ptr, elementType);
402+
}
403+
404+
// This is a placeholder until EHCleanupScope is implemented.
405+
size_t getSize() const override {
406+
assert(!cir::MissingFeatures::ehCleanupScope());
407+
return sizeof(CallObjectDelete);
408+
}
409+
};
410+
} // namespace
411+
412+
/// Emit the code for deleting a single object.
413+
static void emitObjectDelete(CIRGenFunction &cgf, const CXXDeleteExpr *de,
414+
Address ptr, QualType elementType) {
415+
// C++11 [expr.delete]p3:
416+
// If the static type of the object to be deleted is different from its
417+
// dynamic type, the static type shall be a base class of the dynamic type
418+
// of the object to be deleted and the static type shall have a virtual
419+
// destructor or the behavior is undefined.
420+
assert(!cir::MissingFeatures::emitTypeCheck());
421+
422+
const FunctionDecl *operatorDelete = de->getOperatorDelete();
423+
assert(!operatorDelete->isDestroyingOperatorDelete());
424+
425+
// Find the destructor for the type, if applicable. If the
426+
// destructor is virtual, we'll just emit the vcall and return.
427+
const CXXDestructorDecl *dtor = nullptr;
428+
if (const auto *rd = elementType->getAsCXXRecordDecl()) {
429+
if (rd->hasDefinition() && !rd->hasTrivialDestructor()) {
430+
dtor = rd->getDestructor();
431+
432+
if (dtor->isVirtual()) {
433+
cgf.cgm.errorNYI(de->getSourceRange(),
434+
"emitObjectDelete: virtual destructor");
435+
}
436+
}
437+
}
438+
439+
// Make sure that we call delete even if the dtor throws.
440+
// This doesn't have to a conditional cleanup because we're going
441+
// to pop it off in a second.
442+
cgf.ehStack.pushCleanup<CallObjectDelete>(
443+
NormalAndEHCleanup, ptr.getPointer(), operatorDelete, elementType);
444+
445+
if (dtor) {
446+
cgf.emitCXXDestructorCall(dtor, Dtor_Complete,
447+
/*ForVirtualBase=*/false,
448+
/*Delegating=*/false, ptr, elementType);
449+
} else if (elementType.getObjCLifetime()) {
450+
assert(!cir::MissingFeatures::objCLifetime());
451+
cgf.cgm.errorNYI(de->getSourceRange(), "emitObjectDelete: ObjCLifetime");
452+
}
453+
454+
// In traditional LLVM codegen null checks are emitted to save a delete call.
455+
// In CIR we optimize for size by default, the null check should be added into
456+
// this function callers.
457+
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
458+
459+
cgf.popCleanupBlock();
460+
}
461+
462+
void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
463+
const Expr *arg = e->getArgument();
464+
Address ptr = emitPointerWithAlignment(arg);
465+
466+
// Null check the pointer.
467+
//
468+
// We could avoid this null check if we can determine that the object
469+
// destruction is trivial and doesn't require an array cookie; we can
470+
// unconditionally perform the operator delete call in that case. For now, we
471+
// assume that deleted pointers are null rarely enough that it's better to
472+
// keep the branch. This might be worth revisiting for a -O0 code size win.
473+
//
474+
// CIR note: emit the code size friendly by default for now, such as mentioned
475+
// in `emitObjectDelete`.
476+
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
477+
QualType deleteTy = e->getDestroyedType();
478+
479+
// A destroying operator delete overrides the entire operation of the
480+
// delete expression.
481+
if (e->getOperatorDelete()->isDestroyingOperatorDelete()) {
482+
cgm.errorNYI(e->getSourceRange(),
483+
"emitCXXDeleteExpr: destroying operator delete");
484+
return;
485+
}
486+
487+
// We might be deleting a pointer to array.
488+
deleteTy = getContext().getBaseElementType(deleteTy);
489+
ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy));
490+
491+
if (e->isArrayForm()) {
492+
assert(!cir::MissingFeatures::deleteArray());
493+
cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
494+
return;
495+
} else {
496+
emitObjectDelete(*this, e, ptr, deleteTy);
497+
}
498+
}
499+
335500
mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
336501
// The element type being allocated.
337502
QualType allocType = getContext().getBaseElementType(e->getAllocatedType());
@@ -443,3 +608,53 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
443608
allocSizeWithoutCookie);
444609
return result.getPointer();
445610
}
611+
612+
void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD,
613+
mlir::Value ptr, QualType deleteTy) {
614+
assert(!cir::MissingFeatures::deleteArray());
615+
616+
const auto *deleteFTy = deleteFD->getType()->castAs<FunctionProtoType>();
617+
CallArgList deleteArgs;
618+
619+
UsualDeleteParams params = getUsualDeleteParams(deleteFD);
620+
auto paramTypeIt = deleteFTy->param_type_begin();
621+
622+
// Pass std::type_identity tag if present
623+
if (isTypeAwareAllocation(params.typeAwareDelete))
624+
cgm.errorNYI(deleteFD->getSourceRange(),
625+
"emitDeleteCall: type aware delete");
626+
627+
// Pass the pointer itself.
628+
QualType argTy = *paramTypeIt++;
629+
mlir::Value deletePtr =
630+
builder.createBitcast(ptr.getLoc(), ptr, convertType(argTy));
631+
deleteArgs.add(RValue::get(deletePtr), argTy);
632+
633+
// Pass the std::destroying_delete tag if present.
634+
if (params.destroyingDelete)
635+
cgm.errorNYI(deleteFD->getSourceRange(),
636+
"emitDeleteCall: destroying delete");
637+
638+
// Pass the size if the delete function has a size_t parameter.
639+
if (params.size) {
640+
QualType sizeType = *paramTypeIt++;
641+
CharUnits deleteTypeSize = getContext().getTypeSizeInChars(deleteTy);
642+
assert(mlir::isa<cir::IntType>(convertType(sizeType)) &&
643+
"expected cir::IntType");
644+
cir::ConstantOp size = builder.getConstInt(
645+
*currSrcLoc, convertType(sizeType), deleteTypeSize.getQuantity());
646+
647+
deleteArgs.add(RValue::get(size), sizeType);
648+
}
649+
650+
// Pass the alignment if the delete function has an align_val_t parameter.
651+
if (isAlignedAllocation(params.alignment))
652+
cgm.errorNYI(deleteFD->getSourceRange(),
653+
"emitDeleteCall: aligned allocation");
654+
655+
assert(paramTypeIt == deleteFTy->param_type_end() &&
656+
"unknown parameter to usual delete function");
657+
658+
// Emit the call to delete.
659+
emitNewDeleteCall(*this, deleteFD, deleteFTy, deleteArgs);
660+
}

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
687687
mlir::Value VisitCXXNewExpr(const CXXNewExpr *e) {
688688
return cgf.emitCXXNewExpr(e);
689689
}
690+
mlir::Value VisitCXXDeleteExpr(const CXXDeleteExpr *e) {
691+
cgf.emitCXXDeleteExpr(e);
692+
return {};
693+
}
690694

691695
mlir::Value VisitCXXThrowExpr(const CXXThrowExpr *e) {
692696
cgf.emitCXXThrowExpr(e);
@@ -2355,4 +2359,4 @@ mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *e,
23552359
bool isPre) {
23562360
return ScalarExprEmitter(*this, builder)
23572361
.emitScalarPrePostIncDec(e, lv, kind, isPre);
2358-
}
2362+
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,8 @@ class CIRGenFunction : public CIRGenTypeCache {
11971197
bool delegating, Address thisAddr,
11981198
CallArgList &args, clang::SourceLocation loc);
11991199

1200+
void emitCXXDeleteExpr(const CXXDeleteExpr *e);
1201+
12001202
void emitCXXDestructorCall(const CXXDestructorDecl *dd, CXXDtorType type,
12011203
bool forVirtualBase, bool delegating,
12021204
Address thisAddr, QualType thisTy);
@@ -1244,6 +1246,9 @@ class CIRGenFunction : public CIRGenTypeCache {
12441246
void emitDelegatingCXXConstructorCall(const CXXConstructorDecl *ctor,
12451247
const FunctionArgList &args);
12461248

1249+
void emitDeleteCall(const FunctionDecl *deleteFD, mlir::Value ptr,
1250+
QualType deleteTy);
1251+
12471252
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
12481253

12491254
/// Emit an expression as an initializer for an object (variable, field, etc.)

clang/test/CIR/CodeGen/delete.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -mconstructor-aliases -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
7+
8+
typedef __typeof(sizeof(int)) size_t;
9+
10+
struct SizedDelete {
11+
void operator delete(void*, size_t);
12+
int member;
13+
};
14+
void test_sized_delete(SizedDelete *x) {
15+
delete x;
16+
}
17+
18+
// SizedDelete::operator delete(void*, unsigned long)
19+
// CIR: cir.func private @_ZN11SizedDeletedlEPvm(!cir.ptr<!void>, !u64i)
20+
// LLVM: declare void @_ZN11SizedDeletedlEPvm(ptr, i64)
21+
22+
// CIR: cir.func dso_local @_Z17test_sized_deleteP11SizedDelete
23+
// CIR: %[[X:.*]] = cir.load{{.*}} %{{.*}}
24+
// CIR: %[[X_CAST:.*]] = cir.cast(bitcast, %[[X]] : !cir.ptr<!rec_SizedDelete>), !cir.ptr<!void>
25+
// CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<4> : !u64i
26+
// CIR: cir.call @_ZN11SizedDeletedlEPvm(%[[X_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> ()
27+
28+
// LLVM: define dso_local void @_Z17test_sized_deleteP11SizedDelete
29+
// LLVM: %[[X:.*]] = load ptr, ptr %{{.*}}
30+
// LLVM: call void @_ZN11SizedDeletedlEPvm(ptr %[[X]], i64 4)
31+
32+
// OGCG: define dso_local void @_Z17test_sized_deleteP11SizedDelete
33+
// OGCG: %[[X:.*]] = load ptr, ptr %{{.*}}
34+
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[X]], null
35+
// OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]]
36+
// OGCG: [[DELETE_NOTNULL]]:
37+
// OGCG: call void @_ZN11SizedDeletedlEPvm(ptr noundef %[[X]], i64 noundef 4)
38+
39+
// This function is declared below the call in OGCG.
40+
// OGCG: declare void @_ZN11SizedDeletedlEPvm(ptr noundef, i64 noundef)
41+
42+
struct Contents {
43+
~Contents() {}
44+
};
45+
struct Container {
46+
Contents *contents;
47+
~Container();
48+
};
49+
Container::~Container() { delete contents; }
50+
51+
// Contents::~Contents()
52+
// CIR: cir.func comdat linkonce_odr @_ZN8ContentsD2Ev
53+
// LLVM: define linkonce_odr void @_ZN8ContentsD2Ev
54+
55+
// operator delete(void*, unsigned long)
56+
// CIR: cir.func private @_ZdlPvm(!cir.ptr<!void>, !u64i)
57+
// LLVM: declare void @_ZdlPvm(ptr, i64)
58+
59+
// Container::~Container()
60+
// CIR: cir.func dso_local @_ZN9ContainerD2Ev
61+
// CIR: %[[THIS:.*]] = cir.load %{{.*}}
62+
// CIR: %[[CONTENTS_PTR_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "contents"} : !cir.ptr<!rec_Container> -> !cir.ptr<!cir.ptr<!rec_Contents>>
63+
// CIR: %[[CONTENTS_PTR:.*]] = cir.load{{.*}} %[[CONTENTS_PTR_ADDR]]
64+
// CIR: cir.call @_ZN8ContentsD2Ev(%[[CONTENTS_PTR]]) nothrow : (!cir.ptr<!rec_Contents>) -> ()
65+
// CIR: %[[CONTENTS_CAST:.*]] = cir.cast(bitcast, %[[CONTENTS_PTR]] : !cir.ptr<!rec_Contents>), !cir.ptr<!void>
66+
// CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<1> : !u64i
67+
// CIR: cir.call @_ZdlPvm(%[[CONTENTS_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> ()
68+
69+
// LLVM: define dso_local void @_ZN9ContainerD2Ev
70+
// LLVM: %[[THIS:.*]] = load ptr, ptr %{{.*}}
71+
// LLVM: %[[CONTENTS_PTR_ADDR:.*]] = getelementptr %struct.Container, ptr %[[THIS]], i32 0, i32 0
72+
// LLVM: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS_PTR_ADDR]]
73+
// LLVM: call void @_ZN8ContentsD2Ev(ptr %[[CONTENTS_PTR]])
74+
// LLVM: call void @_ZdlPvm(ptr %[[CONTENTS_PTR]], i64 1)
75+
76+
// OGCG: define dso_local void @_ZN9ContainerD2Ev
77+
// OGCG: %[[THIS:.*]] = load ptr, ptr %{{.*}}
78+
// OGCG: %[[CONTENTS:.*]] = getelementptr inbounds nuw %struct.Container, ptr %[[THIS]], i32 0, i32 0
79+
// OGCG: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS]]
80+
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[CONTENTS_PTR]], null
81+
// OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]]
82+
// OGCG: [[DELETE_NOTNULL]]:
83+
// OGCG: call void @_ZN8ContentsD2Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[CONTENTS_PTR]])
84+
// OGCG: call void @_ZdlPvm(ptr noundef %[[CONTENTS_PTR]], i64 noundef 1)
85+
86+
// These functions are declared/defined below the calls in OGCG.
87+
// OGCG: define linkonce_odr void @_ZN8ContentsD2Ev
88+
// OGCG: declare void @_ZdlPvm(ptr noundef, i64 noundef)

0 commit comments

Comments
 (0)