[Mlir-commits] [mlir] [mlir] Fix signed ceiling division of INT_MIN (PR #235199)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Oct 23 21:14:51 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-spirv

@llvm/pr-subscribers-mlir-arith

Author: Matthias Springer (matthias-springer)

<details>
<summary>Changes</summary>

Negating INT_MIN causes signed overflow even when its quotient is representable. For INT_MIN / 2, the Index expansion included:

```
  %zero = llvm.mlir.constant(0 : i64) : i64
  %n = llvm.mlir.constant(-9223372036854775808 : i64) : i64
  %m = llvm.mlir.constant(2 : i64) : i64
  %neg_n = llvm.sub %zero, %n : i64 // Overflow: -INT_MIN is unrepresentable.
  %div = llvm.sdiv %neg_n, %m : i64
  %result = llvm.sub %zero, %div : i64
```

The first subtraction wraps back to INT_MIN, producing +2^62 instead of -2^62. A range inference workaround copied this incorrect sign.

Compute a truncating quotient and increment it only for inexact divisions with same-sign operands. Use this for Index folding and LLVM/SPIR-V lowering, and remove the inference workaround to match the Arith expansion.

Assisted-by: Codex


---
Full diff: https://github.com/llvm/llvm-project/pull/235199.diff


8 Files Affected:

- (modified) mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp (+22-27) 
- (modified) mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp (+21-26) 
- (modified) mlir/lib/Dialect/Index/IR/IndexOps.cpp (+3-17) 
- (modified) mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp (+1-18) 
- (modified) mlir/test/Conversion/IndexToLLVM/index-to-llvm.mlir (+10-17) 
- (modified) mlir/test/Conversion/IndexToSPIRV/index-to-spirv.mlir (+10-17) 
- (modified) mlir/test/Dialect/Arith/int-range-interface.mlir (+29-5) 
- (modified) mlir/test/Dialect/Index/index-canonicalize.mlir (+43) 


``````````diff
diff --git a/mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp b/mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp
index ba7d2b6fb83aa7..b186f6b87684a4 100644
--- a/mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp
+++ b/mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp
@@ -25,8 +25,16 @@ namespace {
 // ConvertIndexCeilDivS
 //===----------------------------------------------------------------------===//
 
-/// Convert `ceildivs(n, m)` into `x = m > 0 ? -1 : 1` and then
-/// `n*m > 0 ? (n+x)/m + 1 : -(-n/m)`.
+/// Compute `q = n / m`, then select `q + 1` when `q * m != n` and the
+/// operands have the same sign, or `q` otherwise. Truncation toward zero already
+/// rounds up for opposite-sign operands; same-sign operands need the increment
+/// only when the division is inexact.
+///
+/// This uses one division and avoids negating operands. Negating INT_MIN would
+/// cause signed overflow because -INT_MIN is not representable, even when a
+/// quotient such as INT_MIN / 2 is representable. For defined divisions,
+/// `q * m` lies between `n` and zero, so it cannot overflow. When the increment
+/// is selected, `q < INT_MAX`, so the selected result is also representable.
 struct ConvertIndexCeilDivS : mlir::ConvertOpToLLVMPattern<CeilDivSOp> {
   using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern;
 
@@ -38,33 +46,20 @@ struct ConvertIndexCeilDivS : mlir::ConvertOpToLLVMPattern<CeilDivSOp> {
     Value m = adaptor.getRhs();
     Value zero = LLVM::ConstantOp::create(rewriter, loc, n.getType(), 0);
     Value posOne = LLVM::ConstantOp::create(rewriter, loc, n.getType(), 1);
-    Value negOne = LLVM::ConstantOp::create(rewriter, loc, n.getType(), -1);
-
-    // Compute `x`.
-    Value mPos =
-        LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::sgt, m, zero);
-    Value x = LLVM::SelectOp::create(rewriter, loc, mPos, negOne, posOne);
 
-    // Compute the positive result.
-    Value nPlusX = LLVM::AddOp::create(rewriter, loc, n, x);
-    Value nPlusXDivM = LLVM::SDivOp::create(rewriter, loc, nPlusX, m);
-    Value posRes = LLVM::AddOp::create(rewriter, loc, nPlusXDivM, posOne);
-
-    // Compute the negative result.
-    Value negN = LLVM::SubOp::create(rewriter, loc, zero, n);
-    Value negNDivM = LLVM::SDivOp::create(rewriter, loc, negN, m);
-    Value negRes = LLVM::SubOp::create(rewriter, loc, zero, negNDivM);
-
-    // Pick the positive result if `n` and `m` have the same sign and `n` is
-    // non-zero, i.e. `(n > 0) == (m > 0) && n != 0`.
-    Value nPos =
-        LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::sgt, n, zero);
+    Value quotient = LLVM::SDivOp::create(rewriter, loc, n, m);
+    Value product = LLVM::MulOp::create(rewriter, loc, quotient, m);
+    Value inexact = LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::ne,
+                                         product, n);
+    Value nNeg =
+        LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::slt, n, zero);
+    Value mNeg =
+        LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::slt, m, zero);
     Value sameSign = LLVM::ICmpOp::create(rewriter, loc,
-                                          LLVM::ICmpPredicate::eq, nPos, mPos);
-    Value nNonZero =
-        LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::ne, n, zero);
-    Value cmp = LLVM::AndOp::create(rewriter, loc, sameSign, nNonZero);
-    rewriter.replaceOpWithNewOp<LLVM::SelectOp>(op, cmp, posRes, negRes);
+                                          LLVM::ICmpPredicate::eq, nNeg, mNeg);
+    Value roundUp = LLVM::AndOp::create(rewriter, loc, inexact, sameSign);
+    Value rounded = LLVM::AddOp::create(rewriter, loc, quotient, posOne);
+    rewriter.replaceOpWithNewOp<LLVM::SelectOp>(op, roundUp, rounded, quotient);
     return success();
   }
 };
