[llvm] [SCEV] Move NoWrapFlags definition outside SCEV scope, use for SCEVUse. (PR #190199)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 2 08:43:26 PDT 2026
https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/190199
The patch moves out of SCEV's scope so they can be re-used for SCEVUse. SCEVUse gets an additional getNoWrapFlags helper that returns the union of the expressions SCEV flags and the use-specific flags.
SCEVExpander has been updated to use this new helper.
In order to avoid other changes, the original names are exposed via constexpr in SCEV. Not sure if there's a nicer way. One alternative would be to define the enum in struct, and have SCEV inherit from it.
The patch also clarifies that the SCEVUse flags encode NUW/NSW, and hides getInt, setInt and getPointer to avoid potential mis-use
>From 4f24d28c08567500b87c3ff025495faa51f1c298 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 25 Mar 2026 11:13:15 +0000
Subject: [PATCH] [SCEV] Move NoWrapFlags definition outside SCEV scope, use
for SCEVUse.
The patch moves out of SCEV's scope so they can be re-used for SCEVUse.
SCEVUse gets an additional getNoWrapFlags helper that returns the union
of the expressions SCEV flags and the use-specific flags.
SCEVExpander has been updated to use this new helper.
In order to avoid other changes, the original names are exposed via
constexpr in SCEV. Not sure if there's a nicer way. One alternative
would be to define the enum in struct, and have SCEV inherit from it.
The patch also clarifies that the SCEVUse flags encode NUW/NSW, and
hides getInt, setInt and getPointer to avoid potential mis-use
---
llvm/include/llvm/Analysis/ScalarEvolution.h | 124 +++++++++++-------
.../Analysis/ScalarEvolutionExpressions.h | 14 ++
.../Utils/ScalarEvolutionExpander.cpp | 18 +--
3 files changed, 99 insertions(+), 57 deletions(-)
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 08c7e43e708b8..d529e29d2c096 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -66,6 +66,52 @@ enum SCEVTypes : unsigned short;
LLVM_ABI extern bool VerifySCEV;
+/// NoWrapFlags are bitfield indices into SCEV's SubclassData.
+///
+/// Add and Mul expressions may have no-unsigned-wrap <NUW> or
+/// no-signed-wrap <NSW> properties, which are derived from the IR
+/// operator. NSW is a misnomer that we use to mean no signed overflow or
+/// underflow.
+///
+/// AddRec expressions may have a no-self-wraparound <NW> property if, in
+/// the integer domain, abs(step) * max-iteration(loop) <=
+/// unsigned-max(bitwidth). This means that the recurrence will never reach
+/// its start value if the step is non-zero. Computing the same value on
+/// each iteration is not considered wrapping, and recurrences with step = 0
+/// are trivially <NW>. <NW> is independent of the sign of step and the
+/// value the add recurrence starts with.
+///
+/// Note that NUW and NSW are also valid properties of a recurrence, and
+/// either implies NW. For convenience, NW will be set for a recurrence
+/// whenever either NUW or NSW are set.
+///
+/// We require that the flag on a SCEV apply to the entire scope in which
+/// that SCEV is defined. A SCEV's scope is set of locations dominated by
+/// a defining location, which is in turn described by the following rules:
+/// * A SCEVUnknown is at the point of definition of the Value.
+/// * A SCEVConstant is defined at all points.
+/// * A SCEVAddRec is defined starting with the header of the associated
+/// loop.
+/// * All other SCEVs are defined at the earlest point all operands are
+/// defined.
+///
+/// The above rules describe a maximally hoisted form (without regards to
+/// potential control dependence). A SCEV is defined anywhere a
+/// corresponding instruction could be defined in said maximally hoisted
+/// form. Note that SCEVUDivExpr (currently the only expression type which
+/// can trap) can be defined per these rules in regions where it would trap
+/// at runtime. A SCEV being defined does not require the existence of any
+/// instruction within the defined scope.
+namespace SCEVWrap {
+enum NoWrapFlags {
+ FlagAnyWrap = 0, // No guarantee.
+ FlagNW = (1 << 0), // No self-wrap.
+ FlagNUW = (1 << 1), // No unsigned wrap.
+ FlagNSW = (1 << 2), // No signed wrap.
+ NoWrapMask = (1 << 3) - 1
+};
+} // namespace SCEVWrap
+
class SCEV;
template <typename SCEVPtrT = const SCEV *>
@@ -73,12 +119,13 @@ struct SCEVUseT : PointerIntPair<SCEVPtrT, 2> {
using Base = PointerIntPair<SCEVPtrT, 2>;
SCEVUseT() : Base() { Base::setFromOpaqueValue(nullptr); }
- SCEVUseT(SCEVPtrT S) : SCEVUseT(S, 0) {}
- SCEVUseT(SCEVPtrT S, unsigned Flags) : Base(S, Flags) {}
+ SCEVUseT(SCEVPtrT S) : Base(S, 0) {}
+ /// Construct with NoWrapFlags; only NUW/NSW are encoded, NW is dropped.
+ SCEVUseT(SCEVPtrT S, SCEVWrap::NoWrapFlags Flags) : Base(S, Flags >> 1) {}
template <typename OtherPtrT, typename = std::enable_if_t<
std::is_convertible_v<OtherPtrT, SCEVPtrT>>>
SCEVUseT(const SCEVUseT<OtherPtrT> &Other)
- : Base(Other.getPointer(), Other.getInt()) {}
+ : SCEVUseT(Other.getPointer(), Other.getUseNoWrapFlags()) {}
operator SCEVPtrT() const { return Base::getPointer(); }
SCEVPtrT operator->() const { return Base::getPointer(); }
@@ -92,7 +139,19 @@ struct SCEVUseT : PointerIntPair<SCEVPtrT, 2> {
/// Return the canonical SCEV for this SCEVUse.
const SCEV *getCanonical() const;
- unsigned getFlags() const { return Base::getInt(); }
+ /// Return the no-wrap flags for this SCEVUse, which is the union of the
+ /// use-specific flags and the underlying SCEV's flags, masked by \p Mask.
+ inline SCEVWrap::NoWrapFlags
+ getNoWrapFlags(SCEVWrap::NoWrapFlags Mask = SCEVWrap::NoWrapMask) const;
+
+ /// Return only the use-specific no-wrap flags (NUW/NSW) without the
+ /// underlying SCEV's flags.
+ SCEVWrap::NoWrapFlags getUseNoWrapFlags() const {
+ unsigned UseFlags = Base::getInt() << 1;
+ if (UseFlags & (SCEVWrap::FlagNUW | SCEVWrap::FlagNSW))
+ UseFlags |= SCEVWrap::FlagNW;
+ return SCEVWrap::NoWrapFlags(UseFlags);
+ }
bool operator==(const SCEVUseT &RHS) const {
return getRawPointer() == RHS.getRawPointer();
@@ -106,6 +165,11 @@ struct SCEVUseT : PointerIntPair<SCEVPtrT, 2> {
/// This method is used for debugging.
void dump() const;
+
+private:
+ using Base::getInt;
+ using Base::setInt;
+ using Base::setPointer;
};
/// Deduction guide for various SCEV subclass pointers.
@@ -165,7 +229,7 @@ struct CastInfo<SCEVUseT<ToSCEVPtrT>, SCEVUse,
static bool isPossible(const SCEVUse &U) { return isa<To>(U.getPointer()); }
static CastReturnType doCast(const SCEVUse &U) {
- return {cast<To>(U.getPointer()), U.getFlags()};
+ return CastReturnType(cast<To>(U.getPointer()), U.getUseNoWrapFlags());
}
static CastReturnType castFailed() { return CastReturnType(nullptr); }
static CastReturnType doCastIfPossible(const SCEVUse &U) {
@@ -206,49 +270,13 @@ class SCEV : public FoldingSetNode {
const SCEV *CanonicalSCEV = nullptr;
public:
- /// NoWrapFlags are bitfield indices into SubclassData.
- ///
- /// Add and Mul expressions may have no-unsigned-wrap <NUW> or
- /// no-signed-wrap <NSW> properties, which are derived from the IR
- /// operator. NSW is a misnomer that we use to mean no signed overflow or
- /// underflow.
- ///
- /// AddRec expressions may have a no-self-wraparound <NW> property if, in
- /// the integer domain, abs(step) * max-iteration(loop) <=
- /// unsigned-max(bitwidth). This means that the recurrence will never reach
- /// its start value if the step is non-zero. Computing the same value on
- /// each iteration is not considered wrapping, and recurrences with step = 0
- /// are trivially <NW>. <NW> is independent of the sign of step and the
- /// value the add recurrence starts with.
- ///
- /// Note that NUW and NSW are also valid properties of a recurrence, and
- /// either implies NW. For convenience, NW will be set for a recurrence
- /// whenever either NUW or NSW are set.
- ///
- /// We require that the flag on a SCEV apply to the entire scope in which
- /// that SCEV is defined. A SCEV's scope is set of locations dominated by
- /// a defining location, which is in turn described by the following rules:
- /// * A SCEVUnknown is at the point of definition of the Value.
- /// * A SCEVConstant is defined at all points.
- /// * A SCEVAddRec is defined starting with the header of the associated
- /// loop.
- /// * All other SCEVs are defined at the earlest point all operands are
- /// defined.
- ///
- /// The above rules describe a maximally hoisted form (without regards to
- /// potential control dependence). A SCEV is defined anywhere a
- /// corresponding instruction could be defined in said maximally hoisted
- /// form. Note that SCEVUDivExpr (currently the only expression type which
- /// can trap) can be defined per these rules in regions where it would trap
- /// at runtime. A SCEV being defined does not require the existence of any
- /// instruction within the defined scope.
- enum NoWrapFlags {
- FlagAnyWrap = 0, // No guarantee.
- FlagNW = (1 << 0), // No self-wrap.
- FlagNUW = (1 << 1), // No unsigned wrap.
- FlagNSW = (1 << 2), // No signed wrap.
- NoWrapMask = (1 << 3) - 1
- };
+ /// Expose SCEVWrap::NoWrapFlags as SCEV::NoWrapFlags.
+ using NoWrapFlags = SCEVWrap::NoWrapFlags;
+ static constexpr auto FlagAnyWrap = SCEVWrap::FlagAnyWrap;
+ static constexpr auto FlagNW = SCEVWrap::FlagNW;
+ static constexpr auto FlagNUW = SCEVWrap::FlagNUW;
+ static constexpr auto FlagNSW = SCEVWrap::FlagNSW;
+ static constexpr auto NoWrapMask = SCEVWrap::NoWrapMask;
explicit SCEV(const FoldingSetNodeIDRef ID, SCEVTypes SCEVTy,
unsigned short ExpressionSize)
diff --git a/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h b/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
index 2fc928d5955d1..828e29cd66d7b 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolutionExpressions.h
@@ -1046,6 +1046,20 @@ class SCEVLoopAddRecRewriter
LoopToScevMapT ⤅
};
+template <typename SCEVPtrT>
+inline SCEVWrap::NoWrapFlags
+SCEVUseT<SCEVPtrT>::getNoWrapFlags(SCEVWrap::NoWrapFlags Mask) const {
+ unsigned Flags = SCEV::FlagAnyWrap;
+ if (auto *NAry = dyn_cast<SCEVNAryExpr>(Base::getPointer()))
+ Flags = NAry->getNoWrapFlags();
+ // Use-flags only encode NUW/NSW in 2 bits; shift to align with NoWrapFlags.
+ unsigned UseFlags = Base::getInt() << 1;
+ // NUW or NSW implies NW.
+ if (UseFlags & (SCEVWrap::FlagNUW | SCEVWrap::FlagNSW))
+ UseFlags |= SCEVWrap::FlagNW;
+ return SCEVWrap::NoWrapFlags((Flags | UseFlags) & Mask);
+}
+
} // end namespace llvm
#endif // LLVM_ANALYSIS_SCALAREVOLUTIONEXPRESSIONS_H
diff --git a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
index 3509a204a3d4e..f83c73eec147d 100644
--- a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
+++ b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
@@ -573,7 +573,7 @@ Value *SCEVExpander::visitAddExpr(SCEVUseT<const SCEVAddExpr *> S) {
X = SE.getSCEV(U->getValue());
NewOps.push_back(X);
}
- Sum = expandAddToGEP(SE.getAddExpr(NewOps), Sum, S->getNoWrapFlags());
+ Sum = expandAddToGEP(SE.getAddExpr(NewOps), Sum, S.getNoWrapFlags());
} else if (Op->isNonConstantNegative()) {
// Instead of doing a negate and add, just do a subtract.
Value *W = expand(SE.getNegativeSCEV(Op));
@@ -586,7 +586,7 @@ Value *SCEVExpander::visitAddExpr(SCEVUseT<const SCEVAddExpr *> S) {
// Canonicalize a constant to the RHS.
if (isa<Constant>(Sum))
std::swap(Sum, W);
- Sum = InsertBinop(Instruction::Add, Sum, W, S->getNoWrapFlags(),
+ Sum = InsertBinop(Instruction::Add, Sum, W, S.getNoWrapFlags(),
/*IsSafeToHoist*/ true);
++I;
}
@@ -670,7 +670,7 @@ Value *SCEVExpander::visitMulExpr(SCEVUseT<const SCEVMulExpr *> S) {
if (match(W, m_Power2(RHS))) {
// Canonicalize Prod*(1<<C) to Prod<<C.
assert(!Ty->isVectorTy() && "vector types are not SCEVable");
- auto NWFlags = S->getNoWrapFlags();
+ auto NWFlags = S.getNoWrapFlags();
// clear nsw flag if shl will produce poison value.
if (RHS->logBase2() == RHS->getBitWidth() - 1)
NWFlags = ScalarEvolution::clearFlags(NWFlags, SCEV::FlagNSW);
@@ -678,7 +678,7 @@ Value *SCEVExpander::visitMulExpr(SCEVUseT<const SCEVMulExpr *> S) {
ConstantInt::get(Ty, RHS->logBase2()), NWFlags,
/*IsSafeToHoist*/ true);
} else {
- Prod = InsertBinop(Instruction::Mul, Prod, W, S->getNoWrapFlags(),
+ Prod = InsertBinop(Instruction::Mul, Prod, W, S.getNoWrapFlags(),
/*IsSafeToHoist*/ true);
}
}
@@ -1340,8 +1340,8 @@ Value *SCEVExpander::visitAddRecExpr(SCEVUseT<const SCEVAddRecExpr *> S) {
SmallVector<SCEVUse, 4> NewOps(S->getNumOperands());
for (unsigned i = 0, e = S->getNumOperands(); i != e; ++i)
NewOps[i] = SE.getAnyExtendExpr(S->getOperand(i), CanonicalIV->getType());
- Value *V = expand(SE.getAddRecExpr(NewOps, S->getLoop(),
- S->getNoWrapFlags(SCEV::FlagNW)));
+ Value *V = expand(
+ SE.getAddRecExpr(NewOps, S->getLoop(), S.getNoWrapFlags(SCEV::FlagNW)));
BasicBlock::iterator NewInsertPt =
findInsertPointAfter(cast<Instruction>(V), &*Builder.GetInsertPoint());
V = expand(SE.getTruncateExpr(SE.getUnknown(V), Ty), NewInsertPt);
@@ -1358,13 +1358,13 @@ Value *SCEVExpander::visitAddRecExpr(SCEVUseT<const SCEVAddRecExpr *> S) {
if (isa<PointerType>(S->getType())) {
Value *StartV = expand(SE.getPointerBase(S));
return expandAddToGEP(SE.removePointerBase(S), StartV,
- S->getNoWrapFlags(SCEV::FlagNUW));
+ S.getNoWrapFlags(SCEV::FlagNUW));
}
SmallVector<SCEVUse, 4> NewOps(S->operands());
NewOps[0] = SE.getConstant(Ty, 0);
- const SCEV *Rest = SE.getAddRecExpr(NewOps, L,
- S->getNoWrapFlags(SCEV::FlagNW));
+ const SCEV *Rest =
+ SE.getAddRecExpr(NewOps, L, S.getNoWrapFlags(SCEV::FlagNW));
// Just do a normal add. Pre-expand the operands to suppress folding.
//
More information about the llvm-commits
mailing list