tldr: Simplify the RegionBranchOpInterface by removing support for certain data flow patterns: the property of “being a successor input” no longer depends on the source of a region control flow edge. The proposed change removes expressivity from the op interface.
Skip background sections if you are familiar with the RegionBranchOpInterface.
Background: Notation and Terms
%r = scf.for %iv = ... iter_args(%arg0 = %0) {
// ...
scf.yield %1
}
%r and %arg0 are successor inputs. %0 and %1 are successor operands. Successor inputs are SSA values that are simply forwarded from successor operands. Note that both %iv and %arg0 are block arguments of the region, but %iv is not a successor input because its value is determined by the scf.for operation “itself”. (It’s not just forwarded.)
Background: RegionBranchOpInterface
The RegionBranchOpInterface models region control flow and data flow. It has an interface method to query control flow edges together with successor inputs.
void ForOp::getSuccessorRegions(RegionBranchPoint point,
SmallVectorImpl<RegionSuccessor> ®ions) {
// Both the operation itself and the region may be branching into the body or
// back into the operation itself. It is possible for loop not to enter the
// body.
regions.push_back(RegionSuccessor(&getRegion(), getRegionIterArgs()));
// ^^^^^^^^^^^^^^^^^^^
// successor inputs
regions.push_back(RegionSuccessor(getOperation(), getResults()));
// ^^^^^^^^^^^^^^ ^^^^^^^^^^^^
// "parent" successor inputs
}
A “region branch point” is the source of a control flow edge. A region successor is the destination of a control flow edge. There are special “parent” source/destination, which indicate branching into / out of the op.
CF edge
region branch point (sucessor operands) ----> region successor (successor inputs)
Overgeneralized Design
The RegionBranchOpInterface may be too general/complex and allow for cases that are not needed in the real world. This is presumably one of the reasons that has lead to incorrect usage of the interface in at least two places in core MLIR: dataflow analysis framework and SliceWalk.cpp. There are likely more such places.
Consider this hypothetical operation with two regions:
// if-like operation with 2 results but a single yielded value per region.
%r0, %r1 = "my_dialect.region_op"(%condition) (
{ // regionA
^bb0:
"my_dialect.yield"(%x) : (i32) -> () // yield A
}, { // regionB
^bb1:
"my_dialect.yield"(%y) : (i32) -> () // yield B
}) : (i1) -> (i32, i32)
And the following region branching behavior:
void MyDialect::RegionOp::getSuccessorRegions(RegionBranchPoint point,
SmallVectorImpl<RegionSuccessor> ®ions) {
if (point.isParent()) {
// When entering the operation for the first time, we dispatch to regionA
// or regionB based on the value of %condition.
regions.push_back(RegionSuccessor(&getRegionA()));
regions.push_back(RegionSuccessor(&getRegionB()));
} else if (point.getTerminatorPredecessorOrNull() == getYieldA()) {
// When leaving from region A, we leave the entire region op and the yielded
// value is forwarded as %r0.
regions.push_back(RegionSuccessor(&getOperation(), getResult(0)));
} else if (point.getTerminatorPredecessorOrNull() == getYieldB()) {
// When leaving from region B, we leave the entire region op and the yielded
// value is forwarded as %r1.
regions.push_back(RegionSuccessor(&getOperation(), getResult(1)));
}
}
Note that either %r0 or %r1 are successor inputs, depending on the region from where we are coming. When coming from region A, %r0 is the forwarded value. When coming from region B, %r1 is the forwarded value.
Is this useful? Do we need such a general RegionBranchOpInterface design?
Proposal: Separate Successor Inputs from Region Branch Point
To simplify the design, I propose to separate successor inputs from region branch points. I.e., a block argument / op result is a successor input regardless of where the branch originated from. Data flow such as the one shown above in "my_dialect.region_op" would no longer be expressible with the interface.
The proposed change is a breaking change: if somebody is relying on this functionality, they would no longer be able to use the RegionBranchOpInterface and potentially analyses/transformations that build on top of it such as the MLIR dataflow analysis framework.
If you have such a use case: please reply to this RFC!
Implementation Sketch
- Remove
ValueRange inputs;fromclass RegionSuccessor. We could even switch tousing RegionSuccessor = Region *;, wherenullptrindicates a branch to the “parent” (leaving the op). - Add a new interface method
RegionBranchOpInterface::getSuccessorInputs(RegionSuccessor)that returns aSmallVector<Value>with all successor inputs. Note that it does not matter what the region branch point was. RegionBranchOpInterface::getSuccessorRegionsimplementations no longer specify successor inputs. They just specify the target regions.