[llvm] [ScalarEvolution] howManyLessThans infer stride isPositive from guard (PR #214014)

Abhay Kanhere via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 14 13:06:31 PDT 2026


https://github.com/AbhayKanhere updated https://github.com/llvm/llvm-project/pull/214014

>From 4eb43c60ff6dbcf2a7db27ed85050d35fa47f5cd Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 4 Aug 2026 10:05:40 -0700
Subject: [PATCH 01/10] [ScalarEvolution] howManyLessThans infer stride
 isPositive from guard

Infer Stride is positive from loop guard.
This enables SCEV to compute symbolic-max backedge-taken count for
multi-exit loops.

Motivating example:

  void example(int *p, int n, int stride, int bound) {
    if (stride <= 0) __builtin_trap();     // dominating guard  => stride > 0
    for (int i = 0; i < n; i += stride) {
      if (i > bound) __builtin_trap();      // per-iteration bounds check (2nd exit)
      p[i] = 0;
    }
  }
---
 llvm/lib/Analysis/ScalarEvolution.cpp         |  8 ++
 .../trip-count-stride-positive-from-guard.ll  | 92 +++++++++++++++++++
 2 files changed, 100 insertions(+)
 create mode 100644 llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index a93ce95e791a7..384007049bf70 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13426,6 +13426,14 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   const SCEV *Stride = IV->getStepRecurrence(*this);
 
   bool PositiveStride = isKnownPositive(Stride);
+  // A dominating guard may prove the stride positive.
+  if (!PositiveStride) {
+    const SCEV *GuardedStride = applyLoopGuards(Stride, L);
+    if (isKnownPositive(GuardedStride)) {
+      Stride = GuardedStride;
+      PositiveStride = true;
+    }
+  }
 
   // Whether the IV may reach the maximum value before the exit is taken.
   bool IVMayOverflow = true;
diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
new file mode 100644
index 0000000000000..90919598b598c
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -0,0 +1,92 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py
+; RUN: opt -disable-output -passes='print<scalar-evolution>' < %s 2>&1 | FileCheck %s
+;
+; howManyLessThans: prove a symbolic stride is positive from a guard dominating
+; the loop, so the backedge-taken count becomes computable.
+
+declare void @llvm.trap()
+
+; Positive: `if (s <= 0) trap` dominates the loop, so on the loop path s > 0.
+; The stride is refined to (1 smax %s) via the loop guards, which is positive,
+; so the backedge-taken count is computable despite the trapping exit.
+define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
+; CHECK-LABEL: 'pos_stride_guard'
+; CHECK-NEXT:  Classifying expressions for: @pos_stride_guard
+; CHECK-NEXT:    %i = phi i32 [ 0, %ph ], [ %i.next, %latch ]
+; CHECK-NEXT:    --> {0,+,%s}<nsw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %addr = getelementptr i32, ptr %p, i32 %i
+; CHECK-NEXT:    --> {%p,+,(4 * (sext i32 %s to i64))<nsw>}<%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nsw i32 %i, %s
+; CHECK-NEXT:    --> {%s,+,%s}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @pos_stride_guard
+; CHECK-NEXT:  Loop %loop: <multiple exits> Unpredictable backedge-taken count.
+; CHECK-NEXT:    exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    exit count for latch: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 2147483648
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    symbolic max exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
+;
+entry:
+  %sle0 = icmp sle i32 %s, 0
+  br i1 %sle0, label %trapg, label %ph
+trapg:
+  call void @llvm.trap()
+  unreachable
+ph:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %ph ], [ %i.next, %latch ]
+  %oob = icmp sgt i32 %i, %bound
+  br i1 %oob, label %trap, label %latch
+trap:
+  call void @llvm.trap()
+  unreachable
+latch:
+  %addr = getelementptr i32, ptr %p, i32 %i
+  store i32 0, ptr %addr
+  %i.next = add nsw i32 %i, %s
+  %cmp = icmp slt i32 %i.next, %n
+  br i1 %cmp, label %loop, label %exit
+exit:
+  ret void
+}
+
+; Negative: no dominating stride guard, so the stride's sign is unknown and the
+; multi-exit (trapping) loop's backedge-taken count stays unpredictable.
+define void @no_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
+; CHECK-LABEL: 'no_guard'
+; CHECK-NEXT:  Classifying expressions for: @no_guard
+; CHECK-NEXT:    %i = phi i32 [ 0, %entry ], [ %i.next, %latch ]
+; CHECK-NEXT:    --> {0,+,%s}<nsw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %addr = getelementptr i32, ptr %p, i32 %i
+; CHECK-NEXT:    --> {%p,+,(4 * (sext i32 %s to i64))<nsw>}<%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nsw i32 %i, %s
+; CHECK-NEXT:    --> {%s,+,%s}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @no_guard
+; CHECK-NEXT:  Loop %loop: <multiple exits> Unpredictable backedge-taken count.
+; CHECK-NEXT:    exit count for loop: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:    exit count for latch: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:  Loop %loop: Unpredictable constant max backedge-taken count.
+; CHECK-NEXT:  Loop %loop: Unpredictable symbolic max backedge-taken count.
+; CHECK-NEXT:    symbolic max exit count for loop: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
+;
+entry:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %entry ], [ %i.next, %latch ]
+  %oob = icmp sgt i32 %i, %bound
+  br i1 %oob, label %trap, label %latch
+trap:
+  call void @llvm.trap()
+  unreachable
+latch:
+  %addr = getelementptr i32, ptr %p, i32 %i
+  store i32 0, ptr %addr
+  %i.next = add nsw i32 %i, %s
+  %cmp = icmp slt i32 %i.next, %n
+  br i1 %cmp, label %loop, label %exit
+exit:
+  ret void
+}

>From 911bb21398ef5b956d4aa37e7249a2a566a14e81 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 2 Sep 2026 10:07:26 -0700
Subject: [PATCH 02/10] [ScalarEvolution] Dont substitute GuardedStride over
 Stride

GuardedStride is context sensitive (based on current loop L's loop guards).
Hence use GuardedStride only for canIVOverflowOnLT.
---
 llvm/lib/Analysis/ScalarEvolution.cpp                 | 11 ++++++-----
 .../trip-count-stride-positive-from-guard.ll          |  6 +++---
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 384007049bf70..0437209b3922b 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13424,20 +13424,21 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   ICmpInst::Predicate Cond = IsSigned ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT;
 
   const SCEV *Stride = IV->getStepRecurrence(*this);
+  const SCEV *StrideForBounds = Stride;
+
+  // Whether the IV may reach the maximum value before the exit is taken.
+  bool IVMayOverflow = true;
 
   bool PositiveStride = isKnownPositive(Stride);
   // A dominating guard may prove the stride positive.
   if (!PositiveStride) {
     const SCEV *GuardedStride = applyLoopGuards(Stride, L);
     if (isKnownPositive(GuardedStride)) {
-      Stride = GuardedStride;
+      StrideForBounds = GuardedStride;
       PositiveStride = true;
     }
   }
 
-  // Whether the IV may reach the maximum value before the exit is taken.
-  bool IVMayOverflow = true;
-
   // Avoid negative or zero stride values.
   if (!PositiveStride) {
     // We can compute the correct backedge taken count for loops with unknown
@@ -13510,7 +13511,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   } else {
     // Avoid proven overflow cases: this will ensure that the backedge taken
     // count will not generate any unsigned overflow.
-    IVMayOverflow = canIVOverflowOnLT(RHS, Stride, IsSigned);
+    IVMayOverflow = canIVOverflowOnLT(RHS, StrideForBounds, IsSigned);
     if (IVMayOverflow && !NoWrap)
       return getCouldNotCompute();
   }
diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
index 90919598b598c..87a9071180cd7 100644
--- a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -20,11 +20,11 @@ define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-NEXT:    --> {%s,+,%s}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @pos_stride_guard
 ; CHECK-NEXT:  Loop %loop: <multiple exits> Unpredictable backedge-taken count.
-; CHECK-NEXT:    exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
 ; CHECK-NEXT:    exit count for latch: ***COULDNOTCOMPUTE***
 ; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 2147483648
-; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
-; CHECK-NEXT:    symbolic max exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (zext i32 (1 smax %s) to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    symbolic max exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
 ; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
 ;
 entry:

>From 73210d75634506b392f6bcc066d6a90897d964e5 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 2 Sep 2026 10:37:45 -0700
Subject: [PATCH 03/10] [test] Update test

test   llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
updated following rebase/conflict resolution.
---
 .../trip-count-stride-positive-from-guard.ll                | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
index 87a9071180cd7..f5f79806f74c5 100644
--- a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -20,11 +20,11 @@ define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-NEXT:    --> {%s,+,%s}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @pos_stride_guard
 ; CHECK-NEXT:  Loop %loop: <multiple exits> Unpredictable backedge-taken count.
-; CHECK-NEXT:    exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:    exit count for loop: ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
 ; CHECK-NEXT:    exit count for latch: ***COULDNOTCOMPUTE***
 ; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 2147483648
-; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
-; CHECK-NEXT:    symbolic max exit count for loop: ((((-1 * (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))<nuw><nsw> + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64)) + (1 umin (0 smax (1 + (sext i32 %bound to i64))<nsw>)))
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
+; CHECK-NEXT:    symbolic max exit count for loop: ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
 ; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
 ;
 entry:

>From d3d2fbbd54f17358ecdac74d64a85b44cb7bd2e6 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Thu, 3 Sep 2026 10:53:48 -0700
Subject: [PATCH 04/10] [SCEV] review updates

test simplified, other review comments addressed.
---
 llvm/lib/Analysis/ScalarEvolution.cpp         |  6 +--
 .../trip-count-stride-positive-from-guard.ll  | 37 ++++++++-----------
 2 files changed, 18 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 0437209b3922b..b3b93da3a3843 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13424,7 +13424,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   ICmpInst::Predicate Cond = IsSigned ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT;
 
   const SCEV *Stride = IV->getStepRecurrence(*this);
-  const SCEV *StrideForBounds = Stride;
+  const SCEV *StrideWithGuards = Stride;
 
   // Whether the IV may reach the maximum value before the exit is taken.
   bool IVMayOverflow = true;
@@ -13434,7 +13434,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   if (!PositiveStride) {
     const SCEV *GuardedStride = applyLoopGuards(Stride, L);
     if (isKnownPositive(GuardedStride)) {
-      StrideForBounds = GuardedStride;
+      StrideWithGuards = GuardedStride;
       PositiveStride = true;
     }
   }
@@ -13511,7 +13511,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   } else {
     // Avoid proven overflow cases: this will ensure that the backedge taken
     // count will not generate any unsigned overflow.
-    IVMayOverflow = canIVOverflowOnLT(RHS, StrideForBounds, IsSigned);
+    IVMayOverflow = canIVOverflowOnLT(RHS, StrideWithGuards, IsSigned);
     if (IVMayOverflow && !NoWrap)
       return getCouldNotCompute();
   }
diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
index f5f79806f74c5..7a97631fa109d 100644
--- a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -4,15 +4,13 @@
 ; howManyLessThans: prove a symbolic stride is positive from a guard dominating
 ; the loop, so the backedge-taken count becomes computable.
 
-declare void @llvm.trap()
-
-; Positive: `if (s <= 0) trap` dominates the loop, so on the loop path s > 0.
+; Positive: an assume dominating the loop establishes s > 0 on the loop path.
 ; The stride is refined to (1 smax %s) via the loop guards, which is positive,
-; so the backedge-taken count is computable despite the trapping exit.
+; so the backedge-taken count of this multi-exit loop is computable.
 define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-LABEL: 'pos_stride_guard'
 ; CHECK-NEXT:  Classifying expressions for: @pos_stride_guard
-; CHECK-NEXT:    %i = phi i32 [ 0, %ph ], [ %i.next, %latch ]
+; CHECK-NEXT:    %i = phi i32 [ 0, %entry ], [ %i.next, %latch ]
 ; CHECK-NEXT:    --> {0,+,%s}<nsw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %addr = getelementptr i32, ptr %p, i32 %i
 ; CHECK-NEXT:    --> {%p,+,(4 * (sext i32 %s to i64))<nsw>}<%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
@@ -28,32 +26,27 @@ define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
 ;
 entry:
-  %sle0 = icmp sle i32 %s, 0
-  br i1 %sle0, label %trapg, label %ph
-trapg:
-  call void @llvm.trap()
-  unreachable
-ph:
+  %sgt = icmp sgt i32 %s, 0
+  call void @llvm.assume(i1 %sgt)
   br label %loop
+
 loop:
-  %i = phi i32 [ 0, %ph ], [ %i.next, %latch ]
+  %i = phi i32 [ 0, %entry ], [ %i.next, %latch ]
   %oob = icmp sgt i32 %i, %bound
-  br i1 %oob, label %trap, label %latch
-trap:
-  call void @llvm.trap()
-  unreachable
+  br i1 %oob, label %exit, label %latch
+
 latch:
   %addr = getelementptr i32, ptr %p, i32 %i
   store i32 0, ptr %addr
   %i.next = add nsw i32 %i, %s
   %cmp = icmp slt i32 %i.next, %n
   br i1 %cmp, label %loop, label %exit
+
 exit:
   ret void
 }
 
