[clang] [CIR] Fix cir.try successor regions (PR #231449)
Henrich Lauko via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 15 00:53:03 PDT 2026
https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/231449
>From a4bd5965bf9d92ee00c50e8f7372689e5edf827f Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Thu, 15 Oct 2026 07:46:35 +0000
Subject: [PATCH 1/2] [CIR] Keep cir.cleanup.scope cleanup regions reachable
RegionBranchOpInterface::getSuccessorRegions must return every region that
may run next, but an unwind out of a call in the body, or a cir.return,
cir.break, cir.continue or cir.goto out of it, runs the cleanup without
passing a region branch point. A cleanup reached only that way had no
predecessor, so generic passes treated it as dead, and -remove-dead-values
erased an EH cleanup that stores to memory. Now the cleanup region is a
successor of the parent and of every body exit, and a body exit that the
cleanup kind does not cover can also leave the op.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 36 ++++++-------
.../remove-dead-values-cleanup-scope.cir | 33 ++++++++++++
clang/unittests/CIR/ControlFlowTest.cpp | 50 ++++++++++---------
3 files changed, 79 insertions(+), 40 deletions(-)
create mode 100644 clang/test/CIR/Transforms/remove-dead-values-cleanup-scope.cir
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index f564eee053705..0e5e58a67768d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2045,31 +2045,33 @@ LogicalResult cir::CleanupScopeOp::verify() {
void cir::CleanupScopeOp::getSuccessorRegions(
mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> ®ions) {
- // Execution always starts in the body region.
+ // The interface has no branch point for the paths that run the cleanup from
+ // the middle of the body: an unwind out of a call, or a cir.return,
+ // cir.break, cir.continue or cir.goto. The cleanup region is therefore a
+ // potential successor of every branch point in the body as well as of the
+ // parent. The edge from the parent stands in for such a path taken before
+ // the body has any effect, and the edges from body exits for one taken
+ // after some of its effects.
if (point.isParent()) {
regions.emplace_back(&getBodyRegion());
+ regions.emplace_back(&getCleanupRegion());
return;
}
mlir::Operation *term = point.getTerminatorPredecessorOrNull();
- assert(term && "expected a terminator predecessor");
-
- if (term->getParentRegion() == &getBodyRegion()) {
- // cir.resume unwinds out of the body. Every other terminator, such as
- // cir.yield or cir.co_return, is a normal exit.
- cir::CleanupKindAttr kind = getCleanupKindAttr();
- if (isa<cir::ResumeOp>(term) ? kind.isEH() : kind.isNormal()) {
- regions.emplace_back(&getCleanupRegion());
- return;
- }
+ if (term->getParentRegion() == &getCleanupRegion()) {
+ regions.emplace_back(getOperation());
+ return;
}
- // Exiting the cleanup region, or a body exit that skips it, leaves the
- // operation. An unwind out of the middle of the body is not a
- // RegionBranchPoint, and neither are cir.return, cir.break, cir.continue or
- // cir.goto, so a cleanup region reached only that way has no predecessor
- // here.
- regions.emplace_back(getOperation());
+ regions.emplace_back(&getCleanupRegion());
+
+ // cir.resume unwinds out of the body. Every other terminator, such as
+ // cir.yield or cir.co_return, is a normal exit. An exit that the cleanup
+ // kind does not cover can also leave the operation directly.
+ cir::CleanupKindAttr kind = getCleanupKindAttr();
+ if (isa<cir::ResumeOp>(term) ? !kind.isEH() : !kind.isNormal())
+ regions.emplace_back(getOperation());
}
LogicalResult cir::CleanupScopeOp::canonicalize(CleanupScopeOp op,
diff --git a/clang/test/CIR/Transforms/remove-dead-values-cleanup-scope.cir b/clang/test/CIR/Transforms/remove-dead-values-cleanup-scope.cir
new file mode 100644
index 0000000000000..5aebe22a04739
--- /dev/null
+++ b/clang/test/CIR/Transforms/remove-dead-values-cleanup-scope.cir
@@ -0,0 +1,33 @@
+// RUN: cir-opt %s -remove-dead-values -o - | FileCheck %s
+
+// The EH cleanup runs only when @may_throw unwinds, which is not a region
+// branch point. The cleanup region must still count as reachable, or generic
+// passes such as -remove-dead-values erase it.
+
+!s32i = !cir.int<s, 32>
+
+module {
+ cir.func private @may_throw()
+
+ cir.func @eh_cleanup(%p : !cir.ptr<!s32i>) {
+ cir.cleanup.scope {
+ cir.call @may_throw() : () -> ()
+ cir.yield
+ } cleanup eh {
+ %c = cir.const #cir.int<7> : !s32i
+ cir.store %c, %p : !s32i, !cir.ptr<!s32i>
+ cir.yield
+ }
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func @eh_cleanup
+ // CHECK: cir.cleanup.scope {
+ // CHECK-NEXT: cir.call @may_throw() : () -> ()
+ // CHECK-NEXT: cir.yield
+ // CHECK-NEXT: } cleanup eh {
+ // CHECK-NEXT: %[[C:.*]] = cir.const #cir.int<7> : !s32i
+ // CHECK-NEXT: cir.store %[[C]], %{{.*}} : !s32i, !cir.ptr<!s32i>
+ // CHECK-NEXT: cir.yield
+ // CHECK-NEXT: }
+}
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp
index e3a6d46ffdbbb..b12001c30cc90 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -80,13 +80,10 @@ static void expectTerminatorSuccessors(Region ®ion,
}
/// Verify control flow interface consistency beyond what mlir::verify checks:
-/// - Every non-empty region is reachable, unless `requireAllReachable` is
-/// false
+/// - Every non-empty region is reachable
/// - Every non-empty region has at least one branching terminator
/// - Op and terminator agree on successor regions at every branch point
-static void
-verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op,
- bool requireAllReachable = true) {
+static void verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op) {
EXPECT_TRUE(succeeded(mlir::verify(op)))
<< "MLIR verifier failed for '" << op->getName().getStringRef().str()
<< "'";
@@ -123,7 +120,7 @@ verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op,
for (Region ®ion : op->getRegions()) {
if (region.empty())
continue;
- EXPECT_TRUE(!requireAllReachable || allReachable.contains(®ion))
+ EXPECT_TRUE(allReachable.contains(®ion))
<< "Region #" << region.getRegionNumber()
<< " is non-empty but not reachable from any branch point";
EXPECT_TRUE(regionsWithExit.contains(®ion))
@@ -135,18 +132,21 @@ verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op,
}
/// Check a cir.cleanup.scope whose body has a single exit. The parent enters
-/// the body, the body exit enters the cleanup region if `bodyRunsCleanup` and
-/// leaves the op otherwise, and the cleanup region leaves the op.
+/// the body or the cleanup region. The body exit enters the cleanup region,
+/// and also leaves the op unless `bodyRunsCleanup`. The cleanup region leaves
+/// the op.
static void expectCleanupScopeSuccessors(cir::CleanupScopeOp op,
bool bodyRunsCleanup) {
Region &body = op.getBodyRegion();
Region &cleanup = op.getCleanupRegion();
- expectSuccessors(op, RegionBranchPoint::parent(), {&body});
+ expectSuccessors(op, RegionBranchPoint::parent(), {&body, &cleanup});
RegionBranchTerminatorOpInterface bodyTerm = getTerminator(body);
ASSERT_TRUE(bodyTerm);
- expectSuccessors(op, RegionBranchPoint(bodyTerm),
- {bodyRunsCleanup ? &cleanup : nullptr});
+ if (bodyRunsCleanup)
+ expectSuccessors(op, RegionBranchPoint(bodyTerm), {&cleanup});
+ else
+ expectSuccessors(op, RegionBranchPoint(bodyTerm), {&cleanup, nullptr});
expectTerminatorSuccessors(cleanup, {nullptr});
RegionBranchOpInterface branch = op;
@@ -154,10 +154,7 @@ static void expectCleanupScopeSuccessors(cir::CleanupScopeOp op,
EXPECT_FALSE(branch.isRepetitiveRegion(1));
EXPECT_FALSE(branch.hasLoop());
- // Without the body edge nothing reaches the cleanup region.
- verifyControlFlowInterfaceConsistency(
- op,
- /*requireAllReachable=*/bodyRunsCleanup);
+ verifyControlFlowInterfaceConsistency(op);
}
//===----------------------------------------------------------------------===//
@@ -573,7 +570,8 @@ TEST_F(CIRControlFlowTest, CleanupScopeOpYieldEH) {
cir.return
}
)CIR");
- // An EH-only cleanup is skipped by the body's normal cir.yield.
+ // An EH-only cleanup does not run on the body's normal cir.yield, but stays
+ // a successor for an unwind from the middle of the body.
expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
/*bodyRunsCleanup=*/false);
}
@@ -609,7 +607,8 @@ TEST_F(CIRControlFlowTest, CleanupScopeOpResumeNormal) {
cir.return
}
)CIR");
- // A normal-only cleanup is skipped by the body's unwinding cir.resume.
+ // A normal-only cleanup does not run on the body's unwinding cir.resume, but
+ // stays a successor for a cir.return or similar from the middle of the body.
expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
/*bodyRunsCleanup=*/false);
}
@@ -637,8 +636,9 @@ TEST_F(CIRControlFlowTest, CleanupScopeOpMultiBlockBodyNormal) {
)CIR");
auto cleanupScopeOp = findFirstOp<cir::CleanupScopeOp>(*module);
- expectSuccessors(cleanupScopeOp, RegionBranchPoint::parent(),
- {&cleanupScopeOp.getBodyRegion()});
+ expectSuccessors(
+ cleanupScopeOp, RegionBranchPoint::parent(),
+ {&cleanupScopeOp.getBodyRegion(), &cleanupScopeOp.getCleanupRegion()});
// The cir.brcond block is not a branch point, so only the two exiting
// blocks show up here.
@@ -653,15 +653,19 @@ TEST_F(CIRControlFlowTest, CleanupScopeOpMultiBlockBodyNormal) {
EXPECT_TRUE(isa<cir::YieldOp>(*bodyTerms[0]));
EXPECT_TRUE(isa<cir::ResumeOp>(*bodyTerms[1]));
- // The normal exit runs the cleanup, the unwind exit skips it.
+ // The normal exit runs the cleanup. The unwind exit can also leave the op.
expectSuccessors(cleanupScopeOp, RegionBranchPoint(bodyTerms[0]),
{&cleanupScopeOp.getCleanupRegion()});
- expectSuccessors(cleanupScopeOp, RegionBranchPoint(bodyTerms[1]), {nullptr});
+ expectSuccessors(cleanupScopeOp, RegionBranchPoint(bodyTerms[1]),
+ {&cleanupScopeOp.getCleanupRegion(), nullptr});
expectTerminatorSuccessors(cleanupScopeOp.getCleanupRegion(), {nullptr});
- EXPECT_FALSE(asRegionBranch(cleanupScopeOp).hasLoop());
+ // hasLoop() reports a loop whenever two exits reach the same region, so
+ // check that no region can reach itself instead.
+ RegionBranchOpInterface branch = asRegionBranch(cleanupScopeOp);
+ EXPECT_FALSE(branch.isRepetitiveRegion(0));
+ EXPECT_FALSE(branch.isRepetitiveRegion(1));
- // The cleanup region does have a predecessor here, via the normal exit.
verifyControlFlowInterfaceConsistency(cleanupScopeOp);
}
>From 5f324c990151fb1c3be8d0608d022ffd90d4869a Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Tue, 13 Oct 2026 10:40:32 +0000
Subject: [PATCH 2/2] [CIR] Fix cir.try successor regions
`TryOp::getSuccessorRegions` sent every exit from the try region back to the
parent, so nothing led from the try region to the handlers. A `cir.resume`
ending the try region, left there once FlattenCFG has flattened an inner
cleanup scope, unwinds into a handler, and `CIRTryOpFlattening` branches it
to the handler dispatch block. Now every exit from the try region can enter
the handlers, and a `cir.resume` leaves the op only when there are none. The
parent keeps its edges to the handlers. An unwind out of a call in the try
region is not a region branch point, so these edges stand in for it, and
without them -remove-dead-values empties a handler that is reached only that
way.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 38 ++++-
.../CIR/Transforms/remove-dead-values-try.cir | 40 +++++
clang/unittests/CIR/ControlFlowTest.cpp | 143 ++++++++++++++++--
3 files changed, 201 insertions(+), 20 deletions(-)
create mode 100644 clang/test/CIR/Transforms/remove-dead-values-try.cir
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 0e5e58a67768d..30f39f07c51b8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -777,7 +777,7 @@ cir::ConditionOp::getMutableSuccessorOperands(RegionSuccessor point) {
MutableOperandRange
cir::ResumeOp::getMutableSuccessorOperands(RegionSuccessor point) {
- // The eh_token operand is not forwarded to the parent region.
+ // The eh_token operand is not modeled as forwarded to any successor.
return MutableOperandRange(getOperation(), 0, 0);
}
@@ -5109,18 +5109,40 @@ LogicalResult cir::TypeInfoAttr::verify(
void cir::TryOp::getSuccessorRegions(
mlir::RegionBranchPoint point,
llvm::SmallVectorImpl<mlir::RegionSuccessor> ®ions) {
- // The `try` and the `catchers` region branch back to the parent operation.
- if (!point.isParent()) {
+ // The interface has no branch point for an unwind out of a call in the try
+ // region, which is how most handlers are entered. The handlers are
+ // therefore potential successors of the parent and of every exit from the
+ // try region. The edge from the parent stands in for a throw before the try
+ // region has any effect, and the edges from its exits for one after some of
+ // its effects.
+ //
+ // TODO(CIR): If we know a target function never throws a specific type, we
+ // can remove the catch handler.
+ auto addHandlers = [&] {
+ for (mlir::Region &handlerRegion : getHandlerRegions())
+ regions.emplace_back(&handlerRegion);
+ };
+
+ if (point.isParent()) {
+ regions.emplace_back(&getTryRegion());
+ addHandlers();
+ return;
+ }
+
+ // A handler exit leaves the operation.
+ mlir::Operation *term = point.getTerminatorPredecessorOrNull();
+ if (term->getParentRegion() != &getTryRegion()) {
regions.emplace_back(getOperation());
return;
}
- regions.push_back(mlir::RegionSuccessor(&getTryRegion()));
+ addHandlers();
- // TODO(CIR): If we know a target function never throws a specific type, we
- // can remove the catch handler.
- for (mlir::Region &handlerRegion : this->getHandlerRegions())
- regions.push_back(mlir::RegionSuccessor(&handlerRegion));
+ // A cir.resume ending the try region unwinds into a handler. It leaves the
+ // operation only when there is no handler to unwind into. Every other exit
+ // can leave the operation.
+ if (!isa<cir::ResumeOp>(term) || getHandlerRegions().empty())
+ regions.emplace_back(getOperation());
}
/// Verify that each cir.init_catch_param in \p handlerRegion, outside any
diff --git a/clang/test/CIR/Transforms/remove-dead-values-try.cir b/clang/test/CIR/Transforms/remove-dead-values-try.cir
new file mode 100644
index 0000000000000..446f0355183b0
--- /dev/null
+++ b/clang/test/CIR/Transforms/remove-dead-values-try.cir
@@ -0,0 +1,40 @@
+// RUN: cir-opt %s -remove-dead-values -o - | FileCheck %s
+
+// The handler runs only when @may_throw throws, which is not a region branch
+// point. The handler region must still count as reachable, or generic passes
+// such as -remove-dead-values erase its contents.
+
+!s32i = !cir.int<s, 32>
+!void = !cir.void
+
+module {
+ cir.func private @may_throw()
+
+ cir.func @catch_all(%p : !cir.ptr<!s32i>) {
+ cir.scope {
+ cir.try {
+ cir.call @may_throw() : () -> ()
+ cir.yield
+ } catch all (%eh : !cir.eh_token) {
+ %ct, %exn = cir.begin_catch %eh : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+ %c = cir.const #cir.int<7> : !s32i
+ cir.store %c, %p : !s32i, !cir.ptr<!s32i>
+ cir.end_catch %ct : !cir.catch_token
+ cir.yield
+ }
+ }
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func @catch_all
+ // CHECK: cir.try {
+ // CHECK-NEXT: cir.call @may_throw() : () -> ()
+ // CHECK-NEXT: cir.yield
+ // CHECK-NEXT: } catch all (%[[EH:.*]]: !cir.eh_token) {
+ // CHECK-NEXT: %[[CT:.*]], %{{.*}} = cir.begin_catch %[[EH]]
+ // CHECK-NEXT: %[[C:.*]] = cir.const #cir.int<7> : !s32i
+ // CHECK-NEXT: cir.store %[[C]], %{{.*}} : !s32i, !cir.ptr<!s32i>
+ // CHECK-NEXT: cir.end_catch %[[CT]] : !cir.catch_token
+ // CHECK-NEXT: cir.yield
+ // CHECK-NEXT: }
+}
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp
index b12001c30cc90..0481f4b87290e 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -67,6 +67,19 @@ static RegionBranchTerminatorOpInterface getTerminator(Region ®ion) {
return branchTerm;
}
+/// Return the RegionBranchTerminatorOpInterface terminators of `region`'s
+/// blocks, in block order. These are the region's branch points. A block that
+/// ends in a plain branch such as cir.brcond is not one and is skipped.
+static SmallVector<RegionBranchTerminatorOpInterface>
+getExitTerminators(Region ®ion) {
+ SmallVector<RegionBranchTerminatorOpInterface> terms;
+ for (Block &block : region)
+ if (auto term =
+ dyn_cast<RegionBranchTerminatorOpInterface>(block.getTerminator()))
+ terms.push_back(term);
+ return terms;
+}
+
/// Check that the terminator of `region` has the given successor regions.
static void expectTerminatorSuccessors(Region ®ion,
ArrayRef<Region *> expected) {
@@ -640,15 +653,8 @@ TEST_F(CIRControlFlowTest, CleanupScopeOpMultiBlockBodyNormal) {
cleanupScopeOp, RegionBranchPoint::parent(),
{&cleanupScopeOp.getBodyRegion(), &cleanupScopeOp.getCleanupRegion()});
- // The cir.brcond block is not a branch point, so only the two exiting
- // blocks show up here.
- SmallVector<RegionBranchTerminatorOpInterface> bodyTerms;
- for (RegionBranchPoint point :
- asRegionBranch(cleanupScopeOp).getAllRegionBranchPoints())
- if (!point.isParent() &&
- point.getTerminatorPredecessorOrNull()->getParentRegion() ==
- &cleanupScopeOp.getBodyRegion())
- bodyTerms.push_back(point.getTerminatorPredecessorOrNull());
+ SmallVector<RegionBranchTerminatorOpInterface> bodyTerms =
+ getExitTerminators(cleanupScopeOp.getBodyRegion());
ASSERT_EQ(bodyTerms.size(), 2u);
EXPECT_TRUE(isa<cir::YieldOp>(*bodyTerms[0]));
EXPECT_TRUE(isa<cir::ResumeOp>(*bodyTerms[1]));
@@ -731,15 +737,128 @@ TEST_F(CIRControlFlowTest, TryOpWithCatchAll) {
MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
ASSERT_EQ(handlerRegions.size(), 1u);
+ // A throw from the middle of the body is not a branch point, so the handler
+ // is a successor of both the parent and the body's cir.yield.
expectSuccessors(tryOp, RegionBranchPoint::parent(),
{&tryRegion, &handlerRegions[0]});
- expectTerminatorSuccessors(tryRegion, {nullptr});
+ expectTerminatorSuccessors(tryRegion, {&handlerRegions[0], nullptr});
+ expectTerminatorSuccessors(handlerRegions[0], {nullptr});
+
+ EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
+
+ verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpResumeInBody) {
+ // A cir.resume left behind in the body by an already-flattened inner
+ // cleanup scope unwinds into the handlers.
+ OwningOpRef<ModuleOp> module = parse(R"CIR(
+ !u8i = !cir.int<u, 8>
+ !void = !cir.void
+ cir.func @f() {
+ cir.scope {
+ cir.try {
+ %tok = cir.eh.initiate cleanup : !cir.eh_token
+ cir.resume %tok : !cir.eh_token
+ } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>]
+ (%eh : !cir.eh_token) {
+ %ct, %exn = cir.begin_catch %eh
+ : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+ cir.end_catch %ct : !cir.catch_token
+ cir.yield
+ } unwind (%eh : !cir.eh_token) {
+ cir.resume %eh : !cir.eh_token
+ }
+ }
+ cir.return
+ }
+ )CIR");
+ auto tryOp = findFirstOp<cir::TryOp>(*module);
+
+ Region &tryRegion = tryOp.getTryRegion();
+ MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
+ ASSERT_EQ(handlerRegions.size(), 2u);
+
+ expectSuccessors(tryOp, RegionBranchPoint::parent(),
+ {&tryRegion, &handlerRegions[0], &handlerRegions[1]});
+ expectTerminatorSuccessors(tryRegion,
+ {&handlerRegions[0], &handlerRegions[1]});
+ // A cir.resume in the unwind handler leaves the op like any handler exit.
expectTerminatorSuccessors(handlerRegions[0], {nullptr});
+ expectTerminatorSuccessors(handlerRegions[1], {nullptr});
EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
- // TODO: TryOp::getSuccessorInputs returns empty for handler regions that
- // have block arguments, so verifyControlFlowInterfaceConsistency fails.
+ verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpMultiBlockBody) {
+ // One block leaves the try body normally and another unwinds. Only the
+ // normal exit can leave the op.
+ OwningOpRef<ModuleOp> module = parse(R"CIR(
+ !void = !cir.void
+ cir.func @f() {
+ cir.scope {
+ cir.try {
+ %cond = cir.const #cir.bool<true> : !cir.bool
+ cir.brcond %cond ^bb_normal, ^bb_unwind
+ ^bb_normal:
+ cir.yield
+ ^bb_unwind:
+ %tok = cir.eh.initiate cleanup : !cir.eh_token
+ cir.resume %tok : !cir.eh_token
+ } catch all (%eh : !cir.eh_token) {
+ %ct, %exn = cir.begin_catch %eh
+ : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+ cir.end_catch %ct : !cir.catch_token
+ cir.yield
+ }
+ }
+ cir.return
+ }
+ )CIR");
+ auto tryOp = findFirstOp<cir::TryOp>(*module);
+ Region &tryRegion = tryOp.getTryRegion();
+ Region &handler = tryOp.getHandlerRegions()[0];
+
+ SmallVector<RegionBranchTerminatorOpInterface> bodyTerms =
+ getExitTerminators(tryRegion);
+ ASSERT_EQ(bodyTerms.size(), 2u);
+ EXPECT_TRUE(isa<cir::YieldOp>(*bodyTerms[0]));
+ EXPECT_TRUE(isa<cir::ResumeOp>(*bodyTerms[1]));
+
+ expectSuccessors(tryOp, RegionBranchPoint(bodyTerms[0]), {&handler, nullptr});
+ expectSuccessors(tryOp, RegionBranchPoint(bodyTerms[1]), {&handler});
+ expectTerminatorSuccessors(handler, {nullptr});
+
+ // hasLoop() reports a loop whenever two exits reach the same region, so
+ // check that no region can reach itself instead.
+ RegionBranchOpInterface branch = asRegionBranch(tryOp);
+ EXPECT_FALSE(branch.isRepetitiveRegion(0));
+ EXPECT_FALSE(branch.isRepetitiveRegion(1));
+
+ verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpResumeInBodyWithoutHandlers) {
+ OwningOpRef<ModuleOp> module = parse(R"CIR(
+ cir.func @f() {
+ cir.scope {
+ cir.try {
+ %tok = cir.eh.initiate cleanup : !cir.eh_token
+ cir.resume %tok : !cir.eh_token
+ }
+ }
+ cir.return
+ }
+ )CIR");
+ auto tryOp = findFirstOp<cir::TryOp>(*module);
+
+ // With no handler to unwind into, the cir.resume leaves the op.
+ expectSuccessors(tryOp, RegionBranchPoint::parent(), {&tryOp.getTryRegion()});
+ expectTerminatorSuccessors(tryOp.getTryRegion(), {nullptr});
+
+ verifyControlFlowInterfaceConsistency(tryOp);
}
TEST_F(CIRControlFlowTest, CoroutineOp) {
More information about the cfe-commits
mailing list