Author: Andy Kaylor
Date: 2026-05-06T09:17:14-07:00
New Revision: c74f833fd5c382b09196ee20dc9c11247c847a09

URL: 
https://github.com/llvm/llvm-project/commit/c74f833fd5c382b09196ee20dc9c11247c847a09
DIFF: 
https://github.com/llvm/llvm-project/commit/c74f833fd5c382b09196ee20dc9c11247c847a09.diff

LOG: [CIR] Implement cleanup for array delete with throwing dtor (#194965)

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

Added: 
    clang/test/CIR/CodeGen/delete-array-throwing-dtor.cpp
    clang/test/CIR/IR/invalid-delete-array.cir

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIROps.td
    clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
    clang/lib/CIR/Dialect/IR/CIRDialect.cpp
    clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
    clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
    clang/test/CIR/CodeGen/delete-array-unsized-dtor.cpp
    clang/test/CIR/CodeGen/delete-array.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index db3ac6340eccb..46ea526eb6021 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3944,15 +3944,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 property, when present, indicates that the
+    element destructor may throw exceptions.
   }];
 
   let arguments = (ins
     CIR_PointerType:$address,
     FlatSymbolRefAttr:$delete_fn,
     CIR_UsualDeleteParamsAttr:$delete_params,
-    OptionalAttr<FlatSymbolRefAttr>:$element_dtor
+    OptionalAttr<FlatSymbolRefAttr>:$element_dtor,
+    UnitProp:$dtor_may_throw
   );
 
   let builders = [
@@ -3960,14 +3965,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=*/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;
 }
@@ -4861,6 +4869,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:
@@ -4877,19 +4888,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 06c4f40fa8561..c7b11aa3f0009 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1519,12 +1519,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(),
@@ -1534,7 +1534,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/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 0e2248a0a57cb..22aaa8602450c 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -361,6 +361,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/lib/CIR/Dialect/Transforms/CXXABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index f4c8170333189..297f4ac3b0916 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -630,57 +630,87 @@ 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) {
+                mlir::Value 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;
+          auto eltSizeVal = cir::ConstantOp::create(
+              b, l, cir::IntAttr::get(sizeTy, eltSizeBytes));
+          mlir::Value allocSize =
+              cir::MulOp::create(b, l, sizeTy, eltSizeVal, numElements);
+          auto 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 dc56a2f21aced..faf6914df6636 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..c39227f3432a7
--- /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> 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)
+
+// 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

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