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

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Jun 11 05:13:50 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-ods

Author: Mehdi Amini (joker-eph)

<details>
<summary>Changes</summary>

Introduce a structured early-exit control-flow model for MLIR regions and add new SCF operations and core IR support for multi-level region exits:

- scf.loop: infinite loop terminated by scf.break, iterated by scf.continue
- scf.break: exits nested region levels, terminating a target loop
- scf.continue: exits nested region levels, re-entering a target loop

A new `RegionTerminator` trait lets a terminator transfer control to a token-defining ancestor operation instead of only to its immediate region parent. Intermediate operations opt in to transparent propagation with the `PropagateControlFlowBreak` trait, while target operations implement `HasBreakingControlFlowOpInterface`. Helper APIs find the token target, collect nested predecessors, and detect when breaking control flow escapes through an operation.

Extend RegionBranchOpInterface with a propagating successor sentinel and add resolveTerminatorSuccessors so analyses can resolve a transparent intermediate op to the real ancestor target. This keeps successor and operand mapping logic tied to the operation that actually receives the break.

Add `scf.loop` as the first user of this model. The loop defines a control token, accepts optional loop-carried `iter_args` and results, and handles `scf.break` and `scf.continue` terminators that consume the token. `scf.break` exits the loop and supplies result values; `scf.continue` re-enters the loop with the next iteration values. Verification checks target tokens, propagation traits on crossed operations, and region-branch operand/result type consistency.

Allow `scf.if` to propagate breaks and continues through its regions. Its implicit terminator handling, parser, printer, result inference, and canonicalizations now account for regions terminated by `scf.yield`, `scf.break`, or `scf.continue`. Yield-specific rewrites are guarded, and a new pattern removes dead code after an if whose branches both transfer breaking control flow.

Teach core analyses and utilities about propagating breaks. Dead code analysis, dense and sparse backward dataflow, region-branch traversal, post-dominance, and region inlining now resolve or block escaping early exits instead of treating the immediate parent as the final successor.

Lower `scf.loop` to CFG in the SCF-to-CF conversion. The lowering legalizes nested regions, collects direct break and continue predecessors, inlines the body, turns breaks into branches to the continuation block, turns continues into backedges, removes the loop token argument, and wires loop results through continuation block arguments. `scf.if` lowering is also adjusted to branch only from `scf.yield` terminators, leaving early-exit terminators for the target loop lowering.

Document the `RegionTerminator` model in the LangRef and add tests for valid and invalid early-exit IR, region-branch interface printing, dominance, dead code analysis, SCCP, SCF-to-CF lowering, SCF canonicalization, and an end-to-end SCF early-exit integration case.

Assisted-by: Claude Code
Assisted-by: Codex

---

Patch is 152.13 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166688.diff


37 Files Affected:

