https://github.com/xlauko updated 
https://github.com/llvm/llvm-project/pull/231449

>From db2d04da1a408242705d33535c180f3e7698e3ae Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Fri, 4 Sep 2026 11:20:11 +0000
Subject: [PATCH 1/2] [CIR] Fix cir.cleanup.scope successor regions

Follow-up to #220507, split out of the review of #220512.

`CleanupScopeOp::getSuccessorRegions` reported the body and the cleanup region
as two alternative entries from the parent, and every region exit as going back
to the parent. That lets the cleanup run without the body and leaves no edge
from the body to the cleanup. The comment right above the code said the
opposite.

This reports what the op actually executes. The parent enters the body. A body
exit that triggers the cleanup enters the cleanup region. The cleanup region
exits to the parent.

Whether a body exit triggers the cleanup depends on the terminator and the
cleanup kind. `cir.resume` leaves while unwinding, so it runs the cleanup when
the kind includes EH. `cir.yield` and `cir.co_return` are normal exits, so they
run it when the kind includes normal. `cir.resume` appears in an unflattened
body once an inner cleanup scope has been flattened, and `FlattenCFG` handles
that case in `collectResumeOps` under the same condition.

`RegionBranchOpInterface` can't describe an unwind from the middle of the body,
only one from a terminator. A cleanup region reachable only that way ends up
with no predecessor.

No edge carries values. The op has no results and `NoRegionArguments`, and
`ResumeOp::getMutableSuccessorOperands` forwards nothing, so
`detail::verifyRegionBranchOpInterface` sees zero operands and inputs on every
edge.

The unit test checks all four pairs of exit terminator and cleanup kind with one
helper, `expectCleanupScopeSuccessors`. For the two pairs that leave the cleanup
region without a predecessor, it calls `verifyControlFlowInterfaceConsistency`
with the new `requireAllReachable` flag set to false.

A body can exit differently from different blocks, so
`verifyControlFlowInterfaceConsistency` now iterates over
`getAllRegionBranchPoints()` instead of each region's `front()` terminator, as
`detail::verifyRegionBranchOpInterface` does. A new test relies on this for a
`cleanup normal` body that yields from one block and resumes from another.

Also drop the `none` cleanup kind, which `CIR_CleanupKind` never defined, from
the op description, along with the claim that the cleanup runs on every body
exit.

The last raw cleanup-kind enum comparisons in `FlattenCFG` move to the same
`isNormal()` and `isEH()` predicates in #221234. That PR is independent and can
land in either order.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |   9 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      |  27 ++-
 clang/unittests/CIR/ControlFlowTest.cpp      | 199 +++++++++++++++----
 3 files changed, 188 insertions(+), 47 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index aa32ada090f917..debe973dd1f9a4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1438,12 +1438,11 @@ def CIR_CleanupScopeOp : 
