[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