[Mlir-commits] [mlir] [mlir] Fix false positive loops in RegionBranchOpInterface::hasLoop (PR #233784)

Henrich Lauko llvmlistbot at llvm.org
Wed Oct 21 01:44:03 PDT 2026


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

>From 16010ab9b5786c84c69adc3e18e793eff2f47241 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Tue, 20 Oct 2026 10:20:53 +0000
Subject: [PATCH 1/2] [mlir] Fix false positive loops in
 RegionBranchOpInterface::hasLoop

hasLoop reported a loop whenever its traversal popped a region it had
already visited. The worklist gets one entry per successor of each
terminator, so a region reachable along two paths was popped twice even
without a cycle, for example when two terminators of a region branch to
the same successor region.

hasLoop now reports a loop only if a region reachable from the parent op
can reach itself, which is the check isRepetitiveRegion already does.
Drop the now unused visited argument of the traversal stop condition.

Assisted-by: Claude Code (Claude Opus 5.5)
---
 mlir/lib/Interfaces/ControlFlowInterfaces.cpp | 53 ++++--------
 .../Interfaces/ControlFlowInterfacesTest.cpp  | 84 ++++++++++++++++++-
 2 files changed, 99 insertions(+), 38 deletions(-)

diff --git a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
index c2a2f7e2561096..060cb99b3a51c7 100644
--- a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
+++ b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
@@ -222,10 +222,8 @@ LogicalResult detail::verifyRegionBranchOpInterface(Operation *op) {
 }
 
 /// Stop condition for `traverseRegionGraph`. The traversal is interrupted if
-/// this function returns "true" for a successor region. The first parameter is
-/// the successor region. The second parameter indicates all already visited
-/// regions.
-using StopConditionFn = function_ref<bool(Region *, ArrayRef<bool> visited)>;
+/// this function returns "true" for a successor region.
+using StopConditionFn = function_ref<bool(Region *)>;
 
 /// Traverse the region graph starting at `begin`. The traversal is interrupted
 /// if `stopCondition` evaluates to "true" for a successor region. In that case,
@@ -280,7 +278,7 @@ static bool traverseRegionGraph(Region *begin,
     LDBG() << "Processing region #" << nextRegion->getRegionNumber()
            << " from worklist (remaining: " << worklist.size() << ")";
 
-    if (stopConditionFn(nextRegion, visited)) {
+    if (stopConditionFn(nextRegion)) {
       LDBG() << "Stop condition met for region #"
              << nextRegion->getRegionNumber() << ", returning true";
       return true;
@@ -309,11 +307,10 @@ static bool traverseRegionGraph(Region *begin,
 static bool isRegionReachable(Region *begin, Region *r) {
   assert(begin->getParentOp() == r->getParentOp() &&
          "expected that both regions belong to the same op");
-  return traverseRegionGraph(begin,
-                             [&](Region *nextRegion, ArrayRef<bool> visited) {
-                               // Interrupt traversal if `r` was reached.
-                               return nextRegion == r;
-                             });
+  return traverseRegionGraph(begin, [&](Region *nextRegion) {
+    // Interrupt traversal if `r` was reached.
+    return nextRegion == r;
+  });
 }
 
 /// Return `true` if `a` and `b` are in mutually exclusive regions.
@@ -405,35 +402,17 @@ bool RegionBranchOpInterface::hasLoop() {
   LDBG() << "Checking if operation " << getOperation()->getName()
          << " has loops";
 
+  // There is a loop iff some region reachable from the parent op can reach
+  // itself. Reaching a region along several paths (e.g., from multiple
+  // terminators that branch to the same successor region) is not a loop.
   SmallVector<RegionSuccessor> entryRegions;
   getSuccessorRegions(RegionBranchPoint::parent(), entryRegions);
-  LDBG() << "Found " << entryRegions.size() << " entry regions";
-
-  for (RegionSuccessor successor : entryRegions) {
-    if (successor.isRegion()) {
-      LDBG() << "Checking entry region #"
-             << successor.getSuccessor()->getRegionNumber() << " for loops";
-
-      bool hasLoop =
-          traverseRegionGraph(successor.getSuccessor(),
-                              [](Region *nextRegion, ArrayRef<bool> visited) {
-                                // Interrupt traversal if the region was already
-                                // visited.
-                                return visited[nextRegion->getRegionNumber()];
-                              });
-
-      if (hasLoop) {
-        LDBG() << "Found loop in entry region #"
-               << successor.getSuccessor()->getRegionNumber();
-        return true;
-      }
-    } else {
-      LDBG() << "Skipping operation successor";
-    }
-  }
-
-  LDBG() << "No loops found in operation";
-  return false;
+  return llvm::any_of(entryRegions, [](RegionSuccessor successor) {
+    return successor.isRegion() &&
+           traverseRegionGraph(successor.getSuccessor(), [](Region *region) {
+             return isRegionReachable(region, region);
+           });
+  });
 }
 
 OperandRange
diff --git a/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp b/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
index 5b5da0534393b0..d11764d87eb1a7 100644
--- a/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
+++ b/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
@@ -132,12 +132,44 @@ struct SequentialRegionsOp
       SequentialRegionsOp>::getSuccessorRegions;
 };
 
+/// Region 0 is entered from the parent op and branches to region 1, which
+/// branches back to the parent op. With the `loop` attribute, region 1 also
+/// branches to itself.
+struct EntryRegionsOp
+    : public Op<EntryRegionsOp, RegionBranchOpInterface::Trait> {
+  using Op::Op;
+  static ArrayRef<StringRef> getAttributeNames() { return {}; }
+
+  static StringRef getOperationName() { return "cftest.entry_regions_op"; }
+
+  void getSuccessorRegions(RegionBranchPoint point,
+                           SmallVectorImpl<RegionSuccessor> &regions) {
+    Operation *op = getOperation();
+    if (point.isParent()) {
+      regions.push_back(RegionSuccessor(&op->getRegion(0)));
+      return;
+    }
+    Region *from = point.getTerminatorPredecessorOrNull()->getParentRegion();
+    if (from == &op->getRegion(0) || op->hasAttr("loop"))
+      regions.push_back(RegionSuccessor(&op->getRegion(1)));
+    if (from == &op->getRegion(1))
+      regions.push_back(RegionSuccessor(op));
+  }
+
+  ValueRange getSuccessorInputs(RegionSuccessor successor) {
+    return successor.isOperation() ? ValueRange(getOperation()->getResults())
+                                   : ValueRange();
+  }
+
+  using RegionBranchOpInterface::Trait<EntryRegionsOp>::getSuccessorRegions;
+};
+
 /// A dialect putting all the above together.
 struct CFTestDialect : Dialect {
   explicit CFTestDialect(MLIRContext *ctx)
       : Dialect(getDialectNamespace(), ctx, TypeID::get<CFTestDialect>()) {
     addOperations<DummyOp, MutuallyExclusiveRegionsOp, LoopRegionsOp,
-                  DoubleLoopRegionsOp, SequentialRegionsOp>();
+                  DoubleLoopRegionsOp, SequentialRegionsOp, EntryRegionsOp>();
   }
   static StringRef getDialectNamespace() { return "cftest"; }
 };
@@ -284,3 +316,53 @@ TEST(RegionBranchOpInterface, NotRecursiveRegions) {
   EXPECT_EQ(getEnclosingRepetitiveRegion(op1), nullptr);
   EXPECT_EQ(getEnclosingRepetitiveRegion(op2), nullptr);
 }
+
+TEST(RegionBranchOpInterface, MultipleExitsToSameRegionIsNotALoop) {
+  // Both terminators of region 0 branch to region 1. Region 1 is reached
+  // twice, but the region graph has no cycle.
+  const char *ir = R"MLIR(
+"cftest.entry_regions_op"() (
+      {
+        "cftest.dummy_op"() : () -> ()
+      ^bb1:
+        "cftest.dummy_op"() : () -> ()
+      },
+      {"cftest.dummy_op"() : () -> ()}
+  ) : () -> ()
+  )MLIR";
+
+  DialectRegistry registry;
+  registry.insert<CFTestDialect>();
+  MLIRContext ctx(registry);
+
+  OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
+  ASSERT_TRUE(module);
+  auto regionOp = cast<RegionBranchOpInterface>(
+      &module->getBody()->getOperations().front());
+
+  EXPECT_FALSE(regionOp.isRepetitiveRegion(0));
+  EXPECT_FALSE(regionOp.isRepetitiveRegion(1));
+  EXPECT_FALSE(regionOp.hasLoop());
+}
+
+TEST(RegionBranchOpInterface, LoopAfterEntryRegion) {
+  const char *ir = R"MLIR(
+"cftest.entry_regions_op"() (
+      {"cftest.dummy_op"() : () -> ()},
+      {"cftest.dummy_op"() : () -> ()}
+  ) {loop} : () -> ()
+  )MLIR";
+
+  DialectRegistry registry;
+  registry.insert<CFTestDialect>();
+  MLIRContext ctx(registry);
+
+  OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
+  ASSERT_TRUE(module);
+  auto regionOp = cast<RegionBranchOpInterface>(
+      &module->getBody()->getOperations().front());
+
+  EXPECT_FALSE(regionOp.isRepetitiveRegion(0));
+  EXPECT_TRUE(regionOp.isRepetitiveRegion(1));
+  EXPECT_TRUE(regionOp.hasLoop());
+}

>From 18b767f163bdeb9d5ce1f049143526cec47a2a93 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Wed, 21 Oct 2026 07:36:16 +0000
Subject: [PATCH 2/2] [mlir] Detect hasLoop cycles with a single DFS

Calling isRegionReachable for every region the traversal popped made
hasLoop quadratic in the number of region successor edges. Use one
depth-first search from the entry regions instead. Reaching a region
that is still on the DFS stack is a loop, reaching a finished one is
not. Each region is queried at most once and the search stops at the
first loop.

Both traversals now get a region's successors from
RegionBranchOpInterface::getSuccessorRegions(Region &) instead of
asking each terminator with null operand attributes. The in-tree
terminators that override getSuccessorRegions (scf.condition and
cir.condition) return the same regions as their parent op when no
operand is constant.

Restore the dropped debug output. Rename the multi-branch test and
drive its mock op from attributes. Add tests for a diamond, a loop
through the entry region, a loop in an unreachable region and
overlapping entry regions.

Assisted-by: Claude Code (Claude Opus 5.5)
---
 mlir/lib/Interfaces/ControlFlowInterfaces.cpp |  98 +++++++-----
 .../Interfaces/ControlFlowInterfacesTest.cpp  | 150 ++++++++++++------
 2 files changed, 155 insertions(+), 93 deletions(-)

diff --git a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
index 060cb99b3a51c7..e033ae3468ee89 100644
--- a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
+++ b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
@@ -221,6 +221,19 @@ LogicalResult detail::verifyRegionBranchOpInterface(Operation *op) {
   return success();
 }
 
+/// Append the regions that the terminators of `region` may branch to.
+static void getRegionSuccessors(Region *region,
+                                SmallVectorImpl<Region *> &successors) {
+  SmallVector<RegionSuccessor> regionSuccessors;
+  cast<RegionBranchOpInterface>(region->getParentOp())
+      .getSuccessorRegions(*region, regionSuccessors);
+  for (RegionSuccessor successor : regionSuccessors)
+    if (successor.isRegion())
+      successors.push_back(successor.getSuccessor());
+  LDBG() << "Region #" << region->getRegionNumber() << " has "
+         << regionSuccessors.size() << " successors";
+}
+
 /// Stop condition for `traverseRegionGraph`. The traversal is interrupted if
 /// this function returns "true" for a successor region.
 using StopConditionFn = function_ref<bool(Region *)>;
@@ -240,36 +253,8 @@ static bool traverseRegionGraph(Region *begin,
   LDBG() << "Initialized visited array with " << op->getNumRegions()
          << " regions";
 
-  // Retrieve all successors of the region and enqueue them in the worklist.
   SmallVector<Region *> worklist;
-  auto enqueueAllSuccessors = [&](Region *region) {
-    LDBG() << "Enqueuing successors for region #" << region->getRegionNumber();
-    SmallVector<Attribute> operandAttributes(op->getNumOperands());
-    for (Block &block : *region) {
-      if (block.empty())
-        continue;
-      auto terminator =
-          dyn_cast<RegionBranchTerminatorOpInterface>(block.back());
-      if (!terminator)
-        continue;
-      SmallVector<RegionSuccessor> successors;
-      operandAttributes.resize(terminator->getNumOperands());
-      terminator.getSuccessorRegions(operandAttributes, successors);
-      LDBG() << "Found " << successors.size()
-             << " successors from terminator in block";
-      for (RegionSuccessor successor : successors) {
-        if (successor.isRegion()) {
-          worklist.push_back(successor.getSuccessor());
-          LDBG() << "Added region #"
-                 << successor.getSuccessor()->getRegionNumber()
-                 << " to worklist";
-        } else {
-          LDBG() << "Skipping operation successor";
-        }
-      }
-    }
-  };
-  enqueueAllSuccessors(begin);
+  getRegionSuccessors(begin, worklist);
   LDBG() << "Initial worklist size: " << worklist.size();
 
   // Process all regions in the worklist via DFS.
@@ -295,7 +280,7 @@ static bool traverseRegionGraph(Region *begin,
     visited[nextRegion->getRegionNumber()] = true;
     LDBG() << "Marking region #" << nextRegion->getRegionNumber()
            << " as visited";
-    enqueueAllSuccessors(nextRegion);
+    getRegionSuccessors(nextRegion, worklist);
   }
 
   LDBG() << "Traversal completed, returning false";
@@ -402,17 +387,48 @@ bool RegionBranchOpInterface::hasLoop() {
   LDBG() << "Checking if operation " << getOperation()->getName()
          << " has loops";
 
-  // There is a loop iff some region reachable from the parent op can reach
-  // itself. Reaching a region along several paths (e.g., from multiple
-  // terminators that branch to the same successor region) is not a loop.
-  SmallVector<RegionSuccessor> entryRegions;
-  getSuccessorRegions(RegionBranchPoint::parent(), entryRegions);
-  return llvm::any_of(entryRegions, [](RegionSuccessor successor) {
-    return successor.isRegion() &&
-           traverseRegionGraph(successor.getSuccessor(), [](Region *region) {
-             return isRegionReachable(region, region);
-           });
-  });
+  SmallVector<RegionSuccessor> entrySuccessors;
+  getSuccessorRegions(RegionBranchPoint::parent(), entrySuccessors);
+  SmallVector<Region *> entryRegions;
+  for (RegionSuccessor successor : entrySuccessors)
+    if (successor.isRegion())
+      entryRegions.push_back(successor.getSuccessor());
+  LDBG() << "Found " << entryRegions.size() << " entry regions";
+
+  // Depth-first search over the regions reachable from the op. There is a loop
+  // iff the search reaches a region that is still on the DFS stack. Reaching a
+  // finished region (e.g., from two terminators that branch to the same
+  // successor region) is not a loop.
+  enum class State { Unvisited, OnStack, Finished };
+  SmallVector<State> states(getOperation()->getNumRegions(), State::Unvisited);
+  // Each stack entry is a region with its successors that are not explored
+  // yet. The bottom entry stands for the op itself and holds the entry regions.
+  SmallVector<std::pair<Region *, SmallVector<Region *>>> stack;
+  stack.emplace_back(nullptr, std::move(entryRegions));
+  while (!stack.empty()) {
+    auto &[region, successors] = stack.back();
+    if (successors.empty()) {
+      if (region)
+        states[region->getRegionNumber()] = State::Finished;
+      stack.pop_back();
+      continue;
+    }
+    Region *next = successors.pop_back_val();
+    State &state = states[next->getRegionNumber()];
+    if (state == State::OnStack) {
+      LDBG() << "Found loop through region #" << next->getRegionNumber();
+      return true;
+    }
+    if (state == State::Finished)
+      continue;
+    state = State::OnStack;
+    // This invalidates `region` and `successors`.
+    stack.emplace_back(next, SmallVector<Region *>());
+    getRegionSuccessors(next, stack.back().second);
+  }
+
+  LDBG() << "No loops found in operation";
+  return false;
 }
 
 OperandRange
diff --git a/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp b/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
index d11764d87eb1a7..d68e686be82b75 100644
--- a/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
+++ b/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
@@ -132,28 +132,29 @@ struct SequentialRegionsOp
       SequentialRegionsOp>::getSuccessorRegions;
 };
 
-/// Region 0 is entered from the parent op and branches to region 1, which
-/// branches back to the parent op. With the `loop` attribute, region 1 also
-/// branches to itself.
-struct EntryRegionsOp
-    : public Op<EntryRegionsOp, RegionBranchOpInterface::Trait> {
+/// The region graph of this op is given by attributes: `entries` lists the
+/// regions that are entered from the op, and `successors[i]` lists the regions
+/// that each terminator of region i branches to. Each terminator also branches
+/// back to the op.
+struct RegionGraphOp
+    : public Op<RegionGraphOp, RegionBranchOpInterface::Trait> {
   using Op::Op;
   static ArrayRef<StringRef> getAttributeNames() { return {}; }
 
-  static StringRef getOperationName() { return "cftest.entry_regions_op"; }
+  static StringRef getOperationName() { return "cftest.region_graph_op"; }
 
   void getSuccessorRegions(RegionBranchPoint point,
                            SmallVectorImpl<RegionSuccessor> &regions) {
     Operation *op = getOperation();
-    if (point.isParent()) {
-      regions.push_back(RegionSuccessor(&op->getRegion(0)));
-      return;
-    }
-    Region *from = point.getTerminatorPredecessorOrNull()->getParentRegion();
-    if (from == &op->getRegion(0) || op->hasAttr("loop"))
-      regions.push_back(RegionSuccessor(&op->getRegion(1)));
-    if (from == &op->getRegion(1))
+    Attribute targets = op->getAttr("entries");
+    if (!point.isParent()) {
+      Region *from = point.getTerminatorPredecessorOrNull()->getParentRegion();
+      targets = cast<ArrayAttr>(op->getAttr("successors"))
+                    .getValue()[from->getRegionNumber()];
       regions.push_back(RegionSuccessor(op));
+    }
+    for (int64_t target : cast<DenseI64ArrayAttr>(targets).asArrayRef())
+      regions.push_back(RegionSuccessor(&op->getRegion(target)));
   }
 
   ValueRange getSuccessorInputs(RegionSuccessor successor) {
@@ -161,7 +162,7 @@ struct EntryRegionsOp
                                    : ValueRange();
   }
 
-  using RegionBranchOpInterface::Trait<EntryRegionsOp>::getSuccessorRegions;
+  using RegionBranchOpInterface::Trait<RegionGraphOp>::getSuccessorRegions;
 };
 
 /// A dialect putting all the above together.
@@ -169,7 +170,7 @@ struct CFTestDialect : Dialect {
   explicit CFTestDialect(MLIRContext *ctx)
       : Dialect(getDialectNamespace(), ctx, TypeID::get<CFTestDialect>()) {
     addOperations<DummyOp, MutuallyExclusiveRegionsOp, LoopRegionsOp,
-                  DoubleLoopRegionsOp, SequentialRegionsOp, EntryRegionsOp>();
+                  DoubleLoopRegionsOp, SequentialRegionsOp, RegionGraphOp>();
   }
   static StringRef getDialectNamespace() { return "cftest"; }
 };
@@ -317,52 +318,97 @@ TEST(RegionBranchOpInterface, NotRecursiveRegions) {
   EXPECT_EQ(getEnclosingRepetitiveRegion(op2), nullptr);
 }
 
-TEST(RegionBranchOpInterface, MultipleExitsToSameRegionIsNotALoop) {
-  // Both terminators of region 0 branch to region 1. Region 1 is reached
-  // twice, but the region graph has no cycle.
-  const char *ir = R"MLIR(
-"cftest.entry_regions_op"() (
+/// Parses `cftest.region_graph_op` ops and keeps them alive for the test.
+class RegionGraphTest : public ::testing::Test {
+protected:
+  RegionGraphTest() { ctx.loadDialect<CFTestDialect>(); }
+
+  /// Parse `ir`, which holds a single op, and return that op.
+  RegionBranchOpInterface parse(StringRef ir) {
+    module = parseSourceString<ModuleOp>(ir, &ctx);
+    if (!module)
+      return nullptr;
+    return cast<RegionBranchOpInterface>(&module->getBody()->front());
+  }
+
+  /// Parse a `cftest.region_graph_op` with one single-block region per entry
+  /// of `successors`. `entries` and each entry of `successors` are
+  /// comma-separated region numbers.
+  RegionBranchOpInterface parse(StringRef entries,
+                                ArrayRef<StringRef> successors) {
+    auto toArray = [](StringRef values) -> std::string {
+      return values.empty() ? "array<i64>"
+                            : ("array<i64: " + values + ">").str();
+    };
+    std::string ir = "\"cftest.region_graph_op\"() (";
+    for (size_t i = 0, e = successors.size(); i < e; ++i)
+      ir += std::string(i ? ", " : "") + "{\"cftest.dummy_op\"() : () -> ()}";
+    ir += ") {entries = " + toArray(entries) + ", successors = [";
+    for (size_t i = 0, e = successors.size(); i < e; ++i)
+      ir += std::string(i ? ", " : "") + toArray(successors[i]);
+    ir += "]} : () -> ()";
+    return parse(ir);
+  }
+
+  MLIRContext ctx;
+  OwningOpRef<ModuleOp> module;
+};
+
+TEST_F(RegionGraphTest, MultipleBranchesToSameRegionIsNotALoop) {
+  // Both terminators of region 0 branch to region 1.
+  RegionBranchOpInterface op = parse(R"MLIR(
+"cftest.region_graph_op"() (
       {
         "cftest.dummy_op"() : () -> ()
       ^bb1:
         "cftest.dummy_op"() : () -> ()
       },
       {"cftest.dummy_op"() : () -> ()}
-  ) : () -> ()
-  )MLIR";
-
-  DialectRegistry registry;
-  registry.insert<CFTestDialect>();
-  MLIRContext ctx(registry);
-
-  OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
-  ASSERT_TRUE(module);
-  auto regionOp = cast<RegionBranchOpInterface>(
-      &module->getBody()->getOperations().front());
+  ) {entries = array<i64: 0>, successors = [array<i64: 1>, array<i64>]}
+    : () -> ()
+  )MLIR");
+  ASSERT_TRUE(op);
+  EXPECT_FALSE(op.isRepetitiveRegion(0));
+  EXPECT_FALSE(op.isRepetitiveRegion(1));
+  EXPECT_FALSE(op.hasLoop());
+}
 
-  EXPECT_FALSE(regionOp.isRepetitiveRegion(0));
-  EXPECT_FALSE(regionOp.isRepetitiveRegion(1));
-  EXPECT_FALSE(regionOp.hasLoop());
+TEST_F(RegionGraphTest, DiamondIsNotALoop) {
+  RegionBranchOpInterface op =
+      parse(/*entries=*/"0", /*successors=*/{"1, 2", "3", "3", ""});
+  ASSERT_TRUE(op);
+  EXPECT_FALSE(op.hasLoop());
 }
 
-TEST(RegionBranchOpInterface, LoopAfterEntryRegion) {
-  const char *ir = R"MLIR(
-"cftest.entry_regions_op"() (
-      {"cftest.dummy_op"() : () -> ()},
-      {"cftest.dummy_op"() : () -> ()}
-  ) {loop} : () -> ()
-  )MLIR";
+TEST_F(RegionGraphTest, LoopAfterEntryRegion) {
+  RegionBranchOpInterface op =
+      parse(/*entries=*/"0", /*successors=*/{"1", "1"});
+  ASSERT_TRUE(op);
+  EXPECT_FALSE(op.isRepetitiveRegion(0));
+  EXPECT_TRUE(op.isRepetitiveRegion(1));
+  EXPECT_TRUE(op.hasLoop());
+}
 
-  DialectRegistry registry;
-  registry.insert<CFTestDialect>();
-  MLIRContext ctx(registry);
+TEST_F(RegionGraphTest, LoopThroughEntryRegion) {
+  RegionBranchOpInterface op =
+      parse(/*entries=*/"0", /*successors=*/{"1", "0"});
+  ASSERT_TRUE(op);
+  EXPECT_TRUE(op.hasLoop());
+}
 
-  OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
-  ASSERT_TRUE(module);
-  auto regionOp = cast<RegionBranchOpInterface>(
-      &module->getBody()->getOperations().front());
+TEST_F(RegionGraphTest, UnreachableLoopIsIgnored) {
+  RegionBranchOpInterface op =
+      parse(/*entries=*/"0", /*successors=*/{"1", "", "2"});
+  ASSERT_TRUE(op);
+  EXPECT_TRUE(op.isRepetitiveRegion(2));
+  EXPECT_FALSE(op.hasLoop());
+}
 
-  EXPECT_FALSE(regionOp.isRepetitiveRegion(0));
-  EXPECT_TRUE(regionOp.isRepetitiveRegion(1));
-  EXPECT_TRUE(regionOp.hasLoop());
+TEST_F(RegionGraphTest, OverlappingEntriesAreNotALoop) {
+  // Region 1 is an entry region and is also reachable from entry region 0.
+  for (StringRef entries : {"0, 1", "1, 0"}) {
+    RegionBranchOpInterface op = parse(entries, /*successors=*/{"1", ""});
+    ASSERT_TRUE(op);
+    EXPECT_FALSE(op.hasLoop()) << "entries = " << entries.str();
+  }
 }



More information about the Mlir-commits mailing list