diff --git a/mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp b/mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp
index 92526da965157d..a41dc2f3c8acc8 100644
--- a/mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp
+++ b/mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp
@@ -100,9 +100,16 @@ struct ConvertIndexConstantOpPattern final : OpConversionPattern<ConstantOp> {
 // ConvertIndexCeilDivS
 //===----------------------------------------------------------------------===//
 
-/// Convert `ceildivs(n, m)` into `x = m > 0 ? -1 : 1` and then
-/// `n*m > 0 ? (n+x)/m + 1 : -(-n/m)`. Formula taken from the equivalent
-/// conversion in IndexToLLVM.
+/// Compute `q = n / m`, then select `q + 1` when `q * m != n` and the
+/// operands have the same sign, or `q` otherwise. Truncation toward zero already
+/// rounds up for opposite-sign operands; same-sign operands need the increment
+/// only when the division is inexact.
+///
+/// This uses one division and avoids negating operands. Negating INT_MIN would
+/// cause signed overflow because -INT_MIN is not representable, even when a
+/// quotient such as INT_MIN / 2 is representable. For defined divisions,
+/// `q * m` lies between `n` and zero, so it cannot overflow. When the increment
+/// is selected, `q < INT_MAX`, so the selected result is also representable.
 struct ConvertIndexCeilDivSPattern final : OpConversionPattern<CeilDivSOp> {
   using Base::Base;
 
@@ -119,30 +126,18 @@ struct ConvertIndexCeilDivSPattern final : OpConversionPattern<CeilDivSOp> {
                                            IntegerAttr::get(nType, 0));
     Value posOne = spirv::ConstantOp::create(rewriter, loc, nType,
                                              IntegerAttr::get(nType, 1));
-    Value negOne = spirv::ConstantOp::create(rewriter, loc, nType,
-                                             IntegerAttr::get(nType, -1));
-
-    // Compute `x`.
-    Value mPos = spirv::SGreaterThanOp::create(rewriter, loc, m, zero);
-    Value x = spirv::SelectOp::create(rewriter, loc, mPos, negOne, posOne);
 
-    // Compute the positive result.
-    Value nPlusX = spirv::IAddOp::create(rewriter, loc, n, x);
-    Value nPlusXDivM = spirv::SDivOp::create(rewriter, loc, nPlusX, m);
-    Value posRes = spirv::IAddOp::create(rewriter, loc, nPlusXDivM, posOne);
-
-    // Compute the negative result.
-    Value negN = spirv::ISubOp::create(rewriter, loc, zero, n);
-    Value negNDivM = spirv::SDivOp::create(rewriter, loc, negN, m);
-    Value negRes = spirv::ISubOp::create(rewriter, loc, zero, negNDivM);
-
-    // Pick the positive result if `n` and `m` have the same sign and `n` is
-    // non-zero, i.e. `(n > 0) == (m > 0) && n != 0`.
-    Value nPos = spirv::SGreaterThanOp::create(rewriter, loc, n, zero);
-    Value sameSign = spirv::LogicalEqualOp::create(rewriter, loc, nPos, mPos);
-    Value nNonZero = spirv::INotEqualOp::create(rewriter, loc, n, zero);
-    Value cmp = spirv::LogicalAndOp::create(rewriter, loc, sameSign, nNonZero);
-    rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, cmp, posRes, negRes);
+    Value quotient = spirv::SDivOp::create(rewriter, loc, n, m);
+    Value product = spirv::IMulOp::create(rewriter, loc, quotient, m);
+    Value inexact = spirv::INotEqualOp::create(rewriter, loc, product, n);
+    Value nNeg = spirv::SLessThanOp::create(rewriter, loc, n, zero);
+    Value mNeg = spirv::SLessThanOp::create(rewriter, loc, m, zero);
+    Value sameSign = spirv::LogicalEqualOp::create(rewriter, loc, nNeg, mNeg);
+    Value roundUp =
+        spirv::LogicalAndOp::create(rewriter, loc, inexact, sameSign);
+    Value rounded = spirv::IAddOp::create(rewriter, loc, quotient, posOne);
+    rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, roundUp, rounded,
+                                                 quotient);
     return success();
   }
 };
