[llvm] [SCEV] Look through back-edge selects in `createAddRecFromPHI` (PR #190726)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 6 20:55:47 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis
Author: Andy Dienes (adienes)
<details>
<summary>Changes</summary>
disclaimer: until now I have only interacted with LLVM incidentally as it is used in Julia. I came across this while investigating bad performance of some Julia code. This PR (and my understanding of it 🙂) was co-developed with Claude Opus 4.6. I have done my very best to audit it, and this description is in my own words, which based on my unfamiliarity with this stuff are liable to be clunky or wrong.
A front end (here: Julia) https://godbolt.org/z/fYsv1G4qT may emit a single-block loop with a loop-carried value flowing through a select guarded by the exit condition. in the example above that's the `value_phi1759` receiving `spec.select64`. continuing with this example, on the back-edge, ` %.not.not56` must be false, so `%spec.select64 == %9`. This PR teaches `createAddRecFromPHI` to look through the select to recognize `%9` as a step expression and thus `value_phi1759` as an `AddRec`.
This is done by comparing the branch condition to the select condition. what we'd really want to know is if there is a general implication `Cond => BrCond` or `!Cond => BrCond`, but we only check a few primitive forms of this directly.
I also applied this patch to Julia's fork of LLVM and verified that
* the resulting `@<!-- -->code_native` is better optimized as expected
<details>
<summary>Before (11 instructions)</summary>
```
LBB0_4: ; %L70
; =>This Inner Loop Header: Depth=1
mov x14, x13
mov x15, x0
sub x10, x10, #<!-- -->1
cbz x11, LBB0_6
; %bb.5: ; %L70
; in Loop: Header=BB0_4 Depth=1
add x13, x14, x12
add x0, x13, x15
add x12, x12, #<!-- -->1
add x13, x14, x9
sub x11, x11, #<!-- -->1
cmp x14, x8
b.ne LBB0_4
```
</details>
<details>
<summary>After (8 instructions)</summary>
```
LBB0_4: ; %L70
; =>This Inner Loop Header: Depth=1
; │ @ REPL[18]:8 within `my_zip_accum`
; │┌ @ essentials.jl:1222 within `+`
add x0, x10, x0
; │└
; │ @ REPL[18]:7 within `my_zip_accum`
cmp x8, x12
b.eq LBB0_6
; %bb.5: ; %L70
; in Loop: Header=BB0_4 Depth=1
mov x14, x13
; │ @ REPL[18] within `my_zip_accum`
add x12, x12, #<!-- -->1
; │ @ REPL[18]:7 within `my_zip_accum`
sub x13, x13, x9
add x10, x10, x11
cbnz x14, LBB0_4
```
</details>
* The performance improves ~20% on the motivating iteration example
<details>
<summary>mini benchmark</summary>
```
@<!-- -->inline function my_zip_iterate(i1, i2, s1, s2)
r1 = iterate(i1, s1); r1 === nothing && return nothing
r2 = iterate(i2, s2); r2 === nothing && return nothing
return ((r1[1], r2[1]), (r1[2], r2[2]))
end
function my_zip_accum(A, B)
s = 0
r1 = iterate(A); r2 = iterate(B)
(r1 === nothing || r2 === nothing) && return s
s += r1[1] + r2[1]
r = my_zip_iterate(A, B, r1[2], r2[2])
while r !== nothing
(a, b), (sa, sb) = r; s += a + b
r = my_zip_iterate(A, B, sa, sb)
end; s
end
N = 10_000; ot = Base.OneTo(N); sr = 1:2:2N
using BenchmarkTools
@<!-- -->benchmark my_zip_accum($ot, $sr)
# master
BenchmarkTools.Trial: 10000 samples with 6 evaluations per sample.
Range (min … max): 5.528 μs … 21.458 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 5.556 μs ┊ GC (median): 0.00%
Time (mean ± σ): 5.611 μs ± 463.864 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
# PR
BenchmarkTools.Trial: 10000 samples with 7 evaluations per sample.
Range (min … max): 4.119 μs … 10.649 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 4.577 μs ┊ GC (median): 0.00%
Time (mean ± σ): 4.598 μs ± 277.535 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
```
</details>
---
Full diff: https://github.com/llvm/llvm-project/pull/190726.diff
2 Files Affected:
- (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+46)
- (added) llvm/test/Analysis/ScalarEvolution/select-poison-backedge.ll (+73)
``````````diff
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 2862acfedb91d..cbeef433edf60 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -5969,6 +5969,52 @@ const SCEV *ScalarEvolution::createAddRecFromPHI(PHINode *PN) {
assert(ValueExprMap.find_as(PN) == ValueExprMap.end() &&
"PHI node already processed?");
+ // May encounter single-block loops where the back-edge
+ // value flows through a select guarded by the exit condition.
+ // On the back-edge that condition has a known value, so we can look
+ // through the select to expose the recurrence.
+ if (auto *SI = dyn_cast<SelectInst>(BEValueV)) {
+ Value *Cond = SI->getCondition();
+ if (BasicBlock *Latch = L->getLoopLatch()) {
+ if (auto *BI = dyn_cast<BranchInst>(Latch->getTerminator())) {
+ if (BI->isConditional()) {
+ Value *BrCond = BI->getCondition();
+ bool ExitOnTrue = !L->contains(BI->getSuccessor(0));
+
+ // Figure out which truth value of Cond forces an exit.
+ bool CondTrueImpliesExit = false;
+ bool CondFalseImpliesExit = false;
+
+ if (Cond == BrCond) {
+ CondTrueImpliesExit = ExitOnTrue;
+ CondFalseImpliesExit = !ExitOnTrue;
+ } else if (match(Cond, m_Not(m_Specific(BrCond)))) {
+ CondTrueImpliesExit = !ExitOnTrue;
+ CondFalseImpliesExit = ExitOnTrue;
+ } else if (ExitOnTrue) {
+ // BrCond = Cond | Other, so Cond == true forces exit.
+ Value *Other;
+ if (match(BrCond, m_c_LogicalOr(m_Specific(Cond), m_Value(Other))))
+ CondTrueImpliesExit = true;
+ } else {
+ // BrCond = Cond & Other, so Cond == false forces exit.
+ Value *Other;
+ if (match(BrCond, m_c_LogicalAnd(m_Specific(Cond), m_Value(Other))))
+ CondFalseImpliesExit = true;
+ }
+
+ if (CondTrueImpliesExit) {
+ // On the back-edge Cond is false, so select yields the false arm.
+ BEValueV = SI->getFalseValue();
+ } else if (CondFalseImpliesExit) {
+ // On the back-edge Cond is true, so select yields the true arm.
+ BEValueV = SI->getTrueValue();
+ }
+ }
+ }
+ }
+ }
+
// First, try to find AddRec expression without creating a fictituos symbolic
// value for PN.
if (auto *S = createSimpleAffineAddRec(PN, BEValueV, StartValueV))
diff --git a/llvm/test/Analysis/ScalarEvolution/select-poison-backedge.ll b/llvm/test/Analysis/ScalarEvolution/select-poison-backedge.ll
new file mode 100644
index 0000000000000..b17cf2bc08679
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/select-poison-backedge.ll
@@ -0,0 +1,73 @@
+; RUN: opt -passes='print<scalar-evolution>' -disable-output %s 2>&1 | FileCheck %s
+
+; Some frontends emit single-block loops where loop-carried values flow
+; through a select guarded by the exit condition. SCEV should look through
+; the select on the back-edge to recover the underlying AddRec.
+
+; Test 1: select with poison
+define i64 @select_poison(i64 %n, i64 %start, i64 %stop, i64 %step) {
+entry:
+ %empty = icmp slt i64 %n, 1
+ br i1 %empty, label %exit, label %first
+
+first:
+ %first_sum = add i64 %start, 1
+ %first_done = icmp eq i64 %n, 1
+ %first_val_done = icmp eq i64 %start, %stop
+ %first_exit = or i1 %first_done, %first_val_done
+ %first_val_next = add i64 %start, %step
+ br i1 %first_exit, label %exit, label %body
+
+body:
+ %accum = phi i64 [ %first_sum, %first ], [ %sum, %body ]
+ %val = phi i64 [ %first_val_next, %first ], [ %sel.val, %body ]
+ %i = phi i64 [ 2, %first ], [ %i.next, %body ]
+ %ab = add i64 %val, %i
+ %sum = add i64 %ab, %accum
+ %done1 = icmp eq i64 %i, %n
+ %i.next = add i64 %i, 1
+ %done2 = icmp eq i64 %val, %stop
+ %either = select i1 %done1, i1 true, i1 %done2
+ %val.next = add i64 %val, %step
+ %sel.val = select i1 %done1, i64 poison, i64 %val.next
+ br i1 %either, label %exit, label %body
+
+exit:
+ %result = phi i64 [ 0, %entry ], [ %first_sum, %first ], [ %sum, %body ]
+ ret i64 %result
+}
+
+; CHECK-LABEL: Classifying expressions for: @select_poison
+; CHECK: %val = phi
+; CHECK-NEXT: --> {(%start + %step),+,%step}<%body>
+
+; Test 2: select with old value
+define i64 @select_oldval(i64 %n, i64 %start, i64 %stop, i64 %step) {
+entry:
+ %guard = icmp slt i64 %n, 1
+ %stop_guard = icmp eq i64 %start, %stop
+ %no_entry = or i1 %guard, %stop_guard
+ br i1 %no_entry, label %exit, label %body
+
+body:
+ %counter = phi i64 [ 1, %entry ], [ %sel.counter, %body ]
+ %stepper = phi i64 [ %start, %entry ], [ %step.next, %body ]
+ %accum = phi i64 [ 0, %entry ], [ %sum, %body ]
+ %step.next = add i64 %stepper, %step
+ %ab = add i64 %counter, %step.next
+ %sum = add i64 %ab, %accum
+ %done1 = icmp sge i64 %counter, %n
+ %counter.next = add i64 %counter, 1
+ %sel.counter = select i1 %done1, i64 %counter, i64 %counter.next
+ %done2 = icmp eq i64 %step.next, %stop
+ %either = select i1 %done1, i1 true, i1 %done2
+ br i1 %either, label %exit, label %body
+
+exit:
+ %result = phi i64 [ 0, %entry ], [ %sum, %body ]
+ ret i64 %result
+}
+
+; CHECK-LABEL: Classifying expressions for: @select_oldval
+; CHECK: %counter = phi
+; CHECK-NEXT: --> {1,+,1}<nuw><%body>
``````````
</details>
https://github.com/llvm/llvm-project/pull/190726
More information about the llvm-commits
mailing list