[clang] [CIR] Fix DeleteArrayOp lowering for unsized delete with destructor (PR #194711)

via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 28 12:50:17 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: adams381

<details>
<summary>Changes</summary>

`CIRDeleteArrayOpABILowering` asserts that `deleteParams.getSize()` must be true when `elementDtorAttr` is set.  This is wrong — a cookie is needed for the destructor loop regardless of whether `operator delete[]` takes a size parameter.  With `-fno-sized-deallocation` (or C++14 defaults on some targets), you get unsized `::operator delete[](void*)` with a non-trivial destructor, which hits the assertion.

Fix: set `cookieRequired` when either `getSize()` or `getElementDtorAttr()` is true, and only compute/pass the allocation size when `getSize()` is true.

Found while building the Eigen test suite with CIR.


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


2 Files Affected:

- (modified) clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp (+17-18) 
- (added) clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp (+118) 


``````````diff
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 2d59d63768f76..f4c8170333189 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -621,9 +621,7 @@ mlir::LogicalResult CIRDeleteArrayOpABILowering::matchAndRewrite(
   mlir::Value loweredAddress = adaptor.getAddress();
 
   cir::UsualDeleteParamsAttr deleteParams = op.getDeleteParams();
-  bool cookieRequired = deleteParams.getSize();
-  assert((deleteParams.getSize() || !op.getElementDtorAttr()) &&
-         "Expected size parameter when dtor fn is provided!");
+  bool cookieRequired = deleteParams.getSize() || op.getElementDtorAttr();
 
   if (deleteParams.getTypeAwareDelete() || deleteParams.getDestroyingDelete() ||
       deleteParams.getAlignment())
@@ -659,22 +657,23 @@ mlir::LogicalResult CIRDeleteArrayOpABILowering::matchAndRewrite(
           });
     }
 
-    // Compute the total allocation size and add it to the call arguments.
     callArgs.push_back(deletePtr);
