[CIR] Add abstract delete operation without AST attribute (#185538)

This introduces the cir.delete_array operation, adds code to emit that
operation during CIR codegen, and adds lowering of the operation to the
CXXABILowering pass.

In order to handle possible variations in the delete representation, we
add the name of the delete function, the usual delete parameters, and,
optionally, the name of the element destructor function.

During the CXXABILoweringPass, the cir.delete_array operation is
expanded to call the delete function. This will be extended in a future
change to handle reading the array cookie, if required, and calling
element destructors.
This commit is contained in:
Andy Kaylor 2026-03-10 13:02:47 -07:00 committed by GitHub
parent 7a104955e7
commit 9e611e82b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 175 additions and 4 deletions

View File

@ -1357,6 +1357,30 @@ def CIR_StaticLocalGuardAttr : CIR_Attr<"StaticLocalGuard",
let assemblyFormat = "`<` $name `>`";
}
//===----------------------------------------------------------------------===//
// UsualDeleteParamsAttr
//===----------------------------------------------------------------------===//
def CIR_UsualDeleteParamsAttr
: CIR_Attr<"UsualDeleteParams", "usual_delete_params"> {
let summary = "Parameters describing the usual operator delete signature";
let description = [{
Captures the properties of the usual deallocation function associated with
an operator delete. These mirror the fields of `clang::UsualDeleteParams`.
}];
let parameters = (ins
DefaultValuedParameter<"bool", "false">:$size,
DefaultValuedParameter<"bool", "false">:$alignment,
DefaultValuedParameter<"bool", "false">:$type_aware_delete,
DefaultValuedParameter<"bool", "false">:$destroying_delete
);
let assemblyFormat = [{
`<` struct($size, $alignment, $type_aware_delete, $destroying_delete) `>`
}];
}
//===----------------------------------------------------------------------===//
// AST Wrappers
//===----------------------------------------------------------------------===//

View File

@ -3601,6 +3601,47 @@ def CIR_LLVMIntrinsicCallOp : CIR_Op<"call_llvm_intrinsic"> {
];
}
//===----------------------------------------------------------------------===//
// DeleteArrayOp
//===----------------------------------------------------------------------===//
def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
let summary = "Delete address representing an array";
let description = [{
`cir.delete_array` operation deletes an array. For example, `delete[] ptr;`
will be translated to `cir.delete_array %ptr`.
The `delete_fn` attribute specifies the operator delete function to call.
The `delete_params` attribute describes the parameters needed by the
operator delete call.
The `element_dtor` attribute, when present, specifies the destructor to call
on each array element before deallocation.
}];
let arguments = (ins
CIR_PointerType:$address,
FlatSymbolRefAttr:$delete_fn,
CIR_UsualDeleteParamsAttr:$delete_params,
OptionalAttr<FlatSymbolRefAttr>:$element_dtor
);
let builders = [
OpBuilder<(ins "mlir::Value":$address,
"mlir::FlatSymbolRefAttr":$delete_fn,
"cir::UsualDeleteParamsAttr":$delete_params), [{
build($_builder, $_state, address, delete_fn, delete_params,
/*element_dtor=*/mlir::FlatSymbolRefAttr{});
}]>
];
let assemblyFormat = [{
$address `:` qualified(type($address)) attr-dict
}];
let hasLLVMLowering = false;
let hasCXXABILowering = true;
}
//===----------------------------------------------------------------------===//
// CallOp and TryCallOp
//===----------------------------------------------------------------------===//

View File

@ -12,6 +12,7 @@
#include "mlir/IR/Attributes.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclTemplate.h"
/// Include the generated interface declarations.

View File

