From 9e611e82b8abcebe2a20fa5337f97ab2f4dddeb0 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Tue, 10 Mar 2026 13:02:47 -0700 Subject: [PATCH] [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. --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 24 +++++++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 41 ++++++++++++ .../clang/CIR/Interfaces/ASTAttrInterfaces.h | 1 + clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 20 +++++- .../CIR/Dialect/Transforms/CXXABILowering.cpp | 29 ++++++++- clang/test/CIR/CodeGen/delete-array.cpp | 64 +++++++++++++++++++ 6 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 clang/test/CIR/CodeGen/delete-array.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index b1be1d5daf4e..16d32802d7c2 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -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 //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 2c109eaeb392..d10bed40e75d 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -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:$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 4bfa8df7ad55..2fb633ee9221 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 35f74e7120b0..98c7000422c5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp @@ -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); } diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp index b76dee98713c..fd30ea78c4b7 100644 --- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp @@ -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(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 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(); target.addIllegalOp(); target.addIllegalOp(); } diff --git a/clang/test/CIR/CodeGen/delete-array.cpp b/clang/test/CIR/CodeGen/delete-array.cpp new file mode 100644 index 000000000000..22de55152a17 --- /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 {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 -> !cir.ptr +// 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 {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 -> !cir.ptr +// 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]]: