[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.
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index b1be1d5..16d3280 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1358,6 +1358,30 @@
}
//===----------------------------------------------------------------------===//
+// 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
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2c109ea..d10bed4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3602,6 +3602,47 @@
}
//===----------------------------------------------------------------------===//
+// 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
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
index 4bfa8df..2fb633e 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
@@ -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.
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 35f74e7..98c7000 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1222,9 +1222,23 @@
}
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);
}
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index b76dee9..fd30ea7 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -58,7 +58,7 @@
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 @@
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 @@
[&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>();
}
diff --git a/clang/test/CIR/CodeGen/delete-array.cpp b/clang/test/CIR/CodeGen/delete-array.cpp
new file mode 100644
index 0000000..22de551
--- /dev/null
+++ b/clang/test/CIR/CodeGen/delete-array.cpp
@@ -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]]: