[clang] cd0f775 - [CIR] Handle irregular partial init destroy (#192158)

via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 15 09:12:21 PDT 2026


Author: Andy Kaylor
Date: 2026-04-15T09:12:16-07:00
New Revision: cd0f775fbc9075897475e4b4cdf56be4a7148d43

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

LOG: [CIR] Handle irregular partial init destroy (#192158)

This implements CIR handling for the case where an array of a destructed
type is being initialized using an initializer list and therefore the
address of the last successfully constructed element must be loaded from
a temporary variable before the partial array destroy loop can begin.

In classic codegen this shares its implementation with the general array
ctor/dtor handling, but in CIR we have dedicated operations to abstract
array ctors/dtors, so the implementation of the irregular partial
destruction happens in a different place in codegen.

Assisted-by: Cursor / claude-4.6-opus-high

Added: 
    

Modified: 
    clang/lib/CIR/CodeGen/CIRGenDecl.cpp
    clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
    clang/lib/CIR/CodeGen/CIRGenFunction.h
    clang/test/CIR/CodeGen/partial-array-cleanup.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index bfb962aaedbd9..865ae336d58cb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -990,8 +990,86 @@ struct CallStackRestore final : EHScopeStack::Cleanup {
     cgf.getBuilder().createStackRestore(loc, v);
   }
 };
+
+/// A cleanup which performs a partial array destroy where the end pointer is
+/// irregularly determined and must be loaded from a local.
+struct IrregularPartialArrayDestroy final : EHScopeStack::Cleanup {
+  mlir::Value arrayBegin;
+  Address arrayEndPointer;
+  QualType elementType;
+  CharUnits elementAlign;
+  CIRGenFunction::Destroyer *destroyer;
+
+  IrregularPartialArrayDestroy(mlir::Value arrayBegin, Address arrayEndPointer,
+                               QualType elementType, CharUnits elementAlign,
+                               CIRGenFunction::Destroyer *destroyer)
+      : arrayBegin(arrayBegin), arrayEndPointer(arrayEndPointer),
+        elementType(elementType), elementAlign(elementAlign),
+        destroyer(destroyer) {}
+
+  void emit(CIRGenFunction &cgf, Flags flags) override {
+    CIRGenBuilderTy &builder = cgf.getBuilder();
+    mlir::Location loc = arrayBegin.getLoc();
+
+    mlir::Value arrayEnd = builder.createLoad(loc, arrayEndPointer);
+
+    // The cleanup is destroying elements in reverse from arrayEnd back to
+    // arrayBegin, but only if arrayEnd != arrayBegin (i.e. something was
+    // constructed).
+    mlir::Type cirElementType = cgf.convertTypeForMem(elementType);
+    cir::PointerType ptrToElmType = builder.getPointerTo(cirElementType);
+
+    mlir::Value ne = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
+                                        arrayEnd, arrayBegin);
+    cir::IfOp::create(
+        builder, loc, ne, /*withElseRegion=*/false,
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          Address iterAddr = cgf.createTempAlloca(
+              ptrToElmType, cgf.getPointerAlign(), loc, "__array_idx");
+          builder.createStore(loc, arrayEnd, iterAddr);
+          builder.createDoWhile(
+              loc,
+              /*condBuilder=*/
+              [&](mlir::OpBuilder &b, mlir::Location loc) {
+                mlir::Value cur = builder.createLoad(loc, iterAddr);
+                mlir::Value cmp = cir::CmpOp::create(
+                    builder, loc, cir::CmpOpKind::ne, cur, arrayBegin);
+                builder.createCondition(cmp);
+              },
+              /*bodyBuilder=*/
+              [&](mlir::OpBuilder &b, mlir::Location loc) {
+                mlir::Value cur = builder.createLoad(loc, iterAddr);
+                cir::ConstantOp negOne = builder.getConstInt(
+                    loc, mlir::cast<cir::IntType>(cgf.ptrDiffTy), -1);
+                mlir::Value prev = cir::PtrStrideOp::create(
+                    builder, loc, ptrToElmType, cur, negOne);
+                builder.createStore(loc, prev, iterAddr);
+                Address elemAddr = Address(prev, cirElementType, elementAlign);
+                destroyer(cgf, elemAddr, elementType);
+                builder.createYield(loc);
+              });
+          builder.createYield(loc);
+        });
+  }
+};
 } // namespace
 
+/// Push an EH cleanup to destroy already-constructed elements of the given
+/// array.  The cleanup may be popped with deactivateCleanupBlock or
+/// popCleanupBlock.
+///
+/// \param elementType - the immediate element type of the array;
+///   possibly still an array type
+void CIRGenFunction::pushIrregularPartialArrayCleanup(mlir::Value arrayBegin,
+                                                      Address arrayEndPointer,
+                                                      QualType elementType,
+                                                      CharUnits elementAlign,
+                                                      Destroyer *destroyer) {
+  ehStack.pushCleanup<IrregularPartialArrayDestroy>(
+      EHCleanup, arrayBegin, arrayEndPointer, elementType, elementAlign,
+      destroyer);
+}
+
 /// Push the standard destructor for the given type as
 /// at least a normal cleanup.
 void CIRGenFunction::pushDestroy(QualType::DestructionKind dtorKind,

diff  --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index 622012292e175..7f84b1e6a827c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -707,11 +707,6 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
   const QualType elementType =
       cgf.getContext().getAsArrayType(arrayQTy)->getElementType();
 
-  if (elementType.isDestructedType() && cgf.cgm.getLangOpts().Exceptions) {
-    cgf.cgm.errorNYI(loc, "initialized array requires destruction");
-    return;
-  }
-
   const QualType elementPtrType = cgf.getContext().getPointerType(elementType);
 
   const mlir::Type cirElementType = cgf.convertType(elementType);
@@ -727,6 +722,21 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
   const CharUnits elementAlign =
       destPtr.getAlignment().alignmentOfArrayElement(elementSize);
 
+  // Exception safety requires us to destroy all the already-constructed
+  // members if an initializer throws. For that, we'll need an EH cleanup.
+  QualType::DestructionKind dtorKind = elementType.isDestructedType();
+  Address endOfInit = Address::invalid();
+
+  if (dtorKind && cgf.getLangOpts().Exceptions) {
+    endOfInit = cgf.createTempAlloca(cirElementPtrType, cgf.getPointerAlign(),
+                                     loc, "arrayinit.endOfInit");
+    builder.createStore(loc, begin, endOfInit);
+
+    cgf.pushIrregularPartialArrayCleanup(begin, endOfInit, elementType,
+                                         elementAlign,
+                                         cgf.getDestroyer(dtorKind));
+  }
+
   // The 'current element to initialize'.  The invariants on this
   // variable are complicated.  Essentially, after each iteration of
   // the loop, it points to the last initialized element, except
@@ -744,6 +754,10 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
     if (i > 0) {
       one = builder.getConstantInt(loc, cgf.ptrDiffTy, i);
       element = builder.createPtrStride(loc, begin, one);
+
+      // Tell the cleanup that it needs to destroy up to this element.
+      if (endOfInit.isValid())
+        builder.createStore(loc, element, endOfInit);
     }
 
     const Address address = Address(element, cirElementType, elementAlign);
@@ -767,6 +781,9 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
       one = builder.getConstantInt(loc, cgf.ptrDiffTy, 1);
       element = cir::PtrStrideOp::create(builder, loc, cirElementPtrType,
                                          element, one);
+
+      if (endOfInit.isValid())
+        builder.createStore(loc, element, endOfInit);
     }
 
     // Allocate the temporary variable
@@ -815,17 +832,16 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
           else
             emitNullInitializationToLValue(loc, elementLV);
 
-          // Tell the EH cleanup that we finished with the last element.
-          if (cgf.cgm.getLangOpts().Exceptions) {
-            cgf.cgm.errorNYI(loc, "update destructed array element for EH");
-            return;
-          }
-
           // Advance pointer and store them to temporary variable
           cir::ConstantOp one = builder.getConstInt(
               loc, mlir::cast<cir::IntType>(cgf.ptrDiffTy), 1);
           auto nextElement = cir::PtrStrideOp::create(
               builder, loc, cirElementPtrType, currentElement, one);
+
+          // Tell the EH cleanup that we finished with the last element.
+          if (endOfInit.isValid())
+            builder.createStore(loc, nextElement, endOfInit);
+
           cgf.emitStoreThroughLValue(RValue::get(nextElement), tmpLV);
 
           builder.createYield(loc);

diff  --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index b83f37cc6dc59..91e15138c365e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1372,6 +1372,12 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   Destroyer *getDestroyer(clang::QualType::DestructionKind kind);
 
+  void pushIrregularPartialArrayCleanup(mlir::Value arrayBegin,
+                                        Address arrayEndPointer,
+                                        QualType elementType,
+                                        CharUnits elementAlign,
+                                        Destroyer *destroyer);
+
   /// Start generating a thunk function.
   void startThunk(cir::FuncOp fn, GlobalDecl gd,
                   const CIRGenFunctionInfo &fnInfo, bool isUnprototyped);

diff  --git a/clang/test/CIR/CodeGen/partial-array-cleanup.cpp b/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
index 520170246eb8e..ce085ecc53be9 100644
--- a/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
+++ b/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
@@ -658,3 +658,165 @@ void test_vla_of_constant_array(int n) {
 // OGCG:       call void @_ZN1SD1Ev
 //
 // OGCG:       resume { ptr, i32 }
+
+void test_init_list_partial_array_cleanup() {
+    S arr[4] = { S(), S() };
+}
+
+// CIR-LABEL:     cir.func {{.*}} @_Z36test_init_list_partial_array_cleanupv()
+// CIR:       %[[ARRAY:.*]] = cir.alloca !cir.array<!rec_S x 4>, !cir.ptr<!cir.array<!rec_S x 4>>, ["arr", init]
+// CIR:       %[[END_OF_INIT:.*]] = cir.alloca !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>, ["arrayinit.endOfInit"]
+// CIR:       %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARRAY]] : !cir.ptr<!cir.array<!rec_S x 4>> -> !cir.ptr<!rec_S>
+// CIR:       cir.store {{.*}} %[[BEGIN]], %[[END_OF_INIT]] : !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>
+// CIR:       cir.cleanup.scope {
+//              --- first explicit init ---
+// CIR:         cir.call @_ZN1SC1Ev(%[[BEGIN]])
+//              --- advance + update endOfInit for second element ---
+// CIR:         %[[SECOND:.*]] = cir.ptr_stride %[[BEGIN]], %{{.*}}
+// CIR:         cir.store {{.*}} %[[SECOND]], %[[END_OF_INIT]]
+//              --- second explicit init ---
+// CIR:         cir.call @_ZN1SC1Ev(%[[SECOND]])
+//              --- advance + update endOfInit for filler start ---
+// CIR:         %[[FILLER_START:.*]] = cir.ptr_stride %[[SECOND]], %{{.*}}
+// CIR:         cir.store {{.*}} %[[FILLER_START]], %[[END_OF_INIT]]
+//              --- do-while filler loop ---
+// CIR:         cir.do {
+// CIR:           %[[CUR:.*]] = cir.load {{.*}} %{{.*}} : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR:           cir.call @_ZN1SC1Ev(%[[CUR]])
+// CIR:           %[[NEXT:.*]] = cir.ptr_stride %[[CUR]], %{{.*}}
+// CIR:           cir.store {{.*}} %[[NEXT]], %[[END_OF_INIT]]
+// CIR:           cir.yield
+// CIR:         } while {
+// CIR:           cir.condition(%{{.*}})
+// CIR:         }
+// CIR:         cir.yield
+//              --- EH cleanup: partial destruction ---
+// CIR:       } cleanup eh {
+// CIR:         %[[END_VAL:.*]] = cir.load {{.*}} %[[END_OF_INIT]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR:         %[[NE:.*]] = cir.cmp ne %[[END_VAL]], %[[BEGIN]] : !cir.ptr<!rec_S>
+// CIR:         cir.if %[[NE]] {
+// CIR:           cir.do {
+// CIR:             %[[EL:.*]] = cir.load {{.*}} %{{.*}} : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR:             %[[NEG1:.*]] = cir.const #cir.int<-1> : !s64i
+// CIR:             %[[PREV:.*]] = cir.ptr_stride %[[EL]], %[[NEG1]] : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
+// CIR:             cir.call @_ZN1SD1Ev(%[[PREV]])
+// CIR:             cir.yield
+// CIR:           } while {
+// CIR:             %[[EL2:.*]] = cir.load {{.*}} %{{.*}} : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR:             %[[NE2:.*]] = cir.cmp ne %[[EL2]], %[[BEGIN]] : !cir.ptr<!rec_S>
+// CIR:             cir.condition(%[[NE2]])
+// CIR:           }
+// CIR:         }
+// CIR:         cir.yield
+// CIR:       }
+
+// LLVM-LABEL:  define dso_local void @_Z36test_init_list_partial_array_cleanupv()
+// LLVM:       %[[ARRAY:.*]] = alloca [4 x %struct.S]
+// LLVM:       %[[BEGIN:.*]] = getelementptr %struct.S, ptr %[[ARRAY]], i32 0
+// LLVM:       store ptr %[[BEGIN]], ptr %[[END_OF_INIT:.*]]
+//
+//            --- first ctor ---
+// LLVM:       invoke void @_ZN1SC1Ev(ptr {{.*}} %[[BEGIN]])
+// LLVM:         to label %[[CONT1:.*]] unwind label %[[LPAD:.*]]
+//
+//            --- second ctor ---
+// LLVM:     [[CONT1]]:
+// LLVM:       %[[SECOND:.*]] = getelementptr %struct.S, ptr %[[BEGIN]], i64 1
+// LLVM:       store ptr %[[SECOND]], ptr %[[END_OF_INIT]]
+// LLVM:       invoke void @_ZN1SC1Ev(ptr {{.*}} %[[SECOND]])
+// LLVM:         to label %{{.*}} unwind label %[[LPAD]]
+//
+//            --- filler loop with invoke ---
+// LLVM:       invoke void @_ZN1SC1Ev(ptr {{.*}})
+// LLVM:         to label %[[FILLER_CONT:.*]] unwind label %[[LPAD]]
+//
+// LLVM:     [[FILLER_CONT]]:
+// LLVM:       %[[FNEXT:.*]] = getelementptr %struct.S, ptr %{{.*}}, i64 1
+// LLVM:       store ptr %[[FNEXT]], ptr %[[END_OF_INIT]]
+//
+//            --- landing pad + cleanup guard ---
+// LLVM:     [[LPAD]]:
+// LLVM:       landingpad { ptr, i32 }
+// LLVM:         cleanup
+// LLVM:       %[[PAD_CUR:.*]] = load ptr, ptr %[[END_OF_INIT]]
+// LLVM:       %[[GUARD:.*]] = icmp ne ptr %[[PAD_CUR]], %[[BEGIN]]
+// LLVM:       br i1 %[[GUARD]], label %[[DTOR_ENTRY:.*]], label %[[EH_RESUME:.*]]
+//
+//            --- partial dtor do-while loop ---
+// LLVM:     [[DTOR_ENTRY]]:
+// LLVM:       store ptr %[[PAD_CUR]], ptr %[[DTOR_ITER:.*]]
+// LLVM:       br label %[[DTOR_BODY:.*]]
+//
+// LLVM:     [[DTOR_LOOP_COND:.*]]:
+// LLVM:       %[[DTOR_CUR:.*]] = load ptr, ptr %[[DTOR_ITER]]
+// LLVM:       %[[DTOR_CONT:.*]] = icmp ne ptr %[[DTOR_CUR]], %[[BEGIN]]
+// LLVM:       br i1 %[[DTOR_CONT]], label %[[DTOR_BODY]], label %[[DTOR_DONE:.*]]
+//
+// LLVM:     [[DTOR_BODY]]:
+// LLVM:       %[[DCUR:.*]] = load ptr, ptr %[[DTOR_ITER]]
+// LLVM:       %[[PREV:.*]] = getelementptr %struct.S, ptr %[[DCUR]], i64 -1
+// LLVM:       store ptr %[[PREV]], ptr %[[DTOR_ITER]]
+// LLVM:       call void @_ZN1SD1Ev(ptr {{.*}} %[[PREV]])
+// LLVM:       br label %[[DTOR_LOOP_COND]]
+//
+// LLVM:     [[DTOR_DONE]]:
+// LLVM:       br label %[[EH_RESUME]]
+//
+// LLVM:     [[EH_RESUME]]:
+// LLVM:       resume { ptr, i32 }
+
+// OGCG-LABEL: define dso_local void @_Z36test_init_list_partial_array_cleanupv()
+// OGCG:       %[[ARR:.*]] = alloca [4 x %struct.S]
+// OGCG:       %[[END_OF_INIT:.*]] = alloca ptr
+// OGCG:       store ptr %[[ARR]], ptr %[[END_OF_INIT]]
+//
+//            --- first explicit ctor ---
+// OGCG:       invoke void @_ZN1SC1Ev(ptr {{.*}} %[[ARR]])
+// OGCG:         to label %[[CONT1:.*]] unwind label %[[LPAD:.*]]
+//
+//            --- second explicit ctor ---
+// OGCG:     [[CONT1]]:
+// OGCG:       %[[EL1:.*]] = getelementptr inbounds %struct.S, ptr %[[ARR]], i64 1
+// OGCG:       store ptr %[[EL1]], ptr %[[END_OF_INIT]]
+// OGCG:       invoke void @_ZN1SC1Ev(ptr {{.*}} %[[EL1]])
+// OGCG:         to label %[[CONT2:.*]] unwind label %[[LPAD]]
+//
+//            --- filler loop setup ---
+// OGCG:     [[CONT2]]:
+// OGCG:       %[[FILLER_START:.*]] = getelementptr inbounds %struct.S, ptr %[[ARR]], i64 2
+// OGCG:       store ptr %[[FILLER_START]], ptr %[[END_OF_INIT]]
+// OGCG:       %[[FILLER_END:.*]] = getelementptr inbounds %struct.S, ptr %[[ARR]], i64 4
+// OGCG:       br label %[[FILLER_BODY:.*]]
+//
+//            --- filler loop body ---
+// OGCG:     [[FILLER_BODY]]:
+// OGCG:       %[[FILLER_CUR:.*]] = phi ptr [ %[[FILLER_START]], %[[CONT2]] ], [ %[[FILLER_NEXT:.*]], %[[FILLER_CONT:.*]] ]
+// OGCG:       invoke void @_ZN1SC1Ev(ptr {{.*}} %[[FILLER_CUR]])
+// OGCG:         to label %[[FILLER_CONT]] unwind label %[[LPAD]]
+//
+// OGCG:     [[FILLER_CONT]]:
+// OGCG:       %[[FILLER_NEXT]] = getelementptr inbounds %struct.S, ptr %[[FILLER_CUR]], i64 1
+// OGCG:       store ptr %[[FILLER_NEXT]], ptr %[[END_OF_INIT]]
+// OGCG:       %[[FILLER_DONE:.*]] = icmp eq ptr %[[FILLER_NEXT]], %[[FILLER_END]]
+// OGCG:       br i1 %[[FILLER_DONE]], label %{{.*}}, label %[[FILLER_BODY]]
+//
+//            --- landing pad + partial destruction ---
+// OGCG:     [[LPAD]]:
+// OGCG:       landingpad { ptr, i32 }
+// OGCG:         cleanup
+// OGCG:       %[[PAD_END:.*]] = load ptr, ptr %[[END_OF_INIT]]
+// OGCG:       %[[ISEMPTY:.*]] = icmp eq ptr %[[ARR]], %[[PAD_END]]
+// OGCG:       br i1 %[[ISEMPTY]], label %[[DTOR_DONE:.*]], label %[[DTOR_LOOP:.*]]
+//
+// OGCG:     [[DTOR_LOOP]]:
+// OGCG:       %[[DTOR_PAST:.*]] = phi ptr [ %[[PAD_END]], %[[LPAD]] ], [ %[[DTOR_PREV:.*]], %[[DTOR_LOOP]] ]
+// OGCG:       %[[DTOR_PREV]] = getelementptr inbounds %struct.S, ptr %[[DTOR_PAST]], i64 -1
+// OGCG:       call void @_ZN1SD1Ev(ptr {{.*}} %[[DTOR_PREV]])
+// OGCG:       %[[DTOR_CONT:.*]] = icmp eq ptr %[[DTOR_PREV]], %[[ARR]]
+// OGCG:       br i1 %[[DTOR_CONT]], label %[[DTOR_DONE]], label %[[DTOR_LOOP]]
+//
+// OGCG:     [[DTOR_DONE]]:
+// OGCG:       br label %[[EH_RESUME:.*]]
+//
+// OGCG:     [[EH_RESUME]]:
+// OGCG:       resume { ptr, i32 }


        


More information about the cfe-commits mailing list