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

Ramkumar Ramachandra via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 30 00:53:52 PDT 2026


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

>From ed7949f641e1b262b664ad96e07817a1488d6e09 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] [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         | 167 ++++++------------
 .../DependenceAnalysis/bounds-check.ll        |   2 +-
 .../ScalarEvolution/ext-addrec-wrap-flags.ll  |   4 +-
 3 files changed, 58 insertions(+), 115 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 5b8e30985ee7f..609ce2b528657 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1612,117 +1612,20 @@ 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.
-
-      // 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::FlagNone, Depth + 1);
-        const SCEV *ZAdd = getZeroExtendExpr(
-            getAddExpr(Start, ZMul, SCEV::FlagNone, 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::FlagNone, Depth + 1),
-                       SCEV::FlagNone, 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::FlagNone, Depth + 1),
-                       SCEV::FlagNone, 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());
-        }
-      }
-    }
-
-    // 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.
-        Start =
-            getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
-        Step = getZeroExtendExpr(Step, Ty, Depth + 1);
-        return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
-      }
+    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);
 
-      // 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());
-        }
-      }
+    // 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());
     }
 
     // zext({C,+,Step}) --> (zext(D) + zext({C-D,+,Step}))<nuw><nsw>
@@ -1741,10 +1644,50 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
       }
     }
 
-    if (proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L)) {
-      setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNUW);
+    // We can prove nusw directly: for a negative step, we can sign-extend the
+    // step iff doing so only traverses values in the range zext([0,UMAX]).
+    unsigned BW = getTypeSizeInBits(AR->getType());
+    const SCEV *N =
+        getConstant(APInt::getMaxValue(BW) - getSignedRangeMin(Step));
+    bool IsNUSW = isKnownNegative(Step) &&
+                  (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+                   isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N));
+
+    // We know that when
+    //
+    //   zext(Start + Step * MaxBTC) = zext(Start) + zext(Step) * MaxBTC
+    //
+    // it does not unsigned-wrap, but we haven't been able to prove nuw.
+    //
+    // Instead, prove that it's equal to zext(Start) + sext(Step) * MaxBTC, i.e.
+    // sign-extending the Step instead of zero-extending it, to prove nusw.
+    // TODO: Is there a cheaper way to prove this?
+    const APInt *MaxBECount = nullptr;
+    unsigned WideBW = 2 * BW;
+    if (!IsNUSW &&
+        match(getConstantMaxBackedgeTakenCount(L), m_scev_APInt(MaxBECount)) &&
+        MaxBECount->getActiveBits() <= WideBW) {
+      const APInt WideBTC(MaxBECount->zextOrTrunc(WideBW));
+      Type *WideTy = IntegerType::get(getContext(), WideBW);
+      const SCEV *ZExtStart = getZeroExtendExpr(Start, WideTy, Depth + 1);
+      const SCEV *SExtStep = getSignExtendExpr(Step, WideTy, Depth + 1);
+      IsNUSW =
+          getZeroExtendExpr(
+              getAddExpr(Start,
+                         getMulExpr(getConstant(MaxBECount->zextOrTrunc(BW)),
+                                    Step, SCEV::FlagNone, Depth + 1),
+                         SCEV::FlagNone, Depth + 1),
+              WideTy, Depth + 1) ==
+          getAddExpr(ZExtStart,
+                     getMulExpr(getConstant(WideBTC), SExtStep, SCEV::FlagNone,
+                                Depth + 1),
+                     SCEV::FlagNone, Depth + 1);
+    }
+    if (IsNUSW) {
+      // We have proved nusw, of which nw is a weaker version.
+      setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
       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());
     }
   }
diff --git a/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll b/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll
index 86086f77d2a47..0531524dd3e79 100644
--- a/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll
@@ -8,7 +8,7 @@
 define void @bounds_check_test(ptr %a) {
 ; CHECK-LABEL: 'bounds_check_test'
 ; CHECK-NEXT:  Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1
-; CHECK-NEXT:    da analyze - output [*]!
+; CHECK-NEXT:    da analyze - none!
 ;
 entry:
   br label %loop
diff --git a/llvm/test/Analysis/ScalarEvolution/ext-addrec-wrap-flags.ll b/llvm/test/Analysis/ScalarEvolution/ext-addrec-wrap-flags.ll
index 60a1f19439dd2..b2915baa17f13 100644
--- a/llvm/test/Analysis/ScalarEvolution/ext-addrec-wrap-flags.ll
+++ b/llvm/test/Analysis/ScalarEvolution/ext-addrec-wrap-flags.ll
@@ -278,7 +278,7 @@ define void @zext.nusw.backedge.guard(i8 %start) {
 ; CHECK-NEXT:    %i.next = add i8 %i, -1
 ; CHECK-NEXT:    --> {(-1 + %start),+,-1}<%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %ext = zext i8 %i to i32
-; CHECK-NEXT:    --> (zext i8 {%start,+,-1}<%loop> to i32) U: [0,256) S: [0,256) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    --> {(zext i8 %start to i32),+,-1}<nw><%loop> U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @zext.nusw.backedge.guard
 ; CHECK-NEXT:  Loop %loop: Unpredictable backedge-taken count.
 ; CHECK-NEXT:  Loop %loop: Unpredictable constant max backedge-taken count.
@@ -337,7 +337,7 @@ define void @zext.nuw.increment(i8 %n) mustprogress {
 ; CHECK-NEXT:    %j = add i8 %i, 1
 ; CHECK-NEXT:    --> {101,+,-1}<nw><%loop> U: full-set S: full-set Exits: (1 + %n) LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:    %ext = zext i8 %j to i32
-; CHECK-NEXT:    --> {101,+,-1}<nw><%loop> U: [-154,102) S: [-154,102) Exits: (101 + (-1 * (zext i8 (100 + (-1 * %n)) to i32))<nsw>)<nsw> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    --> {101,+,255}<nuw><%loop> U: [101,65127) S: [101,65127) Exits: (101 + (255 * (zext i8 (100 + (-1 * %n)) to i32))<nuw><nsw>)<nuw><nsw> LoopDispositions: { %loop: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @zext.nuw.increment
 ; CHECK-NEXT:  Loop %loop: backedge-taken count is (100 + (-1 * %n))
 ; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i8 -1



More information about the llvm-commits mailing list