@ -1222,9 +1222,23 @@ void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
}
if (e->isArrayForm()) {
assert(!cir::MissingFeatures::deleteArray());
cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
return;
// To handle this for cases that require array cookie, we will need to
// add target-specific handling during the lowering of delete_array in
// CXXABILowering, but we can emit a better diagnostic here.
if (e->doesUsualArrayDeleteWantSize() || deleteTy.isDestructedType()) {
cgm.errorNYI(e->getSourceRange(),
"emitCXXDeleteExpr: array delete requires cookies");
}
const FunctionDecl *operatorDelete = e->getOperatorDelete();
cir::FuncOp operatorDeleteFn = cgm.getAddrOfFunction(operatorDelete);
auto deleteFn =
mlir::FlatSymbolRefAttr::get(operatorDeleteFn.getSymNameAttr());
UsualDeleteParams udp = operatorDelete->getUsualDeleteParams();
auto deleteParams = cir::UsualDeleteParamsAttr::get(
builder.getContext(), udp.Size, isAlignedAllocation(udp.Alignment),
isTypeAwareAllocation(udp.TypeAwareDelete), udp.DestroyingDelete);
cir::DeleteArrayOp::create(builder, ptr.getPointer().getLoc(),
ptr.getPointer(), deleteFn, deleteParams);
} else {
emitObjectDelete(*this, e, ptr, deleteTy);
}

View File

