[llvm] [SCEV] Fix incorrect nw-inferrence in zext-addrec (PR #217785)

Ramkumar Ramachandra via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 15:53:27 PDT 2026


https://github.com/artagnon created https://github.com/llvm/llvm-project/pull/217785

The patch can be understood in terms of the changed test:

- i starts at i8 -1 and is incremented by i8 -128 (smin) on each iteration.
- counter starts at 0, increments by 1, and the loop executes exactly twice.
- The relevant change is in i.zext, which zero-extends {-1,+,-128} to i16. -1 + -128 = -129, which would wrap to 127 (umax).
- On the second iteration (backedge-taken once), 127 - 128 = -1, and it hence hits the start value, violating no-self-wrap.

>From 25d15589ec73e67b461cf838322e4d7f30e4dc4b Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Thu, 20 Aug 2026 23:44:04 +0100
Subject: [PATCH 1/2] [SCEV] Pre-commit test

---
 .../ScalarEvolution/incorrect-nw-ext.ll       | 72 +++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll

diff --git a/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll b/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll
new file mode 100644
index 0000000000000..e71f97c9ff075
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll
@@ -0,0 +1,72 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -disable-output -passes='print<scalar-evolution>' %s 2>&1 | FileCheck %s
+
+define void @sext.nw() {
+; CHECK-LABEL: 'sext.nw'
+; CHECK-NEXT:  Classifying expressions for: @sext.nw
+; CHECK-NEXT:    %i = phi i8 [ -1, %entry ], [ %i.inc, %loop ]
+; CHECK-NEXT:    --> {-1,+,-128}<%loop> U: [-1,-128) S: [-1,-128) Exits: 127 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %counter = phi i8 [ 0, %entry ], [ %counter.inc, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,2) S: [0,2) Exits: 1 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.inc = add i8 %i, -128
+; CHECK-NEXT:    --> {127,+,-128}<%loop> U: [127,0) S: [127,0) Exits: -1 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.sext = sext i8 %i to i16
+; CHECK-NEXT:    --> {-1,+,128}<nw><%loop> U: [-1,128) S: [-1,128) Exits: 127 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %counter.inc = add i8 %counter, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,3) S: [1,3) Exits: 2 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @sext.nw
+; CHECK-NEXT:  Loop %loop: backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: Trip multiple is 2
+;
+ entry:
+  br label %loop
+
+ loop:
+  %i = phi i8 [ -1, %entry ], [ %i.inc, %loop ]
+  %counter = phi i8 [ 0, %entry ], [ %counter.inc, %loop ]
+  %i.inc = add i8 %i, -128
+  %i.sext = sext i8 %i to i16
+  %counter.inc = add i8 %counter, 1
+  %continue = icmp eq i8 %counter, 1
+  br i1 %continue, label %exit, label %loop
+
+ exit:
+  ret void
+}
+
+define void @zext.nw() {
+; CHECK-LABEL: 'zext.nw'
+; CHECK-NEXT:  Classifying expressions for: @zext.nw
+; CHECK-NEXT:    %i = phi i8 [ -1, %entry ], [ %i.inc, %loop ]
+; CHECK-NEXT:    --> {-1,+,-128}<%loop> U: [-1,-128) S: [-1,-128) Exits: 127 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %counter = phi i8 [ 0, %entry ], [ %counter.inc, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%loop> U: [0,2) S: [0,2) Exits: 1 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.inc = add i8 %i, -128
+; CHECK-NEXT:    --> {127,+,-128}<%loop> U: [127,0) S: [127,0) Exits: -1 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %i.zext = zext i8 %i to i16
+; CHECK-NEXT:    --> {255,+,-128}<nw><%loop> U: [127,256) S: [127,256) Exits: 127 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %counter.inc = add i8 %counter, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,3) S: [1,3) Exits: 2 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @zext.nw
+; CHECK-NEXT:  Loop %loop: backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is i8 1
+; CHECK-NEXT:  Loop %loop: Trip multiple is 2
+;
+ entry:
+  br label %loop
+
+ loop:
+  %i = phi i8 [ -1, %entry ], [ %i.inc, %loop ]
+  %counter = phi i8 [ 0, %entry ], [ %counter.inc, %loop ]
+  %i.inc = add i8 %i, -128
+  %i.zext = zext i8 %i to i16
+  %counter.inc = add i8 %counter, 1
+  %continue = icmp eq i8 %counter, 1
+  br i1 %continue, label %exit, label %loop
+
+ exit:
+  ret void
+}

>From 6926f9159a88b00527e13767aeeca6e12b363541 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Thu, 20 Aug 2026 23:06:45 +0100
Subject: [PATCH 2/2] [SCEV] Fix incorrect nw-inferrence in zext-addrec

The patch can be understood in terms of the changed test:

- i starts at i8 -1 and is incremented by i8 -128 (smin) on each
  iteration.
- counter starts at 0, increments by 1, and the loop executes exactly
  twice.