-; Negative: no dominating stride guard, so the stride's sign is unknown and the
-; multi-exit (trapping) loop's backedge-taken count stays unpredictable.
+; same as above without precondition showing stride is positive.
 define void @no_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-LABEL: 'no_guard'
 ; CHECK-NEXT:  Classifying expressions for: @no_guard
@@ -74,19 +67,19 @@ define void @no_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ;
 entry:
   br label %loop
+
 loop:
   %i = phi i32 [ 0, %entry ], [ %i.next, %latch ]
   %oob = icmp sgt i32 %i, %bound
-  br i1 %oob, label %trap, label %latch
-trap:
-  call void @llvm.trap()
-  unreachable
+  br i1 %oob, label %exit, label %latch
+
 latch:
   %addr = getelementptr i32, ptr %p, i32 %i
   store i32 0, ptr %addr
   %i.next = add nsw i32 %i, %s
   %cmp = icmp slt i32 %i.next, %n
   br i1 %cmp, label %loop, label %exit
+
 exit:
   ret void
 }

>From a80ea31859c02dc5e4c090d1691be19777104d4c Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 8 Sep 2026 13:13:00 -0700
Subject: [PATCH 05/10] [SCEV]  encode context-aware fact into Stride expr

Using loopguards, we prove stride>0. By encoding this information into
stride (i.e. set stride=umax(stride,1)), we make this information available
to context-free queries to isKnownNonZero etc e.g. in IndVarSimplify's
isSafeToExpand when it tries to simplify expression for last value with
stride s.
---
 llvm/lib/Analysis/ScalarEvolution.cpp                       | 6 ++++++
 .../trip-count-stride-positive-from-guard.ll                | 6 +++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index b3b93da3a3843..52c3d2b737635 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13436,6 +13436,12 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
     if (isKnownPositive(GuardedStride)) {
       StrideWithGuards = GuardedStride;
       PositiveStride = true;
+      // Encode the context-sensitive stride > 0 fact into the expression:
+      // exit-value expansion will only issue a context-free non-zero query on
+      // the raw stride, so clamp Stride here to keep the backedge-count divisor
+      // provably non-zero (it equals Stride on the guarded path where the loop
+      // runs).
+      Stride = getUMaxExpr(Stride, getOne(Stride->getType()));
     }
   }
 
diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
index 7a97631fa109d..853ad57ae71a7 100644
--- a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -18,11 +18,11 @@ define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-NEXT:    --> {%s,+,%s}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @pos_stride_guard
 ; CHECK-NEXT:  Loop %loop: <multiple exits> Unpredictable backedge-taken count.
-; CHECK-NEXT:    exit count for loop: ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
+; CHECK-NEXT:    exit count for loop: ((-1 + (1 umax (sext i32 %s to i64)) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (1 umax (sext i32 %s to i64)))
 ; CHECK-NEXT:    exit count for latch: ***COULDNOTCOMPUTE***
 ; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 2147483648
-; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
-; CHECK-NEXT:    symbolic max exit count for loop: ((-1 + (sext i32 %s to i64) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (sext i32 %s to i64))
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((-1 + (1 umax (sext i32 %s to i64)) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (1 umax (sext i32 %s to i64)))
+; CHECK-NEXT:    symbolic max exit count for loop: ((-1 + (1 umax (sext i32 %s to i64)) + (0 smax (1 + (sext i32 %bound to i64))<nsw>)) /u (1 umax (sext i32 %s to i64)))
 ; CHECK-NEXT:    symbolic max exit count for latch: ***COULDNOTCOMPUTE***
 ;
 entry:

>From 6e70eaeca1b971dde30cc5755ccd2ff4b1bea489 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 8 Sep 2026 13:17:03 -0700
Subject: [PATCH 06/10] [IndVarSimplify] Test exit-value rewrite for
 guard-derived positive stride

---
 .../exit-value-guarded-positive-stride.ll     | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll

diff --git a/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll b/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll
new file mode 100644
index 0000000000000..c59eb76b20c61
--- /dev/null
+++ b/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll
@@ -0,0 +1,53 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=indvars -S < %s | FileCheck %s
+;
+; When a dominating guard proves the symbolic stride positive, howManyLessThans
+; must keep the backedge-count divisor provably non-zero (clamped to 1 umax %s)
+; so the exit value stays safe to expand and IndVarSimplify rewrites it to a
+; closed form. A raw %s divisor would be rejected by isSafeToExpand (a
+; context-free non-zero query) and the loop would be left intact.
+
+define i32 @exit_value_guarded_positive_stride(i32 %s, i32 %n) mustprogress {
+; CHECK-LABEL: @exit_value_guarded_positive_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[G:%.*]] = icmp sgt i32 [[S:%.*]], 0
+; CHECK-NEXT:    br i1 [[G]], label [[PH:%.*]], label [[BAIL:%.*]]
+; CHECK:       bail:
+; CHECK-NEXT:    ret i32 -1
+; CHECK:       ph:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    br i1 false, label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N:%.*]], i32 [[S]])
+; CHECK-NEXT:    [[TMP0:%.*]] = sub i32 [[SMAX]], [[S]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i32 @llvm.umin.i32(i32 [[TMP0]], i32 1)
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[TMP0]], [[UMIN]]
+; CHECK-NEXT:    [[UMAX:%.*]] = call i32 @llvm.umax.i32(i32 [[S]], i32 1)
+; CHECK-NEXT:    [[TMP2:%.*]] = udiv i32 [[TMP1]], [[UMAX]]
+; CHECK-NEXT:    [[TMP3:%.*]] = add i32 [[UMIN]], [[TMP2]]
+; CHECK-NEXT:    [[TMP4:%.*]] = add i32 [[TMP3]], 1
+; CHECK-NEXT:    ret i32 [[TMP4]]
+;
+entry:
+  %g = icmp sgt i32 %s, 0
+  br i1 %g, label %ph, label %bail
+
+bail:
+  ret i32 -1
+
+ph:
+  br label %loop
+
+loop:
+  %i = phi i32 [ 0, %ph ], [ %i.next, %loop ]
+  %c = phi i32 [ 0, %ph ], [ %c.next, %loop ]
+  %c.next = add i32 %c, 1
+  %i.next = add nsw i32 %i, %s
+  %cmp = icmp slt i32 %i.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  %lcssa = phi i32 [ %c.next, %loop ]
+  ret i32 %lcssa
+}

