[llvm] [SCEV] Directly form add recurrences for simple pointer IVs. (PR #222915)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 08:13:09 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/222915

>From abb685cc40f4b1fd0b0604cfaeafb3774824de44 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 11 Sep 2026 11:23:21 +0100
Subject: [PATCH 1/2] [SCEV] Precommit test

---
 .../ScalarEvolution/pointer-iv-addrec.ll      | 343 ++++++++++++++++++
 1 file changed, 343 insertions(+)
 create mode 100644 llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll

diff --git a/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll b/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll
new file mode 100644
index 0000000000000..6c40844a4edf2
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll
@@ -0,0 +1,343 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes='print<scalar-evolution>' -disable-output %s 2>&1 | FileCheck %s
+
+target datalayout = "e-m:e-p:64:64-p1:32:32-i64:64-n32:64"
+
+%struct.S = type { i32, i64 }
+
+define void @gep_iv_constant_step(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_constant_step'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_constant_step
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,4}<nuw><%loop> U: full-set S: full-set Exits: (-4 + (4 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr inbounds i32, ptr %iv, i64 1
+; CHECK-NEXT:    --> {(4 + %p),+,4}<nw><%loop> U: full-set S: full-set Exits: ((4 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_constant_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr inbounds i32, ptr %iv, i64 1
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+define void @gep_iv_invariant_step(ptr %p, i64 %step, i64 %n) {
+; CHECK-LABEL: 'gep_iv_invariant_step'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_invariant_step
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,%step}<%loop> U: full-set S: full-set Exits: (((-1 + %n) * %step) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr i8, ptr %iv, i64 %step
+; CHECK-NEXT:    --> {(%step + %p),+,%step}<%loop> U: full-set S: full-set Exits: ((%step * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_invariant_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr i8, ptr %iv, i64 %step
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; The offset may be negative, so only nw can be derived from nusw.
+define void @gep_iv_nusw_negative_step(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_nusw_negative_step'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_nusw_negative_step
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,-4}<nw><%loop> U: full-set S: full-set Exits: (4 + (-4 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr nusw i32, ptr %iv, i64 -1
+; CHECK-NEXT:    --> {(-4 + %p),+,-4}<nw><%loop> U: full-set S: full-set Exits: ((-4 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_nusw_negative_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr nusw i32, ptr %iv, i64 -1
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; The index is wider than the index type of the address space, so it is
+; truncated before scaling.
+define void @gep_iv_narrow_index_type(ptr addrspace(1) %p, i64 %step, i64 %n) {
+; CHECK-LABEL: 'gep_iv_narrow_index_type'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_narrow_index_type
+; CHECK-NEXT:    %iv = phi ptr addrspace(1) [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,(4 * (trunc i64 %step to i32))}<nw><%loop> U: full-set S: full-set Exits: ((4 * (trunc i64 %step to i32) * (-1 + (trunc i64 %n to i32))) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr inbounds i32, ptr addrspace(1) %iv, i64 %step
+; CHECK-NEXT:    --> {((4 * (trunc i64 %step to i32)) + %p),+,(4 * (trunc i64 %step to i32))}<nw><%loop> U: full-set S: full-set Exits: ((4 * (trunc i64 %step to i32) * (trunc i64 %n to i32)) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_narrow_index_type
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr addrspace(1) [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr inbounds i32, ptr addrspace(1) %iv, i64 %step
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr addrspace(1) %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+define void @gep_iv_scalable(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_scalable'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_scalable
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,(16 * vscale)}<%loop> U: full-set S: full-set Exits: ((16 * vscale * (-1 + %n)) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr <vscale x 4 x i32>, ptr %iv, i64 1
+; CHECK-NEXT:    --> {((16 * vscale) + %p),+,(16 * vscale)}<%loop> U: full-set S: full-set Exits: ((16 * vscale * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_scalable
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr <vscale x 4 x i32>, ptr %iv, i64 1
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+define void @gep_iv_struct_single_index(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_struct_single_index'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_struct_single_index
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,16}<nuw><%loop> U: full-set S: full-set Exits: (-16 + (16 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr inbounds %struct.S, ptr %iv, i64 1
+; CHECK-NEXT:    --> {(16 + %p),+,16}<nw><%loop> U: full-set S: full-set Exits: ((16 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_struct_single_index
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr inbounds %struct.S, ptr %iv, i64 1
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; The step is zero, so the PHI is loop-invariant. This is more precise than the
+; general route, which cannot form an add recurrence here.
+define void @gep_iv_zero_step(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_zero_step'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_zero_step
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> %iv U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr inbounds i32, ptr %iv, i64 0
+; CHECK-NEXT:    --> %iv U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_zero_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr inbounds i32, ptr %iv, i64 0
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; More than one index.
+define void @gep_iv_struct_two_indices(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_struct_two_indices'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_struct_two_indices
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,16}<nuw><%loop> U: full-set S: full-set Exits: (-16 + (16 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr inbounds %struct.S, ptr %iv, i64 1, i32 0
+; CHECK-NEXT:    --> {(16 + %p),+,16}<nw><%loop> U: full-set S: full-set Exits: ((16 * %n) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_struct_two_indices
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr inbounds %struct.S, ptr %iv, i64 1, i32 0
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; The index is not loop-invariant.
+define void @gep_iv_variant_step(ptr %p, i64 %n) {
+; CHECK-LABEL: 'gep_iv_variant_step'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_variant_step
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%p,+,0,+,1}<%loop> U: full-set S: full-set Exits: ((trunc i65 (((zext i64 (-2 + %n) to i65) * (zext i64 (-1 + %n) to i65)) /u 2) to i64) + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr i8, ptr %iv, i64 %i
+; CHECK-NEXT:    --> {%p,+,1,+,1}<%loop> U: full-set S: full-set Exits: (-1 + (trunc i65 (((zext i64 (-2 + %n) to i65) * (zext i64 (-1 + %n) to i65)) /u 2) to i64) + %n + %p) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_variant_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr i8, ptr %iv, i64 %i
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+define void @gep_iv_not_base(ptr %p, ptr %q, i64 %n) {
+; CHECK-LABEL: 'gep_iv_not_base'
+; CHECK-NEXT:  Classifying expressions for: @gep_iv_not_base
+; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> %iv U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = getelementptr i32, ptr %q, i64 %i
+; CHECK-NEXT:    --> {%q,+,4}<%loop> U: full-set S: full-set Exits: (-4 + (4 * %n) + %q) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_not_base
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %iv.next = getelementptr i32, ptr %q, i64 %i
+  %i.next = add nuw nsw i64 %i, 1
+  store i32 0, ptr %iv, align 4
+  %ec = icmp eq i64 %i.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  ret void
+}

>From 2d5751f78a240a1d51f61953d4f84742b35a5d4f Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 11 Sep 2026 08:08:52 +0100
Subject: [PATCH 2/2] [SCEV] Directly form add recurrences for simple pointer
 IVs.

Add support for PN = PHI(Start, gep Self, LoopInvariant) to
createSimpleAffineAddRec.

This allows to handle simple pointer IVs without going through the more
expensive createAddRecFromPHI machinery in many cases.

This is not completely NFC: previously we created SCEVUnknown for a
trivial AddRec with step 0, now we return the start value.

This gives a modest geomean compile-time decrease
 * stage1-O3: -0.05%
 * stage1-ReleaseThinLTO: -0.06%
 * stage1-ReleaseLTO-g: -0.06%
 * stage1-aarch64-O3: -0.07%
 * stage2-O3: -0.05%
 * clang build: -0.08%

With larger decreases for more SCEV-heavy workloads.
https://llvm-compile-time-tracker.com/compare.php?from=c1b661fde124399f0484c4b39501b37613cc4757&to=5cd19e23596c86eae9607286efe86b7a3e885a86&stat=instructions:u
---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 79 ++++++++++++-------
 .../ScalarEvolution/pointer-iv-addrec.ll      |  4 +-
 2 files changed, 51 insertions(+), 32 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 06f350fd2179b..810fc9573e310 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -5693,6 +5693,24 @@ bool PredicatedScalarEvolution::areAddRecsEqualWithPreds(
   return true;
 }
 
+static SCEV::NoWrapFlags
+getNoWrapFlagsForGEP(GEPOperator *GEP, const SCEV *Accum, ScalarEvolution &SE) {
+  SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap;
+  GEPNoWrapFlags NW = GEP->getNoWrapFlags();
+  // If the increment has any nowrap flags, then we know the address
+  // space cannot be wrapped around.
+  if (NW != GEPNoWrapFlags::none())
+    Flags = ScalarEvolution::setFlags(Flags, SCEV::FlagNW);
+  // If the GEP is nuw or nusw with non-negative offset, we know that
+  // no unsigned wrap occurs. We cannot set the nsw flag as only the
+  // offset is treated as signed, while the base is unsigned.
+  if (NW.hasNoUnsignedWrap() ||
+      (NW.hasNoUnsignedSignedWrap() && SE.isKnownNonNegative(Accum)))
+    Flags = ScalarEvolution::setFlags(Flags, SCEV::FlagNUW);
+
+  return Flags;
+}
+
 /// A helper function for createAddRecFromPHI to handle simple cases.
 ///
 /// This function tries to find an AddRec expression for the simplest (yet most
@@ -5706,27 +5724,39 @@ const SCEV *ScalarEvolution::createSimpleAffineAddRec(PHINode *PN,
   assert(L && L->getHeader() == PN->getParent());
   assert(BEValueV && StartValueV);
 
-  auto BO = MatchBinaryOp(BEValueV, getDataLayout(), AC, DT, PN);
-  if (!BO)
-    return nullptr;
+  const SCEV *Accum = nullptr;
+  SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap;
+  if (auto BO = MatchBinaryOp(BEValueV, getDataLayout(), AC, DT, PN)) {
+    if (BO->Opcode != Instruction::Add)
+      return nullptr;
 
-  if (BO->Opcode != Instruction::Add)
-    return nullptr;
+    if (BO->LHS == PN && L->isLoopInvariant(BO->RHS))
+      Accum = getSCEV(BO->RHS);
+    else if (BO->RHS == PN && L->isLoopInvariant(BO->LHS))
+      Accum = getSCEV(BO->LHS);
 
-  const SCEV *Accum = nullptr;
-  if (BO->LHS == PN && L->isLoopInvariant(BO->RHS))
-    Accum = getSCEV(BO->RHS);
-  else if (BO->RHS == PN && L->isLoopInvariant(BO->LHS))
-    Accum = getSCEV(BO->LHS);
+    if (!Accum)
+      return nullptr;
 
-  if (!Accum)
-    return nullptr;
+    if (BO->IsNUW)
+      Flags = setFlags(Flags, SCEV::FlagNUW);
+    if (BO->IsNSW)
+      Flags = setFlags(Flags, SCEV::FlagNSW);
+  } else {
+    // Handle pointer induction variable: PN = PHI(Start, gep PN,
+    // LoopInvariant).
+    auto *GEP = dyn_cast<GEPOperator>(BEValueV);
+    if (!GEP || GEP->getPointerOperand() != PN || GEP->getNumIndices() != 1)
+      return nullptr;
+    Value *Idx = *GEP->idx_begin();
+    if (!L->isLoopInvariant(Idx))
+      return nullptr;
 
-  SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap;
-  if (BO->IsNUW)
-    Flags = setFlags(Flags, SCEV::FlagNUW);
-  if (BO->IsNSW)
-    Flags = setFlags(Flags, SCEV::FlagNSW);
+    Type *IntIdxTy = getEffectiveSCEVType(GEP->getType());
+    Accum = getMulExpr(getTruncateOrSignExtend(getSCEV(Idx), IntIdxTy),
+                       getSizeOfExpr(IntIdxTy, GEP->getSourceElementType()));
+    Flags = getNoWrapFlagsForGEP(GEP, Accum, *this);
+  }
 
   const SCEV *StartVal = getSCEV(StartValueV);
   const SCEV *PHISCEV = getAddRecExpr(StartVal, Accum, L, Flags);
@@ -5832,19 +5862,8 @@ const SCEV *ScalarEvolution::createAddRecFromPHI(PHINode *PN) {
               Flags = setFlags(Flags, SCEV::FlagNSW);
           }
         } else if (GEPOperator *GEP = dyn_cast<GEPOperator>(BEValueV)) {
-          if (GEP->getOperand(0) == PN) {
-            GEPNoWrapFlags NW = GEP->getNoWrapFlags();
-            // If the increment has any nowrap flags, then we know the address
-            // space cannot be wrapped around.
-            if (NW != GEPNoWrapFlags::none())
-              Flags = setFlags(Flags, SCEV::FlagNW);
-            // If the GEP is nuw or nusw with non-negative offset, we know that
-            // no unsigned wrap occurs. We cannot set the nsw flag as only the
-            // offset is treated as signed, while the base is unsigned.
-            if (NW.hasNoUnsignedWrap() ||
-                (NW.hasNoUnsignedSignedWrap() && isKnownNonNegative(Accum)))
-              Flags = setFlags(Flags, SCEV::FlagNUW);
-          }
+          if (GEP->getOperand(0) == PN)
+            Flags = getNoWrapFlagsForGEP(GEP, Accum, *this);
 
           // We cannot transfer nuw and nsw flags from subtraction
           // operations -- sub nuw X, Y is not the same as add nuw X, -Y
diff --git a/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll b/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll
index 6c40844a4edf2..348260df97839 100644
--- a/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll
+++ b/llvm/test/Analysis/ScalarEvolution/pointer-iv-addrec.ll
@@ -212,11 +212,11 @@ define void @gep_iv_zero_step(ptr %p, i64 %n) {
 ; CHECK-LABEL: 'gep_iv_zero_step'
 ; CHECK-NEXT:  Classifying expressions for: @gep_iv_zero_step
 ; CHECK-NEXT:    %iv = phi ptr [ %p, %entry ], [ %iv.next, %loop ]
-; CHECK-NEXT:    --> %iv U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; CHECK-NEXT:    --> %p U: full-set S: full-set Exits: %p LoopDispositions: { %loop: Invariant }
 ; CHECK-NEXT:    %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
 ; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,-9223372036854775808) S: [0,-9223372036854775808) Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %iv.next = getelementptr inbounds i32, ptr %iv, i64 0
-; CHECK-NEXT:    --> %iv U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
+; CHECK-NEXT:    --> %p U: full-set S: full-set Exits: %p LoopDispositions: { %loop: Invariant }
 ; CHECK-NEXT:    %i.next = add nuw nsw i64 %i, 1
 ; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,-9223372036854775808) S: [1,-9223372036854775808) Exits: %n LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @gep_iv_zero_step



More information about the llvm-commits mailing list