[llvm] [SCEV] Missed trip count for same-base inbounds pointer-IV loop (PR #211036)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 22 01:15:24 PDT 2026
https://github.com/sreedivyakottilingal updated https://github.com/llvm/llvm-project/pull/211036
>From f910f17101f19e4009715a275d541ebd21451520 Mon Sep 17 00:00:00 2001
From: Sreedivya K <sreedivya.k at multicorewareinc.com>
Date: Wed, 8 Jul 2026 23:27:11 +0530
Subject: [PATCH 1/2] Test added
---
.../test/Analysis/ScalarEvolution/ptrtoint.ll | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll b/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
index 2b07c944eb1d1..55cfc570db3cc 100644
--- a/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
+++ b/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
@@ -684,3 +684,88 @@ bb13: ; preds = %bb10, %bb8
bb14: ; preds = %bb
ret void
}
+
+define i64 @same_base_lt_204070(ptr %s, i64 %len, ptr %tbl) {
+; X64-LABEL: 'same_base_lt_204070'
+; X64-NEXT: Classifying expressions for: @same_base_lt_204070
+; X64-NEXT: %send = getelementptr inbounds i8, ptr %s, i64 %len
+; X64-NEXT: --> (%len + %s) U: full-set S: full-set
+; X64-NEXT: %matches = phi i64 [ %matches.next, %loop ], [ 0, %entry ]
+; X64-NEXT: --> %matches U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %iv = phi ptr [ %iv.next, %loop ], [ %s, %entry ]
+; X64-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)) + %s) LoopDispositions: { %loop: Computable }
+; X64-NEXT: %iv.next = getelementptr inbounds nuw i8, ptr %iv, i64 1
+; X64-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: ((-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)) + %s) LoopDispositions: { %loop: Computable }
+; X64-NEXT: %ch = load i8, ptr %iv, align 1
+; X64-NEXT: --> %ch U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %chz = zext i8 %ch to i64
+; X64-NEXT: --> (zext i8 %ch to i64) U: [0,256) S: [0,256) Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %tblp = getelementptr inbounds nuw [2 x i8], ptr %tbl, i64 %chz
+; X64-NEXT: --> ((2 * (zext i8 %ch to i64))<nuw><nsw> + %tbl)<nuw> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %v = load i16, ptr %tblp, align 2
+; X64-NEXT: --> %v U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %inc = zext i1 %ge to i64
+; X64-NEXT: --> (zext i1 %ge to i64) U: [0,2) S: [0,2) Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %matches.next = add i64 %matches, %inc
+; X64-NEXT: --> ((zext i1 %ge to i64) + %matches) U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X64-NEXT: %result = phi i64 [ 0, %entry ], [ %matches.next, %loop ]
+; X64-NEXT: --> %result U: full-set S: full-set
+; X64-NEXT: Determining loop execution counts for: @same_base_lt_204070
+; X64-NEXT: Loop %loop: backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)))
+; X64-NEXT: Loop %loop: constant max backedge-taken count is i64 -1
+; X64-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)))
+; X64-NEXT: Loop %loop: Trip multiple is 1
+;
+; X32-LABEL: 'same_base_lt_204070'
+; X32-NEXT: Classifying expressions for: @same_base_lt_204070
+; X32-NEXT: %send = getelementptr inbounds i8, ptr %s, i64 %len
+; X32-NEXT: --> ((trunc i64 %len to i32) + %s) U: full-set S: full-set
+; X32-NEXT: %matches = phi i64 [ %matches.next, %loop ], [ 0, %entry ]
+; X32-NEXT: --> %matches U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %iv = phi ptr [ %iv.next, %loop ], [ %s, %entry ]
+; X32-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))) + %s) LoopDispositions: { %loop: Computable }
+; X32-NEXT: %iv.next = getelementptr inbounds nuw i8, ptr %iv, i64 1
+; X32-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: ((-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))) + %s) LoopDispositions: { %loop: Computable }
+; X32-NEXT: %ch = load i8, ptr %iv, align 1
+; X32-NEXT: --> %ch U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %chz = zext i8 %ch to i64
+; X32-NEXT: --> (zext i8 %ch to i64) U: [0,256) S: [0,256) Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %tblp = getelementptr inbounds nuw [2 x i8], ptr %tbl, i64 %chz
+; X32-NEXT: --> ((2 * (zext i8 %ch to i32))<nuw><nsw> + %tbl)<nuw> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %v = load i16, ptr %tblp, align 2
+; X32-NEXT: --> %v U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %inc = zext i1 %ge to i64
+; X32-NEXT: --> (zext i1 %ge to i64) U: [0,2) S: [0,2) Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %matches.next = add i64 %matches, %inc
+; X32-NEXT: --> ((zext i1 %ge to i64) + %matches) U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; X32-NEXT: %result = phi i64 [ 0, %entry ], [ %matches.next, %loop ]
+; X32-NEXT: --> %result U: full-set S: full-set
+; X32-NEXT: Determining loop execution counts for: @same_base_lt_204070
+; X32-NEXT: Loop %loop: backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))))
+; X32-NEXT: Loop %loop: constant max backedge-taken count is i32 -1
+; X32-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))))
+; X32-NEXT: Loop %loop: Trip multiple is 1
+;
+entry:
+ %send = getelementptr inbounds i8, ptr %s, i64 %len
+ %guard = icmp eq i64 %len, 0
+ br i1 %guard, label %exit, label %loop
+
+loop:
+ %matches = phi i64 [ %matches.next, %loop ], [ 0, %entry ]
+ %iv = phi ptr [ %iv.next, %loop ], [ %s, %entry ]
+ %iv.next = getelementptr inbounds nuw i8, ptr %iv, i64 1
+ %ch = load i8, ptr %iv, align 1
+ %chz = zext i8 %ch to i64
+ %tblp = getelementptr inbounds nuw [2 x i8], ptr %tbl, i64 %chz
+ %v = load i16, ptr %tblp, align 2
+ %ge = icmp sgt i16 %v, -1
+ %inc = zext i1 %ge to i64
+ %matches.next = add i64 %matches, %inc
+ %done = icmp ult ptr %iv.next, %send
+ br i1 %done, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ 0, %entry ], [ %matches.next, %loop ]
+ ret i64 %result
+}
>From 9d562f2d350bc8e739be72a60f1587cb49fc1371 Mon Sep 17 00:00:00 2001
From: Sreedivya K <sreedivya.k at multicorewareinc.com>
Date: Wed, 8 Jul 2026 23:40:19 +0530
Subject: [PATCH 2/2] [SCEV] Fix missed trip count for same-base inbounds
pointer loops
This patch checks if the IV start and exit boundary (RHS) share the same pointer base and collapses the trip count to a clean (-1 + %len), unlocking full loop unrolling optimizations.
---
llvm/lib/Analysis/ScalarEvolution.cpp | 19 +++++++++++++++++++
.../test/Analysis/ScalarEvolution/ptrtoint.ll | 16 ++++++++--------
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index c74708fc73e5a..eff6d9b62c903 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -13531,6 +13531,16 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
bool NoWrap = ControlsOnlyExit && any(IV->getNoWrapFlags(WrapType));
ICmpInst::Predicate Cond = IsSigned ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT;
+ // If this is a pointer IV with an unsigned comparison, it may lack the strict
+ // FlagNUW flag but possess the general FlagNW flag due to originating from an
+ // 'inbounds' GEP. If the exit target RHS shares the same pointer base block
+ // as the IV start, we can safely guarantee that an address space wrap-around
+ // will not occur before the loop terminates.
+ if (!NoWrap && !IsSigned && IV->getType()->isPointerTy() &&
+ (IV->getNoWrapFlags() & SCEV::FlagNW) != SCEV::FlagAnyWrap &&
+ getPointerBase(RHS) == getPointerBase(IV->getStart())) {
+ NoWrap = true;
+ }
const SCEV *Stride = IV->getStepRecurrence(*this);
bool PositiveStride = isKnownPositive(Stride);
@@ -13741,6 +13751,15 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
isKnownPredicate(CondGE, GuardedRHS, GuardedStart))
return true;
+ // For an unsigned pointer comparison, if the loop exit boundary target
+ // shares the exact same base memory allocation block as the starting
+ // pointer, the exit target is structurally guaranteed to be greater
+ // than or equal to the start pointer
+
+ if (!IsSigned && OrigStart->getType()->isPointerTy() &&
+ getPointerBase(OrigRHS) == getPointerBase(OrigStart))
+ return true;
+
// (RHS > Start - 1) implies RHS >= Start.
// * "RHS >= Start" is trivially equivalent to "RHS > Start - 1" if
// "Start - 1" doesn't overflow.
diff --git a/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll b/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
index 55cfc570db3cc..9ef099b8d2e94 100644
--- a/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
+++ b/llvm/test/Analysis/ScalarEvolution/ptrtoint.ll
@@ -693,9 +693,9 @@ define i64 @same_base_lt_204070(ptr %s, i64 %len, ptr %tbl) {
; X64-NEXT: %matches = phi i64 [ %matches.next, %loop ], [ 0, %entry ]
; X64-NEXT: --> %matches U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
; X64-NEXT: %iv = phi ptr [ %iv.next, %loop ], [ %s, %entry ]
-; X64-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)) + %s) LoopDispositions: { %loop: Computable }
+; X64-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + %len + %s) LoopDispositions: { %loop: Computable }
; X64-NEXT: %iv.next = getelementptr inbounds nuw i8, ptr %iv, i64 1
-; X64-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: ((-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)) + %s) LoopDispositions: { %loop: Computable }
+; X64-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: (%len + %s) LoopDispositions: { %loop: Computable }
; X64-NEXT: %ch = load i8, ptr %iv, align 1
; X64-NEXT: --> %ch U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
; X64-NEXT: %chz = zext i8 %ch to i64
@@ -711,9 +711,9 @@ define i64 @same_base_lt_204070(ptr %s, i64 %len, ptr %tbl) {
; X64-NEXT: %result = phi i64 [ 0, %entry ], [ %matches.next, %loop ]
; X64-NEXT: --> %result U: full-set S: full-set
; X64-NEXT: Determining loop execution counts for: @same_base_lt_204070
-; X64-NEXT: Loop %loop: backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)))
+; X64-NEXT: Loop %loop: backedge-taken count is (-1 + %len)
; X64-NEXT: Loop %loop: constant max backedge-taken count is i64 -1
-; X64-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i64)) + ((1 + (ptrtoint ptr %s to i64)) umax ((ptrtoint ptr %s to i64) + %len)))
+; X64-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + %len)
; X64-NEXT: Loop %loop: Trip multiple is 1
;
; X32-LABEL: 'same_base_lt_204070'
@@ -723,9 +723,9 @@ define i64 @same_base_lt_204070(ptr %s, i64 %len, ptr %tbl) {
; X32-NEXT: %matches = phi i64 [ %matches.next, %loop ], [ 0, %entry ]
; X32-NEXT: --> %matches U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
; X32-NEXT: %iv = phi ptr [ %iv.next, %loop ], [ %s, %entry ]
-; X32-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))) + %s) LoopDispositions: { %loop: Computable }
+; X32-NEXT: --> {%s,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + (trunc i64 %len to i32) + %s) LoopDispositions: { %loop: Computable }
; X32-NEXT: %iv.next = getelementptr inbounds nuw i8, ptr %iv, i64 1
-; X32-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: ((-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))) + %s) LoopDispositions: { %loop: Computable }
+; X32-NEXT: --> {(1 + %s),+,1}<nuw><%loop> U: full-set S: full-set Exits: ((trunc i64 %len to i32) + %s) LoopDispositions: { %loop: Computable }
; X32-NEXT: %ch = load i8, ptr %iv, align 1
; X32-NEXT: --> %ch U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
; X32-NEXT: %chz = zext i8 %ch to i64
@@ -741,9 +741,9 @@ define i64 @same_base_lt_204070(ptr %s, i64 %len, ptr %tbl) {
; X32-NEXT: %result = phi i64 [ 0, %entry ], [ %matches.next, %loop ]
; X32-NEXT: --> %result U: full-set S: full-set
; X32-NEXT: Determining loop execution counts for: @same_base_lt_204070
-; X32-NEXT: Loop %loop: backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))))
+; X32-NEXT: Loop %loop: backedge-taken count is (-1 + (trunc i64 %len to i32))
; X32-NEXT: Loop %loop: constant max backedge-taken count is i32 -1
-; X32-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (-1 * (ptrtoint ptr %s to i32)) + ((1 + (ptrtoint ptr %s to i32)) umax ((trunc i64 %len to i32) + (ptrtoint ptr %s to i32))))
+; X32-NEXT: Loop %loop: symbolic max backedge-taken count is (-1 + (trunc i64 %len to i32))
; X32-NEXT: Loop %loop: Trip multiple is 1
;
entry:
More information about the llvm-commits
mailing list