>From 207c473dc40f8b1e94be0febc53b1613ede5e462 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 8 Sep 2026 13:22:01 -0700
Subject: [PATCH 07/10] [SCEV] simple comment fix.

simplified comment.
---
 llvm/lib/Analysis/ScalarEvolution.cpp | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 52c3d2b737635..af1644ee5a736 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13436,11 +13436,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
     if (isKnownPositive(GuardedStride)) {
       StrideWithGuards = GuardedStride;
       PositiveStride = true;
-      // Encode the context-sensitive stride > 0 fact into the expression:
-      // exit-value expansion will only issue a context-free non-zero query on
-      // the raw stride, so clamp Stride here to keep the backedge-count divisor
-      // provably non-zero (it equals Stride on the guarded path where the loop
-      // runs).
+      // Encode the context-sensitive stride > 0 fact into the expression
       Stride = getUMaxExpr(Stride, getOne(Stride->getType()));
     }
   }

>From 1fb7f81a6a8a00dfd2d20a1891cf15f96f9afb99 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Mon, 14 Sep 2026 12:34:22 -0700
Subject: [PATCH 08/10] [SCEV] minor renaming

review fixes.
---
 llvm/lib/Analysis/ScalarEvolution.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index af1644ee5a736..01168e4cab6f9 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13424,7 +13424,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   ICmpInst::Predicate Cond = IsSigned ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT;
 
   const SCEV *Stride = IV->getStepRecurrence(*this);
