Skip to content

Conversation

@monthdev
Copy link
Contributor

Upstream DeleteArrayOp and clang/test/CIR/CodeGen/delete-array.cpp

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Oct 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 27, 2025

@llvm/pr-subscribers-clangir

Author: Shawn K (kimsh02)

Changes

Upstream DeleteArrayOp and clang/test/CIR/CodeGen/delete-array.cpp


Full diff: https://github.com/llvm/llvm-project/pull/165225.diff

4 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+18)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+2-2)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+37)
  • (added) clang/test/CIR/CodeGen/delete-array.cpp (+16)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2b361ed0982c6..b7bdfe7d9f5b6 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4089,6 +4089,24 @@ def CIR_PrefetchOp : CIR_Op<"prefetch"> {
     }];
 }
 
+//===----------------------------------------------------------------------===//
+// 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`.
+  }];
+
+  let arguments = (ins CIR_PointerType:$address);
+
+  let assemblyFormat = [{
+    $address `:` type($address) attr-dict
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // PtrDiffOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 7a35382e79a93..42d066e88225c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -626,8 +626,8 @@ void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
   ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy));
 
   if (e->isArrayForm()) {
-    assert(!cir::MissingFeatures::deleteArray());
-    cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
+    cir::DeleteArrayOp::create(builder, ptr.getPointer().getLoc(),
+                               ptr.getPointer());
     return;
   } else {
     emitObjectDelete(*this, e, ptr, deleteTy);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 5a6193fa8d840..aba182e2c9952 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1704,6 +1704,43 @@ mlir::LogicalResult CIRToLLVMPrefetchOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+// Make sure the LLVM function we are about to create a call for actually
+// exists, if not create one. Returns a function
+static void getOrCreateLLVMFuncOp(mlir::ConversionPatternRewriter &rewriter,
+                                  mlir::Operation *srcOp,
+                                  llvm::StringRef fnName, mlir::Type fnTy) {
+  auto modOp = srcOp->getParentOfType<mlir::ModuleOp>();
+  auto enclosingFnOp = srcOp->getParentOfType<mlir::LLVM::LLVMFuncOp>();
+  mlir::Operation *sourceSymbol =
+      mlir::SymbolTable::lookupSymbolIn(modOp, fnName);
+  if (!sourceSymbol) {
+    mlir::OpBuilder::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPoint(enclosingFnOp);
+    mlir::LLVM::LLVMFuncOp::create(rewriter, srcOp->getLoc(), fnName, fnTy);
+  }
+}
+
+mlir::LogicalResult CIRToLLVMDeleteArrayOpLowering::matchAndRewrite(
+    cir::DeleteArrayOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  StringRef fnName = "_ZdaPv";
+
+  auto voidTy = rewriter.getType<mlir::LLVM::LLVMVoidType>();
+  mlir::Type llvmPtrTy =
+      mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
+
+  mlir::Type fnTy = mlir::LLVM::LLVMFunctionType::get(voidTy, {llvmPtrTy},
+                                                      /*isVarArg=*/false);
+
+  getOrCreateLLVMFuncOp(rewriter, op, fnName, fnTy);
+
+  // Replace the operation with a call to _ZdaPv with the pointer argument
+  rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
+      op, mlir::TypeRange{}, fnName, mlir::ValueRange{adaptor.getAddress()});
+
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMPtrDiffOpLowering::matchAndRewrite(
     cir::PtrDiffOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/delete-array.cpp b/clang/test/CIR/CodeGen/delete-array.cpp
new file mode 100644
index 0000000000000..0ff08be61ef45
--- /dev/null
+++ b/clang/test/CIR/CodeGen/delete-array.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+void test_delete_array(int *ptr) {
+  delete[] ptr;
+}
+
+// CIR: cir.delete.array
+
+// LLVM: call void @_ZdaPv{{(m)?}}(ptr
+
+// OGCG: call void @_ZdaPv{{(m)?}}(ptr

@llvmbot
Copy link
Member

llvmbot commented Oct 27, 2025

@llvm/pr-subscribers-clang

Author: Shawn K (kimsh02)

Changes

Upstream DeleteArrayOp and clang/test/CIR/CodeGen/delete-array.cpp


Full diff: https://github.com/llvm/llvm-project/pull/165225.diff

4 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+18)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+2-2)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+37)
  • (added) clang/test/CIR/CodeGen/delete-array.cpp (+16)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2b361ed0982c6..b7bdfe7d9f5b6 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4089,6 +4089,24 @@ def CIR_PrefetchOp : CIR_Op<"prefetch"> {
     }];
 }
 
