[Mlir-commits] [mlir] [MLIR][CF] Avoid collapsing blocks which participate in cycles (PR #160783)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sun Sep 28 00:12:34 PDT 2025
================
@@ -122,6 +122,18 @@ static LogicalResult collapseBranch(Block *&successor,
Block *successorDest = successorBranch.getDest();
if (successorDest == successor)
return failure();
+ // Don't try to collapse branches which participate in a cycle.
+ BranchOp nextBranch = dyn_cast<BranchOp>(successorDest->getTerminator());
+ llvm::DenseSet<Block *> visited{successorDest};
+ while (nextBranch) {
+ Block *nextBranchDest = nextBranch.getDest();
+ if (nextBranchDest == successor)
+ return failure();
+ else if (visited.contains(nextBranchDest))
+ break;
----------------
benwu25 wrote:
I chose `break` to allow for more merges. The only truly bad case seems to be when `successor` itself is part of the cycle. I.e., it is only absolutely necessary to bail out when this is the case. Consider the testcase from above:
```
func.func @cycle_4_blocks(%c : i1) {
cf.cond_br %c, ^bb6, ^bb7
^bb6:
cf.br ^bb5 {F}
^bb5:
cf.br ^bb1 {A}
^bb1:
cf.br ^bb2 {B}
^bb2:
cf.br ^bb3 {C}
^bb3:
cf.br ^bb4 {D}
^bb4:
cf.br ^bb1 {E}
^bb7:
cf.br ^bb6
}
```
With the prior implementation, this testcase gets simplified to:
```
func.func @cycle_4_blocks(%c : i1) {
cf.br ^bb1
^bb1:
cf.br ^bb1 {E}
}
```
But if we `return failure()`, the result is:
```
func.func @cycle_4_blocks(%c : i1) {
cf.cond_br %c, ^bb1, ^bb3
^bb1: // 2 preds: ^bb0, ^bb3
cf.br ^bb2 {A}
^bb2: // 2 preds: ^bb1, ^bb2
cf.br ^bb2 {E}
^bb3: // pred: ^bb0
cf.br ^bb1
}
```
I believe this is because a few canonicalize passes succeed in merging blocks if we choose `break` before the cycle itself is reached.
However, it doesn't matter much if infinite loops are fully optimized, and it is simpler and still correct to just bail out, so I will add that change.
https://github.com/llvm/llvm-project/pull/160783
More information about the Mlir-commits
mailing list