[llvm] [SCEV] Look for guard conditions through merge blocks. (PR #228135)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 09:47:04 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis
Author: Florian Hahn (fhahn)
<details>
<summary>Changes</summary>
Update LoopGuards::collectFromBlock to continue past single predecessor
chains by traversing further through the immediate dominator chain.
This improves results in a large number of cases
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1548 and
recovers most of the regression we saw from improved
ConstraintElimination (removing a redundant branch in the
single-predecessor chain via a fact from another dominating check).
This increases compile-time slightly (partly due to additional work in
SCEV, partly because additional transformations), but I think in this case
the increase is warranted by the benefits.
stage1-O3: +0.04%
stage1-ReleaseThinLTO: +0.04%
stage1-ReleaseLTO-g: +0.04%
stage1-aarch64-O3: +0.05%
stage2-O3: +0.04%
stage2-clang: -0.01%
https://llvm-compile-time-tracker.com/compare.php?from=8995a358fa77a669679594aa9857ba0e0b224ab1&to=b6a3c933e98791f6b7cfe38fb5db2032947b5256&stat=instructions%3Au
---
Full diff: https://github.com/llvm/llvm-project/pull/228135.diff
2 Files Affected:
- (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+38)
- (added) llvm/test/Analysis/ScalarEvolution/guards-in-dominating-blocks.ll (+229)
``````````diff
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 530b1400ae69e..a14ebdac18609 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -227,6 +227,13 @@ static cl::opt<unsigned> MaxLoopGuardCollectionDepth(
"scalar-evolution-max-loop-guard-collection-depth", cl::Hidden,
cl::desc("Maximum depth for recursive loop guard collection"), cl::init(1));
+static cl::opt<unsigned> MaxGuardDomTreeSteps(
+ "scalar-evolution-max-guard-dom-tree-steps", cl::Hidden,
+ cl::desc(
+ "Maximum number of immediate dominator steps taken to find guarding "
+ "conditions."),
+ cl::init(8));
+
static cl::opt<bool>
ClassifyExpressions("scalar-evolution-classify-expressions",
cl::Hidden, cl::init(true),
@@ -10952,6 +10959,30 @@ ScalarEvolution::getPredecessorWithUniqueSuccessorForBB(const BasicBlock *BB)
return {nullptr, BB};
}
+/// Walk through immediate dominators of \p BB to try to find additional
+/// dominating conditions.
+static void
+collectFromDominatingBranches(const DominatorTree &DT, const BasicBlock *BB,
+ function_ref<void(Value *, bool)> ProcessCond) {
+ const DomTreeNode *Node = DT.getNode(BB);
+ for (unsigned I = 0; Node && I != MaxGuardDomTreeSteps; ++I) {
+ const BasicBlock *ChildBB = Node->getBlock();
+ Node = Node->getIDom();
+ if (!Node)
+ break;
+ auto *Br = dyn_cast<CondBrInst>(Node->getBlock()->getTerminator());
+ if (!Br)
+ continue;
+
+ // If ChildBB is neither or both of the successors, no fact is implied.
+ bool EnterIfTrue = Br->getSuccessor(0) == ChildBB;
+ if (EnterIfTrue == (Br->getSuccessor(1) == ChildBB))
+ continue;
+ if (DT.dominates(BasicBlockEdge(Node->getBlock(), ChildBB), BB))
+ ProcessCond(Br->getCondition(), EnterIfTrue);
+ }
+}
+
/// SCEV structural equivalence is usually sufficient for testing whether two
/// expressions are equal, however for the purposes of looking for a condition
/// guarding a loop, it can be useful to be a little more general, since a
@@ -16133,6 +16164,13 @@ void ScalarEvolution::LoopGuards::collectFromBlock(
if (Depth > 0 && NumCollectedConditions == 2)
break;
}
+
+ if (!Pair.first)
+ collectFromDominatingBranches(SE.DT, Pair.second,
+ [&](Value *Cond, bool EnterIfTrue) {
+ Terms.emplace_back(Cond, EnterIfTrue);
+ });
+
// Finally, if we stopped climbing the predecessor chain because
// there wasn't a unique one to continue, try to collect conditions
// for PHINodes by recursively following all of their incoming
diff --git a/llvm/test/Analysis/ScalarEvolution/guards-in-dominating-blocks.ll b/llvm/test/Analysis/ScalarEvolution/guards-in-dominating-blocks.ll
new file mode 100644
index 0000000000000..2c98c7d2311b2
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/guards-in-dominating-blocks.ll
@@ -0,0 +1,229 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -disable-output "-passes=print<scalar-evolution>" -scalar-evolution-classify-expressions=false < %s 2>&1 | FileCheck %s
+
+; Tests for conditions that guard the loop but are only reachable by walking up
+; the dominator tree.
+
+define void @guard_through_unique_pred_chain(ptr %p) {
+; CHECK-LABEL: 'guard_through_unique_pred_chain'
+; CHECK-NEXT: Determining loop execution counts for: @guard_through_unique_pred_chain
+; CHECK-NEXT: Loop %loop: backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop: symbolic max backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ %guard = icmp ugt i32 %n, 1
+ br i1 %guard, label %ph, label %exit
+
+ph:
+ %m = add i32 %n, -1
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec = icmp ult i32 %iv.next, %m
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @guard_before_merge_without_phi(ptr %p, i1 %c) {
+; CHECK-LABEL: 'guard_before_merge_without_phi'
+; CHECK-NEXT: Determining loop execution counts for: @guard_before_merge_without_phi
+; CHECK-NEXT: Loop %loop: backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop: symbolic max backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ %guard = icmp ugt i32 %n, 1
+ br i1 %guard, label %split, label %exit
+
+split:
+ br i1 %c, label %then, label %else
+
+then:
+ br label %ph
+
+else:
+ br label %ph
+
+ph:
+ %m = add i32 %n, -1
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec = icmp ult i32 %iv.next, %m
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @guard_does_not_dominate_merge(ptr %p, i1 %c) {
+; CHECK-LABEL: 'guard_does_not_dominate_merge'
+; CHECK-NEXT: Determining loop execution counts for: @guard_does_not_dominate_merge
+; CHECK-NEXT: Loop %loop: backedge-taken count is (-1 + (1 umax (-1 + %n)))
+; CHECK-NEXT: Loop %loop: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (1 umax (-1 + %n)))
+; CHECK-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ br i1 %c, label %check.guard, label %ph
+
+check.guard:
+ %guard = icmp ugt i32 %n, 1
+ br i1 %guard, label %ph, label %exit
+
+ph:
+ %m = add i32 %n, -1
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec = icmp ult i32 %iv.next, %m
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @guard_with_reconverging_false_edge(ptr %p) {
+; CHECK-LABEL: 'guard_with_reconverging_false_edge'
+; CHECK-NEXT: Determining loop execution counts for: @guard_with_reconverging_false_edge
+; CHECK-NEXT: Loop %loop: backedge-taken count is (-1 + (1 umax (-1 + %n)))
+; CHECK-NEXT: Loop %loop: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (1 umax (-1 + %n)))
+; CHECK-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ %guard = icmp ugt i32 %n, 1
+ br i1 %guard, label %ph, label %bypass
+
+bypass:
+ br label %ph
+
+ph:
+ %m = add i32 %n, -1
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec = icmp ult i32 %iv.next, %m
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @guard_on_false_edge_before_merge(ptr %p, i1 %c) {
+; CHECK-LABEL: 'guard_on_false_edge_before_merge'
+; CHECK-NEXT: Determining loop execution counts for: @guard_on_false_edge_before_merge
+; CHECK-NEXT: Loop %loop: backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop: symbolic max backedge-taken count is (-2 + %n)
+; CHECK-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ %guard = icmp ule i32 %n, 1
+ br i1 %guard, label %exit, label %split
+
+split:
+ br i1 %c, label %then, label %else
+
+then:
+ br label %ph
+
+else:
+ br label %ph
+
+ph:
+ %m = add i32 %n, -1
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec = icmp ult i32 %iv.next, %m
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @multiple_guards_before_merges(ptr %p, ptr %q, i1 %c1, i1 %c2) {
+; CHECK-LABEL: 'multiple_guards_before_merges'
+; CHECK-NEXT: Determining loop execution counts for: @multiple_guards_before_merges
+; CHECK-NEXT: Loop %loop.header: <multiple exits> backedge-taken count is ((-2 + %k) umin (-2 + %n))
+; CHECK-NEXT: exit count for loop.header: (-2 + %k)
+; CHECK-NEXT: exit count for loop.latch: (-2 + %n)
+; CHECK-NEXT: Loop %loop.header: constant max backedge-taken count is i32 -2
+; CHECK-NEXT: Loop %loop.header: symbolic max backedge-taken count is ((-2 + %k) umin (-2 + %n))
+; CHECK-NEXT: symbolic max exit count for loop.header: (-2 + %k)
+; CHECK-NEXT: symbolic max exit count for loop.latch: (-2 + %n)
+; CHECK-NEXT: Loop %loop.header: Trip multiple is 1
+;
+entry:
+ %n = load i32, ptr %p, align 4
+ %k = load i32, ptr %q, align 4
+ %n.guard = icmp ugt i32 %n, 1
+ br i1 %n.guard, label %split1, label %exit
+
+split1:
+ br i1 %c1, label %then1, label %else1
+
+then1:
+ br label %merge1
+
+else1:
+ br label %merge1
+
+merge1:
+ %k.guard = icmp ugt i32 %k, 1
+ br i1 %k.guard, label %split2, label %exit
+
+split2:
+ br i1 %c2, label %then2, label %else2
+
+then2:
+ br label %ph
+
+else2:
+ br label %ph
+
+ph:
+ %n.m = add i32 %n, -1
+ %k.m = add i32 %k, -1
+ br label %loop.header
+
+loop.header:
+ %i = phi i32 [ 0, %ph ], [ %iv.next, %loop.latch ]
+ store i32 %i, ptr %p, align 4
+ %iv.next = add nuw i32 %i, 1
+ %ec.k = icmp ult i32 %iv.next, %k.m
+ br i1 %ec.k, label %loop.latch, label %exit
+
+loop.latch:
+ %ec.n = icmp ult i32 %iv.next, %n.m
+ br i1 %ec.n, label %loop.header, label %exit
+
+exit:
+ ret void
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/228135
More information about the llvm-commits
mailing list