+//===----------------------------------------------------------------------===//
+// 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`.
+  }];
+
+  let arguments = (ins CIR_PointerType:$address);
+
+  let assemblyFormat = [{
+    $address `:` type($address) attr-dict
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // PtrDiffOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 7a35382e79a93..42d066e88225c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -626,8 +626,8 @@ void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
   ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy));
 
   if (e->isArrayForm()) {
-    assert(!cir::MissingFeatures::deleteArray());
-    cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
+    cir::DeleteArrayOp::create(builder, ptr.getPointer().getLoc(),
+                               ptr.getPointer());
     return;
   } else {
     emitObjectDelete(*this, e, ptr, deleteTy);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 5a6193fa8d840..aba182e2c9952 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1704,6 +1704,43 @@ mlir::LogicalResult CIRToLLVMPrefetchOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+// Make sure the LLVM function we are about to create a call for actually
+// exists, if not create one. Returns a function
+static void getOrCreateLLVMFuncOp(mlir::ConversionPatternRewriter &rewriter,
+                                  mlir::Operation *srcOp,
+                                  llvm::StringRef fnName, mlir::Type fnTy) {
+  auto modOp = srcOp->getParentOfType<mlir::ModuleOp>();
+  auto enclosingFnOp = srcOp->getParentOfType<mlir::LLVM::LLVMFuncOp>();
+  mlir::Operation *sourceSymbol =
+      mlir::SymbolTable::lookupSymbolIn(modOp, fnName);
+  if (!sourceSymbol) {
+    mlir::OpBuilder::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPoint(enclosingFnOp);
+    mlir::LLVM::LLVMFuncOp::create(rewriter, srcOp->getLoc(), fnName, fnTy);
+  }
+}
+
+mlir::LogicalResult CIRToLLVMDeleteArrayOpLowering::matchAndRewrite(
+    cir::DeleteArrayOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  StringRef fnName = "_ZdaPv";
+
+  auto voidTy = rewriter.getType<mlir::LLVM::LLVMVoidType>();
+  mlir::Type llvmPtrTy =
+      mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
+
+  mlir::Type fnTy = mlir::LLVM::LLVMFunctionType::get(voidTy, {llvmPtrTy},
+                                                      /*isVarArg=*/false);
+
+  getOrCreateLLVMFuncOp(rewriter, op, fnName, fnTy);
+
+  // Replace the operation with a call to _ZdaPv with the pointer argument
+  rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
+      op, mlir::TypeRange{}, fnName, mlir::ValueRange{adaptor.getAddress()});
+
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMPtrDiffOpLowering::matchAndRewrite(
     cir::PtrDiffOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/delete-array.cpp b/clang/test/CIR/CodeGen/delete-array.cpp
new file mode 100644
index 0000000000000..0ff08be61ef45
--- /dev/null
+++ b/clang/test/CIR/CodeGen/delete-array.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+void test_delete_array(int *ptr) {
+  delete[] ptr;
+}
+
+// CIR: cir.delete.array
+
+// LLVM: call void @_ZdaPv{{(m)?}}(ptr
+
+// OGCG: call void @_ZdaPv{{(m)?}}(ptr

@monthdev
Copy link
Contributor Author

monthdev commented Oct 27, 2025

I referenced https://godbolt.org/z/63K4779ej . Please let me know if the checks need fixing.

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this is an instance where the implementation in the incubator is incorrect. In particular, this doesn't handle the case of deleting an array of objects that require destruction.

https://godbolt.org/z/bTsqWzbPG

I think this needs to be fixed in the incubator (using the test case I linked above as a reference) before it is upstreamed. If you want to take that on, you'll need to use the behavior in classic codegen as a guide.

Because this implementation (when done correctly) involves reading the array cookie, which is target-specific, it would be nice to have a high-level CIR abstraction like cir.delete.array, but that will eventually need to incorporate array cookie handling when it is lowered, and it needs to get the name of the delete function from the AST, because _ZdaPv (which is hard-coded in the incubator implementation and this PR) isn't always the correct function to call.

I would suggest starting with a lower-level implementation (in the incubator) which follows classic codegen more closely and calls the delete function directly. This will be analogous to the way we are currently handling array new. Later, after this is working, we can introduce higher level cir.array.new and cir.array.delete operations to remove the ABI-specific details.

@wizardengineer is starting to look into this kind of upleveling of the CIR implementation, and I think it's important to have a correctly working implementation as the basis of the upleveling work. It seems cir.delete.array is an example of an operation where we were too hasty in pushing to the higher level representation and missed some important details in the process.

@monthdev monthdev marked this pull request as draft November 3, 2025 20:05
@monthdev
Copy link
Contributor Author

monthdev commented Nov 3, 2025

@andykaylor Okay, thanks for the description of the issue. I'll start taking a look at it. It may take me a while because I'm very inexperienced and don't know much of anything 😅

@bcardosolopes
Copy link
Member

I think this needs to be fixed in the incubator (using the test case I linked above as a reference) before it is upstreamed

+1

Copy link
Contributor

@xlauko xlauko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add also print/parse roudn-trip test for the operation to test/CIR/IR

// DeleteArrayOp
//===----------------------------------------------------------------------===//

def CIR_DeleteArrayOp : CIR_Op<"delete.array"> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def CIR_DeleteArrayOp : CIR_Op<"delete.array"> {
def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {

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;`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`cir.delete.array` operation deletes an array. For example, `delete[] ptr;`
`cir.delete_array` operation deletes an array. For example, `delete[] ptr;`

let arguments = (ins CIR_PointerType:$address);

let assemblyFormat = [{
$address `:` type($address) attr-dict
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$address `:` type($address) attr-dict
$address `:` qualified(type($address)) attr-dict

Comment on lines +12 to +16
// CIR: cir.delete.array

// LLVM: call void @_ZdaPv{{(m)?}}(ptr

// OGCG: call void @_ZdaPv{{(m)?}}(ptr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make more explicit checks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants