[llvm] [SCEV] Rework wrap-flag-inference in zext-addrec (PR #217405)

Ramkumar Ramachandra via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 14:12:00 PDT 2026


https://github.com/artagnon updated https://github.com/llvm/llvm-project/pull/217405

>From 12bc2ccd33de988e3d14bde69101c2ade9fc16b0 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Wed, 19 Aug 2026 18:09:39 +0100
Subject: [PATCH 1/5] [SCEV] Rework wrap-flag-inferrence in zext-addrec

Rewrite getZeroExtendExprImpl to avoid the roundabout method of creating
zero-extend expressions to check no-wrap, by computing the no-wrap
information using induction and reading it off the expression directly.
The new code has much better compile-time, while not being exactly
equivalent to the old code.
---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 132 +++---------------
 .../no-wrap-unknown-becount.ll                |   2 +-
 .../CodeGen/PowerPC/hardware-loops-crash.ll   |   9 +-
 .../test/Transforms/IndVarSimplify/pr66066.ll |   2 +-
 .../PhaseOrdering/scev-custom-dl.ll           |   2 +-
 5 files changed, 28 insertions(+), 119 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index a05b7e9714f01..934a176108b1e 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1656,117 +1656,36 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
   // operands (often constants).  This allows analysis of something like
   // this:  for (unsigned char X = 0; X < 100; ++X) { int Y = X; }
   if (match(Op, m_scev_AffineAddRec(m_SCEV(Start), m_SCEV(Step), m_Loop(L)))) {
+    // Redo the AddRec check, computing nuw this time.
     const auto *AR = cast<SCEVAddRecExpr>(Op);
-    unsigned BitWidth = getTypeSizeInBits(AR->getType());
-
-    // The no-unsigned-wrap case is handled before the uniquing lookup above.
-
-    // Check whether the backedge-taken count is SCEVCouldNotCompute.
-    // Note that this serves two purposes: It filters out loops that are
-    // simply not analyzable, and it covers the case where this code is
-    // being called from within backedge-taken count analysis, such that
-    // attempting to ask for the backedge-taken count would likely result
-    // in infinite recursion. In the later case, the analysis code will
-    // cope with a conservative value, and it will take care to purge
-    // that value once it has finished.
-    const SCEV *MaxBECount = getConstantMaxBackedgeTakenCount(L);
-    if (!isa<SCEVCouldNotCompute>(MaxBECount)) {
-      // Manually compute the final value for AR, checking for overflow.
+    inferNoWrapViaConstantRanges(AR);
+    auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
+    if (!hasFlags(NewFlags, SCEV::FlagNUW) &&
+        proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L))
+      NewFlags |= SCEV::FlagNUW;
+    setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), NewFlags);
 
-      // Check whether the backedge-taken count can be losslessly casted to
-      // the addrec's type. The count is always unsigned.
-      const SCEV *CastedMaxBECount =
-          getTruncateOrZeroExtend(MaxBECount, Start->getType(), Depth);
-      const SCEV *RecastedMaxBECount = getTruncateOrZeroExtend(
-          CastedMaxBECount, MaxBECount->getType(), Depth);
-      if (MaxBECount == RecastedMaxBECount) {
-        Type *WideTy = IntegerType::get(getContext(), BitWidth * 2);
-        // Check whether Start+Step*MaxBECount has no unsigned overflow.
-        const SCEV *ZMul =
-            getMulExpr(CastedMaxBECount, Step, SCEV::FlagAnyWrap, Depth + 1);
-        const SCEV *ZAdd = getZeroExtendExpr(
-            getAddExpr(Start, ZMul, SCEV::FlagAnyWrap, Depth + 1), WideTy,
-            Depth + 1);
-        const SCEV *WideStart = getZeroExtendExpr(Start, WideTy, Depth + 1);
-        const SCEV *WideMaxBECount =
-            getZeroExtendExpr(CastedMaxBECount, WideTy, Depth + 1);
-        const SCEV *OperandExtendedAdd =
-            getAddExpr(WideStart,
-                       getMulExpr(WideMaxBECount,
-                                  getZeroExtendExpr(Step, WideTy, Depth + 1),
-                                  SCEV::FlagAnyWrap, Depth + 1),
-                       SCEV::FlagAnyWrap, Depth + 1);
-        if (ZAdd == OperandExtendedAdd) {
-          // Cache knowledge of AR NUW, which is propagated to this AddRec.
-          setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNUW);
-          // Return the expression with the addrec on the outside.
-          Start =
-              getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-          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());
-        }
-      }
+    // If we have nuw, the zero-extend distributes over the recurrence.
+    if (AR->hasNoUnsignedWrap()) {
+      Start = getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
+      Step = getZeroExtendExpr(Step, Ty, Depth + 1);
+      return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
     }
 
-    // Normally, in the cases we can prove no-overflow via a
-    // backedge guarding condition, we can also compute a backedge
-    // taken count for the loop.  The exceptions are assumptions and
-    // guards present in the loop -- SCEV is not great at exploiting
-    // these to compute max backedge taken counts, but can still use
-    // these to prove lack of overflow.  Use this fact to avoid
-    // doing extra work that may not pay off.
-    if (!isa<SCEVCouldNotCompute>(MaxBECount) || HasGuards ||
-        !AC.assumptions().empty()) {
-
-      auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
-      setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), NewFlags);
-      if (AR->hasNoUnsignedWrap()) {
-        // Same as nuw case above - duplicated here to avoid a compile time
-        // issue.  It's not clear that the order of checks does matter, but
-        // it's one of two issue possible causes for a change which was
-        // reverted.  Be conservative for the moment.
+    // For a negative step, we can sign-extend the step iff doing so only
+    // traverses values in the range sext([0,SMAX]). Note that this does not
+    // imply no-self-wrap.
+    if (isKnownNegative(Step)) {
+      unsigned BitWidth = getTypeSizeInBits(AR->getType());
+      const SCEV *N =
+          getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
+      if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N)) {
         Start =
             getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-        Step = getZeroExtendExpr(Step, Ty, Depth + 1);
+        Step = getSignExtendExpr(Step, Ty, Depth + 1);
         return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
       }
-
-      // For a negative step, we can extend the operands iff doing so only
-      // traverses values in the range zext([0,UINT_MAX]).
-      if (isKnownNegative(Step)) {
-        const SCEV *N =
-            getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
-        if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
-            isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N)) {
-          // 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());
-        }
-      }
     }
 
     // zext({C,+,Step}) --> (zext(D) + zext({C-D,+,Step}))<nuw><nsw>
@@ -1784,13 +1703,6 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
                           Depth + 1);
       }
     }
-
-    if (proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L)) {
-      setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNUW);
-      Start = getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-      Step = getZeroExtendExpr(Step, Ty, Depth + 1);
-      return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
-    }
   }
 
   // zext(A % B) --> zext(A) % zext(B)
diff --git a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
index 0aa13c5b06ca2..bbc36da11dda7 100644
--- a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
+++ b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
@@ -251,7 +251,7 @@ define void @u_2(ptr %cond) {
 ; CHECK-NEXT:    %iv.inc = add i32 %iv, -2
 ; CHECK-NEXT:    --> {29998,+,-2}<%loop> U: [0,-1) S: [-2147483648,2147483647) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %iv.zext = zext i32 %iv to i64
-; CHECK-NEXT:    --> {30000,+,-2}<nw><%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    --> {30000,+,-2}<%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %c = load volatile i1, ptr %cond, align 1
 ; CHECK-NEXT:    --> %c U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
 ; CHECK-NEXT:  Determining loop execution counts for: @u_2
diff --git a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
index afa0f8c4adc0a..7616847afef80 100644
--- a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
+++ b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
@@ -24,22 +24,19 @@ define void @test() {
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 51)
 ; CHECK-NEXT:    br label [[WHILE_COND25:%.*]]
 ; CHECK:       while.cond25:
-; CHECK-NEXT:    [[INDVAR:%.*]] = phi i64 [ 0, [[WHILE_COND25_PREHEADER]] ], [ [[INDVAR_NEXT:%.*]], [[LAND_RHS:%.*]] ]
-; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
+; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS:%.*]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = call i1 @llvm.loop.decrement.i64(i64 1)
 ; CHECK-NEXT:    br i1 [[TMP0]], label [[LAND_RHS]], label [[WHILE_END187:%.*]]
 ; CHECK:       land.rhs:
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT350]] = add nsw i64 [[INDVARS_IV349]], -1
 ; CHECK-NEXT:    [[C_1:%.*]] = call i1 @cond()
-; CHECK-NEXT:    [[INDVAR_NEXT]] = add i64 [[INDVAR]], 1
 ; CHECK-NEXT:    br i1 [[C_1]], label [[WHILE_COND25]], label [[WHILE_END:%.*]]
 ; CHECK:       while.end:
-; CHECK-NEXT:    [[INDVAR_LCSSA1:%.*]] = phi i64 [ [[INDVAR]], [[LAND_RHS]] ]
 ; CHECK-NEXT:    [[C_2:%.*]] = call i1 @cond()
 ; CHECK-NEXT:    br i1 [[C_2]], label [[WHILE_END187]], label [[WHILE_COND35_PREHEADER:%.*]]
 ; CHECK:       while.cond35.preheader:
-; CHECK-NEXT:    [[TMP1:%.*]] = mul nsw i64 [[INDVAR_LCSSA1]], -1
-; CHECK-NEXT:    [[TMP2:%.*]] = add i64 [[TMP1]], 51
+; CHECK-NEXT:    [[TMP1:%.*]] = and i64 [[INDVARS_IV349]], 4294967295
+; CHECK-NEXT:    [[TMP2:%.*]] = add nuw nsw i64 [[TMP1]], 1
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 [[TMP2]])
 ; CHECK-NEXT:    br label [[WHILE_COND35:%.*]]
 ; CHECK:       while.cond35:
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 }

>From 4fced557e6876ca0c35cc636da0ffae158bc4af9 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Fri, 21 Aug 2026 20:29:25 +0100
Subject: [PATCH 2/5] [SCEV] Cover old no-self-wrap logic!

---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 40 +++++++++++--------
 .../no-wrap-unknown-becount.ll                |  2 +-
 .../CodeGen/PowerPC/hardware-loops-crash.ll   |  9 +++--
 .../IndVarSimplify/AArch64/widen-loop-comp.ll |  2 +-
 .../IndVarSimplify/X86/overflow-intrinsics.ll |  7 ++--
 llvm/test/Transforms/IndVarSimplify/lftr.ll   |  2 +-
 .../PhaseOrdering/SystemZ/sub-xor.ll          |  4 +-
 .../PhaseOrdering/scev-custom-dl.ll           |  2 +-
 8 files changed, 38 insertions(+), 30 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 934a176108b1e..95e46fa50adc1 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1656,7 +1656,7 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
   // operands (often constants).  This allows analysis of something like
   // this:  for (unsigned char X = 0; X < 100; ++X) { int Y = X; }
   if (match(Op, m_scev_AffineAddRec(m_SCEV(Start), m_SCEV(Step), m_Loop(L)))) {
-    // Redo the AddRec check, computing nuw this time.
+    // Redo the AddRec check, attempting to prove no-wrap this time.
     const auto *AR = cast<SCEVAddRecExpr>(Op);
     inferNoWrapViaConstantRanges(AR);
     auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
@@ -1672,22 +1672,6 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
       return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
     }
 
-    // For a negative step, we can sign-extend the step iff doing so only
-    // traverses values in the range sext([0,SMAX]). Note that this does not
-    // imply no-self-wrap.
-    if (isKnownNegative(Step)) {
-      unsigned BitWidth = getTypeSizeInBits(AR->getType());
-      const SCEV *N =
-          getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
-      if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
-          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N)) {
-        Start =
-            getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-        Step = getSignExtendExpr(Step, Ty, Depth + 1);
-        return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
-      }
-    }
-
     // zext({C,+,Step}) --> (zext(D) + zext({C-D,+,Step}))<nuw><nsw>
     // if D + (C - D + Step * n) could be proven to not unsigned wrap
     // where D maximizes the number of trailing zeros of (C - D + Step * n)
@@ -1703,6 +1687,28 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
                           Depth + 1);
       }
     }
+
+    // Handle the nusw case. For a negative step, nsw on pre-inc AR and
+    // no-overflow proven with signed overflow limit implies nusw on the parent
+    // AR, of which nw is a weaker version. We can also prove no-wrap on the AR
+    // directly by showing that step only traverses values in the range
+    // sext([0,SMAX]), without worrying about the pre-inc AR.
+    if (isKnownNegative(Step)) {
+      unsigned BitWidth = getTypeSizeInBits(AR->getType());
+      const SCEV *N =
+          getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
+      if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N) ||
+          proveNoWrapByVaryingStart<SCEVSignExtendExpr>(Start, Step, L)) {
+        // no-self-wrap is an opportunistic fact we get for free. It is not a
+        // pre-condition for the transform.
+        setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
+        Start =
+            getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
+        Step = getSignExtendExpr(Step, Ty, Depth + 1);
+        return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
+      }
+    }
   }
 
   // zext(A % B) --> zext(A) % zext(B)
diff --git a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
index bbc36da11dda7..0aa13c5b06ca2 100644
--- a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
+++ b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
@@ -251,7 +251,7 @@ define void @u_2(ptr %cond) {
 ; CHECK-NEXT:    %iv.inc = add i32 %iv, -2
 ; CHECK-NEXT:    --> {29998,+,-2}<%loop> U: [0,-1) S: [-2147483648,2147483647) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %iv.zext = zext i32 %iv to i64
-; CHECK-NEXT:    --> {30000,+,-2}<%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    --> {30000,+,-2}<nw><%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %c = load volatile i1, ptr %cond, align 1
 ; CHECK-NEXT:    --> %c U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
 ; CHECK-NEXT:  Determining loop execution counts for: @u_2
diff --git a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
index 7616847afef80..afa0f8c4adc0a 100644
--- a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
+++ b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
@@ -24,19 +24,22 @@ define void @test() {
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 51)
 ; CHECK-NEXT:    br label [[WHILE_COND25:%.*]]
 ; CHECK:       while.cond25:
-; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS:%.*]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
+; CHECK-NEXT:    [[INDVAR:%.*]] = phi i64 [ 0, [[WHILE_COND25_PREHEADER]] ], [ [[INDVAR_NEXT:%.*]], [[LAND_RHS:%.*]] ]
+; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = call i1 @llvm.loop.decrement.i64(i64 1)
 ; CHECK-NEXT:    br i1 [[TMP0]], label [[LAND_RHS]], label [[WHILE_END187:%.*]]
 ; CHECK:       land.rhs:
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT350]] = add nsw i64 [[INDVARS_IV349]], -1
 ; CHECK-NEXT:    [[C_1:%.*]] = call i1 @cond()
+; CHECK-NEXT:    [[INDVAR_NEXT]] = add i64 [[INDVAR]], 1
 ; CHECK-NEXT:    br i1 [[C_1]], label [[WHILE_COND25]], label [[WHILE_END:%.*]]
 ; CHECK:       while.end:
+; CHECK-NEXT:    [[INDVAR_LCSSA1:%.*]] = phi i64 [ [[INDVAR]], [[LAND_RHS]] ]
 ; CHECK-NEXT:    [[C_2:%.*]] = call i1 @cond()
 ; CHECK-NEXT:    br i1 [[C_2]], label [[WHILE_END187]], label [[WHILE_COND35_PREHEADER:%.*]]
 ; CHECK:       while.cond35.preheader:
-; CHECK-NEXT:    [[TMP1:%.*]] = and i64 [[INDVARS_IV349]], 4294967295
-; CHECK-NEXT:    [[TMP2:%.*]] = add nuw nsw i64 [[TMP1]], 1
+; CHECK-NEXT:    [[TMP1:%.*]] = mul nsw i64 [[INDVAR_LCSSA1]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = add i64 [[TMP1]], 51
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 [[TMP2]])
 ; CHECK-NEXT:    br label [[WHILE_COND35:%.*]]
 ; CHECK:       while.cond35:
diff --git a/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll b/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
index d4498baf0577a..904cac4441354 100644
--- a/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
+++ b/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
@@ -508,7 +508,7 @@ declare void @consume.i1(i1)
 define i32 @test10(i32 %v) {
 ; CHECK-LABEL: @test10(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[SEXT:%.*]] = sext i32 [[V:%.*]] to i64
+; CHECK-NEXT:    [[SEXT:%.*]] = zext i32 [[V:%.*]] to i64
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], [[LOOP]] ], [ 0, [[ENTRY:%.*]] ]
diff --git a/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll b/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
index 9b9bc68ba7ad8..8f168eec86a9f 100644
--- a/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
+++ b/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
@@ -334,13 +334,14 @@ define void @f_usub_overflow(ptr nocapture %a) {
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[CONT:.*]] ], [ 15, %[[ENTRY]] ]
 ; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i8, ptr [[A]], i64 [[INDVARS_IV]]
 ; CHECK-NEXT:    store i8 0, ptr [[ARRAYIDX]], align 1
-; CHECK-NEXT:    br i1 true, label %[[TRAP:.*]], label %[[CONT]], !nosanitize [[META0]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], -1
+; CHECK-NEXT:    br i1 false, label %[[TRAP:.*]], label %[[CONT]], !nosanitize [[META0]]
 ; CHECK:       [[TRAP]]:
 ; CHECK-NEXT:    tail call void @llvm.trap(), !nosanitize [[META0]]
 ; CHECK-NEXT:    unreachable, !nosanitize [[META0]]
 ; CHECK:       [[CONT]]:
-; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], -1
-; CHECK-NEXT:    br i1 true, label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i64 [[INDVARS_IV_NEXT]], -1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP]]
 ;
 entry:
   br label %for.body
diff --git a/llvm/test/Transforms/IndVarSimplify/lftr.ll b/llvm/test/Transforms/IndVarSimplify/lftr.ll
index 5ee62ba357ab6..b6216beb2e36b 100644
--- a/llvm/test/Transforms/IndVarSimplify/lftr.ll
+++ b/llvm/test/Transforms/IndVarSimplify/lftr.ll
@@ -41,7 +41,7 @@ define i32 @pre_to_post_sub() {
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[I:%.*]] = phi i32 [ 1000, [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ]
-; CHECK-NEXT:    [[I_NEXT]] = sub nsw i32 [[I]], 1
+; CHECK-NEXT:    [[I_NEXT]] = sub nuw nsw i32 [[I]], 1
 ; CHECK-NEXT:    store i32 [[I]], ptr @A, align 4
 ; CHECK-NEXT:    [[C:%.*]] = icmp samesign ugt i32 [[I]], 0
 ; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[LOOPEXIT:%.*]]
diff --git a/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll b/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
index 0ae3973daffad..5f28bf539a268 100644
--- a/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
+++ b/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
@@ -16,13 +16,11 @@ define dso_local zeroext i32 @foo(ptr noundef %a) #0 {
 ; CHECK-SAME: ptr nofree noundef readnone captures(none) [[A:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    tail call void @populate(ptr noundef nonnull @ARR) #[[ATTR2:[0-9]+]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), align 4, !tbaa [[INT_TBAA3:![0-9]+]]
 ; CHECK-NEXT:    br label %[[FOR_BODY4:.*]]
 ; CHECK:       [[FOR_BODY4]]:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_7:%.*]], %[[FOR_BODY4]] ]
 ; CHECK-NEXT:    [[SUM_11:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[ADD_7:%.*]], %[[FOR_BODY4]] ]
-; CHECK-NEXT:    [[IDX_NEG:%.*]] = sub nsw i64 0, [[INDVARS_IV]]
-; CHECK-NEXT:    [[ADD_PTR:%.*]] = getelementptr inbounds [4 x i8], ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), i64 [[IDX_NEG]]
-; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[ADD_PTR]], align 4, !tbaa [[INT_TBAA3:![0-9]+]]
 ; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[TMP0]], [[SUM_11]]
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT_NEG:%.*]] = xor i64 [[INDVARS_IV]], -1
 ; CHECK-NEXT:    [[ADD_PTR_110:%.*]] = getelementptr inbounds [4 x i8], ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), i64 [[INDVARS_IV_NEXT_NEG]]
diff --git a/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll b/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
index 60f2c30291eaa..bdcaaca9390c5 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 nsw i32 255, %indvar
+; CHECK-NEXT:    %i.0.reg2mem.0 = sub nuw 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 }

>From a99f35b62e67cf2f56f1fd1bb34e5d0b448ca1d3 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 22 Aug 2026 19:00:33 +0100
Subject: [PATCH 3/5] [SCEV] Fix symmetric bug from sext-addrec

---
 llvm/include/llvm/Analysis/ScalarEvolution.h  |  2 +-
 llvm/lib/Analysis/ScalarEvolution.cpp         | 40 +++++++++----------
 .../IndVarSimplify/AArch64/widen-loop-comp.ll |  2 +-
 .../IndVarSimplify/X86/overflow-intrinsics.ll |  7 ++--
 llvm/test/Transforms/IndVarSimplify/lftr.ll   |  2 +-
 .../PhaseOrdering/SystemZ/sub-xor.ll          |  4 +-
 .../PhaseOrdering/scev-custom-dl.ll           |  2 +-
 7 files changed, 30 insertions(+), 29 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index d17c21ea3401e..3e15471a38a8f 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -2413,7 +2413,7 @@ class ScalarEvolution {
   /// equivalent to proving no signed (resp. unsigned) wrap in
   /// {`Start`,+,`Step`} if `ExtendOpTy` is `SCEVSignExtendExpr`
   /// (resp. `SCEVZeroExtendExpr`).
-  template <typename ExtendOpTy>
+  template <typename ExtendOpTy, SCEVNoWrapFlags WrapType>
   bool proveNoWrapByVaryingStart(const SCEV *Start, const SCEV *Step,
                                  const Loop *L);
 
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 95e46fa50adc1..2440e21301a47 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1471,12 +1471,11 @@ static const SCEV *getExtendAddRecStart(const SCEVAddRecExpr *AR, Type *Ty,
 //
 // In the current context, S is `Start`, X is `Step`, Ext is `ExtendOpTy` and T
 // is `Delta` (defined below).
-template <typename ExtendOpTy>
+template <typename ExtendOpTy,
+          SCEVNoWrapFlags WrapType = ExtendOpTraits<ExtendOpTy>::WrapType>
 bool ScalarEvolution::proveNoWrapByVaryingStart(const SCEV *Start,
                                                 const SCEV *Step,
                                                 const Loop *L) {
-  auto WrapType = ExtendOpTraits<ExtendOpTy>::WrapType;
-
   // We restrict `Start` to a constant to prevent SCEV from spending too much
   // time here.  It is correct (but more expensive) to continue with a
   // non-constant `Start` and do a general SCEV subtraction to compute
@@ -1688,26 +1687,27 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
       }
     }
 
-    // Handle the nusw case. For a negative step, nsw on pre-inc AR and
-    // no-overflow proven with signed overflow limit implies nusw on the parent
-    // AR, of which nw is a weaker version. We can also prove no-wrap on the AR
-    // directly by showing that step only traverses values in the range
-    // sext([0,SMAX]), without worrying about the pre-inc AR.
-    if (isKnownNegative(Step)) {
+    // Handle the nusw case. nsw on pre-inc AR and no-overflow proven with
+    // unsigned overflow limit implies nusw on the parent AR, of which nw is a
+    // weaker version. We can also prove no-wrap on the AR directly by showing
+    // that step only traverses values in the range sext([0,SMAX]), without
+    // worrying about the pre-inc AR.
+    bool IsNoSelfWrap =
+        proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNSW>(Start,
+                                                                     Step, L);
+    if (!IsNoSelfWrap && isKnownNegative(Step)) {
       unsigned BitWidth = getTypeSizeInBits(AR->getType());
       const SCEV *N =
           getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
-      if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
-          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N) ||
-          proveNoWrapByVaryingStart<SCEVSignExtendExpr>(Start, Step, L)) {
-        // no-self-wrap is an opportunistic fact we get for free. It is not a
-        // pre-condition for the transform.
-        setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
-        Start =
-            getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-        Step = getSignExtendExpr(Step, Ty, Depth + 1);
-        return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
-      }
+      IsNoSelfWrap =
+          isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N);
+    }
+    if (IsNoSelfWrap) {
+      setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
+      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/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll b/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
index 904cac4441354..d4498baf0577a 100644
--- a/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
+++ b/llvm/test/Transforms/IndVarSimplify/AArch64/widen-loop-comp.ll
@@ -508,7 +508,7 @@ declare void @consume.i1(i1)
 define i32 @test10(i32 %v) {
 ; CHECK-LABEL: @test10(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[SEXT:%.*]] = zext i32 [[V:%.*]] to i64
+; CHECK-NEXT:    [[SEXT:%.*]] = sext i32 [[V:%.*]] to i64
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], [[LOOP]] ], [ 0, [[ENTRY:%.*]] ]
diff --git a/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll b/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
index 8f168eec86a9f..9b9bc68ba7ad8 100644
--- a/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
+++ b/llvm/test/Transforms/IndVarSimplify/X86/overflow-intrinsics.ll
@@ -334,14 +334,13 @@ define void @f_usub_overflow(ptr nocapture %a) {
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[CONT:.*]] ], [ 15, %[[ENTRY]] ]
 ; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i8, ptr [[A]], i64 [[INDVARS_IV]]
 ; CHECK-NEXT:    store i8 0, ptr [[ARRAYIDX]], align 1
-; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], -1
-; CHECK-NEXT:    br i1 false, label %[[TRAP:.*]], label %[[CONT]], !nosanitize [[META0]]
+; CHECK-NEXT:    br i1 true, label %[[TRAP:.*]], label %[[CONT]], !nosanitize [[META0]]
 ; CHECK:       [[TRAP]]:
 ; CHECK-NEXT:    tail call void @llvm.trap(), !nosanitize [[META0]]
 ; CHECK-NEXT:    unreachable, !nosanitize [[META0]]
 ; CHECK:       [[CONT]]:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i64 [[INDVARS_IV_NEXT]], -1
-; CHECK-NEXT:    br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], -1
+; CHECK-NEXT:    br i1 true, label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP]]
 ;
 entry:
   br label %for.body
