[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