@ -58,7 +58,7 @@ public:
mlir::ConversionPatternRewriter &rewriter) const override {
// Do not match on operations that have dedicated ABI lowering rewrite rules
if (llvm::isa<cir::AllocaOp, cir::BaseDataMemberOp, cir::BaseMethodOp,
cir::CastOp, cir::CmpOp, cir::ConstantOp,
cir::CastOp, cir::CmpOp, cir::ConstantOp, cir::DeleteArrayOp,
cir::DerivedDataMemberOp, cir::DerivedMethodOp, cir::FuncOp,
cir::GetMethodOp, cir::GetRuntimeMemberOp, cir::GlobalOp>(op))
return mlir::failure();
@ -320,6 +320,30 @@ mlir::LogicalResult CIRBaseMethodOpABILowering::matchAndRewrite(
return mlir::success();
}
mlir::LogicalResult CIRDeleteArrayOpABILowering::matchAndRewrite(
cir::DeleteArrayOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::FlatSymbolRefAttr deleteFn = op.getDeleteFnAttr();
mlir::Location loc = op->getLoc();
mlir::Value loweredAddress = adaptor.getAddress();
auto voidPtrTy =
cir::PointerType::get(cir::VoidType::get(rewriter.getContext()));
mlir::Value deletePtr = cir::CastOp::create(
rewriter, loc, voidPtrTy, cir::CastKind::bitcast, loweredAddress);
cir::UsualDeleteParamsAttr deleteParams = op.getDeleteParams();
if (deleteParams.getSize() || deleteParams.getTypeAwareDelete() ||
deleteParams.getDestroyingDelete() || deleteParams.getAlignment())
return rewriter.notifyMatchFailure(
op,
"sized, type-aware, destroying, or aligned delete not yet supported");
llvm::SmallVector<mlir::Value> callArgs{deletePtr};
cir::CallOp::create(rewriter, loc, deleteFn, cir::VoidType(), callArgs);
rewriter.eraseOp(op);
return mlir::success();
}
mlir::LogicalResult CIRDerivedDataMemberOpABILowering::matchAndRewrite(
cir::DerivedDataMemberOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
@ -454,6 +478,9 @@ populateCXXABIConversionTarget(mlir::ConversionTarget &target,
[&typeConverter](cir::GlobalOp op) {
return typeConverter.isLegal(op.getSymType());
});
// Operations that do not use any special types must be explicitly marked as
// illegal to trigger processing here.
target.addIllegalOp<cir::DeleteArrayOp>();
target.addIllegalOp<cir::DynamicCastOp>();
target.addIllegalOp<cir::VTableGetTypeInfoOp>();
}

View File

@ -0,0 +1,64 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir -mmlir -mlir-print-ir-before=cir-cxxabi-lowering %s -o %t.cir 2> %t-before.cir
// RUN: FileCheck --input-file=%t-before.cir -check-prefix=CIR-BEFORE %s
// RUN: FileCheck --input-file=%t.cir --check-prefix=CIR %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll --check-prefix=LLVM %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll --check-prefix=OGCG %s
void test_delete_array(int *ptr) {
delete[] ptr;
}
// CIR-BEFORE: cir.func {{.*}} @_Z17test_delete_arrayPi
// CIR-BEFORE: %[[PTR:.*]] = cir.load{{.*}} %{{.*}}
// CIR-BEFORE: cir.delete_array %[[PTR]] : !cir.ptr<!s32i> {delete_fn = @_ZdaPv, delete_params = #cir.usual_delete_params<>
// CIR: cir.func {{.*}} @_Z17test_delete_arrayPi
// CIR: %[[PTR:.*]] = cir.load{{.*}} %{{.*}}
// CIR: %[[VOID_PTR:.*]] = cir.cast bitcast %[[PTR]] : !cir.ptr<!s32i> -> !cir.ptr<!void>
// CIR: cir.call @_ZdaPv(%[[VOID_PTR]])
// LLVM: define {{.*}} void @_Z17test_delete_arrayPi
// LLVM: %[[PTR:.*]] = load ptr, ptr %{{.*}}
// LLVM: call void @_ZdaPv(ptr %[[PTR]])
// OGCG: define {{.*}} void @_Z17test_delete_arrayPi
// OGCG: %[[PTR:.*]] = load ptr, ptr %{{.*}}
// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[PTR]], null
// OGCG: br i1 %[[IS_NULL]], label %[[DELETE_END:.*]], label %[[DELETE_NOT_NULL:.*]]
// OGCG: [[DELETE_NOT_NULL]]:
// OGCG: call void @_ZdaPv(ptr {{.*}} %[[PTR]])
// OGCG: br label %[[DELETE_END]]
// OGCG: [[DELETE_END]]:
// OGCG: ret void
struct SimpleArrDelete {
void operator delete[](void *);
int member;
};
void test_simple_delete_array(SimpleArrDelete *ptr) {
delete[] ptr;
}
// CIR-BEFORE: cir.func {{.*}} @_Z24test_simple_delete_arrayP15SimpleArrDelete
// CIR-BEFORE: %[[PTR:.*]] = cir.load{{.*}} %{{.*}}
// CIR-BEFORE: cir.delete_array %[[PTR]] : !cir.ptr<!rec_SimpleArrDelete> {delete_fn = @_ZN15SimpleArrDeletedaEPv, delete_params = #cir.usual_delete_params<>
// CIR: cir.func {{.*}} @_Z24test_simple_delete_arrayP15SimpleArrDelete
// CIR: %[[PTR:.*]] = cir.load{{.*}} %{{.*}}
// CIR: %[[VOID_PTR:.*]] = cir.cast bitcast %[[PTR]] : !cir.ptr<!rec_SimpleArrDelete> -> !cir.ptr<!void>
// CIR: cir.call @_ZN15SimpleArrDeletedaEPv(%[[VOID_PTR]])
// LLVM: define {{.*}} void @_Z24test_simple_delete_arrayP15SimpleArrDelete
// LLVM: %[[PTR:.*]] = load ptr, ptr %{{.*}}
// LLVM: call void @_ZN15SimpleArrDeletedaEPv(ptr %[[PTR]])
// OGCG: define {{.*}} void @_Z24test_simple_delete_arrayP15SimpleArrDelete
// OGCG: %[[PTR:.*]] = load ptr, ptr %{{.*}}
// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[PTR]], null
// OGCG: br i1 %[[IS_NULL]], label %[[ARR_DELETE_END:.*]], label %[[ARR_DELETE_NOT_NULL:.*]]
// OGCG: [[ARR_DELETE_NOT_NULL]]:
// OGCG: call void @_ZN15SimpleArrDeletedaEPv(ptr {{.*}} %[[PTR]])
// OGCG: br label %[[ARR_DELETE_END]]
// OGCG: [[ARR_DELETE_END]]: