https://github.com/andykaylor updated 
https://github.com/llvm/llvm-project/pull/194965

>From bc812bc2545895060bd7ffb2f0e16a3b1c90f581 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Tue, 28 Apr 2026 18:14:33 -0700
Subject: [PATCH 1/3] [CIR] Implement cleanup for array delete with throwing
 dtor

This implements cleanup handling to perform partial array destruction
in the case where an exception is thrown while we are calling destructors
for an array delete expression. When an exception occurs, we attempt to
continuing destructing the rest of the array. If a second exception is
thrown, we terminate the process, but if only one exception is thrown
we will complete the array element destruction and then call operator delete.
This matches the behavior of classic codegen.

As part of this implementation, I had to move the array delete operator
call to a cleanup handler in order to ensure that it was called both
in the normal case (when no exception is thrown) and in the case where
an exception is thrown but the partial cleanup completes successfully.
This required updating one existing test.

In the case where exception are not enabled, we still call the array
delete operator from a cleanup handler, but the cleanup executes
unconditionally in the non-exception control flow.

Assisted-by: Cursor / claude-opus-4.7-thinking-xhigh
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  34 ++-
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp       |   6 +-
 .../CIR/Dialect/Transforms/CXXABILowering.cpp | 111 ++++---
 .../Dialect/Transforms/LoweringPrepare.cpp    |  11 +
 .../CodeGen/delete-array-throwing-dtor.cpp    | 279 ++++++++++++++++++
 .../CIR/CodeGen/delete-array-unsized-dtor.cpp |   4 +-
 clang/test/CIR/CodeGen/delete-array.cpp       |  81 +++--
 7 files changed, 444 insertions(+), 82 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index b30dd980f5569..6328e79ff6fd0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3924,15 +3924,20 @@ def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
     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.
+
+    The `dtor_may_throw` unit attribute, when present, indicates that the
+    element destructor is may throw exceptions.
   }];
 
   let arguments = (ins
     CIR_PointerType:$address,
     FlatSymbolRefAttr:$delete_fn,
     CIR_UsualDeleteParamsAttr:$delete_params,
-    OptionalAttr<FlatSymbolRefAttr>:$element_dtor
+    OptionalAttr<FlatSymbolRefAttr>:$element_dtor,
+    UnitAttr:$dtor_may_throw
   );
 
   let builders = [
@@ -3940,7 +3945,17 @@ def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
                    "mlir::FlatSymbolRefAttr":$delete_fn,
                    "cir::UsualDeleteParamsAttr":$delete_params), [{
       build($_builder, $_state, address, delete_fn, delete_params,
-            /*element_dtor=*/mlir::FlatSymbolRefAttr{});
+            /*element_dtor=*/mlir::FlatSymbolRefAttr{},
+            /*dtor_may_throw=*/mlir::UnitAttr{});
+    }]>,
+    OpBuilder<(ins "mlir::Value":$address,
+                   "mlir::FlatSymbolRefAttr":$delete_fn,
+                   "cir::UsualDeleteParamsAttr":$delete_params,
+                   "mlir::FlatSymbolRefAttr":$element_dtor,
+                   "bool":$dtor_may_throw), [{
+      build($_builder, $_state, address, delete_fn, delete_params,
+            element_dtor,
+            dtor_may_throw ? $_builder.getUnitAttr() : mlir::UnitAttr{});
     }]>
   ];
 
