[Mlir-commits] [mlir] [mlir] Fix false positive loops in RegionBranchOpInterface::hasLoop (PR #233784)
Henrich Lauko
llvmlistbot at llvm.org
Wed Oct 21 01:38:35 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> ®ions) {
+ 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 badf9273af645fa74c7353694648549644d1bea7 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 terminator is queried at most once and the search stops at
the first loop. getRegionSuccessors now holds the terminator walk that
traverseRegionGraph also uses.
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 | 114 ++++++++-----
.../Interfaces/ControlFlowInterfacesTest.cpp | 150 ++++++++++++------
2 files changed, 171 insertions(+), 93 deletions(-)
diff --git a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
index 060cb99b3a51c7..9eadd8cce3f84a 100644
--- a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
+++ b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
@@ -221,6 +221,35 @@ LogicalResult detail::verifyRegionBranchOpInterface(Operation *op) {
return success();
}
+/// Append the region successors of all terminators in `region` to
+/// `successors`. Operation successors are skipped.
+static void getRegionSuccessors(Region *region,
+ SmallVectorImpl<Region *> &successors) {
+ LDBG() << "Collecting successors of region #" << region->getRegionNumber();
+ SmallVector<Attribute> operandAttributes;
+ for (Block &block : *region) {
+ if (block.empty())
+ continue;
+ auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(block.back());
+ if (!terminator)
+ continue;
+ SmallVector<RegionSuccessor> regionSuccessors;
+ operandAttributes.resize(terminator->getNumOperands());
+ terminator.getSuccessorRegions(operandAttributes, regionSuccessors);
+ LDBG() << "Found " << regionSuccessors.size()
+ << " successors from terminator in block";
+ for (RegionSuccessor successor : regionSuccessors) {
+ if (successor.isRegion()) {
+ successors.push_back(successor.getSuccessor());
+ LDBG() << "Added region #"
+ << successor.getSuccessor()->getRegionNumber();
+ } else {
+ LDBG() << "Skipping operation successor";
+ }
+ }
+ }
+}
+
/// 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 +269,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 +296,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 +403,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> ®ions) {
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