- (modified) mlir/docs/LangRef.md (+105-11) 
- (modified) mlir/include/mlir/Dialect/SCF/IR/SCF.h (+57) 
- (modified) mlir/include/mlir/Dialect/SCF/IR/SCFOps.td (+188-5) 
- (modified) mlir/include/mlir/IR/Dominance.h (+9) 
- (modified) mlir/include/mlir/IR/OpBase.td (+10) 
- (modified) mlir/include/mlir/IR/OpDefinition.h (+13) 
- (modified) mlir/include/mlir/IR/RegionKindInterface.h (+136) 
- (modified) mlir/include/mlir/IR/RegionKindInterface.td (+80) 
- (modified) mlir/include/mlir/Interfaces/ControlFlowInterfaces.h (+41-6) 
- (modified) mlir/lib/Analysis/DataFlow/DeadCodeAnalysis.cpp (+18-2) 
- (modified) mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp (+13-4) 
- (modified) mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp (+9-2) 
- (modified) mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp (+182-55) 
- (modified) mlir/lib/Dialect/SCF/IR/SCF.cpp (+448-76) 
- (modified) mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp (+2-2) 
- (modified) mlir/lib/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.cpp (+2-2) 
- (modified) mlir/lib/IR/Dominance.cpp (+92-1) 
- (modified) mlir/lib/IR/RegionKindInterface.cpp (+159) 
- (modified) mlir/lib/IR/Verifier.cpp (+1) 
- (modified) mlir/lib/Interfaces/ControlFlowInterfaces.cpp (+104-13) 
- (modified) mlir/lib/Transforms/Utils/CMakeLists.txt (+3) 
- (modified) mlir/lib/Transforms/Utils/InliningUtils.cpp (+6) 
- (added) mlir/test/Analysis/DataFlow/test-dead-code-analysis-early-exit.mlir (+100) 
- (modified) mlir/test/Analysis/test-dominance.mlir (+60) 
- (added) mlir/test/Conversion/SCFToControlFlow/convert-early-exit-to-cfg.mlir (+135) 
- (added) mlir/test/Dialect/SCF/loop_canonicalize.mlir (+300) 
- (added) mlir/test/IR/early-exit-invalid.mlir (+142) 
- (added) mlir/test/IR/early-exit.mlir (+82) 
- (added) mlir/test/Integration/Dialect/SCF/early_exit.mlir (+82) 
- (added) mlir/test/Transforms/sccp-early-exit.mlir (+117) 
- (modified) mlir/test/lib/Dialect/Test/TestOps.td (+6) 
- (modified) mlir/test/lib/IR/TestDominance.cpp (+33) 
- (modified) mlir/test/lib/Interfaces/CMakeLists.txt (+1) 
- (added) mlir/test/lib/Interfaces/RegionBranchOpInterface/CMakeLists.txt (+9) 
- (added) mlir/test/lib/Interfaces/RegionBranchOpInterface/TestRegionBranchOpInterface.cpp (+76) 
- (modified) mlir/tools/mlir-opt/CMakeLists.txt (+1) 
- (modified) mlir/tools/mlir-opt/mlir-opt.cpp (+2) 


``````````diff
diff --git a/mlir/docs/LangRef.md b/mlir/docs/LangRef.md
index 0e6fb006da48b..292b67a153a20 100644
--- a/mlir/docs/LangRef.md
+++ b/mlir/docs/LangRef.md
@@ -504,10 +504,23 @@ In MLIR, control flow semantics of a region is indicated by
 regions support semantics where operations in a region 'execute sequentially'.
 Before an operation executes, its operands have well-defined values. After an
 operation executes, the operands have the same values and results also have
-well-defined values. After an operation executes, the next operation in the
-block executes until the operation is the terminator operation at the end of a
-block, in which case some other operation will execute. The determination of the
-next instruction to execute is the 'passing of control flow'.
+well-defined values.
+
+Usually, after an operation executes, the next operation in the block executes
+until the operation is the terminator operation at the end of a block, in which
+case the control will be transferred to another block or back to the containing
+operation. The determination of the next instruction to execute is the 'passing
+of control flow'. A nested [Region Terminator](#region-terminator) can also
+terminate the current region with a request for a breaking-control-flow event
+addressed to a specific ancestor operation that implements
+`HasBreakingControlFlowOpInterface`. The terminator itself does not jump
+directly to that ancestor; it transfers control back to the containing operation
+with that request. If the containing operation is not the addressed receiver, it
+must define `PropagateControlFlowBreak` and propagate the same request to its own
+parent. This repeats until the addressed receiver handles the request, so every
+intermediate operation on the path from the `RegionTerminator` to the receiving
+`HasBreakingControlFlowOpInterface` operation must define
+`PropagateControlFlowBreak`.
 
 In general, when control flow is passed to an operation, MLIR does not restrict
 when control flow enters or exits the regions contained in that operation.