diff --git a/llvm/test/Transforms/IndVarSimplify/lftr.ll b/llvm/test/Transforms/IndVarSimplify/lftr.ll
index b6216beb2e36b..5ee62ba357ab6 100644
--- a/llvm/test/Transforms/IndVarSimplify/lftr.ll
+++ b/llvm/test/Transforms/IndVarSimplify/lftr.ll
@@ -41,7 +41,7 @@ define i32 @pre_to_post_sub() {
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[I:%.*]] = phi i32 [ 1000, [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ]
-; CHECK-NEXT:    [[I_NEXT]] = sub nuw nsw i32 [[I]], 1
+; CHECK-NEXT:    [[I_NEXT]] = sub nsw i32 [[I]], 1
 ; CHECK-NEXT:    store i32 [[I]], ptr @A, align 4
 ; CHECK-NEXT:    [[C:%.*]] = icmp samesign ugt i32 [[I]], 0
 ; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[LOOPEXIT:%.*]]
diff --git a/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll b/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
index 5f28bf539a268..0ae3973daffad 100644
--- a/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
+++ b/llvm/test/Transforms/PhaseOrdering/SystemZ/sub-xor.ll
@@ -16,11 +16,13 @@ define dso_local zeroext i32 @foo(ptr noundef %a) #0 {
 ; CHECK-SAME: ptr nofree noundef readnone captures(none) [[A:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    tail call void @populate(ptr noundef nonnull @ARR) #[[ATTR2:[0-9]+]]
-; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), align 4, !tbaa [[INT_TBAA3:![0-9]+]]
 ; CHECK-NEXT:    br label %[[FOR_BODY4:.*]]
 ; CHECK:       [[FOR_BODY4]]:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_7:%.*]], %[[FOR_BODY4]] ]
 ; CHECK-NEXT:    [[SUM_11:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[ADD_7:%.*]], %[[FOR_BODY4]] ]
+; CHECK-NEXT:    [[IDX_NEG:%.*]] = sub nsw i64 0, [[INDVARS_IV]]
+; CHECK-NEXT:    [[ADD_PTR:%.*]] = getelementptr inbounds [4 x i8], ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), i64 [[IDX_NEG]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[ADD_PTR]], align 4, !tbaa [[INT_TBAA3:![0-9]+]]
 ; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[TMP0]], [[SUM_11]]
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT_NEG:%.*]] = xor i64 [[INDVARS_IV]], -1
 ; CHECK-NEXT:    [[ADD_PTR_110:%.*]] = getelementptr inbounds [4 x i8], ptr getelementptr inbounds nuw (i8, ptr @ARR, i64 396), i64 [[INDVARS_IV_NEXT_NEG]]
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 }

>From aa7d19da8a19949a58c833ce88bc8a911ddeeaed Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 22 Aug 2026 19:28:52 +0100
Subject: [PATCH 4/5] [SCEV] Template-deduction build fix

---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 23 +++++++++----------
 .../CodeGen/PowerPC/hardware-loops-crash.ll   |  9 +++-----
 2 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 2440e21301a47..31dbbd00cb09f 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1471,8 +1471,7 @@ static const SCEV *getExtendAddRecStart(const SCEVAddRecExpr *AR, Type *Ty,
 //
 // In the current context, S is `Start`, X is `Step`, Ext is `ExtendOpTy` and T
 // is `Delta` (defined below).
-template <typename ExtendOpTy,
-          SCEVNoWrapFlags WrapType = ExtendOpTraits<ExtendOpTy>::WrapType>
+template <typename ExtendOpTy, SCEVNoWrapFlags WrapType>
 bool ScalarEvolution::proveNoWrapByVaryingStart(const SCEV *Start,
                                                 const SCEV *Step,
                                                 const Loop *L) {
@@ -1660,7 +1659,8 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
     inferNoWrapViaConstantRanges(AR);
     auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
     if (!hasFlags(NewFlags, SCEV::FlagNUW) &&
-        proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L))
+        proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNUW>(Start,
+                                                                     Step, L))
       NewFlags |= SCEV::FlagNUW;
     setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), NewFlags);
 
@@ -1692,18 +1692,16 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
     // weaker version. We can also prove no-wrap on the AR directly by showing
     // that step only traverses values in the range sext([0,SMAX]), without
     // worrying about the pre-inc AR.
-    bool IsNoSelfWrap =
-        proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNSW>(Start,
-                                                                     Step, L);
-    if (!IsNoSelfWrap && isKnownNegative(Step)) {
+    bool IsNUSW = proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNSW>(
+        Start, Step, L);
+    if (!IsNUSW && isKnownNegative(Step)) {
       unsigned BitWidth = getTypeSizeInBits(AR->getType());
       const SCEV *N =
           getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
-      IsNoSelfWrap =
-          isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
-          isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N);
+      IsNUSW = isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+               isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N);
     }
-    if (IsNoSelfWrap) {
+    if (IsNUSW) {
       setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
       Start = getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
       Step = getSignExtendExpr(Step, Ty, Depth + 1);
@@ -2068,7 +2066,8 @@ const SCEV *ScalarEvolution::getSignExtendExprImpl(SCEVUse Op, Type *Ty,
       }
     }
 
-    if (proveNoWrapByVaryingStart<SCEVSignExtendExpr>(Start, Step, L)) {
+    if (proveNoWrapByVaryingStart<SCEVSignExtendExpr, SCEV::FlagNSW>(Start,
+                                                                     Step, L)) {
       setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNSW);
       Start = getExtendAddRecStart<SCEVSignExtendExpr>(AR, Ty, this, Depth + 1);
       Step = getSignExtendExpr(Step, Ty, Depth + 1);
diff --git a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
index afa0f8c4adc0a..7616847afef80 100644
--- a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
+++ b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
@@ -24,22 +24,19 @@ define void @test() {
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 51)
 ; CHECK-NEXT:    br label [[WHILE_COND25:%.*]]
 ; CHECK:       while.cond25:
-; CHECK-NEXT:    [[INDVAR:%.*]] = phi i64 [ 0, [[WHILE_COND25_PREHEADER]] ], [ [[INDVAR_NEXT:%.*]], [[LAND_RHS:%.*]] ]
-; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
+; CHECK-NEXT:    [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS:%.*]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = call i1 @llvm.loop.decrement.i64(i64 1)
 ; CHECK-NEXT:    br i1 [[TMP0]], label [[LAND_RHS]], label [[WHILE_END187:%.*]]
 ; CHECK:       land.rhs:
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT350]] = add nsw i64 [[INDVARS_IV349]], -1
 ; CHECK-NEXT:    [[C_1:%.*]] = call i1 @cond()
-; CHECK-NEXT:    [[INDVAR_NEXT]] = add i64 [[INDVAR]], 1
 ; CHECK-NEXT:    br i1 [[C_1]], label [[WHILE_COND25]], label [[WHILE_END:%.*]]
 ; CHECK:       while.end:
-; CHECK-NEXT:    [[INDVAR_LCSSA1:%.*]] = phi i64 [ [[INDVAR]], [[LAND_RHS]] ]
 ; CHECK-NEXT:    [[C_2:%.*]] = call i1 @cond()
 ; CHECK-NEXT:    br i1 [[C_2]], label [[WHILE_END187]], label [[WHILE_COND35_PREHEADER:%.*]]
 ; CHECK:       while.cond35.preheader:
-; CHECK-NEXT:    [[TMP1:%.*]] = mul nsw i64 [[INDVAR_LCSSA1]], -1
-; CHECK-NEXT:    [[TMP2:%.*]] = add i64 [[TMP1]], 51
+; CHECK-NEXT:    [[TMP1:%.*]] = and i64 [[INDVARS_IV349]], 4294967295
+; CHECK-NEXT:    [[TMP2:%.*]] = add nuw nsw i64 [[TMP1]], 1
 ; CHECK-NEXT:    call void @llvm.set.loop.iterations.i64(i64 [[TMP2]])
 ; CHECK-NEXT:    br label [[WHILE_COND35:%.*]]
 ; CHECK:       while.cond35:

>From 1795ba9e31cece2130779043d36984c21a2c1618 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 22 Aug 2026 22:08:50 +0100
Subject: [PATCH 5/5] [SCEV] For nusw, nw on the pre-inc is sufficent?

---
 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 31dbbd00cb09f..38478e242e4fe 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1687,12 +1687,12 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
       }
     }
 
-    // Handle the nusw case. nsw on pre-inc AR and no-overflow proven with
+    // Handle the nusw case. nw on pre-inc AR and no-overflow proven with
     // unsigned overflow limit implies nusw on the parent AR, of which nw is a
-    // weaker version. We can also prove no-wrap on the AR directly by showing
-    // that step only traverses values in the range sext([0,SMAX]), without
-    // worrying about the pre-inc AR.
-    bool IsNUSW = proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNSW>(
+    // weaker version. We can also prove nusw on the AR directly by showing that
+    // step only traverses values in the range sext([0,SMAX]), without worrying
+    // about the pre-inc AR.
+    bool IsNUSW = proveNoWrapByVaryingStart<SCEVZeroExtendExpr, SCEV::FlagNW>(
         Start, Step, L);
     if (!IsNUSW && isKnownNegative(Step)) {
       unsigned BitWidth = getTypeSizeInBits(AR->getType());



More information about the llvm-commits mailing list