CIR_RegionBranchOpBase<"cleanup.scope", [
   let summary = "Represents a scope with associated cleanup code";
   let description = [{
     `cir.cleanup.scope` contains a body region and a cleanup region. The body
-    region is executed first, and the cleanup region is executed when the body
-    region is exited, either normally or due to an exception.
+    region runs first, and the cleanup region may run when the body region is
+    exited.
 
-    The cleanup kind attribute specifies when the cleanup region should be
-    executed:
-    - `none`: No cleanup (cleanup region is empty/unused)
+    The cleanup kind attribute specifies which exits from the body region run
+    the cleanup region:
     - `normal`: Cleanup is executed only on normal exit
     - `eh`: Cleanup is executed only on exception unwinding
     - `all`: Cleanup is executed on both normal exit and exception unwinding
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 0fb9f585d2228c..9679a577a48314 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2037,14 +2037,31 @@ LogicalResult cir::CleanupScopeOp::verify() {
 
 void cir::CleanupScopeOp::getSuccessorRegions(
     mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
-  if (!point.isParent()) {
-    regions.emplace_back(getOperation());
+  // Execution always starts in the body region.
+  if (point.isParent()) {
+    regions.emplace_back(&getBodyRegion());
     return;
   }
 
-  // Execution always proceeds from the body region to the cleanup region.
-  regions.push_back(RegionSuccessor(&getBodyRegion()));
-  regions.push_back(RegionSuccessor(&getCleanupRegion()));
+  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;
+    }
+  }
+
+  // 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());
 }
 
 LogicalResult cir::CleanupScopeOp::canonicalize(CleanupScopeOp op,
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp 
b/clang/unittests/CIR/ControlFlowTest.cpp
index 236f481eef8f47..e3a6d46ffdbbba 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -80,53 +80,86 @@ static void expectTerminatorSuccessors(Region &region,
 }
 
 /// Verify control flow interface consistency beyond what mlir::verify checks:
-///   - Every non-empty region is reachable
-///   - Every terminator implements RegionBranchTerminatorOpInterface
-///   - Op and terminator agree on successor regions
-static void verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op) {
+///   - Every non-empty region is reachable, unless `requireAllReachable` is
+///     false
+///   - 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) {
   EXPECT_TRUE(succeeded(mlir::verify(op)))
       << "MLIR verifier failed for '" << op->getName().getStringRef().str()
       << "'";
 
-  SmallVector<RegionSuccessor> entrySuccessors;
-  op.getSuccessorRegions(RegionBranchPoint::parent(), entrySuccessors);
-  llvm::SmallPtrSet<Region *, 4> allReachable;
-  for (auto &succ : entrySuccessors)
-    allReachable.insert(succ.getSuccessor());
-  for (Region &region : op->getRegions()) {
-    if (region.empty())
-      continue;
-    RegionBranchTerminatorOpInterface term = getTerminator(region);
+  // Walk the same branch points the verifier uses: the op itself plus one per
+  // branching block terminator. Going block by block rather than through
+  // region.front() lets a region exit differently from different blocks.
+  llvm::SmallPtrSet<Region *, 4> allReachable, regionsWithExit;
+  for (RegionBranchPoint point : op.getAllRegionBranchPoints()) {
     SmallVector<RegionSuccessor> opSuccessors;
-    op.getSuccessorRegions(region, opSuccessors);
-    for (auto &succ : opSuccessors)
+    op.getSuccessorRegions(point, opSuccessors);
+    for (RegionSuccessor &succ : opSuccessors)
       allReachable.insert(succ.getSuccessor());
 
+    if (point.isParent())
+      continue;
+
     // Op and terminator should report the same successors.
-    if (term) {
-      SmallVector<RegionSuccessor> termSuccessors;
-      term.getSuccessorRegions(/*operands=*/{}, termSuccessors);
-      SmallPtrSet<Region *, 4> opSet, termSet;
-      for (auto &s : opSuccessors)
-        opSet.insert(s.getSuccessor());
-      for (auto &s : termSuccessors)
-        termSet.insert(s.getSuccessor());
-      EXPECT_EQ(opSet, termSet)
-          << "op and terminator disagree on successors from region #"
-          << region.getRegionNumber();
-    }
+    RegionBranchTerminatorOpInterface term =
+        point.getTerminatorPredecessorOrNull();
+    regionsWithExit.insert(term->getParentRegion());
+    SmallVector<RegionSuccessor> termSuccessors;
+    term.getSuccessorRegions(/*operands=*/{}, termSuccessors);
+    SmallPtrSet<Region *, 4> opSet, termSet;
+    for (RegionSuccessor &s : opSuccessors)
+      opSet.insert(s.getSuccessor());
+    for (RegionSuccessor &s : termSuccessors)
+      termSet.insert(s.getSuccessor());
+    EXPECT_EQ(opSet, termSet)
+        << "op and terminator disagree on successors from region #"
+        << term->getParentRegion()->getRegionNumber();
   }
+
   for (Region &region : op->getRegions()) {
     if (region.empty())
       continue;
-    EXPECT_TRUE(allReachable.contains(&region))
+    EXPECT_TRUE(!requireAllReachable || allReachable.contains(&region))
         << "Region #" << region.getRegionNumber()
         << " is non-empty but not reachable from any branch point";
+    EXPECT_TRUE(regionsWithExit.contains(&region))
+        << "Region #" << region.getRegionNumber()
+        << " has no RegionBranchTerminatorOpInterface terminator";
   }
   EXPECT_TRUE(allReachable.contains(nullptr))
       << "parent (exit) not reachable from any branch point";
 }
 
+/// 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.
+static void expectCleanupScopeSuccessors(cir::CleanupScopeOp op,
+                                         bool bodyRunsCleanup) {
+  Region &body = op.getBodyRegion();
+  Region &cleanup = op.getCleanupRegion();
+  expectSuccessors(op, RegionBranchPoint::parent(), {&body});
+
+  RegionBranchTerminatorOpInterface bodyTerm = getTerminator(body);
+  ASSERT_TRUE(bodyTerm);
+  expectSuccessors(op, RegionBranchPoint(bodyTerm),
+                   {bodyRunsCleanup ? &cleanup : nullptr});
+  expectTerminatorSuccessors(cleanup, {nullptr});
+
+  RegionBranchOpInterface branch = op;
+  EXPECT_FALSE(branch.isRepetitiveRegion(0));
+  EXPECT_FALSE(branch.isRepetitiveRegion(1));
+  EXPECT_FALSE(branch.hasLoop());
+
+  // Without the body edge nothing reaches the cleanup region.
+  verifyControlFlowInterfaceConsistency(
+      op,
+      /*requireAllReachable=*/bodyRunsCleanup);
+}
+
 
//===----------------------------------------------------------------------===//
 // Test fixture
 
//===----------------------------------------------------------------------===//
@@ -513,7 +546,7 @@ TEST_F(CIRControlFlowTest, ForOpWithCleanup) {
   verifyControlFlowInterfaceConsistency(forOp);
 }
 
-TEST_F(CIRControlFlowTest, CleanupScopeOp) {
+TEST_F(CIRControlFlowTest, CleanupScopeOpYieldAll) {
   OwningOpRef<ModuleOp> module = parse(R"CIR(
     cir.func @f() {
       cir.cleanup.scope {
@@ -524,19 +557,111 @@ TEST_F(CIRControlFlowTest, CleanupScopeOp) {
       cir.return
     }
   )CIR");
+  // A cleanup that covers normal exits runs on the body's cir.yield.
+  expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
+                               /*bodyRunsCleanup=*/true);
+}
+
+TEST_F(CIRControlFlowTest, CleanupScopeOpYieldEH) {
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    cir.func @f() {
+      cir.cleanup.scope {
+        cir.yield
+      } cleanup eh {
+        cir.yield
+      }
+      cir.return
+    }
+  )CIR");
+  // An EH-only cleanup is skipped by the body's normal cir.yield.
+  expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
+                               /*bodyRunsCleanup=*/false);
+}
+
+TEST_F(CIRControlFlowTest, CleanupScopeOpResumeEH) {
+  // A cir.resume left behind in the body by an already-flattened inner
+  // cleanup scope exits the body while unwinding.
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    cir.func @f() {
+      cir.cleanup.scope {
+        %tok = cir.eh.initiate cleanup : !cir.eh_token
+        cir.resume %tok : !cir.eh_token
+      } cleanup eh {
+        cir.yield
+      }
+      cir.return
+    }
+  )CIR");
+  // An EH-only cleanup runs on the body's unwinding cir.resume.
+  expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
+                               /*bodyRunsCleanup=*/true);
+}
+
+TEST_F(CIRControlFlowTest, CleanupScopeOpResumeNormal) {
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    cir.func @f() {
+      cir.cleanup.scope {
+        %tok = cir.eh.initiate cleanup : !cir.eh_token
+        cir.resume %tok : !cir.eh_token
+      } cleanup normal {
+        cir.yield
+      }
+      cir.return
+    }
+  )CIR");
+  // A normal-only cleanup is skipped by the body's unwinding cir.resume.
+  expectCleanupScopeSuccessors(findFirstOp<cir::CleanupScopeOp>(*module),
+                               /*bodyRunsCleanup=*/false);
+}
+
+TEST_F(CIRControlFlowTest, CleanupScopeOpMultiBlockBodyNormal) {
+  // One block leaves the body normally and another unwinds, which is what
+  // FlattenCFG produces once an inner cleanup scope has been flattened. Under
+  // `cleanup normal` the two exits disagree, so this is the case that needs a
+  // branch point per block rather than one per region.
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    cir.func @f() {
+      cir.cleanup.scope {
+        %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
+      } cleanup normal {
+        cir.yield
+      }
+      cir.return
+    }
+  )CIR");
   auto cleanupScopeOp = findFirstOp<cir::CleanupScopeOp>(*module);
 
-  expectSuccessors(
-      cleanupScopeOp, RegionBranchPoint::parent(),
-      {&cleanupScopeOp.getBodyRegion(), &cleanupScopeOp.getCleanupRegion()});
-  expectTerminatorSuccessors(cleanupScopeOp.getBodyRegion(), {nullptr});
+  expectSuccessors(cleanupScopeOp, RegionBranchPoint::parent(),
+                   {&cleanupScopeOp.getBodyRegion()});
+
+  // 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());
+  ASSERT_EQ(bodyTerms.size(), 2u);
+  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.
+  expectSuccessors(cleanupScopeOp, RegionBranchPoint(bodyTerms[0]),
+                   {&cleanupScopeOp.getCleanupRegion()});
+  expectSuccessors(cleanupScopeOp, RegionBranchPoint(bodyTerms[1]), {nullptr});
   expectTerminatorSuccessors(cleanupScopeOp.getCleanupRegion(), {nullptr});
 
-  RegionBranchOpInterface cleanupBranch = asRegionBranch(cleanupScopeOp);
-  EXPECT_FALSE(cleanupBranch.isRepetitiveRegion(0));
-  EXPECT_FALSE(cleanupBranch.isRepetitiveRegion(1));
-  EXPECT_FALSE(cleanupBranch.hasLoop());
+  EXPECT_FALSE(asRegionBranch(cleanupScopeOp).hasLoop());
 
+  // The cleanup region does have a predecessor here, via the normal exit.
   verifyControlFlowInterfaceConsistency(cleanupScopeOp);
 }
 