@@ -4841,6 +4856,9 @@ def CIR_ArrayDtor : CIR_Op<"array.dtor", [
     and `num_elements` provides the runtime element count (e.g. from an array
     cookie for `delete[]`).
 
+    When `dtor_may_throw` is present, the element destructor call may throw
+    an exception.
+
     Elements are destroyed in reverse order.
 
     Examples:
@@ -4857,19 +4875,27 @@ def CIR_ArrayDtor : CIR_Op<"array.dtor", [
       ^bb0(%arg0: !cir.ptr<!rec_S>):
         cir.call @_ZN1SD1Ev(%arg0) : (!cir.ptr<!rec_S>) -> ()
     }
+
+    // Dynamic count (delete[] with throwing destructor):
+    cir.array.dtor %ptr, %n : !cir.ptr<!rec_S>, !u64i dtor_may_throw {
+      ^bb0(%arg0: !cir.ptr<!rec_S>):
+        cir.call @_ZN1SD1Ev(%arg0) : (!cir.ptr<!rec_S>) -> ()
+    }
     ```
   }];
 
   let arguments = (ins
     Arg<CIR_AnyPtrType, "array or element address", [MemWrite, MemRead]>:$addr,
-    Optional<CIR_AnyIntType>:$num_elements
+    Optional<CIR_AnyIntType>:$num_elements,
+    UnitProp:$dtor_may_throw
   );
 
   let regions = (region SizedRegion<1>:$body);
 
   let assemblyFormat = [{
     $addr (`,` $num_elements^)? `:` qualified(type($addr))
-    (`,` type($num_elements)^)? $body attr-dict
+    (`,` type($num_elements)^)? (`dtor_may_throw` $dtor_may_throw^)? $body
+    attr-dict
   }];
 
   let builders = [
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 39a2068a7073f..0f8f2ffe23468 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1485,12 +1485,12 @@ void CIRGenFunction::emitCXXDeleteExpr(const 
CXXDeleteExpr *e) {
         isTypeAwareAllocation(udp.TypeAwareDelete), udp.DestroyingDelete);
 
     mlir::FlatSymbolRefAttr elementDtor;
+    bool hasThrowingDtor = false;
     if (const auto *rd = deleteTy->getAsCXXRecordDecl()) {
       if (rd->hasDefinition() && !rd->hasTrivialDestructor()) {
         const CXXDestructorDecl *dtor = rd->getDestructor();
         if (dtor->getType()->castAs<FunctionProtoType>()->canThrow())
-          cgm.errorNYI(e->getSourceRange(),
-                       "emitCXXDeleteExpr: throwing destructor");
+          hasThrowingDtor = true;
         cir::FuncOp dtorFn =
             cgm.getAddrOfCXXStructor(GlobalDecl(dtor, Dtor_Complete));
         elementDtor = mlir::FlatSymbolRefAttr::get(builder.getContext(),
@@ -1500,7 +1500,7 @@ void CIRGenFunction::emitCXXDeleteExpr(const 
CXXDeleteExpr *e) {
 
     cir::DeleteArrayOp::create(builder, ptr.getPointer().getLoc(),
                                ptr.getPointer(), deleteFn, deleteParams,
-                               elementDtor);
+                               elementDtor, hasThrowingDtor);
   } 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 f4c8170333189..35085fa3ca5c0 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -630,57 +630,86 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
 
   const CIRCXXABI &cxxABI = lowerModule->getCXXABI();
   CIRBaseBuilderTy cirBuilder(rewriter);
+
+  // Read the array cookie (or compute the void* pointer for the
+  // non-cookie case) before creating the cleanup scope. The cookie read
+  // produces values that are needed by both the destruction loop in the
+  // body region (numElements for the array.dtor) and the operator
+  // delete[] call in the cleanup region (deletePtr / numElements for the
+  // total-size computation), so it must dominate both regions.
   mlir::Value deletePtr;
-  llvm::SmallVector<mlir::Value> callArgs;
+  mlir::Value numElements;
+  cir::PointerType ptrTy;
+  clang::CharUnits cookieSize;
+  mlir::DataLayout dl(op->getParentOfType<mlir::ModuleOp>());
+  unsigned ptrWidth =
+      lowerModule->getTarget().getPointerWidth(clang::LangAS::Default);
+  cir::IntType sizeTy = cirBuilder.getUIntNTy(ptrWidth);
 
   if (cookieRequired) {
-    mlir::Value numElements;
-    clang::CharUnits cookieSize;
-    auto ptrTy = mlir::cast<cir::PointerType>(loweredAddress.getType());
-    mlir::DataLayout dl(op->getParentOfType<mlir::ModuleOp>());
-
+    ptrTy = mlir::cast<cir::PointerType>(loweredAddress.getType());
     cxxABI.readArrayCookie(loc, loweredAddress, dl, cirBuilder, numElements,
                            deletePtr, cookieSize);
-
-    // If a dtor function is provided, create an array dtor operation.
-    // This will get expanded during LoweringPrepare.
-    mlir::FlatSymbolRefAttr dtorFn = op.getElementDtorAttr();
-    if (dtorFn) {
-      auto eltPtrTy = cir::PointerType::get(ptrTy.getPointee());
-      cir::ArrayDtor::create(
-          rewriter, loc, loweredAddress, numElements,
-          [&](mlir::OpBuilder &b, mlir::Location l) {
-            auto arg = b.getInsertionBlock()->addArgument(eltPtrTy, l);
-            cir::CallOp::create(b, l, dtorFn, cir::VoidType(),
-                                mlir::ValueRange{arg});
-            cir::YieldOp::create(b, l);
-          });
-    }
-
-    callArgs.push_back(deletePtr);
-    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);
-    callArgs.push_back(deletePtr);
   }
 
-  cir::CallOp::create(rewriter, loc, deleteFn, cir::VoidType(), callArgs);
+  // Create a cleanup scope to wrap the ArrayDtor operation (if needed) and
+  // call the array delete operator from the cleanup region. If no exceptions
+  // are thrown during the array dtor, the normal control flow will call the
+  // delete operator. The ArrayDtor operation will get its own cleanup region
+  // when it is expanded during LoweringPrepare. If an exception is thrown, the
+  // exception handling flow will be connected to the cleanup region here to
+  // call the delete operator on the exception path.
+  mlir::FlatSymbolRefAttr dtorFn = op.getElementDtorAttr();
+  cir::CleanupKind cleanupKind =
+      op.getDtorMayThrow() ? cir::CleanupKind::All : cir::CleanupKind::Normal;
+  cir::CleanupScopeOp::create(
+      rewriter, loc, cleanupKind,
+      /*bodyBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location l) {
+        if (dtorFn) {
+          auto eltPtrTy = cir::PointerType::get(ptrTy.getPointee());
+          auto arrayDtor = cir::ArrayDtor::create(
+              b, l, loweredAddress, numElements,
+              [&](mlir::OpBuilder &bb, mlir::Location ll) {
+                auto arg = bb.getInsertionBlock()->addArgument(eltPtrTy, ll);
+                auto dtorCall = cir::CallOp::create(
+                    bb, ll, dtorFn, cir::VoidType(), mlir::ValueRange{arg});
+                if (!op.getDtorMayThrow())
+                  dtorCall.setNothrowAttr(bb.getUnitAttr());
+                cir::YieldOp::create(bb, ll);
+              });
+          if (op.getDtorMayThrow())
+            arrayDtor.setDtorMayThrow(true);
+        }
+        cir::YieldOp::create(b, l);
+      },
+      /*cleanupBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location l) {
+        llvm::SmallVector<mlir::Value> callArgs;
+        callArgs.push_back(deletePtr);
+        if (deleteParams.getSize()) {
+          uint64_t eltSizeBytes = dl.getTypeSizeInBits(ptrTy.getPointee()) / 8;
+          mlir::Value eltSizeVal = cir::ConstantOp::create(
+              b, l, cir::IntAttr::get(sizeTy, eltSizeBytes));
+          mlir::Value allocSize =
+              cir::MulOp::create(b, l, sizeTy, eltSizeVal, numElements);
+          mlir::Value cookieSizeVal = cir::ConstantOp::create(
+              b, l, cir::IntAttr::get(sizeTy, cookieSize.getQuantity()));
+          allocSize =
+              cir::AddOp::create(b, l, sizeTy, allocSize, cookieSizeVal);
+          callArgs.push_back(allocSize);
+        }
+        auto deleteCall =
+            cir::CallOp::create(b, l, deleteFn, cir::VoidType(), callArgs);
+        // operator delete[] is implicitly nothrow per [basic.stc.dynamic],
+        // matching classic CodeGen's `nounwind` attribute on the call.
+        deleteCall.setNothrowAttr(b.getUnitAttr());
+        cir::YieldOp::create(b, l);
+      });
+
   rewriter.eraseOp(op);
   return mlir::success();
 }
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index bd3c8bc0aa8d1..7ea56c0648905 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -1553,6 +1553,17 @@ static void 
lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
     mlir::Region &partialDtor = arrayCtor.getPartialDtor();
     if (!partialDtor.empty())
       partialDtorBlock = &partialDtor.front();
+  } else if (auto arrayDtor = mlir::dyn_cast<cir::ArrayDtor>(op)) {
+    // When the element destructor may throw, reuse the body block as the
+    // partial-dtor block so that an exception thrown by an element's dtor
+    // continues the reverse-destruction loop in the EH cleanup region. The
+    // body block already stores the next element pointer to `tmpAddr`
+    // before invoking the dtor, so when an exception unwinds from the
+    // dtor call `tmpAddr` already points at the element that threw, and
+    // the cleanup loop picks up from `tmpAddr - 1` and walks back to
+    // `begin`.
+    if (arrayDtor.getDtorMayThrow())
+      partialDtorBlock = bodyBlock;
   }
 
   auto emitCtorDtorLoop = [&]() {
diff --git a/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp 
b/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
new file mode 100644
index 0000000000000..dc2647d62b01e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
@@ -0,0 +1,279 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 
-fcxx-exceptions -fexceptions -fclangir -emit-cir -mmlir 
-mlir-print-ir-after=cir-cxxabi-lowering -mmlir 
-mlir-print-ir-before=cir-cxxabi-lowering -mmlir 
-mlir-print-ir-after=cir-cxxabi-lowering %s -o %t.cir 2> %t-cxxabi.cir
+// RUN: FileCheck --input-file=%t-cxxabi.cir --check-prefix=CIR-BEFORE-CXXABI 
%s
+// RUN: FileCheck --input-file=%t-cxxabi.cir --check-prefix=CIR-AFTER-CXXABI %s
+// RUN: FileCheck --input-file=%t.cir --check-prefix=CIR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 
-fcxx-exceptions -fexceptions -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 
-fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll --check-prefix=OGCG %s
+
+struct ThrowingDtor {
+  ~ThrowingDtor() noexcept(false);
+  int x;
+};
+
+void test_delete_array_throwing_dtor(ThrowingDtor *ptr) {
+  delete[] ptr;
+}
+
+// CIR-BEFORE-CXXABI: IR Dump Before CXXABILowering (cir-cxxabi-lowering)
+
+// CIR-BEFORE-CXXABI: cir.func {{.*}} 
@_Z31test_delete_array_throwing_dtorP12ThrowingDtor
+// CIR-BEFORE-CXXABI:   %[[PTR:.*]] = cir.load
+// CIR-BEFORE-CXXABI:   %[[NULL:.*]] = cir.const #cir.ptr<null>
+// CIR-BEFORE-CXXABI:   %[[NOT_NULL:.*]] = cir.cmp ne %[[PTR]], %[[NULL]]
+// CIR-BEFORE-CXXABI:   cir.if %[[NOT_NULL]] {
+// CIR-BEFORE-CXXABI:     cir.delete_array %[[PTR]] : 
!cir.ptr<!rec_ThrowingDtor> {delete_fn = @_ZdaPvm, delete_params = 
#cir.usual_delete_params<size = true>, dtor_may_throw, element_dtor = 
@_ZN12ThrowingDtorD1Ev}
+// CIR-BEFORE-CXXABI:   }
+
+// CIR-AFTER-CXXABI: IR Dump After CXXABILowering (cir-cxxabi-lowering)
+
+// CIR-AFTER-CXXABI: cir.func {{.*}} 
@_Z31test_delete_array_throwing_dtorP12ThrowingDtor
+// CIR-AFTER-CXXABI:   %[[PTR:.*]] = cir.load
+// CIR-AFTER-CXXABI:   %[[NULL:.*]] = cir.const #cir.ptr<null>
+// CIR-AFTER-CXXABI:   %[[NOT_NULL:.*]] = cir.cmp ne %[[PTR]], %[[NULL]]
+// CIR-AFTER-CXXABI:   cir.if %[[NOT_NULL]] {
+// CIR-AFTER-CXXABI:     cir.cleanup.scope {
+// CIR-AFTER-CXXABI:       cir.array.dtor %{{.*}}, %{{.*}} : 
!cir.ptr<!rec_ThrowingDtor>, !u64i dtor_may_throw {
+// CIR-AFTER-CXXABI:         cir.call @_ZN12ThrowingDtorD1Ev({{.*}})
+// CIR-AFTER-CXXABI-NOT:     nothrow
+// CIR-AFTER-CXXABI:       }
+// CIR-AFTER-CXXABI:     } cleanup all {
+// CIR-AFTER-CXXABI:       cir.call @_ZdaPvm({{.*}}) nothrow
+// CIR-AFTER-CXXABI:     }
+// CIR-AFTER-CXXABI:   }
+
+// CIR: cir.func {{.*}} @_Z31test_delete_array_throwing_dtorP12ThrowingDtor
+// CIR:   %[[PTR:.*]] = cir.load
+// CIR:   cir.if
+//
+// CIR:     %[[BYTE_PTR:.*]] = cir.cast bitcast %[[PTR]] : 
!cir.ptr<!rec_ThrowingDtor> -> !cir.ptr<!u8i>
+// CIR:     %[[NEG_COOKIE:.*]] = cir.const #cir.int<-8> : !s64i
+// CIR:     %[[ALLOC_BYTE_PTR:.*]] = cir.ptr_stride %[[BYTE_PTR]], 
%[[NEG_COOKIE]]
+// CIR:     %[[VOID_PTR:.*]] = cir.cast bitcast %[[ALLOC_BYTE_PTR]] : 
!cir.ptr<!u8i> -> !cir.ptr<!void>
+// CIR:     %[[COOKIE_PTR:.*]] = cir.cast bitcast %[[ALLOC_BYTE_PTR]] : 
!cir.ptr<!u8i> -> !cir.ptr<!u64i>
+// CIR:     %[[NUM_ELEM:.*]] = cir.load{{.*}} %[[COOKIE_PTR]]
+//
+// CIR:     cir.cleanup.scope {
+// CIR:       %[[END:.*]] = cir.ptr_stride %[[PTR]], %[[NUM_ELEM]]
+// CIR:       %[[NOT_EMPTY:.*]] = cir.cmp ne %[[END]], %[[PTR]]
+// CIR:       cir.if %[[NOT_EMPTY]] {
+// CIR:         %[[ARR_IDX:.*]] = cir.alloca {{.*}} ["__array_idx"]
+// CIR:         cir.store %[[END]], %[[ARR_IDX]]
+//
+// CIR:         cir.cleanup.scope {
+// CIR:           cir.do {
+// CIR:             %[[CUR:.*]] = cir.load %[[ARR_IDX]]
+// CIR:             %[[STRIDE_M1:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:             %[[PREV:.*]] = cir.ptr_stride %[[CUR]], %[[STRIDE_M1]]
+// CIR:             cir.store %[[PREV]], %[[ARR_IDX]]
+// CIR:             cir.call @_ZN12ThrowingDtorD1Ev(%[[PREV]])
+// CIR-NOT:           nothrow
+// CIR:             cir.yield
+// CIR:           } while {
+// CIR:             %[[CUR2:.*]] = cir.load %[[ARR_IDX]]
+// CIR:             %[[CMP:.*]] = cir.cmp ne %[[CUR2]], %[[PTR]]
+// CIR:             cir.condition(%[[CMP]])
+// CIR:           }
+// CIR:           cir.yield
+// CIR:         } cleanup eh {
+// CIR:           %[[CL_CUR:.*]] = cir.load %[[ARR_IDX]]
+// CIR:           %[[CL_NEMPTY:.*]] = cir.cmp ne %[[CL_CUR]], %[[PTR]]
+// CIR:           cir.if %[[CL_NEMPTY]] {
+// CIR:             cir.do {
+// CIR:               %[[CL_E:.*]] = cir.load %[[ARR_IDX]]
+// CIR:               %[[CL_M1:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:               %[[CL_PREV:.*]] = cir.ptr_stride %[[CL_E]], %[[CL_M1]]
+// CIR:               cir.store %[[CL_PREV]], %[[ARR_IDX]]
+// CIR:               cir.call @_ZN12ThrowingDtorD1Ev(%[[CL_PREV]])
+// CIR-NOT:             nothrow
+// CIR:             } while {
+// CIR:             }
+// CIR:           }
+// CIR:           cir.yield
+// CIR:         }
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } cleanup all {
+// CIR:       %[[ELEM_SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR:       %[[ARRAY_SIZE:.*]] = cir.mul %[[ELEM_SIZE]], %[[NUM_ELEM]]
+// CIR:       %[[COOKIE_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:       %[[TOTAL_SIZE:.*]] = cir.add %[[ARRAY_SIZE]], %[[COOKIE_SIZE]]
+// CIR:       cir.call @_ZdaPvm(%[[VOID_PTR]], %[[TOTAL_SIZE]]) nothrow
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+
+// LLVM: define {{.*}} void 
@_Z31test_delete_array_throwing_dtorP12ThrowingDtor(ptr {{.*}})
+//
+// `__array_idx` alloca and the function-entry null check.
+// LLVM:   %[[ARR_IDX:[0-9]+]] = alloca ptr
+// LLVM:   %[[PTR:.*]] = load ptr
+// LLVM:   %[[NOT_NULL:.*]] = icmp ne ptr %[[PTR]], null
+// LLVM:   br i1 %[[NOT_NULL]], label %[[NOTNULL:[^,]+]], label %{{.*}}
+//
+// Cookie read and is-empty check.
+// LLVM: [[NOTNULL]]:
+// LLVM:   %[[ALLOC_PTR:.*]] = getelementptr i8, ptr %[[PTR]], i64 -8
+// LLVM:   %[[NUM_ELEM:.*]] = load i64, ptr %[[ALLOC_PTR]]
+// LLVM:   %[[ARR_END:.*]] = getelementptr %struct.ThrowingDtor, ptr %[[PTR]], 
i64 %[[NUM_ELEM]]
+// LLVM:   %[[NOT_EMPTY:.*]] = icmp ne ptr %[[ARR_END]], %[[PTR]]
+// LLVM:   br i1 %[[NOT_EMPTY]], label %[[DESTROY:[^,]+]], label 
%[[CALL_DELETE_NORMAL:[^ ]+]]
+//
+// Body loop entry: seed __array_idx with arrayEnd and fall through into the
+// body. (FlattenCFG emits an empty trampoline block between the entry
+// store and the body, which we skip with `{{.*}}`.)
+// LLVM: [[DESTROY]]:
+// LLVM:   store ptr %[[ARR_END]], ptr %[[ARR_IDX]]
+// LLVM:   br label %{{.*}}
+//
+// Body do-while condition block: load __array_idx, compare to begin, branch
+// back to the body or out to the body-loop exit.
+// LLVM:   %[[BODY_CUR:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[BODY_CMP:.*]] = icmp ne ptr %[[BODY_CUR]], %[[PTR]]
+// LLVM:   br i1 %[[BODY_CMP]], label %[[BODY:[^,]+]], label %[[BODY_EXIT:[^ 
]+]]
+//
+// Body do-while body: load current, compute prev = current - 1, store prev
+// back to __array_idx, invoke dtor(prev). On unwind, go to LPAD.
+// LLVM: [[BODY]]:
+// LLVM:   %[[BODY_LOAD:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[BODY_PREV:.*]] = getelementptr %struct.ThrowingDtor, ptr 
%[[BODY_LOAD]], i64 -1
+// LLVM:   store ptr %[[BODY_PREV]], ptr %[[ARR_IDX]]
+// LLVM:   invoke void @_ZN12ThrowingDtorD1Ev(ptr %[[BODY_PREV]])
+// LLVM:           to label %{{.*}} unwind label %[[LPAD:[^ ]+]]
+//
+// Cleanup landing pad: cleanup landingpad, save exn/sel, then check whether
+// any elements remain to destroy. (FlattenCFG emits a trampoline block
+// from the landingpad to the phi-of-exn/sel and the empty-check branch.)
+// LLVM: [[LPAD]]:
+// LLVM:   %[[LPAD_VAL:.*]] = landingpad { ptr, i32 }
+// LLVM:           cleanup
+// LLVM:   %[[LPAD_EXN:.*]] = extractvalue { ptr, i32 } %[[LPAD_VAL]], 0
+// LLVM:   %[[LPAD_SEL:.*]] = extractvalue { ptr, i32 } %[[LPAD_VAL]], 1
+// LLVM:   br label %{{.*}}
+//
+// LPAD continuation: phi the exn/sel forward, load __array_idx, check for
+// empty, and branch into the cleanup loop or to the EH-side delete.
+// LLVM:   phi ptr [ %[[LPAD_EXN]], %{{.*}} ]
+// LLVM:   phi i32 [ %[[LPAD_SEL]], %{{.*}} ]
+// LLVM:   %[[CL_INIT_CUR:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[CL_NEMPTY:.*]] = icmp ne ptr %[[CL_INIT_CUR]], %[[PTR]]
+// LLVM:   br i1 %[[CL_NEMPTY]], label %{{.*}}, label %{{.*}}
+//
+// Cleanup do-while condition block: load __array_idx (which the body
+// already pointed at the element that threw, so the cleanup picks up at
+// prev = element-that-threw - 1), compare to begin, branch into the
+// cleanup body or out to the EH-side delete.
+// LLVM:   %[[CL_CUR:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[CL_CMP:.*]] = icmp ne ptr %[[CL_CUR]], %[[PTR]]
+// LLVM:   br i1 %[[CL_CMP]], label %[[CL_BODY:[^,]+]], label %{{.*}}
+//
+// Cleanup do-while body: load current, decrement, store back, invoke
+// dtor(prev). On a *second* throw, unwind to terminate.lpad.
+// LLVM: [[CL_BODY]]:
+// LLVM:   %[[CL_LOAD:.*]] = load ptr, ptr %[[ARR_IDX]]
+// LLVM:   %[[CL_PREV:.*]] = getelementptr %struct.ThrowingDtor, ptr 
%[[CL_LOAD]], i64 -1
+// LLVM:   store ptr %[[CL_PREV]], ptr %[[ARR_IDX]]
+// LLVM:   invoke void @_ZN12ThrowingDtorD1Ev(ptr %[[CL_PREV]])
+// LLVM:           to label %{{.*}} unwind label %[[TERMINATE_LPAD:[^ ]+]]
+//
+// Terminate landing pad: catch-all + `__clang_call_terminate`.
+// LLVM: [[TERMINATE_LPAD]]:
+// LLVM:   landingpad { ptr, i32 }
+// LLVM:           catch ptr null
+// LLVM:   call void @__clang_call_terminate(ptr %{{.*}})
+// LLVM:   unreachable
+//
+// Normal path: compute total size, call `_ZdaPvm`. Mirrors OGCG's
+// `arraydestroy.done8` block.
+// LLVM: [[CALL_DELETE_NORMAL]]:
+// LLVM:   %[[NORMAL_ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// LLVM:   %[[NORMAL_TOTAL_SIZE:.*]] = add i64 %[[NORMAL_ARRAY_SIZE]], 8
+// LLVM:   call void @_ZdaPvm(ptr %[[ALLOC_PTR]], i64 %[[NORMAL_TOTAL_SIZE]])
+//
+// EH-side delete + resume: the exn/sel are PHI'd one more time as
+// FlattenCFG joins the cleanup-loop exit blocks; then total size is
+// computed, `_ZdaPvm` is called, and the original exception is resumed.
+// Mirrors OGCG's `arraydestroy.done6` -> `eh.resume` chain.
+// LLVM:   %[[RESUME_EXN:.*]] = phi ptr [ %{{.*}}, %{{.*}} ]
+// LLVM:   %[[RESUME_SEL:.*]] = phi i32 [ %{{.*}}, %{{.*}} ]
+// LLVM:   %[[EH_ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// LLVM:   %[[EH_TOTAL_SIZE:.*]] = add i64 %[[EH_ARRAY_SIZE]], 8
+// LLVM:   call void @_ZdaPvm(ptr %[[ALLOC_PTR]], i64 %[[EH_TOTAL_SIZE]])
+// LLVM:   %[[RESUME_VAL:.*]] = insertvalue { ptr, i32 } poison, ptr 
%[[RESUME_EXN]], 0
+// LLVM:   %[[RESUME_VAL2:.*]] = insertvalue { ptr, i32 } %[[RESUME_VAL]], i32 
%[[RESUME_SEL]], 1
+// LLVM:   resume { ptr, i32 } %[[RESUME_VAL2]]
+
+// OGCG: define {{.*}} void 
@_Z31test_delete_array_throwing_dtorP12ThrowingDtor(ptr {{.*}})
+//
+// Function entry and null check.
+// OGCG:   %[[PTR:.*]] = load ptr, ptr %{{.*}}
+// OGCG:   %[[ISNULL:.*]] = icmp eq ptr %[[PTR]], null
+// OGCG:   br i1 %[[ISNULL]], label %[[DELETE_END:[^,]+]], label 
%[[DELETE_NOTNULL:[^ ]+]]
+//
+// Cookie read + is-empty check.
+// OGCG: [[DELETE_NOTNULL]]:
+// OGCG:   %[[ALLOC_PTR:.*]] = getelementptr inbounds i8, ptr %[[PTR]], i64 -8
+// OGCG:   %[[NUM_ELEM:.*]] = load i64, ptr %[[ALLOC_PTR]]
+// OGCG:   %[[ARR_END:.*]] = getelementptr inbounds %struct.ThrowingDtor, ptr 
%[[PTR]], i64 %[[NUM_ELEM]]
+// OGCG:   %[[ISEMPTY:.*]] = icmp eq ptr %[[PTR]], %[[ARR_END]]
+// OGCG:   br i1 %[[ISEMPTY]], label %[[DONE8:[^,]+]], label %[[BODY:[^ ]+]]
+//
+// Body loop: phi-based reverse iteration.
+// OGCG: [[BODY]]:
+// OGCG:   %[[ELT_PAST:.*]] = phi ptr [ %[[ARR_END]], %[[DELETE_NOTNULL]] ], [ 
%[[ELT:.*]], %[[INV_CONT:[^ ]+]] ]
+// OGCG:   %[[ELT]] = getelementptr inbounds %struct.ThrowingDtor, ptr 
%[[ELT_PAST]], i64 -1
+// OGCG:   invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}}%[[ELT]])
+// OGCG:           to label %[[INV_CONT]] unwind label %[[LPAD:[^ ]+]]
+//
+// OGCG: [[INV_CONT]]:
+// OGCG:   %[[BODY_DONE:.*]] = icmp eq ptr %[[ELT]], %[[PTR]]
+// OGCG:   br i1 %[[BODY_DONE]], label %[[DONE8]], label %[[BODY]]
+//
+// Normal path: compute size, call `_ZdaPvm`, fall through.
+// OGCG: [[DONE8]]:
+// OGCG:   %[[NORMAL_ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// OGCG:   %[[NORMAL_TOTAL_SIZE:.*]] = add i64 %[[NORMAL_ARRAY_SIZE]], 8
+// OGCG:   call void @_ZdaPvm(ptr {{.*}}%[[ALLOC_PTR]], i64 
{{.*}}%[[NORMAL_TOTAL_SIZE]])
+// OGCG:   br label %[[DELETE_END]]
+//
+// OGCG: [[DELETE_END]]:
+// OGCG:   ret void
+//
+// Cleanup landing pad: cleanup landingpad, save exn/sel, then check whether
+// any elements remain to destroy.
+// OGCG: [[LPAD]]:
+// OGCG:   %[[LPAD_VAL:.*]] = landingpad { ptr, i32 }
+// OGCG:           cleanup
+// OGCG:   %[[CL_ISEMPTY:.*]] = icmp eq ptr %[[PTR]], %[[ELT]]
+// OGCG:   br i1 %[[CL_ISEMPTY]], label %[[DONE6:[^,]+]], label %[[BODY2:[^ 
]+]]
+//
+// Cleanup loop: phi starts at the element that threw, decrements to the
+// previous element, invokes dtor unwinding to terminate.lpad on a second
+// throw.
+// OGCG: [[BODY2]]:
+// OGCG:   %[[ELT_PAST3:.*]] = phi ptr [ %[[ELT]], %[[LPAD]] ], [ 
%[[ELT4:.*]], %[[INV_CONT5:[^ ]+]] ]
+// OGCG:   %[[ELT4]] = getelementptr inbounds %struct.ThrowingDtor, ptr 
%[[ELT_PAST3]], i64 -1
+// OGCG:   invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}}%[[ELT4]])
+// OGCG:           to label %[[INV_CONT5]] unwind label %[[TERMINATE_LPAD:[^ 
]+]]
+//
+// OGCG: [[INV_CONT5]]:
+// OGCG:   %[[CL_DONE:.*]] = icmp eq ptr %[[ELT4]], %[[PTR]]
+// OGCG:   br i1 %[[CL_DONE]], label %[[DONE6]], label %[[BODY2]]
+//
+// EH path: compute size, call `_ZdaPvm`, resume.
+// OGCG: [[DONE6]]:
+// OGCG:   %[[EH_ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// OGCG:   %[[EH_TOTAL_SIZE:.*]] = add i64 %[[EH_ARRAY_SIZE]], 8
+// OGCG:   call void @_ZdaPvm(ptr {{.*}}%[[ALLOC_PTR]], i64 
{{.*}}%[[EH_TOTAL_SIZE]])
+// OGCG:   br label %[[EH_RESUME:[^ ]+]]
+//
+// OGCG: [[EH_RESUME]]:
+// OGCG:   resume { ptr, i32 }
+//
+// Terminate landing pad.
+// OGCG: [[TERMINATE_LPAD]]:
+// OGCG:   landingpad { ptr, i32 }
+// OGCG:           catch ptr null
+// OGCG:   call void @__clang_call_terminate(ptr {{.*}})
+// OGCG:   unreachable
diff --git a/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp 
b/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
index 12f0b060a997b..2bb8d3aafe87e 100644
--- a/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
+++ b/clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
@@ -51,7 +51,7 @@ void test(Dtor *ptr) {
 // 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.call @_ZN4DtorD1Ev(%[[PREV]]) nothrow : 
(!cir.ptr<!rec_Dtor>) -> ()
 // CIR:         cir.yield
 // CIR:       } while {
 // CIR:         %[[CUR2:.*]] = cir.load %[[ARR_IDX]]
@@ -61,7 +61,7 @@ void test(Dtor *ptr) {
 // CIR:     }
 //
 // Call unsized operator delete[] with just the pointer.
-// CIR:     cir.call @_ZdaPv(%[[VOID_PTR]]) : (!cir.ptr<!void>) -> ()
+// CIR:     cir.call @_ZdaPv(%[[VOID_PTR]]) nothrow : (!cir.ptr<!void>) -> ()
 // CIR:   }
 
 // LLVM: define {{.*}} void @_Z4testP4Dtor
diff --git a/clang/test/CIR/CodeGen/delete-array.cpp 
b/clang/test/CIR/CodeGen/delete-array.cpp
index 25a49cf668978..6ba6f8af17bfd 100644
--- a/clang/test/CIR/CodeGen/delete-array.cpp
+++ b/clang/test/CIR/CodeGen/delete-array.cpp
@@ -24,7 +24,12 @@ void test_delete_array(int *ptr) {
 // CIR:   %[[NOT_NULL:.*]] = cir.cmp ne %[[PTR]], %[[NULL]] : !cir.ptr<!s32i>
 // CIR:   cir.if %[[NOT_NULL]] {
 // CIR:     %[[VOID_PTR:.*]] = cir.cast bitcast %[[PTR]] : !cir.ptr<!s32i> -> 
!cir.ptr<!void>
-// CIR:     cir.call @_ZdaPv(%[[VOID_PTR]])
+// CIR:     cir.cleanup.scope {
+// CIR:       cir.yield
+// CIR:     } cleanup normal {
+// CIR:       cir.call @_ZdaPv(%[[VOID_PTR]]) nothrow
+// CIR:       cir.yield
+// CIR:     }
 // CIR:   }
 
 // LLVM: define {{.*}} void @_Z17test_delete_arrayPi
@@ -69,7 +74,12 @@ void test_simple_delete_array(SimpleArrDelete *ptr) {
 // CIR:   %[[NOT_NULL:.*]] = cir.cmp ne %[[PTR]], %[[NULL]] : 
!cir.ptr<!rec_SimpleArrDelete>
 // CIR:   cir.if %[[NOT_NULL]] {
 // CIR:     %[[VOID_PTR:.*]] = cir.cast bitcast %[[PTR]] : 
!cir.ptr<!rec_SimpleArrDelete> -> !cir.ptr<!void>
-// CIR:     cir.call @_ZN15SimpleArrDeletedaEPv(%[[VOID_PTR]])
+// CIR:     cir.cleanup.scope {
+// CIR:       cir.yield
+// CIR:     } cleanup normal {
+// CIR:       cir.call @_ZN15SimpleArrDeletedaEPv(%[[VOID_PTR]]) nothrow
+// CIR:       cir.yield
+// CIR:     }
 // CIR:   }
 
 // LLVM: define {{.*}} void @_Z24test_simple_delete_arrayP15SimpleArrDelete
@@ -120,11 +130,16 @@ void test_sized_array_delete(SizedArrayDelete *ptr) {
 // CIR:     %[[VOID_PTR:.*]] = cir.cast bitcast %[[ALLOC_BYTE_PTR]] : 
!cir.ptr<!u8i> -> !cir.ptr<!void>
 // CIR:     %[[COOKIE_PTR:.*]] = cir.cast bitcast %[[ALLOC_BYTE_PTR]] : 
!cir.ptr<!u8i> -> !cir.ptr<!u64i>
 // CIR:     %[[NUM_ELEM:.*]] = cir.load align(4) %[[COOKIE_PTR]] : 
!cir.ptr<!u64i>, !u64i
-// CIR:     %[[ELEM_SIZE:.*]] = cir.const #cir.int<4> : !u64i
-// CIR:     %[[ARRAY_SIZE:.*]] = cir.mul %[[ELEM_SIZE]], %[[NUM_ELEM]] : !u64i
-// CIR:     %[[COOKIE_SIZE:.*]] = cir.const #cir.int<8> : !u64i
-// CIR:     %[[TOTAL_SIZE:.*]] = cir.add %[[ARRAY_SIZE]], %[[COOKIE_SIZE]] : 
!u64i
-// CIR:     cir.call @_ZN16SizedArrayDeletedaEPvm(%[[VOID_PTR]], 
%[[TOTAL_SIZE]])
+// CIR:     cir.cleanup.scope {
+// CIR:       cir.yield
+// CIR:     } cleanup normal {
+// CIR:       %[[ELEM_SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR:       %[[ARRAY_SIZE:.*]] = cir.mul %[[ELEM_SIZE]], %[[NUM_ELEM]] : 
!u64i
+// CIR:       %[[COOKIE_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:       %[[TOTAL_SIZE:.*]] = cir.add %[[ARRAY_SIZE]], %[[COOKIE_SIZE]] : 
!u64i
+// CIR:       cir.call @_ZN16SizedArrayDeletedaEPvm(%[[VOID_PTR]], 
%[[TOTAL_SIZE]]) nothrow
+// CIR:       cir.yield
+// CIR:     }
 // CIR:   }
 
 // LLVM: define {{.*}} void @_Z23test_sized_array_deleteP16SizedArrayDelete
@@ -188,31 +203,34 @@ void test_delete_array_destructed(Destructed *ptr) {
 // CIR:     %[[NUM_ELEM:.*]] = cir.load{{.*}} %[[COOKIE_PTR]] : 
!cir.ptr<!u64i>, !u64i
 //
 // Destruct elements in reverse order.
-// CIR:     %[[END:.*]] = cir.ptr_stride %[[PTR]], %[[NUM_ELEM]] : 
(!cir.ptr<!rec_Destructed>, !u64i) -> !cir.ptr<!rec_Destructed>
-// CIR:     %[[NOT_EMPTY:.*]] = cir.cmp ne %[[END]], %[[PTR]] : 
!cir.ptr<!rec_Destructed>
-// CIR:     cir.if %[[NOT_EMPTY]] {
-// CIR:       %[[ARR_IDX:.*]] = cir.alloca !cir.ptr<!rec_Destructed>, 
!cir.ptr<!cir.ptr<!rec_Destructed>>, ["__array_idx"] {alignment = 1 : i64}
-// CIR:       cir.store %[[END]], %[[ARR_IDX]] : !cir.ptr<!rec_Destructed>, 
!cir.ptr<!cir.ptr<!rec_Destructed>>
-// CIR:       cir.do {
-// CIR:         %[[ARR_CUR:.*]] = cir.load{{.*}} %[[ARR_IDX]] : 
!cir.ptr<!cir.ptr<!rec_Destructed>>, !cir.ptr<!rec_Destructed>
-// CIR:         %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i
-// CIR:         %[[ARR_PREV:.*]] = cir.ptr_stride %[[ARR_CUR]], %[[NEG_ONE]] : 
(!cir.ptr<!rec_Destructed>, !s64i) -> !cir.ptr<!rec_Destructed>
-// CIR:         cir.store %[[ARR_PREV]], %[[ARR_IDX]] : 
!cir.ptr<!rec_Destructed>, !cir.ptr<!cir.ptr<!rec_Destructed>>
-// CIR:         cir.call @_ZN10DestructedD1Ev(%[[ARR_PREV]]) : 
(!cir.ptr<!rec_Destructed>) -> ()
-// CIR:         cir.yield
-// CIR:       } while {
-// CIR:         %[[ARR_CUR:.*]] = cir.load{{.*}} %[[ARR_IDX]] : 
!cir.ptr<!cir.ptr<!rec_Destructed>>, !cir.ptr<!rec_Destructed>
-// CIR:         %[[CMP:.*]] = cir.cmp ne %[[ARR_CUR]], %[[PTR]] : 
!cir.ptr<!rec_Destructed>
-// CIR:         cir.condition(%[[CMP]])
+// CIR:     cir.cleanup.scope {
+// CIR:       %[[END:.*]] = cir.ptr_stride %[[PTR]], %[[NUM_ELEM]] : 
(!cir.ptr<!rec_Destructed>, !u64i) -> !cir.ptr<!rec_Destructed>
+// CIR:       %[[NOT_EMPTY:.*]] = cir.cmp ne %[[END]], %[[PTR]] : 
!cir.ptr<!rec_Destructed>
+// CIR:       cir.if %[[NOT_EMPTY]] {
+// CIR:         %[[ARR_IDX:.*]] = cir.alloca !cir.ptr<!rec_Destructed>, 
!cir.ptr<!cir.ptr<!rec_Destructed>>, ["__array_idx"] {alignment = 1 : i64}
+// CIR:         cir.store %[[END]], %[[ARR_IDX]] : !cir.ptr<!rec_Destructed>, 
!cir.ptr<!cir.ptr<!rec_Destructed>>
+// CIR:         cir.do {
+// CIR:           %[[ARR_CUR:.*]] = cir.load{{.*}} %[[ARR_IDX]] : 
!cir.ptr<!cir.ptr<!rec_Destructed>>, !cir.ptr<!rec_Destructed>
+// CIR:           %[[NEG_ONE:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:           %[[ARR_PREV:.*]] = cir.ptr_stride %[[ARR_CUR]], %[[NEG_ONE]] 
: (!cir.ptr<!rec_Destructed>, !s64i) -> !cir.ptr<!rec_Destructed>
+// CIR:           cir.store %[[ARR_PREV]], %[[ARR_IDX]] : 
!cir.ptr<!rec_Destructed>, !cir.ptr<!cir.ptr<!rec_Destructed>>
+// CIR:           cir.call @_ZN10DestructedD1Ev(%[[ARR_PREV]]) nothrow : 
(!cir.ptr<!rec_Destructed>) -> ()
+// CIR:           cir.yield
+// CIR:         } while {
+// CIR:           %[[ARR_CUR:.*]] = cir.load{{.*}} %[[ARR_IDX]] : 
!cir.ptr<!cir.ptr<!rec_Destructed>>, !cir.ptr<!rec_Destructed>
+// CIR:           %[[CMP:.*]] = cir.cmp ne %[[ARR_CUR]], %[[PTR]] : 
!cir.ptr<!rec_Destructed>
+// CIR:           cir.condition(%[[CMP]])
+// CIR:         }
 // CIR:       }
+// CIR:       cir.yield
+// CIR:     } cleanup normal {
+// CIR:       %[[ELEM_SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR:       %[[ARRAY_SIZE:.*]] = cir.mul %[[ELEM_SIZE]], %[[NUM_ELEM]] : 
!u64i
+// CIR:       %[[COOKIE_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:       %[[TOTAL_SIZE:.*]] = cir.add %[[ARRAY_SIZE]], %[[COOKIE_SIZE]] : 
!u64i
+// CIR:       cir.call @_ZdaPvm(%[[VOID_PTR]], %[[TOTAL_SIZE]]) nothrow
+// CIR:       cir.yield
 // CIR:     }
-//
-// Compute total size and call delete function.
-// CIR:     %[[ELEM_SIZE:.*]] = cir.const #cir.int<4> : !u64i
-// CIR:     %[[ARRAY_SIZE:.*]] = cir.mul %[[ELEM_SIZE]], %[[NUM_ELEM]] : !u64i
-// CIR:     %[[COOKIE_SIZE:.*]] = cir.const #cir.int<8> : !u64i
-// CIR:     %[[TOTAL_SIZE:.*]] = cir.add %[[ARRAY_SIZE]], %[[COOKIE_SIZE]] : 
!u64i
-// CIR:     cir.call @_ZdaPvm(%[[VOID_PTR]], %[[TOTAL_SIZE]])
 // CIR:   }
 
 // LLVM: define {{.*}} void @_Z28test_delete_array_destructedP10Destructed
@@ -244,8 +262,7 @@ void test_delete_array_destructed(Destructed *ptr) {
 // LLVM:   %[[ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
 // LLVM:   %[[TOTAL_SIZE:.*]] = add i64 %[[ARRAY_SIZE]], 8
 // LLVM:   call void @_ZdaPvm(ptr %[[ALLOC_PTR]], i64 %[[TOTAL_SIZE]])
-// LLVM:   br label %[[DONE]]
-// LLVM: [[DONE]]:
+// LLVM:   br label %{{.*}}
 // LLVM:   ret void
 
 // OGCG: define {{.*}} void @_Z28test_delete_array_destructedP10Destructed

>From 3f7e144531fa52122f0e0bdc3700704e2983aa07 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Thu, 30 Apr 2026 09:55:51 -0700
Subject: [PATCH 2/3] Address review feedback

---
 clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 35085fa3ca5c0..297f4ac3b0916 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -674,7 +674,8 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
           auto arrayDtor = cir::ArrayDtor::create(
               b, l, loweredAddress, numElements,
               [&](mlir::OpBuilder &bb, mlir::Location ll) {
-                auto arg = bb.getInsertionBlock()->addArgument(eltPtrTy, ll);
+                mlir::Value arg =
+                    bb.getInsertionBlock()->addArgument(eltPtrTy, ll);
                 auto dtorCall = cir::CallOp::create(
                     bb, ll, dtorFn, cir::VoidType(), mlir::ValueRange{arg});
                 if (!op.getDtorMayThrow())
@@ -692,11 +693,11 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
         callArgs.push_back(deletePtr);
         if (deleteParams.getSize()) {
           uint64_t eltSizeBytes = dl.getTypeSizeInBits(ptrTy.getPointee()) / 8;
-          mlir::Value eltSizeVal = cir::ConstantOp::create(
+          auto eltSizeVal = cir::ConstantOp::create(
               b, l, cir::IntAttr::get(sizeTy, eltSizeBytes));
           mlir::Value allocSize =
               cir::MulOp::create(b, l, sizeTy, eltSizeVal, numElements);
-          mlir::Value cookieSizeVal = cir::ConstantOp::create(
+          auto cookieSizeVal = cir::ConstantOp::create(
               b, l, cir::IntAttr::get(sizeTy, cookieSize.getQuantity()));
           allocSize =
               cir::AddOp::create(b, l, sizeTy, allocSize, cookieSizeVal);

>From 9c3213f51769305f5a1be74fd8970cffe04245f8 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Tue, 5 May 2026 15:49:18 -0700
Subject: [PATCH 3/3] Address review feedback

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 21 +++++++------------
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 11 ++++++++++
 .../CodeGen/delete-array-throwing-dtor.cpp    |  2 +-
 clang/test/CIR/IR/invalid-delete-array.cir    | 15 +++++++++++++
 4 files changed, 34 insertions(+), 15 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-delete-array.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6328e79ff6fd0..13e2aa59a1c88 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3928,8 +3928,8 @@ def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
     The `element_dtor` attribute, when present, specifies the destructor to 
call
     on each array element before deallocation.
 
-    The `dtor_may_throw` unit attribute, when present, indicates that the
-    element destructor is may throw exceptions.
+    The `dtor_may_throw` unit property, when present, indicates that the
+    element destructor may throw exceptions.
   }];
 
   let arguments = (ins
@@ -3937,7 +3937,7 @@ def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
     FlatSymbolRefAttr:$delete_fn,
     CIR_UsualDeleteParamsAttr:$delete_params,
     OptionalAttr<FlatSymbolRefAttr>:$element_dtor,
-    UnitAttr:$dtor_may_throw
+    UnitProp:$dtor_may_throw
   );
 
   let builders = [
@@ -3946,23 +3946,16 @@ def CIR_DeleteArrayOp : CIR_Op<"delete_array"> {
                    "cir::UsualDeleteParamsAttr":$delete_params), [{
       build($_builder, $_state, address, delete_fn, delete_params,
             /*element_dtor=*/mlir::FlatSymbolRefAttr{},
-            /*dtor_may_throw=*/mlir::UnitAttr{});
-    }]>,
-    OpBuilder<(ins "mlir::Value":$address,
-                   "mlir::FlatSymbolRefAttr":$delete_fn,
-                   "cir::UsualDeleteParamsAttr":$delete_params,
-                   "mlir::FlatSymbolRefAttr":$element_dtor,
-                   "bool":$dtor_may_throw), [{
-      build($_builder, $_state, address, delete_fn, delete_params,
-            element_dtor,
-            dtor_may_throw ? $_builder.getUnitAttr() : mlir::UnitAttr{});
+            /*dtor_may_throw=*/false);
     }]>
   ];
 
   let assemblyFormat = [{
-    $address `:` qualified(type($address)) attr-dict
+    $address `:` qualified(type($address)) (`dtor_may_throw` $dtor_may_throw^)?
+    attr-dict
   }];
 
+  let hasVerifier = 1;
   let hasLLVMLowering = false;
   let hasCXXABILowering = true;
 }
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 0ebd08180cadc..59af448335d15 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -360,6 +360,17 @@ LogicalResult cir::ArrayCtor::verify() {
 }
 LogicalResult cir::ArrayDtor::verify() { return verifyArrayCtorDtor(*this); }
 
+//===----------------------------------------------------------------------===//
+// DeleteArrayOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::DeleteArrayOp::verify() {
+  if (getDtorMayThrow() && !getElementDtorAttr())
+    return emitOpError(
+        "'dtor_may_throw' requires an 'element_dtor' to be present");
+  return success();
+}
+
 
//===----------------------------------------------------------------------===//
 // BreakOp
 
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp 
b/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
index dc2647d62b01e..c39227f3432a7 100644
--- a/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
+++ b/clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
@@ -23,7 +23,7 @@ void test_delete_array_throwing_dtor(ThrowingDtor *ptr) {
 // CIR-BEFORE-CXXABI:   %[[NULL:.*]] = cir.const #cir.ptr<null>
 // CIR-BEFORE-CXXABI:   %[[NOT_NULL:.*]] = cir.cmp ne %[[PTR]], %[[NULL]]
 // CIR-BEFORE-CXXABI:   cir.if %[[NOT_NULL]] {
-// CIR-BEFORE-CXXABI:     cir.delete_array %[[PTR]] : 
!cir.ptr<!rec_ThrowingDtor> {delete_fn = @_ZdaPvm, delete_params = 
#cir.usual_delete_params<size = true>, dtor_may_throw, element_dtor = 
@_ZN12ThrowingDtorD1Ev}
+// CIR-BEFORE-CXXABI:     cir.delete_array %[[PTR]] : 
!cir.ptr<!rec_ThrowingDtor> dtor_may_throw {delete_fn = @_ZdaPvm, delete_params 
= #cir.usual_delete_params<size = true>, element_dtor = @_ZN12ThrowingDtorD1Ev}
 // CIR-BEFORE-CXXABI:   }
 
 // CIR-AFTER-CXXABI: IR Dump After CXXABILowering (cir-cxxabi-lowering)
diff --git a/clang/test/CIR/IR/invalid-delete-array.cir 
b/clang/test/CIR/IR/invalid-delete-array.cir
new file mode 100644
index 0000000000000..ff2055f525321
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-delete-array.cir
@@ -0,0 +1,15 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!void = !cir.void
+!u8i = !cir.int<u, 8>
+!rec_S = !cir.record<struct "S" padded {!u8i}>
+
+module {
+  cir.func private @_ZdaPvm(!cir.ptr<!void>)
+
+  cir.func @bad_delete_array_throw_without_dtor(%p: !cir.ptr<!rec_S>) {
+    // expected-error@+1 {{'cir.delete_array' op 'dtor_may_throw' requires an 
'element_dtor' to be present}}
+    cir.delete_array %p : !cir.ptr<!rec_S> dtor_may_throw {delete_fn = 
@_ZdaPvm, delete_params = #cir.usual_delete_params<size = true>}
+    cir.return
+  }
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to