-    uint64_t eltSizeBytes = dl.getTypeSizeInBits(ptrTy.getPointee()) / 8;
-    unsigned ptrWidth =
-        lowerModule->getTarget().getPointerWidth(clang::LangAS::Default);
-    cir::IntType sizeTy = cirBuilder.getUIntNTy(ptrWidth);
-
-    mlir::Value eltSizeVal = cir::ConstantOp::create(
-        rewriter, loc, cir::IntAttr::get(sizeTy, eltSizeBytes));
-    mlir::Value allocSize =
-        cir::MulOp::create(rewriter, loc, sizeTy, eltSizeVal, numElements);
-    mlir::Value cookieSizeVal = cir::ConstantOp::create(
-        rewriter, loc, cir::IntAttr::get(sizeTy, cookieSize.getQuantity()));
-    allocSize =
-        cir::AddOp::create(rewriter, loc, sizeTy, allocSize, cookieSizeVal);
-    callArgs.push_back(allocSize);
+    if (deleteParams.getSize()) {
+      uint64_t eltSizeBytes = dl.getTypeSizeInBits(ptrTy.getPointee()) / 8;
+      unsigned ptrWidth =
+          lowerModule->getTarget().getPointerWidth(clang::LangAS::Default);
+      cir::IntType sizeTy = cirBuilder.getUIntNTy(ptrWidth);
+
+      mlir::Value eltSizeVal = cir::ConstantOp::create(
+          rewriter, loc, cir::IntAttr::get(sizeTy, eltSizeBytes));
+      mlir::Value allocSize =
+          cir::MulOp::create(rewriter, loc, sizeTy, eltSizeVal, numElements);
+      mlir::Value cookieSizeVal = cir::ConstantOp::create(
+          rewriter, loc, cir::IntAttr::get(sizeTy, cookieSize.getQuantity()));
+      allocSize =
+          cir::AddOp::create(rewriter, loc, sizeTy, allocSize, cookieSizeVal);
+      callArgs.push_back(allocSize);
+    }
   } else {
     deletePtr = cir::CastOp::create(rewriter, loc, cirBuilder.getVoidPtrTy(),
                                     cir::CastKind::bitcast, loweredAddress);
diff --git a/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp b/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
new file mode 100644
index 0000000000000..12f0b060a997b
--- /dev/null
+++ b/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
@@ -0,0 +1,118 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++14 -fno-sized-deallocation -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++14 -fno-sized-deallocation -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++14 -fno-sized-deallocation -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll --check-prefix=OGCG %s
+
+// When operator delete[] is unsized but the element type has a non-trivial
+// destructor, a cookie is still needed to store the element count for the
+// destructor loop.
+
+struct Dtor {
+  ~Dtor();
+  int x;
+};
+
+void test(Dtor *ptr) {
+  delete[] ptr;
+}
+
+// CIR-BEFORE: cir.func {{.*}} @_Z4testP4Dtor
+// CIR-BEFORE:   cir.delete_array %{{.*}} : !cir.ptr<!rec_Dtor> {
+// CIR-BEFORE-SAME: delete_fn = @_ZdaPv,
+// CIR-BEFORE-SAME: delete_params = #cir.usual_delete_params<>,
+// CIR-BEFORE-SAME: element_dtor = @_ZN4DtorD1Ev}
+
+// CIR: cir.func {{.*}} @_Z4testP4Dtor
+// CIR:   %[[PTR:.*]] = cir.load
+// CIR:   %[[NOT_NULL:.*]] = cir.cmp ne
+// CIR:   cir.if %[[NOT_NULL]] {
+//
+// Read the array cookie.
+// CIR:     %[[BYTE_PTR:.*]] = cir.cast bitcast %[[PTR]] : !cir.ptr<!rec_Dtor> -> !cir.ptr<!u8i>
+// CIR:     %[[NEG_COOKIE:.*]] = cir.const #cir.int<-8> : !s64i
+// CIR:     %[[ALLOC_PTR:.*]] = cir.ptr_stride %[[BYTE_PTR]], %[[NEG_COOKIE]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
+// CIR:     %[[VOID_PTR:.*]] = cir.cast bitcast %[[ALLOC_PTR]] : !cir.ptr<!u8i> -> !cir.ptr<!void>
+// CIR:     %[[COOKIE_PTR:.*]] = cir.cast bitcast %[[ALLOC_PTR]] : !cir.ptr<!u8i> -> !cir.ptr<!u64i>
+// CIR:     %[[NUM_ELEM:.*]] = cir.load{{.*}} %[[COOKIE_PTR]] : !cir.ptr<!u64i>, !u64i
+//
+// Compute end pointer and check for empty array.
+// CIR:     %[[END:.*]] = cir.ptr_stride %[[PTR]], %[[NUM_ELEM]] : (!cir.ptr<!rec_Dtor>, !u64i) -> !cir.ptr<!rec_Dtor>
+// CIR:     %[[NOT_EMPTY:.*]] = cir.cmp ne %[[END]], %[[PTR]] : !cir.ptr<!rec_Dtor>
+// CIR:     cir.if %[[NOT_EMPTY]] {
+//
+// Destruct elements in reverse order.
+// CIR:       %[[ARR_IDX:.*]] = cir.alloca !cir.ptr<!rec_Dtor>, !cir.ptr<!cir.ptr<!rec_Dtor>>, ["__array_idx"]
+// CIR:       cir.store %[[END]], %[[ARR_IDX]]
+// CIR:       cir.do {
+// CIR:         %[[CUR:.*]] = cir.load %[[ARR_IDX]]
+// CIR:         %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:         %[[PREV:.*]] = cir.ptr_stride %[[CUR]], %[[NEG_ONE]] : (!cir.ptr<!rec_Dtor>, !s64i) -> !cir.ptr<!rec_Dtor>
+// CIR:         cir.store %[[PREV]], %[[ARR_IDX]]
+// CIR:         cir.call @_ZN4DtorD1Ev(%[[PREV]]) : (!cir.ptr<!rec_Dtor>) -> ()
+// CIR:         cir.yield
+// CIR:       } while {
+// CIR:         %[[CUR2:.*]] = cir.load %[[ARR_IDX]]
+// CIR:         %[[DONE:.*]] = cir.cmp ne %[[CUR2]], %[[PTR]] : !cir.ptr<!rec_Dtor>
+// CIR:         cir.condition(%[[DONE]])
+// CIR:       }
+// CIR:     }
+//
+// Call unsized operator delete[] with just the pointer.
+// CIR:     cir.call @_ZdaPv(%[[VOID_PTR]]) : (!cir.ptr<!void>) -> ()
+// CIR:   }
+
+// LLVM: define {{.*}} void @_Z4testP4Dtor
+// LLVM:   %[[PTR:.*]] = load ptr, ptr %{{.*}}
+// LLVM:   %[[NOT_NULL:.*]] = icmp ne ptr %[[PTR]], null
+// LLVM:   br i1 %[[NOT_NULL]], label %[[DELETE_NOTNULL:.*]], label %[[DONE:.*]]
+// LLVM: [[DELETE_NOTNULL]]:
+// LLVM:   %[[ALLOC_PTR:.*]] = getelementptr i8, ptr %[[PTR]], i64 -8
+// LLVM:   %[[NUM_ELEM:.*]] = load i64, ptr %[[ALLOC_PTR]], align 4
+// LLVM:   %[[ARR_END:.*]] = getelementptr %struct.Dtor, ptr %[[PTR]], i64 %[[NUM_ELEM]]
+// LLVM:   %[[NOT_EMPTY:.*]] = icmp ne ptr %[[ARR_END]], %[[PTR]]
+// LLVM:   br i1 %[[NOT_EMPTY]], label %[[DESTROY:.*]], label %[[CALL_DELETE:.*]]
+// LLVM: [[DESTROY]]:
+// LLVM:   store ptr %[[ARR_END]], ptr %[[ARR_IDX:.*]]
+// LLVM:   br label %[[DTOR_LOOP:.*]]
+// LLVM: [[LOOP_COND:.*]]:
+// LLVM:   %[[CUR:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[CMP:.*]] = icmp ne ptr %[[CUR]], %[[PTR]]
+// LLVM:   br i1 %[[CMP]], label %[[DTOR_LOOP]], label %[[LOOP_END:.*]]
+// LLVM: [[DTOR_LOOP]]:
+// LLVM:   %[[ELEM:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[PREV:.*]] = getelementptr %struct.Dtor, ptr %[[ELEM]], i64 -1
+// LLVM:   store ptr %[[PREV]], ptr %[[ARR_IDX]]
+// LLVM:   call void @_ZN4DtorD1Ev(ptr %[[PREV]])
+// LLVM:   br label %[[LOOP_COND]]
+// LLVM: [[LOOP_END]]:
+// LLVM:   br label %[[CALL_DELETE]]
+// LLVM: [[CALL_DELETE]]:
+// LLVM:   call void @_ZdaPv(ptr %[[ALLOC_PTR]])
+// LLVM:   br label %[[DONE]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
+// OGCG: define {{.*}} void @_Z4testP4Dtor
+// 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:   %[[ALLOC_PTR:.*]] = getelementptr inbounds i8, ptr %[[PTR]], i64 -8
+// OGCG:   %[[NUM_ELEM:.*]] = load i64, ptr %[[ALLOC_PTR]], align 4
+// OGCG:   %[[ARR_END:.*]] = getelementptr inbounds %struct.Dtor, ptr %[[PTR]], i64 %[[NUM_ELEM]]
+// OGCG:   %[[ARR_EMPTY:.*]] = icmp eq ptr %[[PTR]], %[[ARR_END]]
+// OGCG:   br i1 %[[ARR_EMPTY]], label %[[DESTROY_DONE:.*]], label %[[DESTROY_BODY:.*]]
+// OGCG: [[DESTROY_BODY]]:
+// OGCG:   %[[DESTROY_PAST:.*]] = phi ptr [ %[[ARR_END]], %[[DELETE_NOT_NULL]] ], [ %[[DESTROY_ELEM:.*]], %[[DESTROY_BODY]] ]
+// OGCG:   %[[DESTROY_ELEM]] = getelementptr inbounds %struct.Dtor, ptr %[[DESTROY_PAST]], i64 -1
+// OGCG:   call void @_ZN4DtorD1Ev(ptr {{.*}} %[[DESTROY_ELEM]])
+// OGCG:   %[[DESTROY_CMP:.*]] = icmp eq ptr %[[DESTROY_ELEM]], %[[PTR]]
+// OGCG:   br i1 %[[DESTROY_CMP]], label %[[DESTROY_DONE]], label %[[DESTROY_BODY]]
+// OGCG: [[DESTROY_DONE]]:
+// OGCG:   call void @_ZdaPv(ptr {{.*}} %[[ALLOC_PTR]])
+// OGCG:   br label %[[DELETE_END]]
+// OGCG: [[DELETE_END]]:
+// OGCG:   ret void

``````````

</details>


https://github.com/llvm/llvm-project/pull/194711


More information about the cfe-commits mailing list