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 <[email protected]> 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 <[email protected]> --- 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 <[email protected]> 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 <[email protected]> --- 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) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