diff --git a/mlir/lib/Dialect/Index/IR/IndexOps.cpp b/mlir/lib/Dialect/Index/IR/IndexOps.cpp
index 2b1baa8b446438..a0f261bc0689fa 100644
--- a/mlir/lib/Dialect/Index/IR/IndexOps.cpp
+++ b/mlir/lib/Dialect/Index/IR/IndexOps.cpp
@@ -245,27 +245,13 @@ OpFoldResult DivUOp::fold(FoldAdaptor adaptor) {
 // CeilDivSOp
 //===----------------------------------------------------------------------===//
 
-/// Compute `ceildivs(n, m)` as `x = m > 0 ? -1 : 1` and then
-/// `n*m > 0 ? (n+x)/m + 1 : -(-n/m)`.
+/// Compute signed ceiling division without negating either operand, so that
+/// INT_MIN is handled correctly.
 static std::optional<APInt> calculateCeilDivS(const APInt &n, const APInt &m) {
   // Don't fold division by zero.
   if (m.isZero())
     return std::nullopt;
-  // Short-circuit the zero case.
-  if (n.isZero())
-    return n;
-
-  bool mGtZ = m.sgt(0);
-  if (n.sgt(0) != mGtZ) {
-    // If the operands have different signs, compute the negative result. Signed
-    // division overflow is not possible, since if `m == -1`, `n` can be at most
-    // `INT_MAX`, and `-INT_MAX != INT_MIN` in two's complement.
-    return -(-n).sdiv(m);
-  }
-  // Otherwise, compute the positive result. Signed division overflow is not
-  // possible since if `m == -1`, `x` will be `1`.
-  int64_t x = mGtZ ? -1 : 1;
-  return (n + x).sdiv(m) + 1;
+  return llvm::APIntOps::RoundingSDiv(n, m, APInt::Rounding::UP);
 }
 
 OpFoldResult CeilDivSOp::fold(FoldAdaptor adaptor) {
diff --git a/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp b/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
index fa225e5f2d4b24..fe0c53859241f6 100644
--- a/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
+++ b/mlir/lib/Interfaces/Utils/InferIntRangeCommon.cpp
@@ -378,26 +378,9 @@ mlir::intrange::inferCeilDivS(ArrayRef<ConstantIntRanges> argRanges) {
           result.sadd_ov(APInt(result.getBitWidth(), 1), overflowed);
       return overflowed ? std::optional<APInt>() : corrected;
     }
-    // Special case where the usual implementation of ceilDiv causes
-    // INT_MIN / [positive number] to be positive. This doesn't match the
-    // definition of signed ceiling division mathematically, but it prevents
-    // inconsistent constant-folding results. This arises because (-int_min) is
-    // still negative, so -(-int_min / b) is -(int_min / b), which is
-    // positive See #115293.
-    if (lhs.isMinSignedValue() && rhs.sgt(1)) {
-      return -result;
-    }
     return result;
   };
-  ConstantIntRanges result = inferDivSRange(lhs, rhs, ceilDivSIFix);
-  if (lhs.smin().isMinSignedValue() && lhs.smax().sgt(lhs.smin())) {
-    // If lhs range includes INT_MIN and lhs is not a single value, we can
-    // suddenly wrap to positive val, skipping entire negative range, add
-    // [INT_MIN + 1, smax()] range to the result to handle this.
-    auto newLhs = ConstantIntRanges::fromSigned(lhs.smin() + 1, lhs.smax());
-    result = result.rangeUnion(inferDivSRange(newLhs, rhs, ceilDivSIFix));
-  }
-  return result;
+  return inferDivSRange(lhs, rhs, ceilDivSIFix);
 }
 
 ConstantIntRanges
