[Mlir-commits] [mlir] [MLIR] Introduce support for early exits (PR #166688)
Matthias Springer
llvmlistbot at llvm.org
Fri Jun 12 03:18:15 PDT 2026
================
@@ -524,6 +558,62 @@ RegionBranchOpInterface::getAllRegionBranchPoints() {
branchPoints.push_back(RegionBranchPoint(terminator));
}
}
+
+ Operation *op = getOperation();
+ if (!op->hasTrait<OpTrait::PropagateControlFlowBreak>() &&
+ !isa<HasBreakingControlFlowOpInterface>(op))
+ return branchPoints;
+
+ // The loop above only records terminators that are immediate branch points of
+ // this op: terminators ending blocks directly contained in one of this op's
+ // regions. Breaking control flow adds another class of branch points. A
+ // nested RegionBranchOpInterface that defines PropagateControlFlowBreak can
+ // contain a breaking terminator whose addressed receiver is this op or one of
+ // this op's ancestors. Even though the terminator is not directly contained
+ // in this op's region, it can still create a control-flow edge that leaves or
+ // propagates through this op.
+ //
+ // For example, consider:
+ //
+ // scf.loop token(%outer) {
+ // scf.loop token(%inner) {
+ // scf.if %cond {
+ // scf.break [%outer]
+ // }
+ // scf.continue [%inner]
+ // }
+ // scf.continue [%outer]
+ // }
+ //
+ // When enumerating branch points for the inner loop, its direct body
+ // terminator is only `scf.continue [%inner]`. The `scf.break [%outer]` is
+ // hidden behind the immediately nested `scf.if`, but the inner loop must
+ // still expose a possible edge for that break: the break request propagates
+ // through `scf.if`, then through the inner loop, and is ultimately handled by
+ // the outer loop. Without adding the nested break as a branch point of the
+ // inner loop, generic RegionBranchOpInterface verification and data-flow
+ // consumers only see the continue edge and miss the propagated exit edge.
+ //
+ // This is intentionally broader than collectAllNestedPredecessors(op). That
+ // helper collects only terminators that directly target `op`, which is enough
+ // for a receiver to find incoming breaks but insufficient for an intermediate
+ // PropagateControlFlowBreak op: an escaping terminator targets an ancestor,
+ // not the intermediate op it propagates through.
+ // visitNestedBreakingControlFlowOps reports both nested terminators that
+ // target `op` and those that target an ancestor of `op`, which is exactly the
+ // set that may affect this op's RegionBranchOpInterface edges.
+ visitNestedBreakingControlFlowOps(
+ op, [&](Operation *nestedTerminator, int nestedLevel) {
+ // Immediate region terminators are already covered above. Deeper
+ // terminators may add a propagated control-flow edge through this op.
+ if (nestedLevel <= 1)
+ return;
+ auto terminator =
+ dyn_cast<RegionBranchTerminatorOpInterface>(nestedTerminator);
----------------
matthias-springer wrote:
Does a `RegionExitTerminatorOpInterface` have to implement the `RegionBranchTerminatorOpInterface` when it wants to propagate through a `RegionBranchOpInterface` op? In other words, should this be a `cast<RegionBranchTerminatorOpInterface>`? If not, we may fail to enumerate some propagating CF edges.
(Maybe this is something that the `RegionBranchOpInterface` verifier could check.)
https://github.com/llvm/llvm-project/pull/166688
More information about the Mlir-commits
mailing list