[llvm] [IndVarSimplify] Propagate SCEVUse flags through exit value expansion. (PR #207063)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 3 10:44:43 PDT 2026


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

>From 79e52f79f67b6ba78aa259da98b0caca3ac843c6 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Tue, 24 Mar 2026 13:46:03 +0000
Subject: [PATCH 1/3] [SCEV] Use SCEVUse for ValuesAtScope (NFC).

Update ValuesAtScope to track SCEVUse. This also requires tracking a
mapping of canonical SCEVs to corresponding SCEVUse, for invalidation.

Currently NFC, prepares for follow-up.
---
 llvm/include/llvm/Analysis/ScalarEvolution.h |  44 ++++--
 llvm/lib/Analysis/ScalarEvolution.cpp        | 137 +++++++++++--------
 2 files changed, 111 insertions(+), 70 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 0a9a4370d5e80..e9c28c56596cb 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -140,6 +140,9 @@ struct SCEVUseT : private PointerIntPair<SCEVPtrT, 2> {
   /// operands.
   bool isCanonical() const { return getCanonical() == getOpaqueValue(); }
 
+  /// Returns true if this use itself carries use-specific no-wrap flags.
+  bool hasUseFlags() const { return getOpaqueValue() != getPointer(); }
+
   /// Return the canonical SCEV for this SCEVUse.
   const SCEV *getCanonical() const;
 
@@ -713,9 +716,15 @@ class ScalarEvolution {
   getStrengthenedNoWrapFlagsFromBinOp(const OverflowingBinaryOperator *OBO);
 
   /// Notify this ScalarEvolution that \p User directly uses SCEVs in \p Ops.
-  LLVM_ABI void registerUser(const SCEV *User, ArrayRef<const SCEV *> Ops);
   LLVM_ABI void registerUser(const SCEV *User, ArrayRef<SCEVUse> Ops);
 
+  /// Attach use-specific no-wrap \p Flags to \p S and record the use.
+  SCEVUse getUseWithFlags(const SCEV *S, SCEVNoWrapFlags Flags) {
+    SCEVUse U(S, Flags);
+    registerFlaggedUse(U);
+    return U;
+  }
+
   /// Return true if the SCEV expression contains an undef value.
   LLVM_ABI bool containsUndefs(const SCEV *S) const;
 
@@ -956,7 +965,7 @@ class ScalarEvolution {
   ///
   /// In the case that a relevant loop exit value cannot be computed, the
   /// original value V is returned.
-  LLVM_ABI const SCEV *getSCEVAtScope(const SCEV *S, const Loop *L);
+  LLVM_ABI SCEVUse getSCEVAtScope(SCEVUse S, const Loop *L);
 
   /// This is a convenience function which does getSCEVAtScope(getSCEV(V), L).
   LLVM_ABI const SCEV *getSCEVAtScope(Value *V, const Loop *L);
@@ -1903,14 +1912,17 @@ class ScalarEvolution {
   /// This map contains entries for all the expressions that we attempt to
   /// compute getSCEVAtScope information for, which can be expensive in
   /// extreme cases.
-  DenseMap<const SCEV *, SmallVector<std::pair<const Loop *, const SCEV *>, 2>>
+  DenseMap<SCEVUse, SmallVector<std::pair<const Loop *, SCEVUse>, 2>>
       ValuesAtScopes;
 
   /// Reverse map for invalidation purposes: Stores of which SCEV and which
   /// loop this is the value-at-scope of.
-  DenseMap<const SCEV *, SmallVector<std::pair<const Loop *, const SCEV *>, 2>>
+  DenseMap<SCEVUse, SmallVector<std::pair<const Loop *, SCEVUse>, 2>>
       ValuesAtScopesUsers;
 
+  /// Map canonical SCEV to SCEVUse variants created for it.
+  DenseMap<const SCEV *, SmallSetVector<SCEVUse, 2>> FlaggedUses;
+
   /// Memoized computeLoopDisposition results.
   DenseMap<const SCEV *,
            SmallVector<PointerIntPair<const Loop *, 2, LoopDisposition>, 2>>
@@ -2062,7 +2074,7 @@ class ScalarEvolution {
 
   /// Implementation code for getSCEVAtScope; called at most once for each
   /// SCEV+Loop pair.
-  const SCEV *computeSCEVAtScope(const SCEV *S, const Loop *L);
+  SCEVUse computeSCEVAtScope(SCEVUse S, const Loop *L);
 
   /// Return the BackedgeTakenInfo for the given loop, lazily computing new
   /// values if the loop hasn't been analyzed yet. The returned result is
@@ -2370,7 +2382,14 @@ class ScalarEvolution {
   void forgetMemoizedResults(ArrayRef<SCEVUse> SCEVs);
 
   /// Helper for forgetMemoizedResults.
-  void forgetMemoizedResultsImpl(const SCEV *S);
+  void forgetMemoizedResultsImpl(SCEVUse S);
+
+  /// If \p U carries use-specific flags, record it as a flagged use of the
+  /// underlying canonical SCEV.
+  void registerFlaggedUse(SCEVUse U) {
+    if (U.hasUseFlags())
+      FlaggedUses[U.getPointer()].insert(U);
+  }
 
   /// Iterate over instructions in \p Worklist and their users. Erase entries
   /// from ValueExprMap and collect SCEV expressions in \p ToForget
@@ -2502,16 +2521,17 @@ class ScalarEvolution {
   bool canIVOverflowOnGT(const SCEV *RHS, const SCEV *Stride, bool IsSigned);
 
   /// Get add expr already created or create a new one.
-  const SCEV *getOrCreateAddExpr(ArrayRef<SCEVUse> Ops,
-                                 SCEV::NoWrapFlags Flags);
+  SCEVUse getOrCreateAddExpr(ArrayRef<SCEVUse> Ops, SCEV::NoWrapFlags Flags,
+                             SCEV::NoWrapFlags UseFlags = SCEV::FlagAnyWrap);
 
   /// Get mul expr already created or create a new one.
-  const SCEV *getOrCreateMulExpr(ArrayRef<SCEVUse> Ops,
-                                 SCEV::NoWrapFlags Flags);
+  SCEVUse getOrCreateMulExpr(ArrayRef<SCEVUse> Ops, SCEV::NoWrapFlags Flags,
+                             SCEV::NoWrapFlags UseFlags = SCEV::FlagAnyWrap);
 
   // Get addrec expr already created or create a new one.
-  const SCEV *getOrCreateAddRecExpr(ArrayRef<SCEVUse> Ops, const Loop *L,
-                                    SCEV::NoWrapFlags Flags);
+  SCEVUse getOrCreateAddRecExpr(ArrayRef<SCEVUse> Ops, const Loop *L,
+                                SCEV::NoWrapFlags Flags,
+                                SCEV::NoWrapFlags UseFlags = SCEV::FlagAnyWrap);
 
   /// Return x if \p Val is f(x) where f is a 1-1 function.
   const SCEV *stripInjectiveFunctions(const SCEV *Val) const;
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index ea2d72db97fb0..8580152a43dc6 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1198,7 +1198,7 @@ const SCEV *ScalarEvolution::getLosslessPtrToIntExpr(const SCEV *Op) {
             SCEVPtrToIntExpr(ID.Intern(SCEVAllocator), U, IntPtrTy);
         UniqueSCEVs.InsertNode(S, IP);
         S->computeAndSetCanonical(*this);
-        registerUser(S, U);
+        registerUser(S, {U});
         return static_cast<const SCEV *>(S);
       });
   assert(IntOp->getType()->isIntegerTy() &&
@@ -1232,7 +1232,7 @@ const SCEV *ScalarEvolution::getPtrToAddrExpr(const SCEV *Op) {
             SCEVPtrToAddrExpr(ID.Intern(SCEVAllocator), U, Ty);
         UniqueSCEVs.InsertNode(S, IP);
         S->computeAndSetCanonical(*this);
-        registerUser(S, U);
+        registerUser(S, {U});
         return static_cast<const SCEV *>(S);
       });
   assert(IntOp->getType()->isIntegerTy() &&
@@ -1289,7 +1289,7 @@ const SCEV *ScalarEvolution::getTruncateExpr(const SCEV *Op, Type *Ty,
         new (SCEVAllocator) SCEVTruncateExpr(ID.Intern(SCEVAllocator), Op, Ty);
     UniqueSCEVs.InsertNode(S, IP);
     S->computeAndSetCanonical(*this);
-    registerUser(S, Op);
+    registerUser(S, {Op});
     return S;
   }
 
@@ -1343,7 +1343,7 @@ const SCEV *ScalarEvolution::getTruncateExpr(const SCEV *Op, Type *Ty,
                                                  Op, Ty);
   UniqueSCEVs.InsertNode(S, IP);
   S->computeAndSetCanonical(*this);
-  registerUser(S, Op);
+  registerUser(S, {Op});
   return S;
 }
 
@@ -1714,7 +1714,7 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(const SCEV *Op, Type *Ty,
                                                      Op, Ty);
     UniqueSCEVs.InsertNode(S, IP);
     S->computeAndSetCanonical(*this);
-    registerUser(S, Op);
+    registerUser(S, {Op});
     return S;
   }
 
@@ -2011,7 +2011,7 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(const SCEV *Op, Type *Ty,
                                                    Op, Ty);
   UniqueSCEVs.InsertNode(S, IP);
   S->computeAndSetCanonical(*this);
-  registerUser(S, Op);
+  registerUser(S, {Op});
   return S;
 }
 
@@ -2068,7 +2068,7 @@ const SCEV *ScalarEvolution::getSignExtendExprImpl(const SCEV *Op, Type *Ty,
                                                      Op, Ty);
     UniqueSCEVs.InsertNode(S, IP);
     S->computeAndSetCanonical(*this);
-    registerUser(S, Op);
+    registerUser(S, {Op});
     return S;
   }
 
@@ -2273,7 +2273,7 @@ const SCEV *ScalarEvolution::getSignExtendExprImpl(const SCEV *Op, Type *Ty,
                                                    Op, Ty);
   UniqueSCEVs.InsertNode(S, IP);
   S->computeAndSetCanonical(*this);
-  registerUser(S, Op);
+  registerUser(S, {Op});
   return S;
 }
 
@@ -2672,14 +2672,14 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
 
   // Limit recursion calls depth.
   if (Depth > MaxArithDepth || hasHugeExpression(Ops))
-    return getOrCreateAddExpr(Ops, ComputeFlags(Ops));
+    return getOrCreateAddExpr(Ops, ComputeFlags(Ops), UseFlags);
 
   if (SCEV *S = findExistingSCEVInCache(scAddExpr, Ops)) {
     // Don't strengthen flags if we have no new information.
     SCEVAddExpr *Add = static_cast<SCEVAddExpr *>(S);
     if (Add->getNoWrapFlags(OrigFlags) != OrigFlags)
       Add->setNoWrapFlags(ComputeFlags(Ops));
-    return S;
+    return getUseWithFlags(S, UseFlags);
   }
 
   // Okay, check to see if the same value occurs in the operand list more than
@@ -3101,11 +3101,12 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
 
   // Okay, it looks like we really DO need an add expr.  Check to see if we
   // already have one, otherwise create a new one.
-  return getOrCreateAddExpr(Ops, ComputeFlags(Ops));
+  return getOrCreateAddExpr(Ops, ComputeFlags(Ops), UseFlags);
 }
 
-const SCEV *ScalarEvolution::getOrCreateAddExpr(ArrayRef<SCEVUse> Ops,
-                                                SCEV::NoWrapFlags Flags) {
+SCEVUse ScalarEvolution::getOrCreateAddExpr(ArrayRef<SCEVUse> Ops,
+                                            SCEV::NoWrapFlags Flags,
+                                            SCEV::NoWrapFlags UseFlags) {
   FoldingSetNodeID ID;
   ID.AddInteger(scAddExpr);
   for (const SCEV *Op : Ops)
@@ -3123,12 +3124,13 @@ const SCEV *ScalarEvolution::getOrCreateAddExpr(ArrayRef<SCEVUse> Ops,
     registerUser(S, Ops);
   }
   S->setNoWrapFlags(Flags);
-  return S;
+  return getUseWithFlags(S, UseFlags);
 }
 
-const SCEV *ScalarEvolution::getOrCreateAddRecExpr(ArrayRef<SCEVUse> Ops,
-                                                   const Loop *L,
-                                                   SCEV::NoWrapFlags Flags) {
+SCEVUse ScalarEvolution::getOrCreateAddRecExpr(ArrayRef<SCEVUse> Ops,
+                                               const Loop *L,
+                                               SCEV::NoWrapFlags Flags,
+                                               SCEV::NoWrapFlags UseFlags) {
   FoldingSetNodeID ID;
   ID.AddInteger(scAddRecExpr);
   for (const SCEV *Op : Ops)
@@ -3148,11 +3150,12 @@ const SCEV *ScalarEvolution::getOrCreateAddRecExpr(ArrayRef<SCEVUse> Ops,
     registerUser(S, Ops);
   }
   setNoWrapFlags(S, Flags);
-  return S;
+  return getUseWithFlags(S, UseFlags);
 }
 
-const SCEV *ScalarEvolution::getOrCreateMulExpr(ArrayRef<SCEVUse> Ops,
-                                                SCEV::NoWrapFlags Flags) {
+SCEVUse ScalarEvolution::getOrCreateMulExpr(ArrayRef<SCEVUse> Ops,
+                                            SCEV::NoWrapFlags Flags,
+                                            SCEV::NoWrapFlags UseFlags) {
   FoldingSetNodeID ID;
   ID.AddInteger(scMulExpr);
   for (const SCEV *Op : Ops)
@@ -3170,7 +3173,7 @@ const SCEV *ScalarEvolution::getOrCreateMulExpr(ArrayRef<SCEVUse> Ops,
     registerUser(S, Ops);
   }
   S->setNoWrapFlags(Flags);
-  return S;
+  return getUseWithFlags(S, UseFlags);
 }
 
 static uint64_t umul_ov(uint64_t i, uint64_t j, bool &Overflow) {
@@ -8754,6 +8757,7 @@ void ScalarEvolution::forgetAllLoops() {
   ValueExprMap.clear();
   ValuesAtScopes.clear();
   ValuesAtScopesUsers.clear();
+  FlaggedUses.clear();
   LoopDispositions.clear();
   BlockDispositions.clear();
   UnsignedRanges.clear();
@@ -10163,9 +10167,8 @@ const SCEV *ScalarEvolution::computeExitCountExhaustively(const Loop *L,
   return getCouldNotCompute();
 }
 
-const SCEV *ScalarEvolution::getSCEVAtScope(const SCEV *V, const Loop *L) {
-  SmallVector<std::pair<const Loop *, const SCEV *>, 2> &Values =
-      ValuesAtScopes[V];
+SCEVUse ScalarEvolution::getSCEVAtScope(SCEVUse V, const Loop *L) {
+  auto &Values = ValuesAtScopes[V];
   // Check to see if we've folded this expression at this loop before.
   for (auto &LS : Values)
     if (LS.first == L)
@@ -10174,7 +10177,7 @@ const SCEV *ScalarEvolution::getSCEVAtScope(const SCEV *V, const Loop *L) {
   Values.emplace_back(L, nullptr);
 
   // Otherwise compute it.
-  const SCEV *C = computeSCEVAtScope(V, L);
+  SCEVUse C = computeSCEVAtScope(V, L);
   for (auto &LS : reverse(ValuesAtScopes[V]))
     if (LS.first == L) {
       LS.second = C;
@@ -10292,7 +10295,7 @@ const SCEV *ScalarEvolution::getWithOperands(const SCEV *S,
   llvm_unreachable("Unknown SCEV kind!");
 }
 
-const SCEV *ScalarEvolution::computeSCEVAtScope(const SCEV *V, const Loop *L) {
+SCEVUse ScalarEvolution::computeSCEVAtScope(SCEVUse V, const Loop *L) {
   switch (V->getSCEVType()) {
   case scConstant:
   case scVScale:
@@ -10305,7 +10308,7 @@ const SCEV *ScalarEvolution::computeSCEVAtScope(const SCEV *V, const Loop *L) {
     // Avoid performing the look-up in the common case where the specified
     // expression has no loop-variant portions.
     for (unsigned i = 0, e = AddRec->getNumOperands(); i != e; ++i) {
-      const SCEV *OpAtScope = getSCEVAtScope(AddRec->getOperand(i), L);
+      SCEVUse OpAtScope = getSCEVAtScope(AddRec->getOperand(i), L);
       if (OpAtScope == AddRec->getOperand(i))
         continue;
 
@@ -10361,8 +10364,8 @@ const SCEV *ScalarEvolution::computeSCEVAtScope(const SCEV *V, const Loop *L) {
     // Avoid performing the look-up in the common case where the specified
     // expression has no loop-variant portions.
     for (unsigned i = 0, e = Ops.size(); i != e; ++i) {
-      const SCEV *OpAtScope = getSCEVAtScope(Ops[i].getPointer(), L);
-      if (OpAtScope != Ops[i].getPointer()) {
+      SCEVUse OpAtScope = getSCEVAtScope(Ops[i], L);
+      if (OpAtScope != Ops[i]) {
         // Okay, at least one of these operands is loop variant but might be
         // foldable.  Build a new instance of the folded commutative expression.
         SmallVector<SCEVUse, 8> NewOps;
@@ -10371,7 +10374,7 @@ const SCEV *ScalarEvolution::computeSCEVAtScope(const SCEV *V, const Loop *L) {
         NewOps.push_back(OpAtScope);
 
         for (++i; i != e; ++i) {
-          OpAtScope = getSCEVAtScope(Ops[i].getPointer(), L);
+          OpAtScope = getSCEVAtScope(Ops[i], L);
           NewOps.push_back(OpAtScope);
         }
 
@@ -14139,6 +14142,7 @@ ScalarEvolution::ScalarEvolution(ScalarEvolution &&Arg)
           std::move(Arg.ConstantEvolutionLoopExitValue)),
       ValuesAtScopes(std::move(Arg.ValuesAtScopes)),
       ValuesAtScopesUsers(std::move(Arg.ValuesAtScopesUsers)),
+      FlaggedUses(std::move(Arg.FlaggedUses)),
       LoopDispositions(std::move(Arg.LoopDispositions)),
       LoopPropertiesCache(std::move(Arg.LoopPropertiesCache)),
       BlockDispositions(std::move(Arg.BlockDispositions)),
@@ -14710,7 +14714,7 @@ void ScalarEvolution::forgetMemoizedResults(ArrayRef<SCEVUse> SCEVs) {
       [&](const auto &Entry) { return ToForget.count(Entry.first.first); });
 }
 
-void ScalarEvolution::forgetMemoizedResultsImpl(const SCEV *S) {
+void ScalarEvolution::forgetMemoizedResultsImpl(SCEVUse S) {
   LoopDispositions.erase(S);
   BlockDispositions.erase(S);
   UnsignedRanges.erase(S);
@@ -14733,20 +14737,33 @@ void ScalarEvolution::forgetMemoizedResultsImpl(const SCEV *S) {
     ExprValueMap.erase(ExprIt);
   }
 
-  auto ScopeIt = ValuesAtScopes.find(S);
-  if (ScopeIt != ValuesAtScopes.end()) {
-    for (const auto &Pair : ScopeIt->second)
-      if (!isa_and_nonnull<SCEVConstant>(Pair.second))
-        llvm::erase(ValuesAtScopesUsers[Pair.second],
-                    std::make_pair(Pair.first, S));
-    ValuesAtScopes.erase(ScopeIt);
-  }
+  auto EraseAtScopeKey = [&](SCEVUse Key) {
+    auto ScopeIt = ValuesAtScopes.find(Key);
+    if (ScopeIt != ValuesAtScopes.end()) {
+      for (const auto &Pair : ScopeIt->second)
+        if (!isa_and_nonnull<SCEVConstant>(Pair.second))
+          llvm::erase(ValuesAtScopesUsers[Pair.second],
+                      std::make_pair(Pair.first, Key));
+      ValuesAtScopes.erase(ScopeIt);
+    }
 
-  auto ScopeUserIt = ValuesAtScopesUsers.find(S);
-  if (ScopeUserIt != ValuesAtScopesUsers.end()) {
-    for (const auto &Pair : ScopeUserIt->second)
-      llvm::erase(ValuesAtScopes[Pair.second], std::make_pair(Pair.first, S));
-    ValuesAtScopesUsers.erase(ScopeUserIt);
+    auto ScopeUserIt = ValuesAtScopesUsers.find(Key);
+    if (ScopeUserIt != ValuesAtScopesUsers.end()) {
+      for (const auto &Pair : ScopeUserIt->second)
+        llvm::erase(ValuesAtScopes[Pair.second],
+                    std::make_pair(Pair.first, Key));
+      ValuesAtScopesUsers.erase(ScopeUserIt);
+    }
+  };
+  EraseAtScopeKey(S);
+  // Also erase any use-flagged variants of S.
+  if (!FlaggedUses.empty()) {
+    auto FlaggedIt = FlaggedUses.find(S);
+    if (FlaggedIt != FlaggedUses.end()) {
+      for (SCEVUse Variant : FlaggedIt->second)
+        EraseAtScopeKey(Variant);
+      FlaggedUses.erase(FlaggedIt);
+    }
   }
 
   auto BEUsersIt = BECountUsers.find(S);
@@ -14987,10 +15004,10 @@ void ScalarEvolution::verify() const {
 
   // Verify integrity of ValuesAtScopes users.
   for (const auto &ValueAndVec : ValuesAtScopes) {
-    const SCEV *Value = ValueAndVec.first;
+    SCEVUse Value = ValueAndVec.first;
     for (const auto &LoopAndValueAtScope : ValueAndVec.second) {
       const Loop *L = LoopAndValueAtScope.first;
-      const SCEV *ValueAtScope = LoopAndValueAtScope.second;
+      SCEVUse ValueAtScope = LoopAndValueAtScope.second;
       if (!isa<SCEVConstant>(ValueAtScope)) {
         auto It = ValuesAtScopesUsers.find(ValueAtScope);
         if (It != ValuesAtScopesUsers.end() &&
@@ -15004,10 +15021,10 @@ void ScalarEvolution::verify() const {
   }
 
   for (const auto &ValueAtScopeAndVec : ValuesAtScopesUsers) {
-    const SCEV *ValueAtScope = ValueAtScopeAndVec.first;
+    SCEVUse ValueAtScope = ValueAtScopeAndVec.first;
     for (const auto &LoopAndValue : ValueAtScopeAndVec.second) {
       const Loop *L = LoopAndValue.first;
-      const SCEV *Value = LoopAndValue.second;
+      SCEVUse Value = LoopAndValue.second;
       assert(!isa<SCEVConstant>(Value));
       auto It = ValuesAtScopes.find(Value);
       if (It != ValuesAtScopes.end() &&
@@ -15019,6 +15036,20 @@ void ScalarEvolution::verify() const {
     }
   }
 
+  auto VerifyFlaggedUseTracked = [&](SCEVUse Key) {
+    if (!Key.hasUseFlags())
+      return;
+    auto It = FlaggedUses.find(Key.getPointer());
+    if (It != FlaggedUses.end() && It->second.count(Key))
+      return;
+    dbgs() << "Flagged at-scope key: " << *Key << " missing in FlaggedUses\n";
+    std::abort();
+  };
+  for (const auto &ValueAndVec : ValuesAtScopes)
+    VerifyFlaggedUseTracked(ValueAndVec.first);
+  for (const auto &ValueAtScopeAndVec : ValuesAtScopesUsers)
+    VerifyFlaggedUseTracked(ValueAtScopeAndVec.first);
+
   // Verify integrity of BECountUsers.
   auto VerifyBECountUsers = [&](bool Predicated) {
     auto &BECounts =
@@ -15624,16 +15655,6 @@ PredicatedScalarEvolution::PredicatedScalarEvolution(ScalarEvolution &SE,
   Preds = std::make_unique<SCEVUnionPredicate>(Empty, SE);
 }
 
-void ScalarEvolution::registerUser(const SCEV *User,
-                                   ArrayRef<const SCEV *> Ops) {
-  for (const auto *Op : Ops)
-    // We do not expect that forgetting cached data for SCEVConstants will ever
-    // open any prospects for sharpening or introduce any correctness issues,
-    // so we don't bother storing their dependencies.
-    if (!isa<SCEVConstant>(Op))
-      SCEVUsers[Op].insert(User);
-}
-
 void ScalarEvolution::registerUser(const SCEV *User, ArrayRef<SCEVUse> Ops) {
   for (const SCEV *Op : Ops)
     // We do not expect that forgetting cached data for SCEVConstants will ever

>From c440f83e5ca076a9e12aca136f0c5fa7fc5c6afb Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 6 Apr 2026 14:28:04 +0100
Subject: [PATCH 2/3] [SCEV] Add option to request use-specific SCEV for a GEP
 expr

---
 llvm/include/llvm/Analysis/ScalarEvolution.h  |  41 +-
 llvm/lib/Analysis/IVDescriptors.cpp           |   2 +-
 llvm/lib/Analysis/ScalarEvolution.cpp         | 100 +--
 llvm/lib/Passes/PassRegistry.def              |  11 +-
 .../Transforms/Scalar/LoopStrengthReduce.cpp  |   5 +-
 .../Scalar/StraightLineStrengthReduce.cpp     |   2 +-
 .../Utils/ScalarEvolutionExpander.cpp         |   2 +-
 .../Transforms/Vectorize/VPlanTransforms.cpp  |   2 +-
 .../DependenceAnalysis/Constraints.ll         |   4 +-
 .../NonCanonicalizedSubscript.ll              |   2 +-
 .../use-specific-flags-cache.ll               |  34 +
 .../ScalarEvolution/use-specific-flags-gep.ll | 627 ++++++++++++++++++
 .../Analysis/ScalarEvolutionTest.cpp          |   8 +-
 13 files changed, 766 insertions(+), 74 deletions(-)
 create mode 100644 llvm/test/Analysis/ScalarEvolution/use-specific-flags-cache.ll
 create mode 100644 llvm/test/Analysis/ScalarEvolution/use-specific-flags-gep.ll

diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index e9c28c56596cb..a1cc77252457c 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -733,11 +733,12 @@ class ScalarEvolution {
   LLVM_ABI bool containsErasedValue(const SCEV *S) const;
 
   /// Return a SCEV expression for the full generality of the specified
-  /// expression.
-  LLVM_ABI const SCEV *getSCEV(Value *V);
+  /// expression. If \p UseCtx is true, returns a SCEV with use-specific flags
+  /// valid only for existing uses of \p V.
+  LLVM_ABI SCEVUse getSCEV(Value *V, bool UseCtx = false);
 
   /// Return an existing SCEV for V if there is one, otherwise return nullptr.
-  LLVM_ABI const SCEV *getExistingSCEV(Value *V);
+  LLVM_ABI SCEVUse getExistingSCEV(Value *V);
 
   LLVM_ABI const SCEV *getConstant(ConstantInt *V);
   LLVM_ABI const SCEV *getConstant(const APInt &Val);
@@ -763,9 +764,10 @@ class ScalarEvolution {
   LLVM_ABI const SCEV *getCastExpr(SCEVTypes Kind, const SCEV *Op, Type *Ty);
   LLVM_ABI const SCEV *getAnyExtendExpr(const SCEV *Op, Type *Ty);
 
-  LLVM_ABI const SCEV *getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
-                                  SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap,
-                                  unsigned Depth = 0);
+  LLVM_ABI SCEVUse getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
+                              SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap,
+                              unsigned Depth = 0,
+                              SCEV::NoWrapFlags UseFlags = SCEV::FlagAnyWrap);
   const SCEV *getAddExpr(SCEVUse LHS, SCEVUse RHS,
                          SCEV::NoWrapFlags Flags = SCEV::FlagAnyWrap,
                          unsigned Depth = 0) {
@@ -819,12 +821,11 @@ class ScalarEvolution {
   /// \p GEP The GEP. The indices contained in the GEP itself are ignored,
   /// instead we use IndexExprs.
   /// \p IndexExprs The expressions for the indices.
-  LLVM_ABI const SCEV *getGEPExpr(GEPOperator *GEP,
-                                  ArrayRef<SCEVUse> IndexExprs);
-  LLVM_ABI const SCEV *getGEPExpr(SCEVUse BaseExpr,
-                                  ArrayRef<SCEVUse> IndexExprs,
-                                  Type *SrcElementTy,
-                                  GEPNoWrapFlags NW = GEPNoWrapFlags::none());
+  LLVM_ABI SCEVUse getGEPExpr(GEPOperator *GEP, ArrayRef<SCEVUse> IndexExprs);
+  LLVM_ABI SCEVUse
+  getGEPExpr(SCEVUse BaseExpr, ArrayRef<SCEVUse> IndexExprs, Type *SrcElementTy,
+             GEPNoWrapFlags NW = GEPNoWrapFlags::none(),
+             GEPNoWrapFlags UseSpecificNW = GEPNoWrapFlags::none());
   LLVM_ABI const SCEV *getAbsExpr(const SCEV *Op, bool IsNSW);
   LLVM_ABI const SCEV *getMinMaxExpr(SCEVTypes Kind,
                                      SmallVectorImpl<SCEVUse> &Operands);
@@ -1512,7 +1513,7 @@ class ScalarEvolution {
   /// Return the size of an element read or written by Inst.
   LLVM_ABI const SCEV *getElementSize(Instruction *Inst);
 
-  LLVM_ABI void print(raw_ostream &OS) const;
+  LLVM_ABI void print(raw_ostream &OS, bool UseCtx = false) const;
   LLVM_ABI void verify() const;
   LLVM_ABI bool invalidate(Function &F, const PreservedAnalyses &PA,
                            FunctionAnalysisManager::Invalidator &Inv);
@@ -1707,7 +1708,7 @@ class ScalarEvolution {
 
   /// The type for ValueExprMap.
   using ValueExprMapType =
-      DenseMap<SCEVCallbackVH, const SCEV *, DenseMapInfo<Value *>>;
+      DenseMap<SCEVCallbackVH, SCEVUse, DenseMapInfo<Value *>>;
 
   /// This is a cache of the values we have analyzed so far.
   ValueExprMapType ValueExprMap;
@@ -2022,11 +2023,11 @@ class ScalarEvolution {
 
   /// We know that there is no SCEV for the specified value.  Analyze the
   /// expression recursively.
-  const SCEV *createSCEV(Value *V);
+  SCEVUse createSCEV(Value *V);
 
   /// We know that there is no SCEV for the specified value. Create a new SCEV
   /// for \p V iteratively.
-  const SCEV *createSCEVIter(Value *V);
+  SCEVUse createSCEVIter(Value *V);
   /// Collect operands of \p V for which SCEV expressions should be constructed
   /// first. Returns a SCEV directly if it can be constructed trivially for \p
   /// V.
@@ -2070,7 +2071,7 @@ class ScalarEvolution {
                                        Value *FalseVal);
 
   /// Provide the special handling we need to analyze GEP SCEVs.
-  const SCEV *createNodeForGEP(GEPOperator *GEP);
+  SCEVUse createNodeForGEP(GEPOperator *GEP);
 
   /// Implementation code for getSCEVAtScope; called at most once for each
   /// SCEV+Loop pair.
@@ -2401,7 +2402,7 @@ class ScalarEvolution {
   void eraseValueFromMap(Value *V);
 
   /// Insert V to S mapping into ValueExprMap and ExprValueMap.
-  void insertValueToMap(Value *V, const SCEV *S);
+  void insertValueToMap(Value *V, SCEVUse S);
 
   /// Return false iff given SCEV contains a SCEVUnknown with NULL value-
   /// pointer.
@@ -2606,9 +2607,11 @@ class ScalarEvolutionVerifierPass
 class ScalarEvolutionPrinterPass
     : public RequiredPassInfoMixin<ScalarEvolutionPrinterPass> {
   raw_ostream &OS;
+  bool UseCtx;
 
 public:
-  explicit ScalarEvolutionPrinterPass(raw_ostream &OS) : OS(OS) {}
+  explicit ScalarEvolutionPrinterPass(raw_ostream &OS, bool UseCtx = false)
+      : OS(OS), UseCtx(UseCtx) {}
 
   LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
 };
diff --git a/llvm/lib/Analysis/IVDescriptors.cpp b/llvm/lib/Analysis/IVDescriptors.cpp
index 9d30928d1751c..6b5c13817a02e 100644
--- a/llvm/lib/Analysis/IVDescriptors.cpp
+++ b/llvm/lib/Analysis/IVDescriptors.cpp
@@ -1652,7 +1652,7 @@ bool InductionDescriptor::isInductionPHI(
     return false;
 
   // Check that the PHI is consecutive.
-  const SCEV *PhiScev = Expr ? Expr : SE->getSCEV(Phi);
+  const SCEV *PhiScev = Expr ? Expr : SE->getSCEV(Phi).getPointer();
   const SCEV *Step;
 
   // FIXME: We are currently matching the specific loop TheLoop; if it doesn't
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 8580152a43dc6..82b265bccf7ce 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -2638,9 +2638,9 @@ bool ScalarEvolution::isAvailableAtLoopEntry(const SCEV *S, const Loop *L) {
 }
 
 /// Get a canonical add expression, or something simpler if possible.
-const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
-                                        SCEV::NoWrapFlags OrigFlags,
-                                        unsigned Depth) {
+SCEVUse ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
+                                    SCEV::NoWrapFlags OrigFlags, unsigned Depth,
+                                    SCEV::NoWrapFlags UseFlags) {
   assert(!(OrigFlags & ~(SCEV::FlagNUW | SCEV::FlagNSW)) &&
          "only nuw or nsw allowed");
   assert(!Ops.empty() && "Cannot get empty add!");
@@ -2843,6 +2843,9 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
     // common NUW flag for expression after inlining. Other flags cannot be
     // preserved, because they may depend on the original order of operations.
     SCEV::NoWrapFlags CommonFlags = maskFlags(OrigFlags, SCEV::FlagNUW);
+    SCEV::NoWrapFlags CommonUseFlags = maskFlags(UseFlags, SCEV::FlagNUW);
+    // Track whether all inlined adds have NUW for use-specific flag
+    // preservation.
     while (const SCEVAddExpr *Add = dyn_cast<SCEVAddExpr>(Ops[Idx])) {
       if (Ops.size() > AddOpsInlineThreshold ||
           Add->getNumOperands() > AddOpsInlineThreshold)
@@ -2853,13 +2856,14 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
       append_range(Ops, Add->operands());
       DeletedAdd = true;
       CommonFlags = maskFlags(CommonFlags, Add->getNoWrapFlags());
+      CommonUseFlags = maskFlags(CommonUseFlags, Add->getNoWrapFlags());
     }
 
     // If we deleted at least one add, we added operands to the end of the list,
     // and they are not necessarily sorted.  Recurse to resort and resimplify
     // any operands we just acquired.
     if (DeletedAdd)
-      return getAddExpr(Ops, CommonFlags, Depth + 1);
+      return getAddExpr(Ops, CommonFlags, Depth + 1, CommonUseFlags);
   }
 
   // Skip over the add expression until we get to a multiply.
@@ -3046,7 +3050,8 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
       const SCEV *NewRec = getAddRecExpr(AddRecOps, AddRecLoop, Flags);
 
       // If all of the other operands were loop invariant, we are done.
-      if (Ops.size() == 1) return NewRec;
+      if (Ops.size() == 1)
+        return SCEVUse(NewRec, UseFlags);
 
       // Otherwise, add the folded AddRec by the non-invariant parts.
       for (unsigned i = 0;; ++i)
@@ -3909,9 +3914,9 @@ const SCEV *ScalarEvolution::getAddRecExpr(SmallVectorImpl<SCEVUse> &Operands,
   return getOrCreateAddRecExpr(Operands, L, Flags);
 }
 
-const SCEV *ScalarEvolution::getGEPExpr(GEPOperator *GEP,
-                                        ArrayRef<SCEVUse> IndexExprs) {
-  const SCEV *BaseExpr = getSCEV(GEP->getPointerOperand());
+SCEVUse ScalarEvolution::getGEPExpr(GEPOperator *GEP,
+                                    ArrayRef<SCEVUse> IndexExprs) {
+  SCEVUse BaseExpr = getSCEV(GEP->getPointerOperand());
   // getSCEV(Base)->getType() has the same address space as Base->getType()
   // because SCEV::getType() preserves the address space.
   GEPNoWrapFlags NW = GEP->getNoWrapFlags();
@@ -3926,12 +3931,14 @@ const SCEV *ScalarEvolution::getGEPExpr(GEPOperator *GEP,
       NW = GEPNoWrapFlags::none();
   }
 
-  return getGEPExpr(BaseExpr, IndexExprs, GEP->getSourceElementType(), NW);
+  return getGEPExpr(BaseExpr, IndexExprs, GEP->getSourceElementType(), NW,
+                    /*UseSpecificNW=*/GEP->getNoWrapFlags());
 }
 
-const SCEV *ScalarEvolution::getGEPExpr(SCEVUse BaseExpr,
-                                        ArrayRef<SCEVUse> IndexExprs,
-                                        Type *SrcElementTy, GEPNoWrapFlags NW) {
+SCEVUse ScalarEvolution::getGEPExpr(SCEVUse BaseExpr,
+                                    ArrayRef<SCEVUse> IndexExprs,
+                                    Type *SrcElementTy, GEPNoWrapFlags NW,
+                                    GEPNoWrapFlags UseSpecificNW) {
   SCEV::NoWrapFlags OffsetWrap = SCEV::FlagAnyWrap;
   if (NW.hasNoUnsignedSignedWrap())
     OffsetWrap = setFlags(OffsetWrap, SCEV::FlagNSW);
@@ -3986,7 +3993,14 @@ const SCEV *ScalarEvolution::getGEPExpr(SCEVUse BaseExpr,
   bool NUW = NW.hasNoUnsignedWrap() ||
              (NW.hasNoUnsignedSignedWrap() && isKnownNonNegative(Offset));
   SCEV::NoWrapFlags BaseWrap = NUW ? SCEV::FlagNUW : SCEV::FlagAnyWrap;
-  auto *GEPExpr = getAddExpr(BaseExpr, Offset, BaseWrap);
+  SCEV::NoWrapFlags UseFlags = SCEV::FlagAnyWrap;
+  if (!NUW) {
+    if (UseSpecificNW.hasNoUnsignedWrap() ||
+        (UseSpecificNW.hasNoUnsignedSignedWrap() && isKnownNonNegative(Offset)))
+      UseFlags = SCEV::FlagNUW;
+  }
+  SmallVector<SCEVUse, 2> AddOps = {BaseExpr, Offset};
+  SCEVUse GEPExpr = getAddExpr(AddOps, BaseWrap, /*Depth=*/0, UseFlags);
   assert(BaseExpr->getType() == GEPExpr->getType() &&
          "GEP should not change type mid-flight.");
   return GEPExpr;
@@ -4727,7 +4741,7 @@ void ScalarEvolution::eraseValueFromMap(Value *V) {
   }
 }
 
-void ScalarEvolution::insertValueToMap(Value *V, const SCEV *S) {
+void ScalarEvolution::insertValueToMap(Value *V, SCEVUse S) {
   // A recursive query may have already computed the SCEV. It should be
   // equivalent, but may not necessarily be exactly the same, e.g. due to lazily
   // inferred nowrap flags.
@@ -4740,20 +4754,26 @@ void ScalarEvolution::insertValueToMap(Value *V, const SCEV *S) {
 
 /// Return an existing SCEV if it exists, otherwise analyze the expression and
 /// create a new one.
-const SCEV *ScalarEvolution::getSCEV(Value *V) {
+SCEVUse ScalarEvolution::getSCEV(Value *V, bool UseCtx) {
   assert(isSCEVable(V->getType()) && "Value is not SCEVable!");
 
-  if (const SCEV *S = getExistingSCEV(V))
-    return S;
-  return createSCEVIter(V);
+  SCEVUse S = getExistingSCEV(V);
+  if (!S)
+    S = createSCEVIter(V);
+
+  // When not using context-specific flags, return the canonical SCEV
+  // without any use-specific flags.
+  if (!UseCtx)
+    return S.getPointer();
+  return S;
 }
 
-const SCEV *ScalarEvolution::getExistingSCEV(Value *V) {
+SCEVUse ScalarEvolution::getExistingSCEV(Value *V) {
   assert(isSCEVable(V->getType()) && "Value is not SCEVable!");
 
   ValueExprMapType::iterator I = ValueExprMap.find_as(V);
   if (I != ValueExprMap.end()) {
-    const SCEV *S = I->second;
+    SCEVUse S = I->second;
     assert(checkValidity(S) &&
            "existing SCEV has not been properly invalidated");
     return S;
@@ -6418,9 +6438,9 @@ createNodeForSelectViaUMinSeq(ScalarEvolution *SE, Value *Cond, Value *TrueVal,
   if (!isa<ConstantInt>(TrueVal) && !isa<ConstantInt>(FalseVal))
     return std::nullopt;
 
-  const auto *SECond = SE->getSCEV(Cond);
-  const auto *SETrue = SE->getSCEV(TrueVal);
-  const auto *SEFalse = SE->getSCEV(FalseVal);
+  const SCEV *SECond = SE->getSCEV(Cond);
+  const SCEV *SETrue = SE->getSCEV(TrueVal);
+  const SCEV *SEFalse = SE->getSCEV(FalseVal);
   return createNodeForSelectViaUMinSeq(SE, SECond, SETrue, SEFalse);
 }
 
@@ -6464,7 +6484,7 @@ const SCEV *ScalarEvolution::createNodeForSelectOrPHI(Value *V, Value *Cond,
 
 /// Expand GEP instructions into add and multiply operations. This allows them
 /// to be analyzed by regular SCEV code.
-const SCEV *ScalarEvolution::createNodeForGEP(GEPOperator *GEP) {
+SCEVUse ScalarEvolution::createNodeForGEP(GEPOperator *GEP) {
   assert(GEP->getSourceElementType()->isSized() &&
          "GEP source element type must be sized");
 
@@ -7725,7 +7745,7 @@ bool ScalarEvolution::loopIsFiniteByAssumption(const Loop *L) {
   return isFinite(L) || (isMustProgress(L) && loopHasNoSideEffects(L));
 }
 
-const SCEV *ScalarEvolution::createSCEVIter(Value *V) {
+SCEVUse ScalarEvolution::createSCEVIter(Value *V) {
   // Worklist item with a Value and a bool indicating whether all operands have
   // been visited already.
   using PointerTy = PointerIntPair<Value *, 1, bool>;
@@ -7742,7 +7762,7 @@ const SCEV *ScalarEvolution::createSCEVIter(Value *V) {
     }
 
     SmallVector<Value *> Ops;
-    const SCEV *CreatedSCEV = nullptr;
+    SCEVUse CreatedSCEV;
     // If all operands have been visited already, create the SCEV.
     if (E.getInt()) {
       CreatedSCEV = createSCEV(CurV);
@@ -8001,7 +8021,7 @@ ScalarEvolution::getOperandsToCreate(Value *V, SmallVectorImpl<Value *> &Ops) {
   return nullptr;
 }
 
-const SCEV *ScalarEvolution::createSCEV(Value *V) {
+SCEVUse ScalarEvolution::createSCEV(Value *V) {
   if (!isSCEVable(V->getType()))
     return getUnknown(V);
 
@@ -8036,7 +8056,7 @@ const SCEV *ScalarEvolution::createSCEV(Value *V) {
       SmallVector<SCEVUse, 4> AddOps;
       do {
         if (BO->Op) {
-          if (auto *OpSCEV = getExistingSCEV(BO->Op)) {
+          if (SCEVUse OpSCEV = getExistingSCEV(BO->Op)) {
             AddOps.push_back(OpSCEV);
             break;
           }
@@ -8082,7 +8102,7 @@ const SCEV *ScalarEvolution::createSCEV(Value *V) {
       SmallVector<SCEVUse, 4> MulOps;
       do {
         if (BO->Op) {
-          if (auto *OpSCEV = getExistingSCEV(BO->Op)) {
+          if (SCEVUse OpSCEV = getExistingSCEV(BO->Op)) {
             MulOps.push_back(OpSCEV);
             break;
           }
@@ -9364,7 +9384,7 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromCondImpl(
     NWR.getEquivalentICmp(Pred, NewRHSC, Offset);
     if (!ExitIfTrue)
       Pred = ICmpInst::getInversePredicate(Pred);
-    auto *LHS = getSCEV(WO->getLHS());
+    const SCEV *LHS = getSCEV(WO->getLHS());
     if (Offset != 0)
       LHS = getAddExpr(LHS, getConstant(Offset));
     auto EL = computeExitLimitFromICmp(L, Pred, LHS, getConstant(NewRHSC),
@@ -12873,7 +12893,7 @@ bool ScalarEvolution::isImpliedCondOperandsViaShift(CmpPredicate Pred,
   using namespace PatternMatch;
   if (match(SUFoundRHS->getValue(),
             m_LShr(m_Value(Shiftee), m_Value(ShiftValue)))) {
-    auto *ShifteeS = getSCEV(Shiftee);
+    const SCEV *ShifteeS = getSCEV(Shiftee);
     // Prove one of the following:
     // LHS <u (shiftee >> shiftvalue) && shiftee <=u RHS ---> LHS <u RHS
     // LHS <=u (shiftee >> shiftvalue) && shiftee <=u RHS ---> LHS <=u RHS
@@ -13137,7 +13157,7 @@ bool ScalarEvolution::isImpliedViaOperations(CmpPredicate Pred, const SCEV *LHS,
 
       // We want to make sure that LHS = FoundLHS / Denominator. If it is so,
       // then a SCEV for the numerator already exists and matches with FoundLHS.
-      auto *Numerator = getExistingSCEV(LL);
+      SCEVUse Numerator = getExistingSCEV(LL);
       if (!Numerator || Numerator->getType() != FoundLHS->getType())
         return false;
 
@@ -13717,7 +13737,7 @@ ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
         //
         // FIXME: Should isLoopEntryGuardedByCond do this for us?
         auto CondGT = IsSigned ? ICmpInst::ICMP_SGT : ICmpInst::ICMP_UGT;
-        auto *StartMinusOne =
+        const SCEV *StartMinusOne =
             getAddExpr(OrigStart, getMinusOne(OrigStart->getType()));
         return isLoopEntryGuardedByCond(L, CondGT, OrigRHS, StartMinusOne);
       };
@@ -14394,7 +14414,7 @@ static raw_ostream &operator<<(raw_ostream &OS,
 }
 } // namespace llvm
 
-void ScalarEvolution::print(raw_ostream &OS) const {
+void ScalarEvolution::print(raw_ostream &OS, bool UseCtx) const {
   // ScalarEvolution's implementation of the print method is to print
   // out SCEV values of all instructions that are interesting. Doing
   // this potentially causes it to create new SCEV objects though,
@@ -14411,8 +14431,8 @@ void ScalarEvolution::print(raw_ostream &OS) const {
       if (isSCEVable(I.getType()) && !isa<CmpInst>(I)) {
         OS << I << '\n';
         OS << "  -->  ";
-        const SCEV *SV = SE.getSCEV(&I);
-        SV->print(OS);
+        SCEVUse SV = SE.getSCEV(&I, UseCtx);
+        SV.print(OS);
         if (!isa<SCEVCouldNotCompute>(SV)) {
           OS << " U: ";
           SE.getUnsignedRange(SV).print(OS);
@@ -15182,7 +15202,7 @@ ScalarEvolutionPrinterPass::run(Function &F, FunctionAnalysisManager &AM) {
   // update_analyze_test_checks.py working.
   OS << "Printing analysis 'Scalar Evolution Analysis' for function '"
      << F.getName() << "':\n";
-  AM.getResult<ScalarEvolutionAnalysis>(F).print(OS);
+  AM.getResult<ScalarEvolutionAnalysis>(F).print(OS, UseCtx);
   return PreservedAnalyses::all();
 }
 
@@ -15794,7 +15814,7 @@ void PredicatedScalarEvolution::print(raw_ostream &OS, unsigned Depth) const {
       if (!SE.isSCEVable(I.getType()))
         continue;
 
-      auto *Expr = SE.getSCEV(&I);
+      const SCEV *Expr = SE.getSCEV(&I);
       auto II = RewriteMap.find(Expr);
 
       if (II == RewriteMap.end())
@@ -16277,8 +16297,8 @@ void ScalarEvolution::LoopGuards::collectFromBlock(
       if (auto *Cmp = dyn_cast<ICmpInst>(Cond)) {
         auto Predicate =
             EnterIfTrue ? Cmp->getPredicate() : Cmp->getInversePredicate();
-        const auto *LHS = SE.getSCEV(Cmp->getOperand(0));
-        const auto *RHS = SE.getSCEV(Cmp->getOperand(1));
+        const SCEV *LHS = SE.getSCEV(Cmp->getOperand(0));
+        const SCEV *RHS = SE.getSCEV(Cmp->getOperand(1));
         // If LHS is a constant, apply information to the other expression.
         // TODO: If LHS is not a constant, check if using CompareSCEVComplexity
         // can improve results.
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 64331e04c155b..0dccb4d9ebc19 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -531,7 +531,6 @@ FUNCTION_PASS("print<memoryssa-walker>", MemorySSAWalkerPrinterPass(errs()))
 FUNCTION_PASS("print<phi-values>", PhiValuesPrinterPass(errs()))
 FUNCTION_PASS("print<postdomtree>", PostDominatorTreePrinterPass(errs()))
 FUNCTION_PASS("print<regions>", RegionInfoPrinterPass(errs()))
-FUNCTION_PASS("print<scalar-evolution>", ScalarEvolutionPrinterPass(errs()))
 FUNCTION_PASS("print<scev-division>", SCEVDivisionPrinterPass(errs()))
 FUNCTION_PASS("print<stack-safety-local>", StackSafetyPrinterPass(errs()))
 FUNCTION_PASS("print<uniformity>", UniformityInfoPrinterPass(errs()))
@@ -687,6 +686,16 @@ FUNCTION_PASS_WITH_PARAMS(
       return MemorySSAPrinterPass(errs(), !NoEnsureOptimizedUses);
     },
     parseMemorySSAPrinterPassOptions, "no-ensure-optimized-uses")
+FUNCTION_PASS_WITH_PARAMS(
+    "print<scalar-evolution>", "ScalarEvolutionPrinterPass",
+    [](bool UseCtx) {
+      return ScalarEvolutionPrinterPass(errs(), UseCtx);
+    },
+    [](StringRef Params) {
+      return PassBuilder::parseSinglePassOption(
+          Params, "use-context", "ScalarEvolutionPrinterPass");
+    },
+    "use-context")
 FUNCTION_PASS_WITH_PARAMS(
     "print<stack-lifetime>", "StackLifetimePrinterPass",
     [](StackLifetime::LivenessType Type) {
diff --git a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
index d382a22da6782..f33fdb33bd772 100644
--- a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
@@ -5961,9 +5961,8 @@ Value *LSRInstance::Expand(const LSRUse &LU, const LSRFixup &LF,
   }
 
   // Emit instructions summing all the operands.
-  const SCEV *FullS = Ops.empty() ?
-                      SE.getConstant(IntTy, 0) :
-                      SE.getAddExpr(Ops);
+  const SCEV *FullS =
+      Ops.empty() ? SE.getConstant(IntTy, 0) : SE.getAddExpr(Ops).getPointer();
   Value *FullV = Rewriter.expandCodeFor(FullS, Ty);
 
   // We're done expanding now, so reset the rewriter.
diff --git a/llvm/lib/Transforms/Scalar/StraightLineStrengthReduce.cpp b/llvm/lib/Transforms/Scalar/StraightLineStrengthReduce.cpp
index abca7020e1c4f..301e7a60f6a42 100644
--- a/llvm/lib/Transforms/Scalar/StraightLineStrengthReduce.cpp
+++ b/llvm/lib/Transforms/Scalar/StraightLineStrengthReduce.cpp
@@ -496,7 +496,7 @@ class StraightLineStrengthReduce {
   } CandidateDict;
 
   const SCEV *getAndRecordSCEV(Value *V) {
-    auto *S = SE->getSCEV(V);
+    const SCEV *S = SE->getSCEV(V);
     if (isa<Instruction>(V) && !(isa<SCEVCouldNotCompute>(S) ||
                                  isa<SCEVUnknown>(S) || isa<SCEVConstant>(S)))
       SCEVToInsts[S].insert(cast<Instruction>(V));
diff --git a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
index 125571b18dddc..2b7a00b05444e 100644
--- a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
+++ b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
@@ -1278,7 +1278,7 @@ Value *SCEVExpander::tryToReuseLCSSAPhi(SCEVUseT<const SCEVAddRecExpr *> S) {
   for (auto &PN : EB->phis()) {
     if (!SE.isSCEVable(PN.getType()))
       continue;
-    auto *ExitSCEV = SE.getSCEV(&PN);
+    const SCEV *ExitSCEV = SE.getSCEV(&PN);
     if (!isa<SCEVAddRecExpr>(ExitSCEV))
       continue;
     Type *PhiTy = PN.getType();
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index e5d16ccbec434..8acba6d552f76 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -5350,7 +5350,7 @@ void VPlanTransforms::materializeConstantVectorTripCount(
   // TODO: Compute vector trip counts for loops requiring a scalar epilogue and
   // tail-folded loops.
   ScalarEvolution &SE = *PSE.getSE();
-  auto *TCScev = SE.getSCEV(TC->getLiveInIRValue());
+  const SCEV *TCScev = SE.getSCEV(TC->getLiveInIRValue());
   if (!isa<SCEVConstant>(TCScev))
     return;
   const SCEV *VFxUF = SE.getElementCount(TCScev->getType(), BestVF * BestUF);
diff --git a/llvm/test/Analysis/DependenceAnalysis/Constraints.ll b/llvm/test/Analysis/DependenceAnalysis/Constraints.ll
index f8f8c5d7f5501..810f9e5a8cdb5 100644
--- a/llvm/test/Analysis/DependenceAnalysis/Constraints.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/Constraints.ll
@@ -47,7 +47,7 @@ define void @dep_constraint_crash_test(i32 %M, i32 %N) {
 ; CHECK-NEXT:  Src: store i32 0, ptr %13, align 4 --> Dst: store i32 %24, ptr %25, align 4
 ; CHECK-NEXT:    da analyze - output [S * *|<]!
 ; CHECK-NEXT:  Src: store i32 0, ptr %13, align 4 --> Dst: %27 = load i32, ptr %26, align 4
-; CHECK-NEXT:    da analyze - flow [* * *|<]!
+; CHECK-NEXT:    da analyze - flow [S * *|<]!
 ; CHECK-NEXT:  Src: store i32 0, ptr %13, align 4 --> Dst: %29 = load i32, ptr %28, align 4
 ; CHECK-NEXT:    da analyze - none!
 ; CHECK-NEXT:  Src: store i32 0, ptr %13, align 4 --> Dst: store i32 %30, ptr %31, align 4
@@ -85,7 +85,7 @@ define void @dep_constraint_crash_test(i32 %M, i32 %N) {
 ; CHECK-NEXT:  Src: %20 = load i32, ptr %19, align 4 --> Dst: %29 = load i32, ptr %28, align 4
 ; CHECK-NEXT:    da analyze - none!
 ; CHECK-NEXT:  Src: %20 = load i32, ptr %19, align 4 --> Dst: store i32 %30, ptr %31, align 4
-; CHECK-NEXT:    da analyze - anti [* * *|<]!
+; CHECK-NEXT:    da analyze - anti [S * *|<]!
 ; CHECK-NEXT:  Src: %20 = load i32, ptr %19, align 4 --> Dst: %32 = load i32, ptr %6, align 4
 ; CHECK-NEXT:    da analyze - input [S|<]!
 ; CHECK-NEXT:  Src: %20 = load i32, ptr %19, align 4 --> Dst: store i32 %33, ptr @out_l, align 4
diff --git a/llvm/test/Analysis/DependenceAnalysis/NonCanonicalizedSubscript.ll b/llvm/test/Analysis/DependenceAnalysis/NonCanonicalizedSubscript.ll
index a3c69270cf4f6..46101c025c4af 100644
--- a/llvm/test/Analysis/DependenceAnalysis/NonCanonicalizedSubscript.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/NonCanonicalizedSubscript.ll
@@ -59,7 +59,7 @@ define void @coupled_miv_type_mismatch(i32 %n) {
 ; CHECK-NEXT:  Src: %2 = load i32, ptr %arrayidx5, align 4 --> Dst: %2 = load i32, ptr %arrayidx5, align 4
 ; CHECK-NEXT:    da analyze - input [* *]!
 ; CHECK-NEXT:  Src: %2 = load i32, ptr %arrayidx5, align 4 --> Dst: store i32 %add6, ptr %arrayidx10, align 4
-; CHECK-NEXT:    da analyze - anti [* *|<]!
+; CHECK-NEXT:    da analyze - anti [* <>]!
 ; CHECK-NEXT:  Src: store i32 %add6, ptr %arrayidx10, align 4 --> Dst: store i32 %add6, ptr %arrayidx10, align 4
 ; CHECK-NEXT:    da analyze - none!
 ;
diff --git a/llvm/test/Analysis/ScalarEvolution/use-specific-flags-cache.ll b/llvm/test/Analysis/ScalarEvolution/use-specific-flags-cache.ll
new file mode 100644
index 0000000000000..a785ed3e4629c
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/use-specific-flags-cache.ll
@@ -0,0 +1,34 @@
+; RUN: opt -S -disable-output "-passes=print<scalar-evolution><use-context>,print<scalar-evolution>" %s 2>&1 | FileCheck %s --check-prefix=CTX-THEN-NOCTX
+; RUN: opt -S -disable-output "-passes=print<scalar-evolution>,print<scalar-evolution><use-context>" %s 2>&1 | FileCheck %s --check-prefix=NOCTX-THEN-CTX
+; RUN: opt -S -disable-output "-passes=print<scalar-evolution>,print<scalar-evolution>" %s 2>&1 | FileCheck %s --check-prefix=NOCTX-BOTH
+
+define void @f(ptr %base, i32 %n) {
+  %ext = zext i32 %n to i64
+  %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+  ret void
+}
+
+; use-context first: shows (u nuw). Without context second: strips it.
+; CTX-THEN-NOCTX-LABEL: 'f'
+; CTX-THEN-NOCTX:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; CTX-THEN-NOCTX-NEXT:    -->  ((zext i32 %n to i64) + %base)(u nuw) U:
+; CTX-THEN-NOCTX:       'f'
+; CTX-THEN-NOCTX:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; CTX-THEN-NOCTX-NEXT:    -->  ((zext i32 %n to i64) + %base) U:
+
+; Without context first, use-context second: use-specific flags are still
+; available from cache because they are always computed.
+; NOCTX-THEN-CTX-LABEL: 'f'
+; NOCTX-THEN-CTX:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; NOCTX-THEN-CTX-NEXT:    -->  ((zext i32 %n to i64) + %base) U:
+; NOCTX-THEN-CTX:       'f'
+; NOCTX-THEN-CTX:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; NOCTX-THEN-CTX-NEXT:    -->  ((zext i32 %n to i64) + %base)(u nuw) U:
+
+; Without context both times: no use-specific flags shown.
+; NOCTX-BOTH-LABEL: 'f'
+; NOCTX-BOTH:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; NOCTX-BOTH-NEXT:    -->  ((zext i32 %n to i64) + %base) U:
+; NOCTX-BOTH:       'f'
+; NOCTX-BOTH:         %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; NOCTX-BOTH-NEXT:    -->  ((zext i32 %n to i64) + %base) U:
diff --git a/llvm/test/Analysis/ScalarEvolution/use-specific-flags-gep.ll b/llvm/test/Analysis/ScalarEvolution/use-specific-flags-gep.ll
new file mode 100644
index 0000000000000..4ef0a3020b38e
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/use-specific-flags-gep.ll
@@ -0,0 +1,627 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py
+; RUN: opt -S -disable-output "-passes=print<scalar-evolution><use-context>" %s 2>&1 | FileCheck %s
+
+declare void @use(ptr)
+
+define void @gep_nonneg_offset(ptr %base, i32 %n) {
+; CHECK-LABEL: 'gep_nonneg_offset'
+; CHECK-NEXT:  Classifying expressions for: @gep_nonneg_offset
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_nonneg_offset
+;
+  %ext = zext i32 %n to i64
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %ext
+  call void @use(ptr %gep.inbounds)
+  %gep.plain = getelementptr i8, ptr %base, i64 %ext
+  call void @use(ptr %gep.plain)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %ext
+  call void @use(ptr %gep.nuw)
+  ret void
+}
+
+; TODO: Use specific flags not propagated through multiplies yet.
+define void @gep_i32_nonneg_offset(ptr %base, i32 %n) {
+; CHECK-LABEL: 'gep_i32_nonneg_offset'
+; CHECK-NEXT:  Classifying expressions for: @gep_i32_nonneg_offset
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((4 * (zext i32 %n to i64))<nuw><nsw> + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i32, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((4 * (zext i32 %n to i64))<nuw><nsw> + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i32, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((4 * (zext i32 %n to i64))<nuw><nsw> + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_i32_nonneg_offset
+;
+  %ext = zext i32 %n to i64
+  %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %ext
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i32, ptr %base, i64 %ext
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i32, ptr %base, i64 %ext
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+
+
+define void @gep_maybe_neg_offset(ptr %base, i64 %n) {
+; CHECK-LABEL: 'gep_maybe_neg_offset'
+; CHECK-NEXT:  Classifying expressions for: @gep_maybe_neg_offset
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %n
+; CHECK-NEXT:    --> (%n + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %n
+; CHECK-NEXT:    --> (%n + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %n
+; CHECK-NEXT:    --> (%n + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_maybe_neg_offset
+;
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %n
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %n
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %n
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+; Chained inbounds GEPs with non-negative offsets: both get use-specific nuw.
+define ptr @chained_inbounds_geps(ptr %base, i32 %n, i32 %m) {
+; CHECK-LABEL: 'chained_inbounds_geps'
+; CHECK-NEXT:  Classifying expressions for: @chained_inbounds_geps
+; CHECK-NEXT:    %ext.n = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep1 = getelementptr inbounds i8, ptr %base, i64 %ext.n
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %ext.m = zext i32 %m to i64
+; CHECK-NEXT:    --> (zext i32 %m to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ext.m
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + (zext i32 %m to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @chained_inbounds_geps
+;
+  %ext.n = zext i32 %n to i64
+  %gep1 = getelementptr inbounds i8, ptr %base, i64 %ext.n
+  %ext.m = zext i32 %m to i64
+  %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ext.m
+  ret ptr %gep2
+}
+
+; TODO: Use specific flags not propagated through multiplies yet.
+define void @gep_i32_maybe_neg_offset(ptr %base, i64 %n) {
+; CHECK-LABEL: 'gep_i32_maybe_neg_offset'
+; CHECK-NEXT:  Classifying expressions for: @gep_i32_maybe_neg_offset
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %n
+; CHECK-NEXT:    --> ((4 * %n) + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i32, ptr %base, i64 %n
+; CHECK-NEXT:    --> ((4 * %n) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i32, ptr %base, i64 %n
+; CHECK-NEXT:    --> ((4 * %n) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_i32_maybe_neg_offset
+;
+  %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %n
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i32, ptr %base, i64 %n
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i32, ptr %base, i64 %n
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+define void @gep_i64_nonneg_offset(ptr %base, i32 %n) {
+; CHECK-LABEL: 'gep_i64_nonneg_offset'
+; CHECK-NEXT:  Classifying expressions for: @gep_i64_nonneg_offset
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i64, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((8 * (zext i32 %n to i64))<nuw><nsw> + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i64, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((8 * (zext i32 %n to i64))<nuw><nsw> + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i64, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((8 * (zext i32 %n to i64))<nuw><nsw> + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_i64_nonneg_offset
+;
+  %ext = zext i32 %n to i64
+  %gep.inbounds = getelementptr inbounds i64, ptr %base, i64 %ext
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i64, ptr %base, i64 %ext
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i64, ptr %base, i64 %ext
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+define void @gep_i32_add_no_nuw_index(ptr %base, i64 %a, i64 %b) {
+; CHECK-LABEL: 'gep_i32_add_no_nuw_index'
+; CHECK-NEXT:  Classifying expressions for: @gep_i32_add_no_nuw_index
+; CHECK-NEXT:    %idx = add i64 %a, %b
+; CHECK-NEXT:    --> (%a + %b) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i32, ptr %base, i64 %idx
+; CHECK-NEXT:    --> ((4 * (%a + %b)) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %idx
+; CHECK-NEXT:    --> ((4 * (%a + %b)) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_i32_add_no_nuw_index
+;
+  %idx = add i64 %a, %b
+  %gep.nuw = getelementptr nuw i32, ptr %base, i64 %idx
+  call void @use(ptr %gep.nuw)
+  %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %idx
+  call void @use(ptr %gep.inbounds)
+  ret void
+}
+
+; Three chained GEPs: first and third inbounds, middle not. Only first get
+;use-specific nuw; the others don't.
+define ptr @three_chained_geps_middle_not_inbounds(ptr %base, i32 %n, i32 %m, i32 %k) {
+; CHECK-LABEL: 'three_chained_geps_middle_not_inbounds'
+; CHECK-NEXT:  Classifying expressions for: @three_chained_geps_middle_not_inbounds
+; CHECK-NEXT:    %ext.n = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep1 = getelementptr inbounds i8, ptr %base, i64 %ext.n
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %ext.m = zext i32 %m to i64
+; CHECK-NEXT:    --> (zext i32 %m to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep2 = getelementptr i8, ptr %gep1, i64 %ext.m
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + (zext i32 %m to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:    %ext.k = zext i32 %k to i64
+; CHECK-NEXT:    --> (zext i32 %k to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep3 = getelementptr inbounds i8, ptr %gep2, i64 %ext.k
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + (zext i32 %m to i64) + (zext i32 %k to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @three_chained_geps_middle_not_inbounds
+;
+  %ext.n = zext i32 %n to i64
+  %gep1 = getelementptr inbounds i8, ptr %base, i64 %ext.n
+  %ext.m = zext i32 %m to i64
+  %gep2 = getelementptr i8, ptr %gep1, i64 %ext.m
+  %ext.k = zext i32 %k to i64
+  %gep3 = getelementptr inbounds i8, ptr %gep2, i64 %ext.k
+  ret ptr %gep3
+}
+
+; Chained GEPs: outer inbounds, inner not inbounds. None get nuw.
+define ptr @chained_geps_outer_inbounds(ptr %base, i32 %n, i32 %m) {
+; CHECK-LABEL: 'chained_geps_outer_inbounds'
+; CHECK-NEXT:  Classifying expressions for: @chained_geps_outer_inbounds
+; CHECK-NEXT:    %ext.n = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep1 = getelementptr i8, ptr %base, i64 %ext.n
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:    %ext.m = zext i32 %m to i64
+; CHECK-NEXT:    --> (zext i32 %m to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ext.m
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + (zext i32 %m to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @chained_geps_outer_inbounds
+;
+  %ext.n = zext i32 %n to i64
+  %gep1 = getelementptr i8, ptr %base, i64 %ext.n
+  %ext.m = zext i32 %m to i64
+  %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ext.m
+  ret ptr %gep2
+}
+
+define i64 @gep_to_ptrtoint(ptr %base, i32 %n) {
+; CHECK-LABEL: 'gep_to_ptrtoint'
+; CHECK-NEXT:  Classifying expressions for: @gep_to_ptrtoint
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:    %int = ptrtoint ptr %gep to i64
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + (ptrtoint ptr %base to i64)) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @gep_to_ptrtoint
+;
+  %ext = zext i32 %n to i64
+  %gep = getelementptr inbounds i8, ptr %base, i64 %ext
+  %int = ptrtoint ptr %gep to i64
+  ret i64 %int
+}
+
+define noundef ptr @inbounds_nuw_gep_poison_ub(ptr %base, i32 %n) {
+; CHECK-LABEL: 'inbounds_nuw_gep_poison_ub'
+; CHECK-NEXT:  Classifying expressions for: @inbounds_nuw_gep_poison_ub
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep = getelementptr inbounds nuw i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)<nuw> U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @inbounds_nuw_gep_poison_ub
+;
+  %ext = zext i32 %n to i64
+  %gep = getelementptr inbounds nuw i8, ptr %base, i64 %ext
+  ret ptr %gep
+}
+
+define void @inbounds_gep_add_no_nuw_index(ptr %base, i64 range(i64 0, 4294967296) %a, i64 range(i64 0, 4294967296) %b) {
+; CHECK-LABEL: 'inbounds_gep_add_no_nuw_index'
+; CHECK-NEXT:  Classifying expressions for: @inbounds_gep_add_no_nuw_index
+; CHECK-NEXT:    %idx = add i64 %a, %b
+; CHECK-NEXT:    --> (%a + %b) U: [0,8589934591) S: [0,8589934591)
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @inbounds_gep_add_no_nuw_index
+;
+  %idx = add i64 %a, %b
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+define void @inbounds_gep_add_nuw_index(ptr %base, i64 range(i64 0, 4294967296) %a, i64 range(i64 0, 4294967296) %b) {
+; CHECK-LABEL: 'inbounds_gep_add_nuw_index'
+; CHECK-NEXT:  Classifying expressions for: @inbounds_gep_add_nuw_index
+; CHECK-NEXT:    %idx = add nuw i64 %a, %b
+; CHECK-NEXT:    --> (%a + %b) U: [0,8589934591) S: [0,8589934591)
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @inbounds_gep_add_nuw_index
+;
+  %idx = add nuw i64 %a, %b
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+; Inbounds GEP where the index is an add that may be negative.
+define void @inbounds_gep_add_maybe_neg_index(ptr %base, i64 %a, i64 %b) {
+; CHECK-LABEL: 'inbounds_gep_add_maybe_neg_index'
+; CHECK-NEXT:  Classifying expressions for: @inbounds_gep_add_maybe_neg_index
+; CHECK-NEXT:    %idx = add nuw i64 %a, %b
+; CHECK-NEXT:    --> (%a + %b) U: full-set S: full-set
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> (%a + %b + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @inbounds_gep_add_maybe_neg_index
+;
+  %idx = add nuw i64 %a, %b
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %idx
+  call void @use(ptr %gep.plain)
+  ret void
+}
+
+; Multi-index struct GEP with add for first dimension.
+define ptr @inbounds_struct_gep_add_index(ptr %base, i32 %a, i32 %b) {
+; CHECK-LABEL: 'inbounds_struct_gep_add_index'
+; CHECK-NEXT:  Classifying expressions for: @inbounds_struct_gep_add_index
+; CHECK-NEXT:    %ea = zext i32 %a to i64
+; CHECK-NEXT:    --> (zext i32 %a to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %eb = zext i32 %b to i64
+; CHECK-NEXT:    --> (zext i32 %b to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %idx = add nuw nsw i64 %ea, %eb
+; CHECK-NEXT:    --> ((zext i32 %a to i64) + (zext i32 %b to i64)) U: [0,8589934591) S: [0,8589934591)
+; CHECK-NEXT:    %gep = getelementptr inbounds [8 x i8], ptr %base, i64 %idx, i64 4
+; CHECK-NEXT:    --> (4 + (8 * ((zext i32 %a to i64) + (zext i32 %b to i64)))<nuw><nsw> + %base)(u nuw) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @inbounds_struct_gep_add_index
+;
+  %ea = zext i32 %a to i64
+  %eb = zext i32 %b to i64
+  %idx = add nuw nsw i64 %ea, %eb
+  %gep = getelementptr inbounds [8 x i8], ptr %base, i64 %idx, i64 4
+  ret ptr %gep
+}
+
+; Non-inbounds GEP with add index chained to inbounds GEP.
+define ptr @non_inbounds_add_then_inbounds(ptr %base, i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: 'non_inbounds_add_then_inbounds'
+; CHECK-NEXT:  Classifying expressions for: @non_inbounds_add_then_inbounds
+; CHECK-NEXT:    %ea = zext i32 %a to i64
+; CHECK-NEXT:    --> (zext i32 %a to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %eb = zext i32 %b to i64
+; CHECK-NEXT:    --> (zext i32 %b to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %idx = add nuw nsw i64 %ea, %eb
+; CHECK-NEXT:    --> ((zext i32 %a to i64) + (zext i32 %b to i64)) U: [0,8589934591) S: [0,8589934591)
+; CHECK-NEXT:    %gep1 = getelementptr i8, ptr %base, i64 %idx
+; CHECK-NEXT:    --> ((zext i32 %a to i64) + (zext i32 %b to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:    %ec = zext i32 %c to i64
+; CHECK-NEXT:    --> (zext i32 %c to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ec
+; CHECK-NEXT:    --> ((zext i32 %a to i64) + (zext i32 %b to i64) + (zext i32 %c to i64) + %base) U: full-set S: full-set
+; CHECK-NEXT:  Determining loop execution counts for: @non_inbounds_add_then_inbounds
+;
+  %ea = zext i32 %a to i64
+  %eb = zext i32 %b to i64
+  %idx = add nuw nsw i64 %ea, %eb
+  %gep1 = getelementptr i8, ptr %base, i64 %idx
+  %ec = zext i32 %c to i64
+  %gep2 = getelementptr inbounds i8, ptr %gep1, i64 %ec
+  ret ptr %gep2
+}
+
+; %iv may wrap, so we cannot preserve use-specific NUW after folding the GEP
+; into an AddRec.
+define void @loop_gep_flags(ptr %base, i64 %n) {
+; CHECK-LABEL: 'loop_gep_flags'
+; CHECK-NEXT:  Classifying expressions for: @loop_gep_flags
+; CHECK-NEXT:    %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop>(u nuw) U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop>(u nuw) U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = add i64 %iv, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%loop> U: full-set S: full-set Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @loop_gep_flags
+; 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 i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.nuw)
+  %gep.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.nusw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.plain)
+  %iv.next = add i64 %iv, 1
+  %cmp = icmp ne i64 %iv.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+define void @loop_inbounds_neg_step(ptr %base, i64 %n) {
+; CHECK-LABEL: 'loop_inbounds_neg_step'
+; CHECK-NEXT:  Classifying expressions for: @loop_inbounds_neg_step
+; CHECK-NEXT:    %iv = phi i64 [ %n, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {%n,+,-1}<nsw><%loop> U: full-set S: full-set Exits: (1 + (0 smin (-1 + %n)))<nsw> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {(%n + %base),+,-1}<nw><%loop> U: full-set S: full-set Exits: (1 + (0 smin (-1 + %n)) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {(%n + %base),+,-1}<nw><%loop>(u nuw) U: full-set S: full-set Exits: (1 + (0 smin (-1 + %n)) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {(%n + %base),+,-1}<nw><%loop>(u nuw) U: full-set S: full-set Exits: (1 + (0 smin (-1 + %n)) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {(%n + %base),+,-1}<nw><%loop> U: full-set S: full-set Exits: (1 + (0 smin (-1 + %n)) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = add nsw i64 %iv, -1
+; CHECK-NEXT:    --> {(-1 + %n),+,-1}<nw><%loop> U: full-set S: full-set Exits: (0 smin (-1 + %n)) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @loop_inbounds_neg_step
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + (-1 * (0 smin (-1 + %n))) + %n)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 9223372036854775807
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + (-1 * (0 smin (-1 + %n))) + %n)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %n, %entry ], [ %iv.next, %loop ]
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.nuw)
+  %gep.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.nusw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.plain)
+  %iv.next = add nsw i64 %iv, -1
+  %cmp = icmp sgt i64 %iv.next, 0
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+; Nested loop with inbounds and nuw GEPs at both levels.
+define void @nested_loop_inbounds_addrecs(ptr %base, i64 %n, i64 %m) {
+; CHECK-LABEL: 'nested_loop_inbounds_addrecs'
+; CHECK-NEXT:  Classifying expressions for: @nested_loop_inbounds_addrecs
+; CHECK-NEXT:    %iv.outer = phi i64 [ 0, %entry ], [ %iv.outer.next, %outer.latch ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%outer> U: full-set S: full-set Exits: (-1 + %n) LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:    %gep.outer = getelementptr inbounds i8, ptr %base, i64 %iv.outer
+; CHECK-NEXT:    --> {%base,+,1}<nw><%outer> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:    %gep.outer.nuw = getelementptr nuw i8, ptr %base, i64 %iv.outer
+; CHECK-NEXT:    --> {%base,+,1}<nw><%outer>(u nuw) U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:    %gep.outer.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv.outer
+; CHECK-NEXT:    --> {%base,+,1}<nw><%outer>(u nuw) U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:    %gep.outer.plain = getelementptr i8, ptr %base, i64 %iv.outer
+; CHECK-NEXT:    --> {%base,+,1}<nw><%outer> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:    %iv.inner = phi i64 [ 0, %outer ], [ %iv.inner.next, %inner ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%inner> U: full-set S: full-set Exits: (-1 + %m) LoopDispositions: { %inner: Computable, %outer: Uniform }
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %gep.outer, i64 %iv.inner
+; CHECK-NEXT:    --> {{\{\{}}%base,+,1}<nw><%outer>,+,1}<nw><%inner> U: full-set S: full-set Exits: {(-1 + %m + %base),+,1}<nw><%outer> LoopDispositions: { %inner: Computable, %outer: Variant }
+; CHECK-NEXT:    %gep.inner.nuw = getelementptr nuw i8, ptr %gep.outer.nuw, i64 %iv.inner
+; CHECK-NEXT:    --> {{\{\{}}%base,+,1}<nw><%outer>,+,1}<nw><%inner>(u nuw) U: full-set S: full-set Exits: {(-1 + %m + %base),+,1}<nw><%outer> LoopDispositions: { %inner: Computable, %outer: Variant }
+; CHECK-NEXT:    %gep.inner.nusw = getelementptr inbounds nuw i8, ptr %gep.outer.nuw, i64 %iv.inner
+; CHECK-NEXT:    --> {{\{\{}}%base,+,1}<nw><%outer>,+,1}<nw><%inner>(u nuw) U: full-set S: full-set Exits: {(-1 + %m + %base),+,1}<nw><%outer> LoopDispositions: { %inner: Computable, %outer: Variant }
+; CHECK-NEXT:    %gep.inner.plain = getelementptr i8, ptr %gep.outer, i64 %iv.inner
+; CHECK-NEXT:    --> {{\{\{}}%base,+,1}<nw><%outer>,+,1}<nw><%inner> U: full-set S: full-set Exits: {(-1 + %m + %base),+,1}<nw><%outer> LoopDispositions: { %inner: Computable, %outer: Variant }
+; CHECK-NEXT:    %iv.inner.next = add nuw i64 %iv.inner, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%inner> U: full-set S: full-set Exits: %m LoopDispositions: { %inner: Computable, %outer: Uniform }
+; CHECK-NEXT:    %iv.outer.next = add nuw i64 %iv.outer, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%outer> U: full-set S: full-set Exits: %n LoopDispositions: { %outer: Computable, %inner: Invariant }
+; CHECK-NEXT:  Determining loop execution counts for: @nested_loop_inbounds_addrecs
+; CHECK-NEXT:  Loop %inner: backedge-taken count is (-1 + %m)
+; CHECK-NEXT:  Loop %inner: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %inner: symbolic max backedge-taken count is (-1 + %m)
+; CHECK-NEXT:  Loop %inner: Trip multiple is 1
+; CHECK-NEXT:  Loop %outer: backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %outer: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %outer: symbolic max backedge-taken count is (-1 + %n)
+; CHECK-NEXT:  Loop %outer: Trip multiple is 1
+;
+entry:
+  br label %outer
+
+outer:
+  %iv.outer = phi i64 [ 0, %entry ], [ %iv.outer.next, %outer.latch ]
+  %gep.outer = getelementptr inbounds i8, ptr %base, i64 %iv.outer
+  call void @use(ptr %gep.outer)
+  %gep.outer.nuw = getelementptr nuw i8, ptr %base, i64 %iv.outer
+  call void @use(ptr %gep.outer.nuw)
+  %gep.outer.nusw = getelementptr inbounds nuw i8, ptr %base, i64 %iv.outer
+  call void @use(ptr %gep.outer.nusw)
+  %gep.outer.plain = getelementptr i8, ptr %base, i64 %iv.outer
+  call void @use(ptr %gep.outer.plain)
+  br label %inner
+
+inner:
+  %iv.inner = phi i64 [ 0, %outer ], [ %iv.inner.next, %inner ]
+  %gep.inbounds = getelementptr inbounds i8, ptr %gep.outer, i64 %iv.inner
+  call void @use(ptr %gep.inbounds)
+  %gep.inner.nuw = getelementptr nuw i8, ptr %gep.outer.nuw, i64 %iv.inner
+  call void @use(ptr %gep.inner.nuw)
+  %gep.inner.nusw = getelementptr inbounds nuw i8, ptr %gep.outer.nuw, i64 %iv.inner
+  call void @use(ptr %gep.inner.nusw)
+  %gep.inner.plain = getelementptr i8, ptr %gep.outer, i64 %iv.inner
+  call void @use(ptr %gep.inner.plain)
+  %iv.inner.next = add nuw i64 %iv.inner, 1
+  %cmp.inner = icmp ne i64 %iv.inner.next, %m
+  br i1 %cmp.inner, label %inner, label %outer.latch
+
+outer.latch:
+  %iv.outer.next = add nuw i64 %iv.outer, 1
+  %cmp.outer = icmp ne i64 %iv.outer.next, %n
+  br i1 %cmp.outer, label %outer, label %exit
+
+exit:
+  ret void
+}
+
+define void @loop_gep_nuw_iv(ptr %base, i64 %n) {
+; CHECK-LABEL: 'loop_gep_nuw_iv'
+; CHECK-NEXT:  Classifying expressions for: @loop_gep_nuw_iv
+; CHECK-NEXT:    %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop>(u nuw) U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.plain = getelementptr i8, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,1}<nw><%loop> U: full-set S: full-set Exits: (-1 + %n + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = add nuw i64 %iv, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%loop> U: full-set S: full-set Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @loop_gep_nuw_iv
+; 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 i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %gep.inbounds = getelementptr inbounds i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.inbounds)
+  %gep.nuw = getelementptr nuw i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.nuw)
+  %gep.plain = getelementptr i8, ptr %base, i64 %iv
+  call void @use(ptr %gep.plain)
+  %iv.next = add nuw i64 %iv, 1
+  %cmp = icmp ne i64 %iv.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+; TODO: Use specific flags not propagated through multiplies yet.
+define void @loop_gep_i32_nuw_iv(ptr %base, i64 %n) {
+; CHECK-LABEL: 'loop_gep_i32_nuw_iv'
+; CHECK-NEXT:  Classifying expressions for: @loop_gep_i32_nuw_iv
+; CHECK-NEXT:    %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + %n) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.nuw = getelementptr nuw i32, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,4}<%loop>(u nuw) U: full-set S: full-set Exits: (-4 + (4 * %n) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,4}<%loop> U: full-set S: full-set Exits: (-4 + (4 * %n) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.plain = getelementptr i32, ptr %base, i64 %iv
+; CHECK-NEXT:    --> {%base,+,4}<%loop> U: full-set S: full-set Exits: (-4 + (4 * %n) + %base) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %iv.next = add nuw i64 %iv, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%loop> U: full-set S: full-set Exits: %n LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @loop_gep_i32_nuw_iv
+; 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 i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %gep.nuw = getelementptr nuw i32, ptr %base, i64 %iv
+  call void @use(ptr %gep.nuw)
+  %gep.inbounds = getelementptr inbounds i32, ptr %base, i64 %iv
+  call void @use(ptr %gep.inbounds)
+  %gep.plain = getelementptr i32, ptr %base, i64 %iv
+  call void @use(ptr %gep.plain)
+  %iv.next = add nuw i64 %iv, 1
+  %cmp = icmp ne i64 %iv.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+define void @loop_invariant_flagged_gep(ptr %base, i32 %n, i64 %m) {
+; CHECK-LABEL: 'loop_invariant_flagged_gep'
+; CHECK-NEXT:  Classifying expressions for: @loop_invariant_flagged_gep
+; CHECK-NEXT:    %ext = zext i32 %n to i64
+; CHECK-NEXT:    --> (zext i32 %n to i64) U: [0,4294967296) S: [0,4294967296)
+; CHECK-NEXT:    %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><%loop> U: full-set S: full-set Exits: (-1 + %m) LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:    %gep.inv = getelementptr inbounds i8, ptr %base, i64 %ext
+; CHECK-NEXT:    --> ((zext i32 %n to i64) + %base)(u nuw) U: full-set S: full-set Exits: ((zext i32 %n to i64) + %base) LoopDispositions: { %loop: Invariant }
+; CHECK-NEXT:    %iv.next = add i64 %iv, 1
+; CHECK-NEXT:    --> {1,+,1}<nw><%loop> U: full-set S: full-set Exits: %m LoopDispositions: { %loop: Computable }
+; CHECK-NEXT:  Determining loop execution counts for: @loop_invariant_flagged_gep
+; CHECK-NEXT:  Loop %loop: backedge-taken count is (-1 + %m)
+; CHECK-NEXT:  Loop %loop: constant max backedge-taken count is i64 -1
+; CHECK-NEXT:  Loop %loop: symbolic max backedge-taken count is (-1 + %m)
+; CHECK-NEXT:  Loop %loop: Trip multiple is 1
+;
+entry:
+  %ext = zext i32 %n to i64
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %gep.inv = getelementptr inbounds i8, ptr %base, i64 %ext
+  call void @use(ptr %gep.inv)
+  %iv.next = add i64 %iv, 1
+  %cmp = icmp ne i64 %iv.next, %m
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index 4bd4463dda787..df265b64b21e6 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1520,7 +1520,7 @@ TEST_F(ScalarEvolutionsTest, MatchURem) {
   runWithSE(*M, "test", [&](Function &F, LoopInfo &LI, ScalarEvolution &SE) {
     for (auto *N : {"rem1", "rem2", "rem3", "rem5"}) {
       auto *URemI = getInstructionByName(F, N);
-      auto *S = SE.getSCEV(URemI);
+      SCEVUse S = SE.getSCEV(URemI);
       const SCEV *LHS, *RHS;
       EXPECT_TRUE(match(S, m_scev_URem(m_SCEV(LHS), m_SCEV(RHS), SE)));
       EXPECT_EQ(LHS, SE.getSCEV(URemI->getOperand(0)));
@@ -1533,7 +1533,7 @@ TEST_F(ScalarEvolutionsTest, MatchURem) {
     // match results are extended to the size of the input expression.
     auto *Ext = getInstructionByName(F, "ext");
     auto *URem1 = getInstructionByName(F, "rem4");
-    auto *S = SE.getSCEV(Ext);
+    SCEVUse S = SE.getSCEV(Ext);
     const SCEV *LHS, *RHS;
     EXPECT_TRUE(match(S, m_scev_URem(m_SCEV(LHS), m_SCEV(RHS), SE)));
     EXPECT_NE(LHS, SE.getSCEV(URem1->getOperand(0)));
@@ -1660,11 +1660,11 @@ TEST_F(ScalarEvolutionsTest, ForgetValueWithOverflowInst) {
     auto *ExtractValue = getInstructionByName(F, "extractvalue");
     auto *IV = getInstructionByName(F, "iv");
 
-    auto *ExtractValueScev = SE.getSCEV(ExtractValue);
+    SCEVUse ExtractValueScev = SE.getSCEV(ExtractValue);
     EXPECT_NE(ExtractValueScev, nullptr);
 
     SE.forgetValue(IV);
-    auto *ExtractValueScevForgotten = SE.getExistingSCEV(ExtractValue);
+    SCEVUse ExtractValueScevForgotten = SE.getExistingSCEV(ExtractValue);
     EXPECT_EQ(ExtractValueScevForgotten, nullptr);
   });
 }

>From a3a1c331e7e8729f76b8485615b8948d12bfb167 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 6 Apr 2026 14:28:04 +0100
Subject: [PATCH 3/3] [IndVarSimplify] Propagate SCEVUse flags through exit
 value expansion.

Propagate use-specific flags from SCEVUse (e.g. NUW from inbounds GEPs)
through evaluateAtIteration and getSCEVAtScope.

This adds a new SCEVAddRecExpr::evaluateAtIteration overload that takes
a SCEVUse and preserves use-specific no-wrap flags when the step is
non-negative. The LoopUtils exit value rewriting now requests
use-context-aware SCEVs, enabling SCEVExpander to emit GEPs with nuw
flags where the original code used inbounds GEPs.
---
 .../Analysis/ScalarEvolutionExpressions.h     |  5 ++++
 llvm/lib/Analysis/ScalarEvolution.cpp         | 25 ++++++++++++++++---
 llvm/lib/Transforms/Utils/LoopUtils.cpp       |  9 ++++---
 .../IndVarSimplify/exit-value-gep-inbounds.ll |  4 +--
 4 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h b/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
index f103a789716ca..216e22727d33a 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
@@ -414,6 +414,11 @@ class SCEVAddRecExpr : public SCEVNAryExpr {
   LLVM_ABI const SCEV *evaluateAtIteration(const SCEV *It,
                                            ScalarEvolution &SE) const;
 
+  /// Return the value of the chain of recurrences of \p AR at the specified
+  /// iteration number, using the use-specific flags if possible.
+  LLVM_ABI static SCEVUse evaluateAtIteration(SCEVUse AR, const SCEV *It,
+                                              ScalarEvolution &SE);
+
   /// Return the value of this chain of recurrences at the specified iteration
   /// number. Takes an explicit list of operands to represent an AddRec.
   LLVM_ABI static const SCEV *evaluateAtIteration(ArrayRef<SCEVUse> Operands,
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 82b265bccf7ce..c857169f6541e 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1077,6 +1077,20 @@ const SCEV *SCEVAddRecExpr::evaluateAtIteration(const SCEV *It,
   return evaluateAtIteration(operands(), It, SE);
 }
 
+SCEVUse SCEVAddRecExpr::evaluateAtIteration(SCEVUse ARU, const SCEV *It,
+                                            ScalarEvolution &SE) {
+  auto *AR = cast<SCEVAddRecExpr>(ARU);
+  const SCEV *Result = evaluateAtIteration(AR->operands(), It, SE);
+
+  // Preserve use-specific NUW if the closed-form result is an SCEVAddExpr. The
+  // flag is valid only valid if the start is also known to not wrap.
+  SCEVNoWrapFlags UseFlags = ARU.getUseNoWrapFlags();
+  if (UseFlags != SCEVNoWrapFlags::FlagAnyWrap && AR->isAffine() &&
+      isa<SCEVAddExpr>(Result) && isa<SCEVUnknown>(AR->getStart()))
+    return SE.getUseWithFlags(Result, UseFlags);
+  return Result;
+}
+
 const SCEV *SCEVAddRecExpr::evaluateAtIteration(ArrayRef<SCEVUse> Operands,
                                                 const SCEV *It,
                                                 ScalarEvolution &SE) {
@@ -3051,7 +3065,7 @@ SCEVUse ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
 
       // If all of the other operands were loop invariant, we are done.
       if (Ops.size() == 1)
-        return SCEVUse(NewRec, UseFlags);
+        return getUseWithFlags(NewRec, UseFlags);
 
       // Otherwise, add the folded AddRec by the non-invariant parts.
       for (unsigned i = 0;; ++i)
@@ -10361,8 +10375,13 @@ SCEVUse ScalarEvolution::computeSCEVAtScope(SCEVUse V, const Loop *L) {
       if (BackedgeTakenCount == getCouldNotCompute())
         return AddRec;
 
-      // Then, evaluate the AddRec.
-      return AddRec->evaluateAtIteration(BackedgeTakenCount, *this);
+      // Then, evaluate the AddRec. Preserve the use-specific flags from the
+      // original V if the AddRec was not folded above.
+      SCEVNoWrapFlags UseFlags = AddRec == V.getPointer()
+                                     ? V.getUseNoWrapFlags()
+                                     : SCEVNoWrapFlags::FlagAnyWrap;
+      return SCEVAddRecExpr::evaluateAtIteration(SCEVUse(AddRec, UseFlags),
+                                                 BackedgeTakenCount, *this);
     }
 
     return AddRec;
diff --git a/llvm/lib/Transforms/Utils/LoopUtils.cpp b/llvm/lib/Transforms/Utils/LoopUtils.cpp
index 39032f710eadb..1b24fe189f3d5 100644
--- a/llvm/lib/Transforms/Utils/LoopUtils.cpp
+++ b/llvm/lib/Transforms/Utils/LoopUtils.cpp
@@ -1733,11 +1733,11 @@ static bool hasHardUserWithinLoop(const Loop *L, const Instruction *I) {
 struct RewritePhi {
   PHINode *PN;               // For which PHI node is this replacement?
   unsigned Ith;              // For which incoming value?
-  const SCEV *ExpansionSCEV; // The SCEV of the incoming value we are rewriting.
+  SCEVUse ExpansionSCEV;     // The SCEV of the incoming value we are rewriting.
   Instruction *ExpansionPoint; // Where we'd like to expand that SCEV?
   bool HighCost;               // Is this expansion a high-cost?
 
-  RewritePhi(PHINode *P, unsigned I, const SCEV *Val, Instruction *ExpansionPt,
+  RewritePhi(PHINode *P, unsigned I, SCEVUse Val, Instruction *ExpansionPt,
              bool H)
       : PN(P), Ith(I), ExpansionSCEV(Val), ExpansionPoint(ExpansionPt),
         HighCost(H) {}
@@ -1908,7 +1908,8 @@ int llvm::rewriteLoopExitValues(Loop *L, LoopInfo *LI, TargetLibraryInfo *TLI,
         // expressions which are true for all exits (so as to maximize
         // expression reuse by the SCEVExpander), but resort to per-exit
         // evaluation if that fails.
-        const SCEV *ExitValue = SE->getSCEVAtScope(Inst, L->getParentLoop());
+        SCEVUse ExitValue = SE->getSCEVAtScope(
+            SE->getSCEV(Inst, /*UseCtx=*/true), L->getParentLoop());
         if (isa<SCEVCouldNotCompute>(ExitValue) ||
             !SE->isLoopInvariant(ExitValue, L) ||
             !Rewriter.isSafeToExpand(ExitValue)) {
@@ -1939,7 +1940,7 @@ int llvm::rewriteLoopExitValues(Loop *L, LoopInfo *LI, TargetLibraryInfo *TLI,
 
         // Check if expansions of this SCEV would count as being high cost.
         bool HighCost = Rewriter.isHighCostExpansion(
-            ExitValue, L, SCEVCheapExpansionBudget, TTI, Inst);
+            ExitValue.getPointer(), L, SCEVCheapExpansionBudget, TTI, Inst);
 
         // Note that we must not perform expansions until after
         // we query *all* the costs, because if we perform temporary expansion
diff --git a/llvm/test/Transforms/IndVarSimplify/exit-value-gep-inbounds.ll b/llvm/test/Transforms/IndVarSimplify/exit-value-gep-inbounds.ll
index 4d07c99a78b3e..218b7cab33c2c 100644
--- a/llvm/test/Transforms/IndVarSimplify/exit-value-gep-inbounds.ll
+++ b/llvm/test/Transforms/IndVarSimplify/exit-value-gep-inbounds.ll
@@ -10,7 +10,7 @@ define ptr @unknown_start_inbounds(ptr %p, i64 %n) {
 ; CHECK-NEXT:    br i1 true, label %[[EXIT:.*]], label %[[LOOP]]
 ; CHECK:       [[EXIT]]:
 ; CHECK-NEXT:    [[TMP0:%.*]] = add i64 [[N]], -1
-; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP0]]
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr nuw i8, ptr [[P]], i64 [[TMP0]]
 ; CHECK-NEXT:    ret ptr [[SCEVGEP]]
 ;
 entry:
@@ -38,7 +38,7 @@ define ptr @unknown_start_nonneg_step(ptr %p, i64 %n, i32 %s) {
 ; CHECK-NEXT:    [[STEP:%.*]] = zext i32 [[S]] to i64
 ; CHECK-NEXT:    [[TMP0:%.*]] = add i64 [[N]], -1
 ; CHECK-NEXT:    [[TMP1:%.*]] = mul i64 [[TMP0]], [[STEP]]
-; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr nuw i8, ptr [[P]], i64 [[TMP1]]
 ; CHECK-NEXT:    ret ptr [[SCEVGEP]]
 ;
 entry:



More information about the llvm-commits mailing list