Skip to content

Commit 38953f4

Browse files
authored
[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.
1 parent 782ab83 commit 38953f4

File tree

5 files changed

+314
-0
lines changed

5 files changed

+314
-0
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: 4 additions & 0 deletions
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);

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)