[clang] [CIR] Fix CFG flattening for loops with cleanup in special regions (PR #187369)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 18 12:55:19 PDT 2026
https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/187369
If a loop required a cleanup scope in the condition or step region of the loop, we crashed during CFG flattening because the flattening of the cleanup scope created multiple blocks in the region, but we were assuming there would only be one block.
This change updates the CFG flattening code to look for the cir.condition or cir.yield operation in the last block of the region.
>From 607185246e79e552821b031971dc57e660cceb0c Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 18 Mar 2026 11:53:38 -0700
Subject: [PATCH] [CIR] Fix CFG flattening for loops with cleanup in special
regions
If a loop required a cleanup scope in the condition or step region of the
loop, we crashed during CFG flattening because the flattening of the
cleanup scope created multiple blocks in the region, but we were assuming
there would only be one block.
This change updates the CFG flattening code to look for the cir.condition
or cir.yield operation in the last block of the region.
---
.../lib/CIR/Dialect/Transforms/FlattenCFG.cpp | 17 +-
clang/test/CIR/CodeGen/loop-cond-cleanup.cpp | 243 ++++++++++++++++++
2 files changed, 255 insertions(+), 5 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/loop-cond-cleanup.cpp
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 50e8baba94a98..5b1f656cac3da 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -453,8 +453,12 @@ class CIRLoopOpInterfaceFlattening
rewriter.setInsertionPointToEnd(entry);
cir::BrOp::create(rewriter, op.getLoc(), &op.getEntry().front());
- // Branch from condition region to body or exit.
- auto conditionOp = cast<cir::ConditionOp>(cond->getTerminator());
+ // Branch from condition region to body or exit. The ConditionOp may not
+ // be in the first block of the condition region if a cleanup scope was
+ // already flattened within it, introducing multiple blocks. The
+ // ConditionOp is always the terminator of the last block.
+ auto conditionOp =
+ cast<cir::ConditionOp>(op.getCond().back().getTerminator());
lowerConditionOp(conditionOp, body, exit, rewriter);
// TODO(cir): Remove the walks below. It visits operations unnecessarily.
@@ -488,10 +492,13 @@ class CIRLoopOpInterfaceFlattening
lowerTerminator(bodyYield, (step ? step : cond), rewriter);
}
- // Lower mandatory step region yield.
+ // Lower mandatory step region yield. Like the condition region, the
+ // YieldOp may be in the last block rather than the first if a cleanup
+ // scope was already flattened within the step region.
if (step)
- lowerTerminator(cast<cir::YieldOp>(step->getTerminator()), cond,
- rewriter);
+ lowerTerminator(
+ cast<cir::YieldOp>(op.maybeGetStep()->back().getTerminator()), cond,
+ rewriter);
// Move region contents out of the loop op.
rewriter.inlineRegionBefore(op.getCond(), exit);
diff --git a/clang/test/CIR/CodeGen/loop-cond-cleanup.cpp b/clang/test/CIR/CodeGen/loop-cond-cleanup.cpp
new file mode 100644
index 0000000000000..933f1926c9d8c
--- /dev/null
+++ b/clang/test/CIR/CodeGen/loop-cond-cleanup.cpp
@@ -0,0 +1,243 @@
+// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -fclangir -emit-cir -std=c++17 -fcxx-exceptions -fexceptions -o %t.cir
+// RUN: FileCheck -check-prefixes=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -fclangir -emit-llvm -std=c++17 -fcxx-exceptions -fexceptions -o %t-cir.ll
+// RUN: FileCheck -check-prefixes=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -emit-llvm -std=c++17 -fcxx-exceptions -fexceptions -o %t.ll
+// RUN: FileCheck -check-prefixes=OGCG --input-file=%t.ll %s
+
+struct S {
+ S();
+ ~S();
+ operator bool();
+};
+
+S makeS();
+
+void while_cond_cleanup(int n) {
+ while (makeS())
+ --n;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z18while_cond_cleanupi
+// CIR: cir.while {
+// CIR: cir.call @_Z5makeSv()
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1ScvbEv(
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow
+// CIR: }
+// CIR: cir.condition(
+// CIR: } do {
+
+// LLVM-LABEL: define dso_local void @_Z18while_cond_cleanupi(i32 %0) {{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: call %struct.S @_Z5makeSv()
+// LLVM: invoke i1 @_ZN1ScvbEv(ptr {{.*}} %[[TMP]])
+// LLVM: to label %[[CONT:.*]] unwind label %[[UNWIND:.*]]
+// LLVM: [[CONT]]:
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: [[UNWIND]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM: cleanup
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: resume { ptr, i32 }
+// LLVM: br i1
+// LLVM: ret void
+
+// OGCG-LABEL: define dso_local void @_Z18while_cond_cleanupi(i32 %n) {{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: while.cond:
+// OGCG: %[[CALL:.*]] = invoke {{.*}} i1 @_ZN1ScvbEv(
+// OGCG: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[CONT]]:
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: br i1 %[[CALL]], label %[[BODY:.*]], label %[[END:.*]]
+// OGCG: [[BODY]]:
+// OGCG: br label %while.cond
+// OGCG: [[LPAD]]:
+// OGCG: landingpad { ptr, i32 }
+// OGCG: cleanup
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: [[END]]:
+// OGCG: ret void
+
+void do_while_cond_cleanup(int n) {
+ do {
+ --n;
+ } while (makeS());
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z21do_while_cond_cleanupi
+// CIR: cir.do {
+// CIR: cir.yield
+// CIR: } while {
+// CIR: cir.call @_Z5makeSv()
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1ScvbEv(
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow
+// CIR: }
+// CIR: cir.condition(
+
+// LLVM-LABEL: define dso_local void @_Z21do_while_cond_cleanupi(i32 %0) {{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: call %struct.S @_Z5makeSv()
+// LLVM: invoke i1 @_ZN1ScvbEv(ptr {{.*}} %[[TMP]])
+// LLVM: to label %[[CONT:.*]] unwind label %[[UNWIND:.*]]
+// LLVM: [[CONT]]:
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: [[UNWIND]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM: cleanup
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: resume { ptr, i32 }
+// LLVM: br i1
+// LLVM: ret void
+
+// OGCG-LABEL: define dso_local void @_Z21do_while_cond_cleanupi(i32 %n) {{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: do.cond:
+// OGCG: %[[CALL:.*]] = invoke {{.*}} i1 @_ZN1ScvbEv(
+// OGCG: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[CONT]]:
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: br i1 %[[CALL]]
+// OGCG: ret void
+// OGCG: [[LPAD]]:
+// OGCG: landingpad { ptr, i32 }
+// OGCG: cleanup
+// OGCG: call void @_ZN1SD1Ev(
+
+void for_cond_cleanup(int n) {
+ for (int i = 0; makeS(); ++i)
+ --n;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z16for_cond_cleanupi
+// CIR: cir.for : cond {
+// CIR: cir.call @_Z5makeSv()
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1ScvbEv(
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow
+// CIR: }
+// CIR: cir.condition(
+// CIR: } body {
+
+// LLVM-LABEL: define dso_local void @_Z16for_cond_cleanupi(i32 %0) {{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: call %struct.S @_Z5makeSv()
+// LLVM: invoke i1 @_ZN1ScvbEv(ptr {{.*}} %[[TMP]])
+// LLVM: to label %[[CONT:.*]] unwind label %[[UNWIND:.*]]
+// LLVM: [[CONT]]:
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: [[UNWIND]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM: cleanup
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: resume { ptr, i32 }
+// LLVM: br i1
+// LLVM: ret void
+
+// OGCG-LABEL: define dso_local void @_Z16for_cond_cleanupi(i32 %n) {{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: for.cond:
+// OGCG: %[[CALL:.*]] = invoke {{.*}} i1 @_ZN1ScvbEv(
+// OGCG: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[CONT]]:
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: br i1 %[[CALL]], label %[[BODY:.*]], label %[[END:.*]]
+// OGCG: [[LPAD]]:
+// OGCG: landingpad { ptr, i32 }
+// OGCG: cleanup
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: [[END]]:
+// OGCG: ret void
+
+void for_step_cleanup(int n) {
+ for (int i = 0; i < n; (void)makeS())
+ --n;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z16for_step_cleanupi
+// CIR: cir.for : cond {
+// CIR: } body {
+// CIR: } step {
+// CIR: cir.call @_Z5makeSv()
+// CIR: cir.cleanup.scope {
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow
+// CIR: }
+// CIR: }
+
+// LLVM-LABEL: define dso_local void @_Z16for_step_cleanupi(i32 %0) {{.*}} {
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: call %struct.S @_Z5makeSv()
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: br label
+// LLVM: ret void
+
+// OGCG-LABEL: define dso_local void @_Z16for_step_cleanupi(i32 %n) {{.*}} {
+// OGCG: for.inc:
+// OGCG: call void @_Z5makeSv(
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: br label %for.cond
+
+struct EndSentinel {};
+
+struct Iter {
+ int operator*();
+ Iter &operator++();
+};
+
+S operator!=(Iter, EndSentinel);
+
+struct Range {
+ Iter begin();
+ EndSentinel end();
+};
+
+Range getRange();
+
+void range_for_cond_cleanup() {
+ for (int x : getRange()) {
+ (void)x;
+ }
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22range_for_cond_cleanupv
+// CIR: cir.for : cond {
+// CIR: cir.call @_Zne4Iter11EndSentinel(
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1ScvbEv(
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow
+// CIR: }
+// CIR: cir.condition(
+// CIR: } body {
+
+// LLVM-LABEL: define dso_local void @_Z22range_for_cond_cleanupv() {{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: call %struct.S @_Zne4Iter11EndSentinel(
+// LLVM: invoke i1 @_ZN1ScvbEv(ptr {{.*}} %[[TMP]])
+// LLVM: to label %[[CONT:.*]] unwind label %[[UNWIND:.*]]
+// LLVM: [[CONT]]:
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: [[UNWIND]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM: cleanup
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: resume { ptr, i32 }
+// LLVM: br i1
+// LLVM: ret void
+
+// OGCG-LABEL: define dso_local void @_Z22range_for_cond_cleanupv() {{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: for.cond:
+// OGCG: %[[CALL:.*]] = invoke {{.*}} i1 @_ZN1ScvbEv(
+// OGCG: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[CONT]]:
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: br i1 %[[CALL]], label %[[BODY:.*]], label %[[END:.*]]
+// OGCG: [[LPAD]]:
+// OGCG: landingpad { ptr, i32 }
+// OGCG: cleanup
+// OGCG: call void @_ZN1SD1Ev(
+// OGCG: [[END]]:
+// OGCG: ret void
More information about the cfe-commits
mailing list