@@ -515,9 +528,14 @@ However, when control flow enters a region, it always begins in the first block
 of the region, called the *entry* block. Terminator operations ending each block
 represent control flow by explicitly specifying the successor blocks of the
 block. Control flow can only pass to one of the specified successor blocks as in
-a `branch` operation, or back to the containing operation as in a `return`
-operation. Terminator operations without successors can only pass control back
-to the containing operation. Within these restrictions, the particular semantics
+a `branch` operation, or back to the containing operation, as in a `return`
+operation. Terminator operations without block successors can only pass control
+back to the containing operation. The dialect may define additional information
+on the terminator to request that the containing operation propagate breaking
+control flow outward toward a specific ancestor. This target designator is
+dialect-defined: it may be a builtin `token` operand, an integer count of region
+levels to exit, a named symbolic relationship to a parent operation, or another
+dialect-specific mechanism. Within these restrictions, the particular semantics
 of terminator operations is determined by the specific dialect operations
 involved. Blocks (other than the entry block) that are not listed as a successor
 of a terminator operation are defined to be unreachable and can be removed
@@ -525,12 +543,12 @@ without affecting the semantics of the containing operation.
 
 Although control flow always enters a region through the entry block, control
 flow may exit a region through any block with an appropriate terminator. The
-standard dialect leverages this capability to define operations with
+SCF dialect for example leverages this capability to define operations with
 Single-Entry-Multiple-Exit (SEME) regions, possibly flowing through different
 blocks in the region and exiting through any block with a `return` operation.
-This behavior is similar to that of a function body in most programming
-languages. In addition, control flow may also not reach the end of a block or
-region, for example if a function call does not return.
+This behavior can model that of a function body in most programming languages.
+In addition, control flow may also not reach the end of a block or region, for
+example if a function call does not return.
 
 Example:
 
@@ -558,6 +576,82 @@ func.func @accelerator_compute(i64, i1) -> i64 { // An SSACFG region
 }
 ```
 
+#### Region Terminator
+
+A `RegionTerminator` is a specialization of a block terminator (the `Terminator`
+trait) that terminates the current region with a breaking-control-flow request
+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 the `RegionTerminator` 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 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 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]
+    // %value becomes the loop result.
+    scf.break [%loop] %value : f32
+  }
+  // Re-enter the loop for the next iteration (no iter_args here).
+  scf.continue [%loop]
+}
+```
+
 #### Operations with Multiple Regions
 
 An operation containing multiple regions also completely determines the
diff --git a/mlir/include/mlir/Dialect/SCF/IR/SCF.h b/mlir/include/mlir/Dialect/SCF/IR/SCF.h
index 44cbb458d94fe..61d9aed4abd9b 100644
--- a/mlir/include/mlir/Dialect/SCF/IR/SCF.h
+++ b/mlir/include/mlir/Dialect/SCF/IR/SCF.h
@@ -30,6 +30,11 @@
 namespace mlir {
 namespace scf {
 void buildTerminatedBody(OpBuilder &builder, Location loc);
+
+namespace op_impl {
+struct IfOpImplicitTerminatorType;
+struct LoopOpImplicitTerminatorType;
+} // namespace op_impl
 } // namespace scf
 } // namespace mlir
 
@@ -112,6 +117,58 @@ SmallVector<Value> replaceAndCastForOpIterArg(RewriterBase &rewriter,
                                               OpOperand &operand,
                                               Value replacement,
                                               const ValueTypeCastFnTy &castFn);
