[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