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

Abhay Kanhere via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 2 09:51:59 PDT 2026


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

>From c7c1879308af3ffc44c54d1f1cc9ff704ae8cc8f 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 1/2] [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 031ca237289e4..816b6e442e41d 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13465,6 +13465,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;
+    }
+  }
 
   // Avoid negative or zero stride values.
   if (!PositiveStride) {
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 78b1992c54f431d37b9dddef9d4f220df8e37bc3 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 2 Sep 2026 09:39:52 -0700
Subject: [PATCH 2/2] [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                       | 5 +++--
 .../trip-count-stride-positive-from-guard.ll                | 6 +++---
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 816b6e442e41d..755115ce1ae65 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13463,13 +13463,14 @@ 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;
 
   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;
     }
   }
@@ -13546,7 +13547,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
   } else if (!NoWrap) {
     // Avoid proven overflow cases: this will ensure that the backedge taken
     // count will not generate any unsigned overflow.
-    if (canIVOverflowOnLT(RHS, Stride, IsSigned))
+    if (canIVOverflowOnLT(RHS, StrideForBounds, IsSigned))
       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:



More information about the llvm-commits mailing list