+namespace op_impl {
+
+//===----------------------------------------------------------------------===//
+// ControlFlowImplicitTerminatorOperation
+//===----------------------------------------------------------------------===//
+
+/// This class provides an interface compatible with
+/// SingleBlockImplicitTerminator, but allows multiple types of potential
+/// terminators aside from just one. If a terminator isn't present, this will
+/// generate a `ImplicitOpT` operation.
+template <typename ImplicitOpT, typename... OtherTerminatorOpTs>
+struct ControlFlowImplicitTerminatorOpType {
+  /// Implementation of `classof` that supports all of the potential terminator
+  /// operations.
+  static bool classof(Operation *op) {
+    return isa<ImplicitOpT, OtherTerminatorOpTs...>(op);
+  }
+
+  //===--------------------------------------------------------------------===//
+  // Implicit Terminator Methods
+
+  /// The following methods are all used when interacting with the "implicit"
+  /// terminator.
+
+  template <typename... Args>
+  static void build(Args &&...args) {
+    ImplicitOpT::build(std::forward<Args>(args)...);
+  }
+  static constexpr StringLiteral getOperationName() {
+    return ImplicitOpT::getOperationName();
+  }
+};
+/// An implicit terminator type for `if` operations, which can contain:
+/// break, continue, yield.
+struct IfOpImplicitTerminatorType
+    : public ControlFlowImplicitTerminatorOpType<YieldOp, BreakOp, ContinueOp> {
+};
+struct LoopOpImplicitTerminatorType
+    : public ControlFlowImplicitTerminatorOpType<ContinueOp, BreakOp> {
+  /// Build the implicit `scf.continue` terminator. The control token consumed
+  /// by the terminator is the loop body's entry block argument #0; the
+  /// insertion block is guaranteed to be that body when the implicit
+  /// terminator is materialized. This keeps `scf.continue`'s public builders
+  /// free of any hidden block-argument assumption.
+  static void build(OpBuilder &builder, OperationState &state) {
+    Block *block = builder.getInsertionBlock();
+    assert(block && block->getNumArguments() > 0 &&
+           "expected insertion block with a loop control token");
+    state.addOperands(block->getArgument(0));
+  }
+};
+} // namespace op_impl
 
 /// Helper function to compute the difference between two values. This is used
 /// by the loop implementations to compute the trip count.
diff --git a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
index 0b33ecb48b7f2..d77ebb7af83f7 100644
--- a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
+++ b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
@@ -15,6 +15,7 @@
 
 include "mlir/Interfaces/ControlFlowInterfaces.td"
 include "mlir/Interfaces/LoopLikeInterface.td"
+include "mlir/IR/OpAsmInterface.td"
 include "mlir/IR/RegionKindInterface.td"
 include "mlir/Dialect/SCF/IR/DeviceMappingInterface.td"
 include "mlir/Interfaces/DestinationStyleOpInterface.td"
@@ -146,6 +147,180 @@ def ExecuteRegionOp : SCF_Op<"execute_region", [
 
   let hasVerifier = 1;
 }