- The relevant change is in i.zext, which zero-extends {-1,+,-128} to
  i16. -1 + -128 = -129, which would wrap to 127 (umax). On the second
  iteration (backedge-taken once), 127 - 128 = -1, and it hence hits the
  start value, violating no-self-wrap.
---
 llvm/lib/Analysis/ScalarEvolution.cpp          | 18 ------------------
 .../ScalarEvolution/incorrect-nw-ext.ll        |  2 +-
 llvm/test/Transforms/IndVarSimplify/pr66066.ll |  2 +-
 .../Transforms/PhaseOrdering/scev-custom-dl.ll |  2 +-
 4 files changed, 3 insertions(+), 21 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index ebd58c825faa5..0755aacf7b330 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1705,24 +1705,6 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
           Step = getZeroExtendExpr(Step, Ty, Depth + 1);
           return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
         }
-        // Similar to above, only this time treat the step value as signed.
-        // This covers loops that count down.
-        OperandExtendedAdd =
-            getAddExpr(WideStart,
-                       getMulExpr(WideMaxBECount,
-                                  getSignExtendExpr(Step, WideTy, Depth + 1),
-                                  SCEV::FlagAnyWrap, Depth + 1),
-                       SCEV::FlagAnyWrap, Depth + 1);
-        if (ZAdd == OperandExtendedAdd) {
-          // Cache knowledge of AR NW, which is propagated to this AddRec.
-          // Negative step causes unsigned wrap, but it still can't self-wrap.
-          setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
-          // Return the expression with the addrec on the outside.
-          Start =
-              getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-          Step = getSignExtendExpr(Step, Ty, Depth + 1);
-          return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
-        }
       }
     }
 
diff --git a/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll b/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll
index e71f97c9ff075..98214f6f4e0fe 100644
--- a/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll
+++ b/llvm/test/Analysis/ScalarEvolution/incorrect-nw-ext.ll
@@ -46,7 +46,7 @@ define void @zext.nw() {
 ; CHECK-NEXT:    %i.inc = add i8 %i, -128
 ; CHECK-NEXT:    --> {127,+,-128}<%loop> U: [127,0) S: [127,0) Exits: -1 LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %i.zext = zext i8 %i to i16
-; CHECK-NEXT:    --> {255,+,-128}<nw><%loop> U: [127,256) S: [127,256) Exits: 127 LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    --> (127 + (zext i8 {-128,+,-128}<nw><%loop> to i16))<nuw><nsw> U: [127,256) S: [127,383) Exits: 127 LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %counter.inc = add i8 %counter, 1
 ; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%loop> U: [1,3) S: [1,3) Exits: 2 LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @zext.nw
diff --git a/llvm/test/Transforms/IndVarSimplify/pr66066.ll b/llvm/test/Transforms/IndVarSimplify/pr66066.ll
index 5bb0d8371b3e3..cfd29876b302d 100644
--- a/llvm/test/Transforms/IndVarSimplify/pr66066.ll
+++ b/llvm/test/Transforms/IndVarSimplify/pr66066.ll
@@ -9,7 +9,7 @@ define void @test() {
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i8 [ 1, [[ENTRY:%.*]] ], [ [[IV_DEC:%.*]], [[LOOP]] ]
 ; CHECK-NEXT:    [[IV_DEC]] = add nsw i8 [[IV]], -1
-; CHECK-NEXT:    [[SHL:%.*]] = shl nuw i8 [[IV]], 7
+; CHECK-NEXT:    [[SHL:%.*]] = shl i8 [[IV]], 7
 ; CHECK-NEXT:    call void @use(i8 [[SHL]])
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp eq i8 [[SHL]], 0
 ; CHECK-NEXT:    br i1 [[CMP1]], label [[EXIT:%.*]], label [[LOOP]]
diff --git a/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll b/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
index bdcaaca9390c5..60f2c30291eaa 100644
--- a/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
+++ b/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
@@ -141,7 +141,7 @@ define i32 @test_loop_idiom_recogize(i32 %x, i32 %y, ptr %lam, ptr %alp) nounwin
 ; CHECK-NEXT:  Classifying expressions for: @test_loop_idiom_recogize
 ; CHECK-NEXT:    %indvar = phi i32 [ 0, %bb1.thread ], [ %indvar.next, %bb1 ]
 ; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%bb1> U: [0,256) S: [0,256) Exits: 255 LoopDispositions: { %bb1: Computable }
-; CHECK-NEXT:    %i.0.reg2mem.0 = sub nuw nsw i32 255, %indvar
+; CHECK-NEXT:    %i.0.reg2mem.0 = sub nsw i32 255, %indvar
 ; CHECK-NEXT:    --> {255,+,-1}<nsw><%bb1> U: [0,256) S: [0,256) Exits: 0 LoopDispositions: { %bb1: Computable }
 ; CHECK-NEXT:    %0 = getelementptr [4 x i8], ptr %alp, i32 %i.0.reg2mem.0
 ; CHECK-NEXT:    --> {(1020 + %alp),+,-4}<nw><%bb1> U: full-set S: full-set Exits: %alp LoopDispositions: { %bb1: Computable }



More information about the llvm-commits mailing list