[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