-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[mlir][transforms] Process RegionBranchOp with empty region #123895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This PR adds process for RegionBranchOp with empty region, such as `scf.if`.
@llvm/pr-subscribers-mlir @llvm/pr-subscribers-mlir-core Author: Longsheng Mou (CoTinker) ChangesThis PR adds process for RegionBranchOp with empty region, such as 'else' region of Full diff: https://github.com/llvm/llvm-project/pull/123895.diff 2 Files Affected:
diff --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index 3e7a0cca31c772..c20c54551cdf80 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -375,6 +375,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
// Mark live arguments in the regions of `regionBranchOp` in `liveArgs`.
auto markLiveArgs = [&](DenseMap<Region *, BitVector> &liveArgs) {
for (Region ®ion : regionBranchOp->getRegions()) {
+ if (region.empty())
+ continue;
SmallVector<Value> arguments(region.front().getArguments());
BitVector regionLiveArgs = markLives(arguments, nonLiveSet, la);
liveArgs[®ion] = regionLiveArgs;
@@ -420,6 +422,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
auto markNonForwardedReturnValues =
[&](DenseMap<Operation *, BitVector> &nonForwardedRets) {
for (Region ®ion : regionBranchOp->getRegions()) {
+ if (region.empty())
+ continue;
Operation *terminator = region.front().getTerminator();
nonForwardedRets[terminator] =
BitVector(terminator->getNumOperands(), true);
@@ -499,6 +503,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
// Recompute `resultsToKeep` and `argsToKeep` based on
// `terminatorOperandsToKeep`.
for (Region ®ion : regionBranchOp->getRegions()) {
+ if (region.empty())
+ continue;
Operation *terminator = region.front().getTerminator();
for (const RegionSuccessor &successor : getSuccessors(®ion)) {
Region *successorRegion = successor.getSuccessor();
@@ -547,6 +553,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
// Update the terminator operands that need to be kept.
for (Region ®ion : regionBranchOp->getRegions()) {
+ if (region.empty())
+ continue;
updateOperandsOrTerminatorOperandsToKeep(
terminatorOperandsToKeep[region.back().getTerminator()],
resultsToKeep, argsToKeep, ®ion);
@@ -611,8 +619,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
// Do (2.a) and (2.b).
for (Region ®ion : regionBranchOp->getRegions()) {
- assert(!region.empty() && "expected a non-empty region in an op "
- "implementing `RegionBranchOpInterface`");
+ if (region.empty())
+ continue;
BitVector argsToRemove = argsToKeep[®ion].flip();
cl.blocks.push_back({®ion.front(), argsToRemove});
collectNonLiveValues(nonLiveSet, region.front().getArguments(),
@@ -621,6 +629,8 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
// Do (2.c).
for (Region ®ion : regionBranchOp->getRegions()) {
+ if (region.empty())
+ continue;
Operation *terminator = region.front().getTerminator();
cl.operands.push_back(
{terminator, terminatorOperandsToKeep[terminator].flip()});
diff --git a/mlir/test/Transforms/remove-dead-values.mlir b/mlir/test/Transforms/remove-dead-values.mlir
index fe7bcbc7c490b6..e549926b904562 100644
--- a/mlir/test/Transforms/remove-dead-values.mlir
+++ b/mlir/test/Transforms/remove-dead-values.mlir
@@ -408,6 +408,22 @@ func.func @main(%arg3 : i32, %arg4 : i1) {
// -----
+// The scf.if operation represents an if-then-else construct for conditionally
+// executing two regions of code. The 'the' region has exactly 1 block, and
+// the 'else' region may have 0 or 1 block. This case is to ensure 'else' region
+// with 0 block not crash.
+
+// CHECK-LABEL: func.func @clean_region_branch_op_with_empty_region
+func.func @clean_region_branch_op_with_empty_region(%arg0: i1, %arg1: memref<f32>) {
+ %cst = arith.constant 1.000000e+00 : f32
+ scf.if %arg0 {
+ memref.store %cst, %arg1[] : memref<f32>
+ }
+ return
+}
+
+// -----
+
#map = affine_map<(d0)[s0, s1] -> (d0 * s0 + s1)>
func.func @kernel(%arg0: memref<18xf32>) {
%c1 = arith.constant 1 : index
|
Hi @Mogball, would you mind taking a look at this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! This seems like a pretty major bug, and I'm surprised that this is the only region affected by this bug, given how prevalent scf.if
with an empty else region must be.
That being said, I am not convinced this is the right fix. First, if this indeed an issue, there will be other passes that suffer from the same bug. We should clarify the contract of RegionBranchOpInterface has with its operations. Should ops be allowed to have empty regions? For example, IfOp::getRegionSuccessors
will ignore the else region if it's empty.
I personally think scf.if
should ban empty else region and simply require it to be else { scf.yield }
and we can sugar this in the syntax.
Thanks for the comment. I'm uncertain about handling the builder, specifically:
This function doesn't create an else region. The caller will use inlineRegionBefore to add the region. Perhaps we should add a check to ensure the else region is not empty in IfOp::verify() and leave the builder as is? What do you think? |
That builder creates the then and else regions but leaves them empty. That's fine, since that's the default behaviour of the builders. I think fixing the verifier to require a non-empty region is a good solution, and then you will have to go about and fix places in the code where the caller doesn't create an But I would actually like to invite the opinion of others first, cc @matthias-springer @joker-eph |
Regions can't be dynamically added, they are fixed after creation, which is why the convention has been for ops like scf.if to create both regions immediately. Adding a block and a yield is "not free" and in general we avoided consuming extra resources when not needed, hence the convention about empty regions.
If we want to change, I would look at it holistically: it goes beyond just |
So, it is better for RegionBranchOpInterface not to assume a non-empty region when using getRegions, since getRegions is not a method of RegionBranchOpInterface and there're many cases empty regions are allowed right now? |
An alternate solution would be to change the code here to look only at the reachable regions by traversing the region successors. If this happens to be the only pass with this issue, I'm okay with the original change if |
I did a quick scan for the use of RegionBranchOpInterface. The only place I saw that assumes the region should not be empty is RemoveDeadValues. |
Thanks for your review. I am currently on vacation and will carefully look into your feedback once I return to the office. Thank you very much! |
Yes, the main difference lies in using |
Thanks. |
) This PR adds process for RegionBranchOp with empty region, such as 'else' region of `scf.if`. Fixes llvm#123246.
) This PR adds process for RegionBranchOp with empty region, such as 'else' region of `scf.if`. Fixes llvm#123246.
) This PR adds process for RegionBranchOp with empty region, such as 'else' region of `scf.if`. Fixes llvm#123246.
This PR adds process for RegionBranchOp with empty region, such as 'else' region of
scf.if
. Fixes #123246.