[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