[Mlir-commits] [mlir] 35bf6e7 - [mlir] Improve signed div/rem integer range inference (#238124)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue Nov 3 00:23:11 PST 2026


Author: Ivan Butygin
Date: 2026-11-03T11:23:06+03:00
New Revision: 35bf6e7d2acbf8be1b5c857513c6f3cdf4caba92

URL: https://github.com/llvm/llvm-project/commit/35bf6e7d2acbf8be1b5c857513c6f3cdf4caba92
DIFF: https://github.com/llvm/llvm-project/commit/35bf6e7d2acbf8be1b5c857513c6f3cdf4caba92.diff

LOG: [mlir] Improve signed div/rem integer range inference (#238124)

div/rem by zero is poison so instead of bailing on zero-crossing ranges
union final range from the strictly positive and strictly negative
subranges.

AI-assisted, obviously.

Added: 
    

Modified: 
    mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
    mlir/test/Dialect/Arith/int-range-interface.mlir
    mlir/unittests/Interfaces/CMakeLists.txt
    mlir/unittests/Interfaces/InferIntRangeInterfaceTest.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp b/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
index 7f07680e5d35cd..681e786cd52843 100644
--- a/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
+++ b/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
@@ -346,8 +346,10 @@ static ConstantIntRanges inferDivSRange(const ConstantIntRanges &lhs,
               &rhsMax = rhs.smax();
   // An empty (min > max) divisor range can arise from poison, e.g. a
   // saturated `nsw`/`nuw` subtraction. Don't trust its bounds.
-  bool canDivide = rhsMin.sle(rhsMax) &&
-                   (rhsMin.isStrictlyPositive() || rhsMax.isNegative());
+  unsigned width = rhsMin.getBitWidth();
+  if (rhsMin.sgt(rhsMax))
+    return ConstantIntRanges::maxRange(width);
+  bool canDivide = rhsMin.isStrictlyPositive() || rhsMax.isNegative();
 
   if (canDivide) {
     auto sdiv = [&fixup](const APInt &a,
@@ -359,7 +361,18 @@ static ConstantIntRanges inferDivSRange(const ConstantIntRanges &lhs,
     return minMaxBy(sdiv, {lhsMin, lhsMax}, {rhsMin, rhsMax},
                     /*isSigned=*/true);
   }
-  return ConstantIntRanges::maxRange(rhsMin.getBitWidth());
+  // Zero divisors produce poison. Bound each nonzero interval separately.
+  std::optional<ConstantIntRanges> result;
+  if (rhsMin.isNegative())
+    result = inferDivSRange(
+        lhs, ConstantIntRanges::fromSigned(rhsMin, APInt::getAllOnes(width)),
+        fixup);
+  if (rhsMax.isStrictlyPositive()) {
+    ConstantIntRanges positive = inferDivSRange(
+        lhs, ConstantIntRanges::fromSigned(APInt(width, 1), rhsMax), fixup);
+    result = result ? result->rangeUnion(positive) : positive;
+  }
+  return result.value_or(ConstantIntRanges::maxRange(width));
 }
 
 ConstantIntRanges
@@ -400,6 +413,12 @@ mlir::intrange::inferCeilDivS(ArrayRef<ConstantIntRanges> argRanges) {
     auto newLhs = ConstantIntRanges::fromSigned(lhs.smin() + 1, lhs.smax());
     result = result.rangeUnion(inferDivSRange(newLhs, rhs, ceilDivSIFix));
   }
+  if (lhs.smin().isMinSignedValue() && rhs.smax().sgt(1) && rhs.smin().sle(2)) {
+    // The sign changes between divisors 1 and 2. Include the positive peak.
+    APInt two(lhs.smin().getBitWidth(), 2);
+    result =
+        result.rangeUnion(ConstantIntRanges::constant(-lhs.smin().sdiv(two)));
+  }
   return result;
 }
 
@@ -431,28 +450,26 @@ mlir::intrange::inferRemS(ArrayRef<ConstantIntRanges> argRanges) {
               &rhsMax = rhs.smax();
 
   unsigned width = rhsMax.getBitWidth();
-  APInt smin = APInt::getSignedMinValue(width);
-  APInt smax = APInt::getSignedMaxValue(width);
-  // No bounds if zero could be a divisor.
-  bool canBound = (rhsMin.isStrictlyPositive() || rhsMax.isNegative());
-  if (canBound) {
-    APInt maxDivisor = rhsMin.isStrictlyPositive() ? rhsMax : rhsMin.abs();
-    bool canNegativeDividend = lhsMin.isNegative();
-    bool canPositiveDividend = lhsMax.isStrictlyPositive();
-    APInt zero = APInt::getZero(maxDivisor.getBitWidth());
-    APInt maxPositiveResult = maxDivisor - 1;
-    APInt minNegativeResult = -maxPositiveResult;
-    smin = canNegativeDividend ? minNegativeResult : zero;
-    smax = canPositiveDividend ? maxPositiveResult : zero;
-    // Special case: sweeping out a contiguous range in N/[modulus].
-    if (rhsMin == rhsMax) {
-      if ((lhsMax - lhsMin).ult(maxDivisor)) {
-        APInt minRem = lhsMin.srem(maxDivisor);
-        APInt maxRem = lhsMax.srem(maxDivisor);
-        if (minRem.sle(maxRem)) {
-          smin = std::move(minRem);
-          smax = std::move(maxRem);
-        }
+  if (rhsMin.sgt(rhsMax) || (rhsMin.isZero() && rhsMax.isZero()))
+    return ConstantIntRanges::maxRange(width);
+
+  // Magnitudes are unsigned: abs(INT_MIN) is not a positive signed value.
+  APInt maxDivisor = llvm::APIntOps::umax(rhsMin.abs(), rhsMax.abs());
+  APInt zero = APInt::getZero(width);
+  APInt maxPositiveResult = maxDivisor - 1;
+  // Remainder has the dividend's sign and cannot exceed its magnitude.
+  APInt smin = llvm::APIntOps::smax(llvm::APIntOps::smin(lhsMin, zero),
+                                    -maxPositiveResult);
+  APInt smax = llvm::APIntOps::smin(llvm::APIntOps::smax(lhsMax, zero),
+                                    maxPositiveResult);
+  // Special case: sweeping out a contiguous range in N/[modulus].
+  if (rhsMin == rhsMax) {
+    if ((lhsMax - lhsMin).ult(maxDivisor)) {
+      APInt minRem = lhsMin.srem(maxDivisor);
+      APInt maxRem = lhsMax.srem(maxDivisor);
+      if (minRem.sle(maxRem)) {
+        smin = std::move(minRem);
+        smax = std::move(maxRem);
       }
     }
   }

diff  --git a/mlir/test/Dialect/Arith/int-range-interface.mlir b/mlir/test/Dialect/Arith/int-range-interface.mlir
index e2f8ac72d56e9a..e5aac380fc21cb 100644
--- a/mlir/test/Dialect/Arith/int-range-interface.mlir
+++ b/mlir/test/Dialect/Arith/int-range-interface.mlir
@@ -1060,3 +1060,33 @@ func.func @noninteger_operation_result(%lb: index, %ub: index, %step: index, %co
   "use"(%result, %outs#1) : (i32, f32) -> ()
   return
 }
+
+
+// A zero divisor produces poison; it must not hide nonnegative result bounds.
+// CHECK-LABEL: func @signed_div_rem_nonnegative
+// CHECK: %[[TRUE:.*]] = arith.constant true
+// CHECK: return %[[TRUE]], %[[TRUE]]
+func.func @signed_div_rem_nonnegative(%a: i32, %b: i32) -> (i1, i1) {
+  %zero = arith.constant 0 : i32
+  %lhs = arith.maxsi %a, %zero : i32
+  %rhs = arith.maxsi %b, %zero : i32
+  %div = arith.divsi %lhs, %rhs : i32
+  %rem = arith.remsi %lhs, %rhs : i32
+  %div_nonnegative = arith.cmpi sge, %div, %zero : i32
+  %rem_nonnegative = arith.cmpi sge, %rem, %zero : i32
+  return %div_nonnegative, %rem_nonnegative : i1, i1
+}
+
+// Remainder magnitude is bounded by the dividend even with an unknown divisor.
+// CHECK-LABEL: func @signed_rem_small_dividend
+// CHECK: %[[TRUE:.*]] = arith.constant true
+// CHECK: return %[[TRUE]]
+func.func @signed_rem_small_dividend(%a: i32, %b: i32) -> i1 {
+  %zero = arith.constant 0 : i32
+  %seven = arith.constant 7 : i32
+  %nonnegative = arith.maxsi %a, %zero : i32
+  %lhs = arith.minsi %nonnegative, %seven : i32
+  %rem = arith.remsi %lhs, %b : i32
+  %bounded = arith.cmpi ule, %rem, %seven : i32
+  return %bounded : i1
+}

diff  --git a/mlir/unittests/Interfaces/CMakeLists.txt b/mlir/unittests/Interfaces/CMakeLists.txt
index 193edda6bf8d8a..a759d04875cfcb 100644
--- a/mlir/unittests/Interfaces/CMakeLists.txt
+++ b/mlir/unittests/Interfaces/CMakeLists.txt
@@ -18,6 +18,7 @@ mlir_target_link_libraries(MLIRInterfacesTests
   MLIRDataLayoutInterfaces
   MLIRDLTIDialect
   MLIRFuncDialect
+  MLIRInferIntRangeCommon
   MLIRInferIntRangeInterface
   MLIRInferTypeOpInterface
   MLIRMemorySlotUtils

diff  --git a/mlir/unittests/Interfaces/InferIntRangeInterfaceTest.cpp b/mlir/unittests/Interfaces/InferIntRangeInterfaceTest.cpp
index 97c75b36805679..8aa4b82d934b83 100644
--- a/mlir/unittests/Interfaces/InferIntRangeInterfaceTest.cpp
+++ b/mlir/unittests/Interfaces/InferIntRangeInterfaceTest.cpp
@@ -7,7 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "mlir/Interfaces/InferIntRangeInterface.h"
+#include "mlir/Interfaces/Utils/InferIntRangeCommon.h"
 #include "llvm/ADT/APInt.h"
+#include <array>
 #include <limits>
 
 #include <gtest/gtest.h>
@@ -97,3 +99,167 @@ TEST(IntRangeAttrs, Join) {
   ConstantIntRanges zeroOneSignedOnly(zero, uintMax, zero, one);
   EXPECT_EQ(zeroOneUnsignedOnly.rangeUnion(zeroOneSignedOnly), maximal);
 }
+
+static ConstantIntRanges signedRange(unsigned width, int64_t lo, int64_t hi) {
+  return ConstantIntRanges::fromSigned(APInt(width, lo, true),
+                                       APInt(width, hi, true));
+}
+
+TEST(IntRangeInference, SignedDivRemBounds) {
+  auto check = [](int64_t lhsMin, int64_t lhsMax, int64_t rhsMin,
+                  int64_t rhsMax, int64_t divMin, int64_t divMax,
+                  int64_t remMin, int64_t remMax) {
+    std::array operands{signedRange(32, lhsMin, lhsMax),
+                        signedRange(32, rhsMin, rhsMax)};
+    EXPECT_EQ(intrange::inferDivS(operands), signedRange(32, divMin, divMax));
+    EXPECT_EQ(intrange::inferRemS(operands), signedRange(32, remMin, remMax));
+  };
+  check(0, 127, 0, 16, 0, 127, 0, 15);
+  check(-127, 0, 0, 16, -127, 0, -15, 0);
+  check(0, 127, -16, 0, -127, 0, 0, 15);
+  check(-127, 0, -16, 0, 0, 127, -15, 0);
+  check(-127, 127, -16, 16, -127, 127, -15, 15);
+  check(0, 7, 0, 1024, 0, 7, 0, 7);
+  check(-7, 0, -1024, 0, 0, 7, -7, 0);
+  check(-7, 7, -1024, 1024, -7, 7, -7, 7);
+  check(0, 7, 1, 1024, 0, 7, 0, 7);
+  check(-7, 0, -1024, -1, 0, 7, -7, 0);
+}
+
+static bool containsSigned(const ConstantIntRanges &range, int64_t value) {
+  APInt v(range.smin().getBitWidth(), value, true);
+  return range.smin().sle(v) && range.smax().sge(v) && range.umin().ule(v) &&
+         range.umax().uge(v);
+}
+
+static void checkSignedDivRem(unsigned width, int64_t lhsMin, int64_t lhsMax,
+                              int64_t rhsMin, int64_t rhsMax) {
+  std::array operands{signedRange(width, lhsMin, lhsMax),
+                      signedRange(width, rhsMin, rhsMax)};
+  ConstantIntRanges div = intrange::inferDivS(operands);
+  ConstantIntRanges rem = intrange::inferRemS(operands);
+  for (int64_t lhs = lhsMin; lhs <= lhsMax; ++lhs) {
+    for (int64_t rhs = rhsMin; rhs <= rhsMax; ++rhs) {
+      if (rhs == 0)
+        continue;
+      // Signed division overflow produces poison.
+      if (!(lhs == -(int64_t{1} << (width - 1)) && rhs == -1)) {
+        ASSERT_TRUE(containsSigned(div, lhs / rhs))
+            << width << ": [" << lhsMin << ", " << lhsMax << "] / [" << rhsMin
+            << ", " << rhsMax << "] at " << lhs << ", " << rhs;
+      }
+      ASSERT_TRUE(containsSigned(rem, lhs % rhs))
+          << width << ": [" << lhsMin << ", " << lhsMax << "] % [" << rhsMin
+          << ", " << rhsMax << "] at " << lhs << ", " << rhs;
+    }
+  }
+}
+
+TEST(IntRangeInference, SignedDivRemExhaustive) {
+  for (unsigned width = 1; width <= 4; ++width) {
+    int64_t min = -(int64_t{1} << (width - 1));
+    int64_t max = -min - 1;
+    for (int64_t lhsMin = min; lhsMin <= max; ++lhsMin)
+      for (int64_t lhsMax = lhsMin; lhsMax <= max; ++lhsMax)
+        for (int64_t rhsMin = min; rhsMin <= max; ++rhsMin)
+          for (int64_t rhsMax = rhsMin; rhsMax <= max; ++rhsMax)
+            checkSignedDivRem(width, lhsMin, lhsMax, rhsMin, rhsMax);
+  }
+}
+
+TEST(IntRangeInference, SignedDivRemPoisonDivisor) {
+  for (unsigned width : {1, 4, 32, 64}) {
+    ConstantIntRanges lhs = ConstantIntRanges::maxRange(width);
+    for (ConstantIntRanges rhs :
+         {signedRange(width, 0, 0), signedRange(width, 0, -1)}) {
+      std::array operands{lhs, rhs};
+      EXPECT_EQ(intrange::inferDivS(operands), lhs);
+      EXPECT_EQ(intrange::inferRemS(operands), lhs);
+    }
+  }
+}
+
+TEST(IntRangeInference, RoundedSignedDivZeroEndpoint) {
+  std::array positive{signedRange(32, 1, 7), signedRange(32, 0, 3)};
+  EXPECT_EQ(intrange::inferCeilDivS(positive), signedRange(32, 1, 7));
+  EXPECT_EQ(intrange::inferFloorDivS(positive), signedRange(32, 0, 7));
+  std::array negative{signedRange(32, -7, -1), signedRange(32, 0, 3)};
+  EXPECT_EQ(intrange::inferCeilDivS(negative), signedRange(32, -7, 0));
+  EXPECT_EQ(intrange::inferFloorDivS(negative), signedRange(32, -7, -1));
+}
+
+TEST(IntRangeInference, SignedRemMinDivisor) {
+  for (unsigned width : {1, 4, 32, 64}) {
+    APInt min = APInt::getSignedMinValue(width);
+    APInt zero = APInt::getZero(width);
+    std::array operands{ConstantIntRanges::fromSigned(min, zero),
+                        ConstantIntRanges::fromSigned(min, zero)};
+    EXPECT_EQ(intrange::inferRemS(operands),
+              ConstantIntRanges::fromSigned(min + 1, zero));
+  }
+}
+
+static void checkRoundedSignedDiv(unsigned width, int64_t lhsMin,
+                                  int64_t lhsMax, int64_t rhsMin,
+                                  int64_t rhsMax) {
+  std::array operands{signedRange(width, lhsMin, lhsMax),
+                      signedRange(width, rhsMin, rhsMax)};
+  ConstantIntRanges ceil = intrange::inferCeilDivS(operands);
+  ConstantIntRanges floor = intrange::inferFloorDivS(operands);
+  for (int64_t lhs = lhsMin; lhs <= lhsMax; ++lhs) {
+    for (int64_t rhs = rhsMin; rhs <= rhsMax; ++rhs) {
+      if (rhs == 0 || (lhs == -(int64_t{1} << (width - 1)) && rhs == -1))
+        continue;
+      int64_t quotient = lhs / rhs;
+      int64_t remainder = lhs % rhs;
+      int64_t ceilValue = quotient + (remainder != 0 && (lhs < 0) == (rhs < 0));
+      // Index folding and lowering negate INT_MIN before division.
+      if (lhs == -(int64_t{1} << (width - 1)) && rhs > 1)
+        ceilValue = -quotient;
+      int64_t floorValue =
+          quotient - (remainder != 0 && (lhs < 0) != (rhs < 0));
+      ASSERT_TRUE(containsSigned(ceil, ceilValue))
+          << width << ": [" << lhsMin << ", " << lhsMax << "] ceildiv ["
+          << rhsMin << ", " << rhsMax << "] at " << lhs << ", " << rhs;
+      ASSERT_TRUE(containsSigned(floor, floorValue))
+          << width << ": [" << lhsMin << ", " << lhsMax << "] floordiv ["
+          << rhsMin << ", " << rhsMax << "] at " << lhs << ", " << rhs;
+    }
+  }
+}
+
+TEST(IntRangeInference, RoundedSignedDivExhaustive) {
+  for (unsigned width = 1; width <= 4; ++width) {
+    int64_t min = -(int64_t{1} << (width - 1));
+    int64_t max = -min - 1;
+    for (int64_t lhsMin = min; lhsMin <= max; ++lhsMin)
+      for (int64_t lhsMax = lhsMin; lhsMax <= max; ++lhsMax)
+        for (int64_t rhsMin = min; rhsMin <= max; ++rhsMin)
+          for (int64_t rhsMax = rhsMin; rhsMax <= max; ++rhsMax)
+            checkRoundedSignedDiv(width, lhsMin, lhsMax, rhsMin, rhsMax);
+  }
+}
+
+TEST(IntRangeInference, SignedCeilDivMinDividend) {
+  for (unsigned width : {4, 32, 64}) {
+    APInt min = APInt::getSignedMinValue(width);
+    APInt two(width, 2);
+    std::array singleton{ConstantIntRanges::constant(min),
+                         ConstantIntRanges::constant(two)};
+    EXPECT_EQ(intrange::inferCeilDivS(singleton),
+              ConstantIntRanges::constant(-min.sdiv(two)));
+
+    for (int64_t lower : {0, 1}) {
+      std::array divisors{ConstantIntRanges::constant(min),
+                          signedRange(width, lower, 3)};
+      EXPECT_EQ(intrange::inferCeilDivS(divisors),
+                ConstantIntRanges::fromSigned(min, -min.sdiv(two)));
+    }
+
+    std::array interval{ConstantIntRanges::fromSigned(min, min + 1),
+                        ConstantIntRanges::constant(two)};
+    EXPECT_EQ(
+        intrange::inferCeilDivS(interval),
+        ConstantIntRanges::fromSigned((min + 1).sdiv(two), -min.sdiv(two)));
+  }
+}


        


More information about the Mlir-commits mailing list