+//===----------------------------------------------------------------------===//
+// LoopOp
+//===----------------------------------------------------------------------===//
+
+def LoopOp : SCF_Op<"loop",[
+    AutomaticAllocationScope,
+    OpAsmOpInterface,
+    RecursiveMemoryEffects,
+    PropagateControlFlowBreak,
+    TokenProducerTrait,
+    DeclareOpInterfaceMethods<RegionBranchOpInterface,
+        ["getEntrySuccessorOperands", "getSuccessorInputs"]>,
+    SingleBlockImplicitTerminator<"op_impl::LoopOpImplicitTerminatorType">,
+    HasBreakingControlFlowOpInterface,
+    HasNestedTerminator<["ContinueOp", "BreakOp"]>
+  ]> {
+  let summary = "Loop until a break operation";
+  let description = [{
+    The `loop` operation represents an unstructured infinite loop that executes
+    until a `break` is reached.
+
+    The loop consists of (1) a set of loop-carried values which are initialized by
+    `initValues` and updated by each iteration of the loop, and
+    (2) a region which represents the loop body.
+
+    The loop will execute the body of the loop until a `break` is dynamically executed.
+
+    Each control path of the loop must be terminated by:
+
+    - a `continue` that yields the next iteration's value for each loop carried variable.
+    - a `break` that terminates the loop and yields the final loop carried values.
+
+    As long as each loop iteration is terminated by one of these operations they may be combined with other control
+    flow operations to express different control flow patterns.
+
+    The loop operation produces one return value for each loop carried variable. The type of the `i`-th return
+    value is that of the `i`-th loop carried variable and its value is the final value of the
+    `i`-th loop carried variable.
+  }];
+
+  let arguments = (ins Variadic<AnyType>:$initValues);
+  let results = (outs Variadic<AnyType>:$resultValues);
+  let regions = (region SizedRegion<1>:$region);
+
+  let extraClassDeclaration = [{
+    /// Return the iteration values of the loop region.
+    Block::BlockArgListType getRegionIterValues() {
+      return getRegion().getArguments().drop_front();
+    }
+
+    /// Return the `index`-th region iteration value.
+    BlockArgument getRegionIterValue(unsigned index) {
+      return getRegionIterValues()[index];
+    }
+
+    /// Return the loop control token.
+    BlockArgument getControlToken() {
+      return getRegion().getArgument(0);
+    }
+
+    /// Returns the number of region arguments for loop-carried values.
+    unsigned getNumRegionIterValues() {
+      return getRegion().getNumArguments() - 1;
+    }
+
+    /// Returns the loop block body
+    Block *getBody() { return &getRegion().front(); }
+  }];
+
+  let hasCustomAssemblyFormat = 1;
+  let hasRegionVerifier = 1;
+  let hasCanonicalizer = 1;
+}
+
+
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : SCF_Op<"break", [
+    Terminator, RegionTerminator, BreakingTerminatorOpInterface,
+    DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface,
+      ["getMutableSuccessorOperands"]>,
+    ParentOneOf<["IfOp", "LoopOp"]>
+  ]> {
+  let summary = "Break from loop";
+  let description = [{
+    The `break` operation is a `RegionTerminator` that exits one or more nested
+    regions and terminates the `scf.loop` that defined its control token.
+
+    The `break` may yield any number of operands; their types must match the
+    result types of the target `scf.loop`.
+
+    Example — break out of the immediately enclosing loop:
+    ```mlir
+    scf.loop token(%loop) -> i32 {
+      scf.break [%loop] %result : i32
+    }
+    ```
+
+    Example — break out of a loop through an enclosing `scf.if`:
+    ```mlir
+    scf.loop token(%loop) {
+      scf.if %cond {
+        scf.break [%loop]
+      }
+      scf.continue [%loop]
+    }
+    ```
+  }];
+
+
+  let arguments = (ins Token:$target, Variadic<AnyType>:$args);
+  let assemblyFormat = [{
+    ` ` `[` $target `]` ($args^ `:` type($args))? attr-dict
+  }];
+  let extraClassDeclaration = [{
+    /// BreakingTerminatorOpInterface: the control token identifying the loop.
+    ::mlir::Value getTargetToken() { return getTarget(); }
+  }];
+  let hasVerifier = 1;
+}
+
+
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+def ContinueOp : SCF_Op<"continue", [
+    Terminator, RegionTerminator, BreakingTerminatorOpInterface,
+    DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface,
+      ["getMutableSuccessorOperands"]>, ParentOneOf<["IfOp", "LoopOp"]>
+  ]> {
+  let summary = "Continue to next loop iteration";
+  let description = [{
+    The `continue` operation is a `RegionTerminator` that re-enters a `scf.loop`
+    for its next iteration. The target loop is the one that defined the control
+    token operand.
+
+    The operands of `continue` become the loop-carried values (iter_args) for
+    the next iteration; their types must match the loop's iter_arg types.
+
+    Example — continue the immediately enclosing loop:
+    ```mlir
+    scf.loop token(%loop) iter_args(%i = %init) : i32 {
+      %next = arith.addi %i, %one : i32
+      scf.continue [%loop] %next : i32
+    }
+    ```
+
+    Example — continue an outer loop from inside a nested `scf.if`:
+    ```mlir
+    scf.loop token(%outer) iter_args(%counter = %init) : i64 {
+      scf.loop token(%inner) iter_args(%inner_arg = %counter) : i64 {
+        scf.if %restart_outer {
+          scf.continue [%outer] %inner_arg : i64
+        }
+        scf.continue [%inner] %inner_arg : i64
+      }
+      scf.continue [%outer] %counter : i64
+    }
+    ```
+  }];
+
+  let arguments = (ins Token:$target, Variadic<AnyType>:$args);
+  let assemblyFormat = [{
+    ` ` `[` $target `]` ($args^ `:` type($args))? attr-dict
+  }];
+  let extraClassDeclaration = [{
+    /// BreakingTerminatorOpInterface: the control token identifying the loop.
+    ::mlir::Value getTargetToken() { return getTarget(); }
+  }];
+  let hasVerifier = 1;
+}
 
 //===----------------------------------------------------------------------===//
 // ForOp