diff --git a/mlir/test/Conversion/IndexToLLVM/index-to-llvm.mlir b/mlir/test/Conversion/IndexToLLVM/index-to-llvm.mlir
index 007929ed677fa9..7c6aab544a8c90 100644
--- a/mlir/test/Conversion/IndexToLLVM/index-to-llvm.mlir
+++ b/mlir/test/Conversion/IndexToLLVM/index-to-llvm.mlir
@@ -55,24 +55,17 @@ func.func @ceildivs(%n: index, %m: index) -> index {
   // CHECK-DAG: %[[M:.*]] = builtin.unrealized_conversion_cast %[[MI]]
   // CHECK: %[[ZERO:.*]] = llvm.mlir.constant(0 :
   // CHECK: %[[POS_ONE:.*]] = llvm.mlir.constant(1 :
-  // CHECK: %[[NEG_ONE:.*]] = llvm.mlir.constant(-1 :
 
-  // CHECK: %[[M_POS:.*]] = llvm.icmp "sgt" %[[M]], %[[ZERO]]
-  // CHECK: %[[X:.*]] = llvm.select %[[M_POS]], %[[NEG_ONE]], %[[POS_ONE]]
-
-  // CHECK: %[[N_PLUS_X:.*]] = llvm.add %[[N]], %[[X]]
-  // CHECK: %[[N_PLUS_X_DIV_M:.*]] = llvm.sdiv %[[N_PLUS_X]], %[[M]]
-  // CHECK: %[[POS_RES:.*]] = llvm.add %[[N_PLUS_X_DIV_M]], %[[POS_ONE]]
-
-  // CHECK: %[[NEG_N:.*]] = llvm.sub %[[ZERO]], %[[N]]
-  // CHECK: %[[NEG_N_DIV_M:.*]] = llvm.sdiv %[[NEG_N]], %[[M]]
-  // CHECK: %[[NEG_RES:.*]] = llvm.sub %[[ZERO]], %[[NEG_N_DIV_M]]
-
-  // CHECK: %[[N_POS:.*]] = llvm.icmp "sgt" %[[N]], %[[ZERO]]
-  // CHECK: %[[SAME_SIGN:.*]] = llvm.icmp "eq" %[[N_POS]], %[[M_POS]]
-  // CHECK: %[[N_NON_ZERO:.*]] = llvm.icmp "ne" %[[N]], %[[ZERO]]
-  // CHECK: %[[CMP:.*]] = llvm.and %[[SAME_SIGN]], %[[N_NON_ZERO]]
-  // CHECK: %[[RESULT:.*]] = llvm.select %[[CMP]], %[[POS_RES]], %[[NEG_RES]]
+  // Divide the original numerator directly, including when it is INT_MIN.
+  // CHECK: %[[Q:.*]] = llvm.sdiv %[[N]], %[[M]]
+  // CHECK: %[[PRODUCT:.*]] = llvm.mul %[[Q]], %[[M]]
+  // CHECK: %[[INEXACT:.*]] = llvm.icmp "ne" %[[PRODUCT]], %[[N]]
+  // CHECK: %[[N_NEG:.*]] = llvm.icmp "slt" %[[N]], %[[ZERO]]
+  // CHECK: %[[M_NEG:.*]] = llvm.icmp "slt" %[[M]], %[[ZERO]]
+  // CHECK: %[[SAME_SIGN:.*]] = llvm.icmp "eq" %[[N_NEG]], %[[M_NEG]]
+  // CHECK: %[[ROUND_UP:.*]] = llvm.and %[[INEXACT]], %[[SAME_SIGN]]
+  // CHECK: %[[ROUNDED:.*]] = llvm.add %[[Q]], %[[POS_ONE]]
+  // CHECK: %[[RESULT:.*]] = llvm.select %[[ROUND_UP]], %[[ROUNDED]], %[[Q]]
   %result = index.ceildivs %n, %m
 
   // CHECK: %[[RESULTI:.*]] = builtin.unrealized_conversion_cast %[[RESULT]]
diff --git a/mlir/test/Conversion/IndexToSPIRV/index-to-spirv.mlir b/mlir/test/Conversion/IndexToSPIRV/index-to-spirv.mlir
index 2d00adff2e8baa..144e91af530a05 100644
--- a/mlir/test/Conversion/IndexToSPIRV/index-to-spirv.mlir
+++ b/mlir/test/Conversion/IndexToSPIRV/index-to-spirv.mlir
@@ -72,24 +72,17 @@ func.func @ceildivs(%n: index, %m: index) -> index {
   // CHECK-DAG: %[[M:.*]] = builtin.unrealized_conversion_cast %[[MI]]
   // CHECK: %[[ZERO:.*]] = spirv.Constant 0
   // CHECK: %[[POS_ONE:.*]] = spirv.Constant 1
-  // CHECK: %[[NEG_ONE:.*]] = spirv.Constant -1
-
-  // CHECK: %[[M_POS:.*]] = spirv.SGreaterThan %[[M]], %[[ZERO]]
-  // CHECK: %[[X:.*]] = spirv.Select %[[M_POS]], %[[NEG_ONE]], %[[POS_ONE]]
-
-  // CHECK: %[[N_PLUS_X:.*]] = spirv.IAdd %[[N]], %[[X]]
-  // CHECK: %[[N_PLUS_X_DIV_M:.*]] = spirv.SDiv %[[N_PLUS_X]], %[[M]]
-  // CHECK: %[[POS_RES:.*]] = spirv.IAdd %[[N_PLUS_X_DIV_M]], %[[POS_ONE]]
-
-  // CHECK: %[[NEG_N:.*]] = spirv.ISub %[[ZERO]], %[[N]]
-  // CHECK: %[[NEG_N_DIV_M:.*]] = spirv.SDiv %[[NEG_N]], %[[M]]
-  // CHECK: %[[NEG_RES:.*]] = spirv.ISub %[[ZERO]], %[[NEG_N_DIV_M]]
 
-  // CHECK: %[[N_POS:.*]] = spirv.SGreaterThan %[[N]], %[[ZERO]]
-  // CHECK: %[[SAME_SIGN:.*]] = spirv.LogicalEqual %[[N_POS]], %[[M_POS]]
-  // CHECK: %[[N_NON_ZERO:.*]] = spirv.INotEqual %[[N]], %[[ZERO]]
-  // CHECK: %[[CMP:.*]] = spirv.LogicalAnd %[[SAME_SIGN]], %[[N_NON_ZERO]]
-  // CHECK: %[[RESULT:.*]] = spirv.Select %[[CMP]], %[[POS_RES]], %[[NEG_RES]]
+  // Divide the original numerator directly, including when it is INT_MIN.
+  // CHECK: %[[Q:.*]] = spirv.SDiv %[[N]], %[[M]]
+  // CHECK: %[[PRODUCT:.*]] = spirv.IMul %[[Q]], %[[M]]
+  // CHECK: %[[INEXACT:.*]] = spirv.INotEqual %[[PRODUCT]], %[[N]]
+  // CHECK: %[[N_NEG:.*]] = spirv.SLessThan %[[N]], %[[ZERO]]
+  // CHECK: %[[M_NEG:.*]] = spirv.SLessThan %[[M]], %[[ZERO]]
+  // CHECK: %[[SAME_SIGN:.*]] = spirv.LogicalEqual %[[N_NEG]], %[[M_NEG]]
+  // CHECK: %[[ROUND_UP:.*]] = spirv.LogicalAnd %[[INEXACT]], %[[SAME_SIGN]]
+  // CHECK: %[[ROUNDED:.*]] = spirv.IAdd %[[Q]], %[[POS_ONE]]
+  // CHECK: %[[RESULT:.*]] = spirv.Select %[[ROUND_UP]], %[[ROUNDED]], %[[Q]]
   %result = index.ceildivs %n, %m
 
   // %[[RESULTI:.*] = builtin.unrealized_conversion_cast %[[RESULT]]
diff --git a/mlir/test/Dialect/Arith/int-range-interface.mlir b/mlir/test/Dialect/Arith/int-range-interface.mlir
index e2f8ac72d56e9a..f3ef19a802cd42 100644
--- a/mlir/test/Dialect/Arith/int-range-interface.mlir
+++ b/mlir/test/Dialect/Arith/int-range-interface.mlir
@@ -273,16 +273,40 @@ func.func @ceil_divsi_full_range(%6: index) -> index {
 }
 
 // CHECK-LABEL: func @ceil_divsi_intmin_bug_115293
-// CHECK: %[[ret:.*]] = arith.constant true
+// CHECK: %[[ret:.*]] = arith.constant -7754212542 : i64
 // CHECK: return %[[ret]]
-func.func @ceil_divsi_intmin_bug_115293() -> i1 {
+func.func @ceil_divsi_intmin_bug_115293() -> i64 {
     %intMin_i64 = test.with_bounds < smin = -9223372036854775808 : si64, smax = -9223372036854775808 : si64, umin = 9223372036854775808 : ui64, umax = 9223372036854775808 : ui64 > : i64
     %denom_i64 = test.with_bounds < smin = 1189465982 : si64, smax = 1189465982 : si64, umin = 1189465982 : ui64, umax = 1189465982 : ui64 > : i64
-    %res_i64 = test.with_bounds < smin = 7754212542 : si64, smax = 7754212542 : si64, umin = 7754212542 : ui64, umax = 7754212542 : ui64 >  : i64
 
     %0 = arith.ceildivsi %intMin_i64, %denom_i64 : i64
-    %1 = arith.cmpi eq, %0, %res_i64 : i64
-    func.return %1 : i1
+    func.return %0 : i64
+}
+
+// INT_MIN divided by a positive number stays negative, both for exact and
+// inexact quotients.
+// CHECK-LABEL: func @ceil_divsi_intmin
+// CHECK-DAG: %[[EXACT:.*]] = arith.constant -64 : i8
+// CHECK-DAG: %[[INEXACT:.*]] = arith.constant -42 : i8
+// CHECK: return %[[EXACT]], %[[INEXACT]] : i8, i8
+func.func @ceil_divsi_intmin() -> (i8, i8) {
+  %min = test.with_bounds <umin = 128 : ui8, umax = 128 : ui8, smin = -128 : si8, smax = -128 : si8> : i8
+  %c2 = arith.constant 2 : i8
+  %c3 = arith.constant 3 : i8
+  %exact = arith.ceildivsi %min, %c2 : i8
+  %inexact = arith.ceildivsi %min, %c3 : i8
+  return %exact, %inexact : i8, i8
+}
+
+// A range starting at INT_MIN does not wrap to positive quotients.
+// CHECK-LABEL: func @ceil_divsi_range_from_intmin
+// CHECK: test.reflect_bounds <umin = 192 : ui8, umax = 193 : ui8, smin = -64 : si8, smax = -63 : si8>
+func.func @ceil_divsi_range_from_intmin() -> i8 {
+  %lhs = test.with_bounds <umin = 128 : ui8, umax = 129 : ui8, smin = -128 : si8, smax = -127 : si8> : i8
+  %c2 = arith.constant 2 : i8
+  %q = arith.ceildivsi %lhs, %c2 : i8
+  %bounds = test.reflect_bounds %q : i8
+  return %bounds : i8
 }
 
 // CHECK-LABEL: func @floor_divsi
diff --git a/mlir/test/Dialect/Index/index-canonicalize.mlir b/mlir/test/Dialect/Index/index-canonicalize.mlir
index 45da6ea57d796e..7c8278f6e2ca85 100644
--- a/mlir/test/Dialect/Index/index-canonicalize.mlir
+++ b/mlir/test/Dialect/Index/index-canonicalize.mlir
@@ -176,6 +176,49 @@ func.func @ceildivs_edge() -> (index, index) {
   return %0, %1 : index, index
 }
 
+// Negating INT_MIN before division would incorrectly make the first quotient
+// positive on a 64-bit target. The truncated operands and results are zero on
+// a 32-bit target, so both quotients can be folded.
+// CHECK-LABEL: @ceildivs_intmin64
+func.func @ceildivs_intmin64() -> (index, index) {
+  %min = index.constant -9223372036854775808
+  %c2 = index.constant 2
+  %cn2 = index.constant -2
+  // CHECK-DAG: %[[NEG:.*]] = index.constant -4611686018427387904
+  // CHECK-DAG: %[[POS:.*]] = index.constant 4611686018427387904
+  %negative = index.ceildivs %min, %c2
+  %positive = index.ceildivs %min, %cn2
+  // CHECK: return %[[NEG]], %[[POS]] : index, index
+  return %negative, %positive : index, index
+}
+
+// Both exact and inexact quotients agree across index widths when the
+// numerator is the signed 32-bit minimum.
+// CHECK-LABEL: @ceildivs_intmin32
+func.func @ceildivs_intmin32() -> (index, index) {
+  %min = index.constant -2147483648
+  %c2 = index.constant 2
+  %c3 = index.constant 3
+  // CHECK-DAG: %[[EXACT:.*]] = index.constant -1073741824
+  // CHECK-DAG: %[[INEXACT:.*]] = index.constant -715827882
+  %exact = index.ceildivs %min, %c2
+  %inexact = index.ceildivs %min, %c3
+  // CHECK: return %[[EXACT]], %[[INEXACT]] : index, index
+  return %exact, %inexact : index, index
+}
+
+// This numerator is positive on 64-bit targets and INT_MIN on 32-bit targets.
+// The quotients have different signs, so folding must preserve the operation.
+// CHECK-LABEL: @ceildivs_intmin32_nofold
+func.func @ceildivs_intmin32_nofold() -> index {
+  %lhs = index.constant 2147483648
+  %c2 = index.constant 2
+  // CHECK: %[[Q:.*]] = index.ceildivs
+  %q = index.ceildivs %lhs, %c2
+  // CHECK: return %[[Q]] : index
+  return %q : index
+}
+
 // CHECK-LABEL: @ceildivu
 func.func @ceildivu() -> index {
   %0 = index.constant 0x200000001

``````````

</details>


https://github.com/llvm/llvm-project/pull/235199


More information about the Mlir-commits mailing list