-  const SCEV *StrideWithGuards = Stride;
+  const SCEV *GuardedStride = Stride;
 
   // Whether the IV may reach the maximum value before the exit is taken.
   bool IVMayOverflow = true;
@@ -13432,9 +13432,9 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   bool PositiveStride = isKnownPositive(Stride);
   // A dominating guard may prove the stride positive.
   if (!PositiveStride) {
-    const SCEV *GuardedStride = applyLoopGuards(Stride, L);
-    if (isKnownPositive(GuardedStride)) {
-      StrideWithGuards = GuardedStride;
+    const SCEV* LoopGuardedStride = applyLoopGuards(Stride, L);
+    if (isKnownPositive(LoopGuardedStride)) {
+      GuardedStride = LoopGuardedStride;
       PositiveStride = true;
       // Encode the context-sensitive stride > 0 fact into the expression
       Stride = getUMaxExpr(Stride, getOne(Stride->getType()));
@@ -13513,7 +13513,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   } else {
     // Avoid proven overflow cases: this will ensure that the backedge taken
     // count will not generate any unsigned overflow.
-    IVMayOverflow = canIVOverflowOnLT(RHS, StrideWithGuards, IsSigned);
+    IVMayOverflow = canIVOverflowOnLT(RHS, GuardedStride, IsSigned);
     if (IVMayOverflow && !NoWrap)
       return getCouldNotCompute();
   }

>From 9710cabe21ed91f7e0ede736bdf2d996a8f11e31 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Mon, 14 Sep 2026 12:48:35 -0700
Subject: [PATCH 09/10] [SCEV] Add single-exit contrast case to
 stride-from-guard trip-count test

The single-exit loop is computable without the guard (via the 1 umax stride
clamp), so only the multi-exit form exercises the guard-derived positive-stride
inference. Also tighten the test comments.
---
 .../trip-count-stride-positive-from-guard.ll  | 40 +++++++++++++++++--
 1 file changed, 36 insertions(+), 4 deletions(-)

diff --git a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
index 853ad57ae71a7..b1a7188c277f7 100644
--- a/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
+++ b/llvm/test/Analysis/ScalarEvolution/trip-count-stride-positive-from-guard.ll
@@ -4,9 +4,8 @@
 ; howManyLessThans: prove a symbolic stride is positive from a guard dominating
 ; the loop, so the backedge-taken count becomes computable.
 
-; Positive: an assume dominating the loop establishes s > 0 on the loop path.
-; The stride is refined to (1 smax %s) via the loop guards, which is positive,
-; so the backedge-taken count of this multi-exit loop is computable.
+; Multi-exit symbolic-stride loop with a dominating assume(s > 0): the stride is
+; proven positive from the guard, so the backedge-taken count is computable.
 define void @pos_stride_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-LABEL: 'pos_stride_guard'
 ; CHECK-NEXT:  Classifying expressions for: @pos_stride_guard
@@ -46,7 +45,8 @@ exit:
   ret void
 }
 
-; same as above without precondition showing stride is positive.
+; Multi-exit symbolic-stride loop with no guard: the stride's sign is unknown,
+; so the backedge-taken count is not computable.
 define void @no_guard(i32 %n, i32 %s, i32 %bound, ptr %p) {
 ; CHECK-LABEL: 'no_guard'
 ; CHECK-NEXT:  Classifying expressions for: @no_guard
@@ -83,3 +83,35 @@ latch:
 exit:
   ret void
 }
+
+; Single-exit symbolic-stride loop: the backedge-taken count is computable
+; without a guard, via the (1 umax stride) clamp.
+define void @single_exit_stride(i32 %n, i32 %s, ptr %p) mustprogress {
+; CHECK-LABEL: 'single_exit_stride'
+; CHECK-NEXT:  Classifying expressions for: @single_exit_stride
+; CHECK-NEXT:    %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,%s}<nsw><%loop> U: full-set S: full-set Exits: (((((-1 * (1 umin ((-1 * %s) + (%n smax %s))))<nuw><nsw> + (-1 * %s) + (%n smax %s)) /u (1 umax %s)) + (1 umin ((-1 * %s) + (%n smax %s)))) * %s) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %addr = getelementptr i32, ptr %p, i32 %i
+; CHECK-NEXT:    --> {%p,+,(4 * (sext i32 %s to i64))<nsw>}<%loop> U: full-set S: full-set Exits: ((4 * (zext i32 ((((-1 * (1 umin ((-1 * %s) + (%n smax %s))))<nuw><nsw> + (-1 * %s) + (%n smax %s)) /u (1 umax %s)) + (1 umin ((-1 * %s) + (%n smax %s)))) to i64) * (sext i32 %s to i64)) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nsw i32 %i, %s
+; CHECK-NEXT:    --> {%s,+,%s}<nsw><%loop> U: full-set S: full-set Exits: ((1 + (((-1 * (1 umin ((-1 * %s) + (%n smax %s))))<nuw><nsw> + (-1 * %s) + (%n smax %s)) /u (1 umax %s)) + (1 umin ((-1 * %s) + (%n smax %s)))) * %s) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @single_exit_stride
+; CHECK-NEXT:  Loop %loop: backedge-taken count is ((((-1 * (1 umin ((-1 * %s) + (%n smax %s))))<nuw><nsw> + (-1 * %s) + (%n smax %s)) /u (1 umax %s)) + (1 umin ((-1 * %s) + (%n smax %s))))
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i32 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is ((((-1 * (1 umin ((-1 * %s) + (%n smax %s))))<nuw><nsw> + (-1 * %s) + (%n smax %s)) /u (1 umax %s)) + (1 umin ((-1 * %s) + (%n smax %s))))
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+  %addr = getelementptr i32, ptr %p, i32 %i
+  store i32 0, ptr %addr
+  %i.next = add nsw i32 %i, %s
+  %cmp = icmp slt i32 %i.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}

>From f735ee75b04dc6a5c755c339ea2e44195cf7fc80 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Mon, 14 Sep 2026 13:05:26 -0700
Subject: [PATCH 10/10] [test] Make exit-value-guarded-positive-stride
 distinguish the patch

Drop mustprogress so the symbolic-stride loop is not provably finite on its own:
IndVarSimplify leaves it intact unless the dominating guard proves the stride
positive, which lets the exit value be rewritten to a closed form.
---
 .../exit-value-guarded-positive-stride.ll             | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll b/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll
index c59eb76b20c61..7176f5ef9ecfe 100644
--- a/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll
+++ b/llvm/test/Transforms/IndVarSimplify/exit-value-guarded-positive-stride.ll
@@ -1,13 +1,12 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
 ; RUN: opt -passes=indvars -S < %s | FileCheck %s
 ;
-; When a dominating guard proves the symbolic stride positive, howManyLessThans
-; must keep the backedge-count divisor provably non-zero (clamped to 1 umax %s)
-; so the exit value stays safe to expand and IndVarSimplify rewrites it to a
-; closed form. A raw %s divisor would be rejected by isSafeToExpand (a
-; context-free non-zero query) and the loop would be left intact.
+; The loop is not mustprogress and the stride is symbolic, so on its own SCEV
+; cannot bound the trip count (a zero stride would loop forever) and
+; IndVarSimplify leaves the loop intact. The dominating guard proves %s > 0,
+; which makes the exit value computable and rewritten to a closed form.
 
-define i32 @exit_value_guarded_positive_stride(i32 %s, i32 %n) mustprogress {
+define i32 @exit_value_guarded_positive_stride(i32 %s, i32 %n) {
 ; CHECK-LABEL: @exit_value_guarded_positive_stride(
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[G:%.*]] = icmp sgt i32 [[S:%.*]], 0



More information about the llvm-commits mailing list