@@ -706,8 +881,8 @@ def IfOp : SCF_Op<"if", [DeclareOpInterfaceMethods<RegionBranchOpInterface, [
     "getNumRegionInvocations", "getRegionInvocationBounds",
     "getEntrySuccessorRegions", "getSuccessorInputs"]>,
     DeclareOpInterfaceMethods<PromotableRegionOpInterface>,
-    InferTypeOpAdaptor, SingleBlockImplicitTerminator<"scf::YieldOp">,
-    RecursiveMemoryEffects, RecursivelySpeculatable, NoRegionArguments]> {
+    InferTypeOpAdaptor, SingleBlockImplicitTerminator<"op_impl::IfOpImplicitTerminatorType">,
+    RecursiveMemoryEffects, RecursivelySpeculatable, NoRegionArguments, PropagateControlFlowBreak]> {
   let summary = "if-then-else operation";
   let description = [{
     The `scf.if` operation represents an if-then-else construct for
@@ -790,9 +965,17 @@ def IfOp : SCF_Op<"if", [DeclareOpInterfaceMethods<RegionBranchOpInterface, [
                                   : OpBuilder::atBlockEnd(body, listener);
     }
     Block* thenBlock();
-    YieldOp thenYield();
+    /// Returns the terminator of the then block. May be scf.break,
+    /// scf.continue, or scf.yield.
+    Operation *thenTerminator() {
+      return thenBlock()->getTerminator();
+    }
     Block* elseBlock();
-    YieldOp elseYield();
+    /// Returns the terminator of the else block. May be scf.break,
+    /// scf.continue, or scf.yield.
+    Operation *elseTerminator() {
+      return elseBlock()->getTerminator();
+    }
   }];
   let hasFolder = 1;
   let hasCanonicalizer = 1;
@@ -909,7 +1092,7 @@ def ParallelOp : SCF_Op<"parallel",
 //===----------------------------------------------------------------------===//
 
 def ReduceOp : SCF_Op<"reduce", [
-    Terminator, HasParent<"ParallelOp">, RecursiveMemoryEffects,
+    Terminator, RegionTerminator, HasParent<"ParallelOp">, RecursiveMemoryEffects,
     DeclareOpInterfaceMethods<PromotableRegionOpInterface>,
     DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface,
                               ["getMutableSuccessorOperands"]>]> {
diff --git a/mlir/include/mlir/IR/Dominance.h b/mlir/include/mlir/IR/Dominance.h
index 70924a2e9ae59..d31fbe4d6d3d1 100644
--- a/mlir/include/mlir/IR/Dominance.h
+++ b/mlir/include/mlir/IR/Dominance.h
@@ -123,6 +123,11 @@ class DominanceInfoBase {
                              Block::iterator bIt,
                              bool enclosingOk = true) const;
 
+  /// Return true if `op` ...
[truncated]

``````````

</details>


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


More information about the Mlir-commits mailing list