>From 34443b4027d7c7ad05736b74ec4b9a49f878f3d7 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` listed the try region and every handler as
entries from the parent, so a handler could run without the try body. Now
the parent enters the try region, a `cir.resume` ending the try region
unwinds into the handlers, and every other exit leaves the op. This matches
`CIRTryOpFlattening`, which branches such resumes to the handler dispatch
block. A `cir.try` with no handlers has nowhere to unwind to, so a
`cir.resume` in its body leaves the op.

The interface only describes exits through a region terminator. A throw from
a call inside the try body is not one, so it gets no edge, and a handler
reached only that way has no predecessor. The catch-all unit test therefore
runs `verifyControlFlowInterfaceConsistency` with `requireAllReachable` set
to false, which replaces its TODO.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp |  28 +++--
 clang/unittests/CIR/ControlFlowTest.cpp | 140 +++++++++++++++++++++---
 2 files changed, 147 insertions(+), 21 deletions(-)

diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 9679a577a48314..3f748d69aa29f2 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -766,7 +766,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);
 }
 
@@ -5120,18 +5120,30 @@ LogicalResult cir::TypeInfoAttr::verify(
 void cir::TryOp::getSuccessorRegions(
     mlir::RegionBranchPoint point,
     llvm::SmallVectorImpl<mlir::RegionSuccessor> &regions) {
-  // The `try` and the `catchers` region branch back to the parent operation.
-  if (!point.isParent()) {
-    regions.emplace_back(getOperation());
+  // Execution always starts in the try region.
+  if (point.isParent()) {
+    regions.emplace_back(&getTryRegion());
     return;
   }
 
-  regions.push_back(mlir::RegionSuccessor(&getTryRegion()));
-
+  // A cir.resume ending the try region unwinds into the handlers, and the
+  // exception type decides which one runs.
+  //
   // 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));
+  mlir::Operation *term = point.getTerminatorPredecessorOrNull();
+  assert(term && "expected a terminator predecessor");
+  if (term->getParentRegion() == &getTryRegion() && isa<cir::ResumeOp>(term) &&
+      !getHandlerRegions().empty()) {
+    for (mlir::Region &handlerRegion : getHandlerRegions())
+      regions.emplace_back(&handlerRegion);
+    return;
+  }
+
+  // Every other exit leaves the operation. An unwind out of the middle of the
+  // try region is not a RegionBranchPoint, so a handler reached only that way
+  // has no predecessor here.
+  regions.emplace_back(getOperation());
 }
 
 /// Verify that each cir.init_catch_param in \p handlerRegion, outside any
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp 
b/clang/unittests/CIR/ControlFlowTest.cpp
index e3a6d46ffdbbba..1da98b88fce587 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -67,6 +67,19 @@ static RegionBranchTerminatorOpInterface 
getTerminator(Region &region) {
   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 &region) {
+  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 &region,
                                        ArrayRef<Region *> expected) {
@@ -640,15 +653,8 @@ TEST_F(CIRControlFlowTest, 
CleanupScopeOpMultiBlockBodyNormal) {
   expectSuccessors(cleanupScopeOp, RegionBranchPoint::parent(),
                    {&cleanupScopeOp.getBodyRegion()});
 
-  // 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]));
@@ -727,15 +733,123 @@ TEST_F(CIRControlFlowTest, TryOpWithCatchAll) {
   MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
   ASSERT_EQ(handlerRegions.size(), 1u);
 
-  expectSuccessors(tryOp, RegionBranchPoint::parent(),
-                   {&tryRegion, &handlerRegions[0]});
+  // The body's cir.yield is a normal exit, so it skips the handler.
+  expectSuccessors(tryOp, RegionBranchPoint::parent(), {&tryRegion});
   expectTerminatorSuccessors(tryRegion, {nullptr});
   expectTerminatorSuccessors(handlerRegions[0], {nullptr});
 
   EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
 
-  // TODO: TryOp::getSuccessorInputs returns empty for handler regions that
-  // have block arguments, so verifyControlFlowInterfaceConsistency fails.
+  // Only a throw from the middle of the body reaches the handler, and that is
+  // not a branch point, so the handler region has no predecessor.
+  verifyControlFlowInterfaceConsistency(tryOp, /*requireAllReachable=*/false);
+}
+
+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});
+  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());
+
+  verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpMultiBlockBody) {
+  // One block leaves the try body normally and another unwinds, so only the
+  // unwinding exit reaches the handler.
+  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]), {nullptr});
+  expectSuccessors(tryOp, RegionBranchPoint(bodyTerms[1]), {&handler});
+  expectTerminatorSuccessors(handler, {nullptr});
+
+  EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
+
+  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

Reply via email to