Skip to content

Conversation

@matthias-springer
Copy link
Member

Document that implementing the RegionBranchTerminatorOpInterface is mandatory. Omitting this interface on a block terminator that models region branching may lead to invalid/incomplete analyses and transformations.

This commit does not change the op/interface semantics. It just puts in writing an assumption that is made throughout the code base. For example:

  • It is baked into the API design of the RegionBranchOpInterface. You cannot query the region successors of block terminators that do not implement the RegionBranchTerminatorOpInterface: RegionBranchOpInterface::getSuccessors() takes a RegionBranchPoint parameter, and such region branch points can be constructed only from RegionBranchTerminatorOpInterface and not arbitrary Operation *.
  • Helper functions + default interface method implementations enumerate region branch points by looking for RegionBranchTerminatorOpInterface and ignoring other operations. E.g., RegionBranchOpInterface::getAllRegionBranchPoints, default implementation of RegionBranchOpInterface::getSuccessorRegions(Region &), default implementation of RegionBranchOpInterface::getPredecessorValues.
  • Core analyses such as DeadCodeAnalysis look for RegionBranchTerminatorOpInterface and misbehave when the interface is not implemented. The analysis does not (and cannot) query region successors of a region branching terminator that does not implement the RegionBranchTerminatorOpInterface. In practice, this means that forgetting to implement the RegionBranchTerminatorOpInterface may incorrectly classify regions as dead.
  • Other analyses / transformations that look for and depend on the implementation of RegionBranchTerminatorOpInterface: mlir::getControlFlowPredecessors, AbstractDenseBackwardDataFlowAnalysis, ownership-based buffer deallocation pass.

@llvmbot
Copy link
Member

llvmbot commented Jan 8, 2026

@llvm/pr-subscribers-mlir

Author: Matthias Springer (matthias-springer)

Changes

Document that implementing the RegionBranchTerminatorOpInterface is mandatory. Omitting this interface on a block terminator that models region branching may lead to invalid/incomplete analyses and transformations.

This commit does not change the op/interface semantics. It just puts in writing an assumption that is made throughout the code base. For example:

  • It is baked into the API design of the RegionBranchOpInterface. You cannot query the region successors of block terminators that do not implement the RegionBranchTerminatorOpInterface: RegionBranchOpInterface::getSuccessors() takes a RegionBranchPoint parameter, and such region branch points can be constructed only from RegionBranchTerminatorOpInterface and not arbitrary Operation *.
  • Helper functions + default interface method implementations enumerate region branch points by looking for RegionBranchTerminatorOpInterface and ignoring other operations. E.g., RegionBranchOpInterface::getAllRegionBranchPoints, default implementation of RegionBranchOpInterface::getSuccessorRegions(Region &), default implementation of RegionBranchOpInterface::getPredecessorValues.
  • Core analyses such as DeadCodeAnalysis look for RegionBranchTerminatorOpInterface and misbehave when the interface is not implemented. The analysis does not (and cannot) query region successors of a region branching terminator that does not implement the RegionBranchTerminatorOpInterface. In practice, this means that forgetting to implement the RegionBranchTerminatorOpInterface may incorrectly classify regions as dead.
  • Other analyses / transformations that look for and depend on the implementation of RegionBranchTerminatorOpInterface: mlir::getControlFlowPredecessors, AbstractDenseBackwardDataFlowAnalysis, ownership-based buffer deallocation pass.

Full diff: https://github.com/llvm/llvm-project/pull/174978.diff

1 Files Affected:

  • (modified) mlir/include/mlir/Interfaces/ControlFlowInterfaces.td (+13-2)
