[Mlir-commits] [mlir] [MLIR] Introduce support for early exits (PR #166688)

Matthias Springer llvmlistbot at llvm.org
Fri Jun 12 03:18:11 PDT 2026


================
@@ -565,6 +580,87 @@ func.func @accelerator_compute(i64, i1) -> i64 { // An SSACFG region
 }
 ```
 
+#### Region-Exit Terminators
+
+A terminator that implements `RegionExitTerminatorOpInterface` terminates the
+current region and identifies the operation that may receive the region exit. A
+normal region exit can target the immediately containing operation. For
+example, `scf.yield` exits only its own immediately enclosing region and
+returns control to the parent operation.
+
+Some terminators instead request a breaking-control-flow event addressed to a
+specific ancestor. The terminator transfers control back only to its immediate
+parent operation; reaching the addressed ancestor requires each intermediate
+parent operation to collaborate by propagating the request outward. The way a
+terminator designates the ancestor is dialect-defined. For example, the SCF
+dialect uses a builtin `token` value: `scf.loop` defines a control token as an
+entry block argument, and `scf.break` / `scf.continue` consume that token to
+identify the loop that should ultimately receive the early-exit event.
+
+Every intermediate operation between a breaking terminator and the addressed
+receiver must define `PropagateControlFlowBreak`. The receiver must implement
+`HasBreakingControlFlowOpInterface`. For example, a loop operation nested
+inside another loop operation body carries both traits simultaneously: it
+handles breaks whose token identifies itself, and propagates breaks whose token
+identifies an outer loop.
+
+Region-exit terminators may carry values, which are propagated to the
+target operation. For example, when breaking out of a loop that produces
+results, the terminator supplies those result values. The exact mapping between
+terminator operands and the receiving op's results is dialect-defined (the
+receiving op may also ignore operands entirely).
+
+Examples:
+
+```mlir
+// scf.yield is the standard immediate region-exit terminator.
+// It exits only its own immediately enclosing region.
+scf.if %cond {
+  scf.yield  // returns control to the immediate parent of scf.if
+}
+```
+
+```mlir
+// Trait legend:
+//   [H]    = HasBreakingControlFlowOpInterface (receives/catches the break)
+//   [P]    = PropagateControlFlowBreak (passes the break request upward)
+//   [H][P] = both: handles breaks whose token identifies it, and propagates
+//            breaks whose token identifies an outer loop
+scf.loop token(%outer) {                    // [H]
+  scf.loop token(%inner) {                  // [H][P]
+    scf.if %cond1 {                  // [P]
+      // Requests a break of the inner loop.
+      scf.break [%inner]
+    }
+    scf.if %cond2 {                  // [P]
+      // Requests a break of the outer loop. The scf.if and inner scf.loop
+      // both propagate the request upward.
+      scf.break [%outer]
+    }
+    scf.if %cond3 {                  // [P]
+      // Requests re-entry into the inner loop.
+      scf.continue [%inner]
+    }
+  }
+}
+return
+```
+
+```mlir
+// A loop that yields a result value on early exit.
+// scf.break carries operands that become the loop's results. (scf.continue
+// would instead carry operands that become the next iteration's iter_args,
+// but this loop has none.)
+%result = scf.loop token(%loop) -> f32 {    // [H]
+  scf.if %found {                    // [P]
----------------
matthias-springer wrote:

nit: indentation of `[P]`

https://github.com/llvm/llvm-project/pull/166688


More information about the Mlir-commits mailing list