[llvm] [ConstantRange] Compute exact no-wrap region w/o materializing CR. (PR #223969)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 02:08:49 PDT 2026


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

>From 994cb60b0573201770a24c6489eeb3071985aa46 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 14 Sep 2026 09:50:14 +0100
Subject: [PATCH 1/2] [ConstantRange] Compute exact no-wrap regions without
 materializing a range.

makeExactNoWrapRegion() forwarded to makeGuaranteedNoWrapRegion() with a
single-element ConstantRange. That temporary CR just to query the bounds
comes with some overhead in terms of compile-time.

Together with using makeExecuteNoWrapRegion in SCEV, this improves
compile-time on SCEV-heavy workloads.

stage1-O3: -0.08%
stage1-ReleaseThinLTO: -0.07%
stage1-ReleaseLTO-g: -0.08%
stage1-aarch64-O3: -0.08%
stage2-O3: -0.07%

Note that LLVM_ATTRIBUTE_ALWAYS_INLINE is needed for some version on GCC
(including the one on compile-time-tracker), because without it GCC no
longer inlines (as there are now 2 call sites I think), causing a slight
regression on mafft with just this patch.
---
 llvm/lib/IR/ConstantRange.cpp           | 101 ++++++++++++++++--------
 llvm/unittests/IR/ConstantRangeTest.cpp |   3 +
 2 files changed, 72 insertions(+), 32 deletions(-)

diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index bf6be068591c6..75c25d20da12d 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -295,7 +295,8 @@ bool ConstantRange::icmp(CmpInst::Predicate Pred,
 }
 
 /// Exact mul nuw region for single element RHS.
-static ConstantRange makeExactMulNUWRegion(const APInt &V) {
+LLVM_ATTRIBUTE_ALWAYS_INLINE static ConstantRange
+makeExactMulNUWRegion(const APInt &V) {
   unsigned BitWidth = V.getBitWidth();
   if (V == 0)
     return ConstantRange::getFull(V.getBitWidth());
@@ -331,20 +332,39 @@ static ConstantRange makeExactMulNSWRegion(const APInt &V) {
   return ConstantRange::getNonEmpty(Lower, Upper + 1);
 }
 
-ConstantRange
-ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
-                                          const ConstantRange &Other,
-                                          unsigned NoWrapKind) {
+namespace {
+/// Wrapper providing a ConstantRange-like API for an APInt.
+struct SingleElementBounds {
+  const APInt &C;
+
+  SingleElementBounds(const APInt &C) : C(C) {}
+
+  unsigned getBitWidth() const { return C.getBitWidth(); }
+  const APInt &getSignedMin() const { return C; }
+  const APInt &getSignedMax() const { return C; }
+  const APInt &getUnsignedMax() const { return C; }
+  const APInt *getSingleElement() const { return &C; }
+  ConstantRange intersectWith(const ConstantRange &CR) const {
+    return ConstantRange(C).intersectWith(CR);
+  }
+};
+} // end anonymous namespace
+
+/// No-wrap region for \p BinOp, where \p RHS is a ConstantRange or a
+/// SingleElementBounds.
+template <typename RHSTy>
+LLVM_ATTRIBUTE_ALWAYS_INLINE static ConstantRange
+makeNoWrapRegion(Instruction::BinaryOps BinOp, const RHSTy &RHS,
+                 unsigned NoWrapKind) {
   using OBO = OverflowingBinaryOperator;
 
   assert(Instruction::isBinaryOp(BinOp) && "Binary operators only!");
-
   assert((NoWrapKind == OBO::NoSignedWrap ||
           NoWrapKind == OBO::NoUnsignedWrap) &&
          "NoWrapKind invalid!");
 
   bool Unsigned = NoWrapKind == OBO::NoUnsignedWrap;
-  unsigned BitWidth = Other.getBitWidth();
+  unsigned BitWidth = RHS.getBitWidth();
 
   switch (BinOp) {
   default:
@@ -352,66 +372,83 @@ ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
 
   case Instruction::Add: {
     if (Unsigned)
-      return getNonEmpty(APInt::getZero(BitWidth), -Other.getUnsignedMax());
-
-    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
-    APInt SMin = Other.getSignedMin(), SMax = Other.getSignedMax();
-    return getNonEmpty(
-        SMin.isNegative() ? SignedMinVal - SMin : SignedMinVal,
-        SMax.isStrictlyPositive() ? SignedMinVal - SMax : SignedMinVal);
+      return ConstantRange::getNonEmpty(APInt::getZero(BitWidth),
+                                        -RHS.getUnsignedMax());
+
+    const APInt &SMin = RHS.getSignedMin(), &SMax = RHS.getSignedMax();
+    APInt Lower = APInt::getSignedMinValue(BitWidth);
+    APInt Upper = APInt::getSignedMinValue(BitWidth);
+    if (SMin.isNegative())
+      Lower -= SMin;
+    if (SMax.isStrictlyPositive())
+      Upper -= SMax;
+    return ConstantRange::getNonEmpty(std::move(Lower), std::move(Upper));
   }
 
   case Instruction::Sub: {
     if (Unsigned)
-      return getNonEmpty(Other.getUnsignedMax(), APInt::getMinValue(BitWidth));
-
-    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
-    APInt SMin = Other.getSignedMin(), SMax = Other.getSignedMax();
-    return getNonEmpty(
-        SMax.isStrictlyPositive() ? SignedMinVal + SMax : SignedMinVal,
-        SMin.isNegative() ? SignedMinVal + SMin : SignedMinVal);
+      return ConstantRange::getNonEmpty(RHS.getUnsignedMax(),
+                                        APInt::getMinValue(BitWidth));
+
+    const APInt &SMin = RHS.getSignedMin(), &SMax = RHS.getSignedMax();
+    APInt Lower = APInt::getSignedMinValue(BitWidth);
+    APInt Upper = APInt::getSignedMinValue(BitWidth);
+    if (SMax.isStrictlyPositive())
+      Lower += SMax;
+    if (SMin.isNegative())
+      Upper += SMin;
+    return ConstantRange::getNonEmpty(std::move(Lower), std::move(Upper));
   }
 
   case Instruction::Mul:
     if (Unsigned)
-      return makeExactMulNUWRegion(Other.getUnsignedMax());
+      return makeExactMulNUWRegion(RHS.getUnsignedMax());
 
     // Avoid one makeExactMulNSWRegion() call for the common case of constants.
-    if (const APInt *C = Other.getSingleElement())
+    if (const APInt *C = RHS.getSingleElement())
       return makeExactMulNSWRegion(*C);
 
-    return makeExactMulNSWRegion(Other.getSignedMin())
-        .intersectWith(makeExactMulNSWRegion(Other.getSignedMax()));
+    return makeExactMulNSWRegion(RHS.getSignedMin())
+        .intersectWith(makeExactMulNSWRegion(RHS.getSignedMax()));
 
   case Instruction::Shl: {
     // For given range of shift amounts, if we ignore all illegal shift amounts
     // (that always produce poison), what shift amount range is left?
-    ConstantRange ShAmt = Other.intersectWith(
+    ConstantRange ShAmt = RHS.intersectWith(
         ConstantRange(APInt(BitWidth, 0), APInt(BitWidth, (BitWidth - 1) + 1)));
     if (ShAmt.isEmptySet()) {
       // If the entire range of shift amounts is already poison-producing,
       // then we can freely add more poison-producing flags ontop of that.
-      return getFull(BitWidth);
+      return ConstantRange::getFull(BitWidth);
     }
     // There are some legal shift amounts, we can compute conservatively-correct
     // range of no-wrap inputs. Note that by now we have clamped the ShAmtUMax
     // to be at most bitwidth-1, which results in most conservative range.
     APInt ShAmtUMax = ShAmt.getUnsignedMax();
     if (Unsigned)
-      return getNonEmpty(APInt::getZero(BitWidth),
-                         APInt::getMaxValue(BitWidth).lshr(ShAmtUMax) + 1);
-    return getNonEmpty(APInt::getSignedMinValue(BitWidth).ashr(ShAmtUMax),
-                       APInt::getSignedMaxValue(BitWidth).ashr(ShAmtUMax) + 1);
+      return ConstantRange::getNonEmpty(
+          APInt::getZero(BitWidth),
+          APInt::getMaxValue(BitWidth).lshr(ShAmtUMax) + 1);
+    return ConstantRange::getNonEmpty(
+        APInt::getSignedMinValue(BitWidth).ashr(ShAmtUMax),
+        APInt::getSignedMaxValue(BitWidth).ashr(ShAmtUMax) + 1);
   }
   }
 }
 
+ConstantRange
+ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
+                                          const ConstantRange &Other,
+                                          unsigned NoWrapKind) {
+  return makeNoWrapRegion(BinOp, Other, NoWrapKind);
+}
+
 ConstantRange ConstantRange::makeExactNoWrapRegion(Instruction::BinaryOps BinOp,
                                                    const APInt &Other,
                                                    unsigned NoWrapKind) {
   // makeGuaranteedNoWrapRegion() is exact for single-element ranges, as
   // "for all" and "for any" coincide in this case.
-  return makeGuaranteedNoWrapRegion(BinOp, ConstantRange(Other), NoWrapKind);
+  return makeNoWrapRegion(BinOp, SingleElementBounds(Other), NoWrapKind);
 }
 
 ConstantRange ConstantRange::makeMaskNotEqualRange(const APInt &Mask,
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 57d3e52b3f206..86da83b8d82b2 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2101,6 +2101,9 @@ void TestNoWrapRegionExhaustive(Instruction::BinaryOps BinOp,
 
       ConstantRange NoWrap =
           ConstantRange::makeGuaranteedNoWrapRegion(BinOp, CR, NoWrapKind);
+      if (const APInt *C = CR.getSingleElement())
+        EXPECT_EQ(NoWrap,
+                  ConstantRange::makeExactNoWrapRegion(BinOp, *C, NoWrapKind));
       EnumerateAPInts(Bits, [&](const APInt &N1) {
         bool NoOverflow = true;
         bool Overflow = true;

>From 491d5e6673b826877a893d0f3fbe481f635286e1 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 28 Sep 2026 10:03:31 +0100
Subject: [PATCH 2/2] !fixup inline add, sub, mul, shl support

---
 llvm/lib/IR/ConstantRange.cpp | 144 +++++++++++++++++-----------------
 1 file changed, 73 insertions(+), 71 deletions(-)

diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 75c25d20da12d..d9bf19f1d026c 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -295,8 +295,7 @@ bool ConstantRange::icmp(CmpInst::Predicate Pred,
 }
 
 /// Exact mul nuw region for single element RHS.
-LLVM_ATTRIBUTE_ALWAYS_INLINE static ConstantRange
-makeExactMulNUWRegion(const APInt &V) {
+static ConstantRange makeExactMulNUWRegion(const APInt &V) {
   unsigned BitWidth = V.getBitWidth();
   if (V == 0)
     return ConstantRange::getFull(V.getBitWidth());
@@ -332,39 +331,20 @@ static ConstantRange makeExactMulNSWRegion(const APInt &V) {
   return ConstantRange::getNonEmpty(Lower, Upper + 1);
 }
 
-namespace {
-/// Wrapper providing a ConstantRange-like API for an APInt.
-struct SingleElementBounds {
-  const APInt &C;
-
-  SingleElementBounds(const APInt &C) : C(C) {}
-
-  unsigned getBitWidth() const { return C.getBitWidth(); }
-  const APInt &getSignedMin() const { return C; }
-  const APInt &getSignedMax() const { return C; }
-  const APInt &getUnsignedMax() const { return C; }
-  const APInt *getSingleElement() const { return &C; }
-  ConstantRange intersectWith(const ConstantRange &CR) const {
-    return ConstantRange(C).intersectWith(CR);
-  }
-};
-} // end anonymous namespace
-
-/// No-wrap region for \p BinOp, where \p RHS is a ConstantRange or a
-/// SingleElementBounds.
-template <typename RHSTy>
-LLVM_ATTRIBUTE_ALWAYS_INLINE static ConstantRange
-makeNoWrapRegion(Instruction::BinaryOps BinOp, const RHSTy &RHS,
-                 unsigned NoWrapKind) {
+ConstantRange
+ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
+                                          const ConstantRange &Other,
+                                          unsigned NoWrapKind) {
   using OBO = OverflowingBinaryOperator;
 
   assert(Instruction::isBinaryOp(BinOp) && "Binary operators only!");
+
   assert((NoWrapKind == OBO::NoSignedWrap ||
           NoWrapKind == OBO::NoUnsignedWrap) &&
          "NoWrapKind invalid!");
 
   bool Unsigned = NoWrapKind == OBO::NoUnsignedWrap;
-  unsigned BitWidth = RHS.getBitWidth();
+  unsigned BitWidth = Other.getBitWidth();
 
   switch (BinOp) {
   default:
@@ -372,83 +352,105 @@ makeNoWrapRegion(Instruction::BinaryOps BinOp, const RHSTy &RHS,
 
   case Instruction::Add: {
     if (Unsigned)
-      return ConstantRange::getNonEmpty(APInt::getZero(BitWidth),
-                                        -RHS.getUnsignedMax());
-
-    const APInt &SMin = RHS.getSignedMin(), &SMax = RHS.getSignedMax();
-    APInt Lower = APInt::getSignedMinValue(BitWidth);
-    APInt Upper = APInt::getSignedMinValue(BitWidth);
-    if (SMin.isNegative())
-      Lower -= SMin;
-    if (SMax.isStrictlyPositive())
-      Upper -= SMax;
-    return ConstantRange::getNonEmpty(std::move(Lower), std::move(Upper));
+      return getNonEmpty(APInt::getZero(BitWidth), -Other.getUnsignedMax());
+
+    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
+    APInt SMin = Other.getSignedMin(), SMax = Other.getSignedMax();
+    return getNonEmpty(
+        SMin.isNegative() ? SignedMinVal - SMin : SignedMinVal,
+        SMax.isStrictlyPositive() ? SignedMinVal - SMax : SignedMinVal);
   }
 
   case Instruction::Sub: {
     if (Unsigned)
-      return ConstantRange::getNonEmpty(RHS.getUnsignedMax(),
-                                        APInt::getMinValue(BitWidth));
-
-    const APInt &SMin = RHS.getSignedMin(), &SMax = RHS.getSignedMax();
-    APInt Lower = APInt::getSignedMinValue(BitWidth);
-    APInt Upper = APInt::getSignedMinValue(BitWidth);
-    if (SMax.isStrictlyPositive())
-      Lower += SMax;
-    if (SMin.isNegative())
-      Upper += SMin;
-    return ConstantRange::getNonEmpty(std::move(Lower), std::move(Upper));
+      return getNonEmpty(Other.getUnsignedMax(), APInt::getMinValue(BitWidth));
+
+    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
+    APInt SMin = Other.getSignedMin(), SMax = Other.getSignedMax();
+    return getNonEmpty(
+        SMax.isStrictlyPositive() ? SignedMinVal + SMax : SignedMinVal,
+        SMin.isNegative() ? SignedMinVal + SMin : SignedMinVal);
   }
 
   case Instruction::Mul:
     if (Unsigned)
-      return makeExactMulNUWRegion(RHS.getUnsignedMax());
+      return makeExactMulNUWRegion(Other.getUnsignedMax());
 
     // Avoid one makeExactMulNSWRegion() call for the common case of constants.
-    if (const APInt *C = RHS.getSingleElement())
+    if (const APInt *C = Other.getSingleElement())
       return makeExactMulNSWRegion(*C);
 
-    return makeExactMulNSWRegion(RHS.getSignedMin())
-        .intersectWith(makeExactMulNSWRegion(RHS.getSignedMax()));
+    return makeExactMulNSWRegion(Other.getSignedMin())
+        .intersectWith(makeExactMulNSWRegion(Other.getSignedMax()));
 
   case Instruction::Shl: {
     // For given range of shift amounts, if we ignore all illegal shift amounts
     // (that always produce poison), what shift amount range is left?
-    ConstantRange ShAmt = RHS.intersectWith(
+    ConstantRange ShAmt = Other.intersectWith(
         ConstantRange(APInt(BitWidth, 0), APInt(BitWidth, (BitWidth - 1) + 1)));
     if (ShAmt.isEmptySet()) {
       // If the entire range of shift amounts is already poison-producing,
       // then we can freely add more poison-producing flags ontop of that.
-      return ConstantRange::getFull(BitWidth);
+      return getFull(BitWidth);
     }
     // There are some legal shift amounts, we can compute conservatively-correct
     // range of no-wrap inputs. Note that by now we have clamped the ShAmtUMax
     // to be at most bitwidth-1, which results in most conservative range.
     APInt ShAmtUMax = ShAmt.getUnsignedMax();
     if (Unsigned)
-      return ConstantRange::getNonEmpty(
-          APInt::getZero(BitWidth),
-          APInt::getMaxValue(BitWidth).lshr(ShAmtUMax) + 1);
-    return ConstantRange::getNonEmpty(
-        APInt::getSignedMinValue(BitWidth).ashr(ShAmtUMax),
-        APInt::getSignedMaxValue(BitWidth).ashr(ShAmtUMax) + 1);
+      return getNonEmpty(APInt::getZero(BitWidth),
+                         APInt::getMaxValue(BitWidth).lshr(ShAmtUMax) + 1);
+    return getNonEmpty(APInt::getSignedMinValue(BitWidth).ashr(ShAmtUMax),
+                       APInt::getSignedMaxValue(BitWidth).ashr(ShAmtUMax) + 1);
   }
   }
 }
 
-ConstantRange
-ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
-                                          const ConstantRange &Other,
-                                          unsigned NoWrapKind) {
-  return makeNoWrapRegion(BinOp, Other, NoWrapKind);
-}
-
 ConstantRange ConstantRange::makeExactNoWrapRegion(Instruction::BinaryOps BinOp,
                                                    const APInt &Other,
                                                    unsigned NoWrapKind) {
-  // makeGuaranteedNoWrapRegion() is exact for single-element ranges, as
-  // "for all" and "for any" coincide in this case.
-  return makeNoWrapRegion(BinOp, SingleElementBounds(Other), NoWrapKind);
+  using OBO = OverflowingBinaryOperator;
+
+  assert(
+      (NoWrapKind == OBO::NoSignedWrap || NoWrapKind == OBO::NoUnsignedWrap) &&
+      "NoWrapKind invalid!");
+
+  bool Unsigned = NoWrapKind == OBO::NoUnsignedWrap;
+  unsigned BitWidth = Other.getBitWidth();
+  switch (BinOp) {
+  case Instruction::Add: {
+    if (Unsigned)
+      return getNonEmpty(APInt::getZero(BitWidth), -Other);
+    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
+    return Other.isNegative() ? getNonEmpty(SignedMinVal - Other, SignedMinVal)
+                              : getNonEmpty(SignedMinVal, SignedMinVal - Other);
+  }
+
+  case Instruction::Sub: {
+    if (Unsigned)
+      return getNonEmpty(Other, APInt::getZero(BitWidth));
+    APInt SignedMinVal = APInt::getSignedMinValue(BitWidth);
+    return Other.isNegative() ? getNonEmpty(SignedMinVal, SignedMinVal + Other)
+                              : getNonEmpty(SignedMinVal + Other, SignedMinVal);
+  }
+
+  case Instruction::Mul:
+    return Unsigned ? makeExactMulNUWRegion(Other)
+                    : makeExactMulNSWRegion(Other);
+
+  case Instruction::Shl:
+    // Shift amounts >= BitWidth always produce poison.
+    if (Other.uge(BitWidth))
+      return getFull(BitWidth);
+    if (Unsigned)
+      return getNonEmpty(APInt::getZero(BitWidth),
+                         APInt::getMaxValue(BitWidth).lshr(Other) + 1);
+    return getNonEmpty(APInt::getSignedMinValue(BitWidth).ashr(Other),
+                       APInt::getSignedMaxValue(BitWidth).ashr(Other) + 1);
+
+  default:
+    llvm_unreachable("Unsupported binary op");
+  }
 }
 
 ConstantRange ConstantRange::makeMaskNotEqualRange(const APInt &Mask,



More information about the llvm-commits mailing list