diff --git a/mlir/include/mlir/Interfaces/ControlFlowInterfaces.td b/mlir/include/mlir/Interfaces/ControlFlowInterfaces.td
index ff99e220c179f..74c7841d52b3f 100644
--- a/mlir/include/mlir/Interfaces/ControlFlowInterfaces.td
+++ b/mlir/include/mlir/Interfaces/ControlFlowInterfaces.td
@@ -127,7 +127,8 @@ def RegionBranchOpInterface : OpInterface<"RegionBranchOpInterface"> {
 
     A "region branch point" indicates a point from which a branch originates. It
     can indicate:
-    1. A terminator in any of the immediately nested region of this op.
+    1. A `RegionBranchTerminatorOpInterface` terminator in any of the
+       immediately nested region of this op.
     2. `RegionBranchPoint::parent()`: the branch originates from outside of the
        op, i.e., when first executing this op.
 
@@ -147,6 +148,15 @@ def RegionBranchOpInterface : OpInterface<"RegionBranchOpInterface"> {
     results must have the same type. `areTypesCompatible` can be implemented to
     allow non-equal types.
 
+    Note: This interface works in conjunction with
+    `RegionBranchTerminatorOpInterface`. All immediately nested block
+    terminators that model branching between regions must implement the
+    `RegionBranchTerminatorOpInterface`. Otherwise, analyses/transformations
+    may miss control flow edges and produce incorrect results. Not every block
+    terminator is necessarily a region branch terminator: e.g., in the presence
+    of unstructured control flow, a block terminator could indicate a branch to
+    a different block within the same region.
+
     Example:
 
     ```
@@ -379,7 +389,8 @@ def RegionBranchTerminatorOpInterface :
   let description = [{
     This interface provides information for branching terminator operations
     in the presence of a parent `RegionBranchOpInterface` implementation. It
-    specifies which operands are passed to which region successor.
+    acts as a marker for valid region branch points and specifies which
+    operands are passed to which region successor.
   }];
   let cppNamespace = "::mlir";
 

Copy link
Member

@linuxlonelyeagle linuxlonelyeagle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.please wait for the other reviewer.

@linuxlonelyeagle
Copy link
Member

linuxlonelyeagle commented Jan 8, 2026

I am thinking about whether this conclusion is correct: A TerminatorOp does not necessarily have to implement the RegionBranchTerminatorOpInterface. If a TerminatorOp does not implement the RegionBranchTerminatorOpInterface, does that mean it has no successors? If this conclusion is correct, I think it would be better to add it to the assumptions or doc.

@matthias-springer matthias-springer force-pushed the users/matthias-springer/region_terminator_mandatory branch from ff3af1d to 94c1209 Compare January 8, 2026 14:18
@matthias-springer
Copy link
Member Author

Yes, that's correct. I added a note to the RegionBranchTerminatorOpInterface documentation.

@linuxlonelyeagle
Copy link
Member

Yes, that's correct. I added a note to the RegionBranchTerminatorOpInterface documentation.

Thanks.

@matthias-springer matthias-springer changed the base branch from users/matthias-springer/region_successor to main January 11, 2026 10:13
@matthias-springer matthias-springer force-pushed the users/matthias-springer/region_terminator_mandatory branch from 7787a1d to 8e04b6b Compare January 11, 2026 10:13
@matthias-springer
Copy link
Member Author

Rebased to main, so that I can merge this before #174945 (which is still in review).

@matthias-springer matthias-springer enabled auto-merge (squash) January 11, 2026 10:15
@matthias-springer matthias-springer merged commit 48a6157 into main Jan 11, 2026
5 of 9 checks passed
@matthias-springer matthias-springer deleted the users/matthias-springer/region_terminator_mandatory branch January 11, 2026 10:20
matthias-springer added a commit that referenced this pull request Jan 13, 2026
…terminator (#174221)

`scf.forall` does not completely implement the
`RegionBranchOpInterface`: `scf.forall.in_parallel` does not implement
the `RegionBranchTerminatorOpInterface`.

Incomplete interface implementation is a problem for transformations
that try to understand the control flow by querying the
`RegionBranchOpInterface`.

Detailed explanation of what is wrong with the current implementation.
- There is exactly one region branch point: "parent". `in_parallel` is
not a region branch point because it does not implement the
`RegionBranchTerminatorOpInterface`. (Clarified in #174978.)
- `ForallOp::getSuccessorRegions(parent)` returns one region successors:
the region of the `scf.forall` op.
- Since there is no region branch point in the region, there is no way
to leave the region. This means: once you enter the region, you are
stuck in it indefinitely. (It is unspecified what happens once you are
in the region, but we can be sure that you cannot leave it.)

This commit adds the `RegionBranchTerminatorOpInterface` (via
`ReturnLike`) to `scf.forall.in_parallel` to indicate the after leaving
the region, the control flow returns to the parent. (Note: Only block
terminators in directly nested regions can be region branch terminators,
so `in_parallel` is the only possible op. I.e., `parallel_insert_slice`
cannot be a region branch terminator.)

This commit also removes all successor operands / inputs from the
implementation. The number of successor operands and successor inputs
must match, but `scf.forall.in_parallel` has no operands. Therefore, the
region must also have 0 successor inputs. Therefore, the `scf.forall` op
must also have 0 successor operands.

This commit also adds a missing control flow edge from "parent" to
"parent": in case of 0 threads, the region is not entered.

Depends on #174978.
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
…ace` is mandatory (llvm#174978)

Document that implementing the `RegionBranchTerminatorOpInterface` is
mandatory. Omitting this interface on a block terminator that models
region branching may lead to invalid/incomplete analyses and
transformations.

This commit does not change the op/interface semantics. It just puts in
writing an assumption that is made throughout the code base. For
example:

- It is baked into the API design of the `RegionBranchOpInterface`. You
cannot query the region successors of block terminators that do not
implement the `RegionBranchTerminatorOpInterface`:
`RegionBranchOpInterface::getSuccessors()` takes a `RegionBranchPoint`
parameter, and such region branch points can be constructed only from
`RegionBranchTerminatorOpInterface` and not arbitrary `Operation *`.
- Helper functions + default interface method implementations enumerate
region branch points by looking for `RegionBranchTerminatorOpInterface`
and ignoring other operations. E.g.,
`RegionBranchOpInterface::getAllRegionBranchPoints`, default
implementation of `RegionBranchOpInterface::getSuccessorRegions(Region
&)`, default implementation of
`RegionBranchOpInterface::getPredecessorValues`.
- Core analyses such as `DeadCodeAnalysis` look for
`RegionBranchTerminatorOpInterface` and misbehave when the interface is
not implemented. The analysis does not (and cannot) query region
successors of a region branching terminator that does not implement the
`RegionBranchTerminatorOpInterface`. In practice, this means that
forgetting to implement the `RegionBranchTerminatorOpInterface` may
incorrectly classify regions as dead.
- Other analyses / transformations that look for and depend on the
implementation of `RegionBranchTerminatorOpInterface`:
`mlir::getControlFlowPredecessors`,
`AbstractDenseBackwardDataFlowAnalysis`, ownership-based buffer
deallocation pass.
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
…terminator (llvm#174221)

`scf.forall` does not completely implement the
`RegionBranchOpInterface`: `scf.forall.in_parallel` does not implement
the `RegionBranchTerminatorOpInterface`.

Incomplete interface implementation is a problem for transformations
that try to understand the control flow by querying the
`RegionBranchOpInterface`.

Detailed explanation of what is wrong with the current implementation.
- There is exactly one region branch point: "parent". `in_parallel` is
not a region branch point because it does not implement the
`RegionBranchTerminatorOpInterface`. (Clarified in llvm#174978.)
- `ForallOp::getSuccessorRegions(parent)` returns one region successors:
the region of the `scf.forall` op.
- Since there is no region branch point in the region, there is no way
to leave the region. This means: once you enter the region, you are
stuck in it indefinitely. (It is unspecified what happens once you are
in the region, but we can be sure that you cannot leave it.)

This commit adds the `RegionBranchTerminatorOpInterface` (via
`ReturnLike`) to `scf.forall.in_parallel` to indicate the after leaving
the region, the control flow returns to the parent. (Note: Only block
terminators in directly nested regions can be region branch terminators,
so `in_parallel` is the only possible op. I.e., `parallel_insert_slice`
cannot be a region branch terminator.)

This commit also removes all successor operands / inputs from the
implementation. The number of successor operands and successor inputs
must match, but `scf.forall.in_parallel` has no operands. Therefore, the
region must also have 0 successor inputs. Therefore, the `scf.forall` op
must also have 0 successor operands.

This commit also adds a missing control flow edge from "parent" to
"parent": in case of 0 threads, the region is not entered.

Depends on llvm#174978.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants