[Mlir-commits] [mlir] [mlir][arith] Fold ceildivsi with MININT operands (PR #214637)
曾鈜寬 Tseng Hung Kuan
llvmlistbot at llvm.org
Fri Aug 7 18:54:13 PDT 2026
https://github.com/Tim096 updated https://github.com/llvm/llvm-project/pull/214637
>From 12eab7931db1cd4e1865b2844fed594a6db8877a Mon Sep 17 00:00:00 2001
From: Hung-Kuan Tseng <p76091014 at gs.ncku.edu.tw>
Date: Fri, 7 Aug 2026 12:24:41 +0800
Subject: [PATCH 1/2] [mlir][arith] Add ceildivsi tests for a MININT divisor
CeilDivSIOp::fold negates a negative divisor, so it also gives up when the
divisor, rather than the dividend, is MININT. Only the dividend case is
covered today, and only the dividend case is mentioned in the TODO on the
folder.
Pre-commit the missing cases, plus MININT / -1, which must keep folding
back to the original op because its result is not representable.
---
mlir/test/Transforms/constant-fold.mlir | 57 +++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/mlir/test/Transforms/constant-fold.mlir b/mlir/test/Transforms/constant-fold.mlir
index 0b393bf0556b9..79382c06b32b7 100644
--- a/mlir/test/Transforms/constant-fold.mlir
+++ b/mlir/test/Transforms/constant-fold.mlir
@@ -516,6 +516,63 @@ func.func @simple_arith.ceildivsi_overflow() -> (i8, i16, i32) {
// -----
+// The divisor, rather than the dividend, is MININT here. The folder negates a
+// negative divisor, which overflows for MININT, so neither of the first two
+// operations folds even though both results are representable.
+
+// TODO: The folder should be able to fold the following by avoiding
+// intermediate operations that overflow.
+
+// CHECK-LABEL: func @simple_arith.ceildivsi_minint_divisor
+// CHECK-DAG: %[[MIN_I8:.*]] = arith.constant -128 : i8
+// CHECK-DAG: %[[C_7:.*]] = arith.constant 7 : i8
+// CHECK-DAG: %[[C_M9:.*]] = arith.constant -9 : i8
+// CHECK-DAG: %[[C_1:.*]] = arith.constant 1 : i8
+// CHECK: %[[DIV_1:.*]] = arith.ceildivsi %[[C_7]], %[[MIN_I8]] : i8
+// CHECK-NEXT: %[[DIV_2:.*]] = arith.ceildivsi %[[C_M9]], %[[MIN_I8]] : i8
+// CHECK-NEXT: return %[[DIV_1]], %[[DIV_2]], %[[C_1]]
+func.func @simple_arith.ceildivsi_minint_divisor() -> (i8, i8, i8) {
+ %min_int_i8 = arith.constant -128 : i8
+ %0 = arith.constant 7 : i8
+ %1 = arith.constant -9 : i8
+
+ // ceil(7 / -128) = 0
+ %2 = arith.ceildivsi %0, %min_int_i8 : i8
+ // ceil(-9 / -128) = 1
+ %3 = arith.ceildivsi %1, %min_int_i8 : i8
+ // ceil(-128 / -128) = 1, already folded by the ceildivsi(x, x) -> 1 pattern.
+ %4 = arith.ceildivsi %min_int_i8, %min_int_i8 : i8
+
+ return %2, %3, %4 : i8, i8, i8
+}
+
+// -----
+
+// ceil(MININT / -1) is -MININT, which is not representable. Unlike the cases
+// above, these must never fold.
+
+// CHECK-LABEL: func @simple_arith.ceildivsi_minint_div_minus_one
+// CHECK: arith.ceildivsi
+// CHECK-NEXT: arith.ceildivsi
+// CHECK-NEXT: arith.ceildivsi
+func.func @simple_arith.ceildivsi_minint_div_minus_one() -> (i8, i16, i32) {
+ %min_int_i8 = arith.constant -128 : i8
+ %0 = arith.constant -1 : i8
+ %1 = arith.ceildivsi %min_int_i8, %0 : i8
+
+ %min_int_i16 = arith.constant -32768 : i16
+ %2 = arith.constant -1 : i16
+ %3 = arith.ceildivsi %min_int_i16, %2 : i16
+
+ %min_int_i32 = arith.constant -2147483648 : i32
+ %4 = arith.constant -1 : i32
+ %5 = arith.ceildivsi %min_int_i32, %4 : i32
+
+ return %1, %3, %5 : i8, i16, i32
+}
+
+// -----
+
// CHECK-LABEL: func @simple_arith.ceildivui
func.func @simple_arith.ceildivui() -> (i32, i32, i32, i32, i32) {
// CHECK-DAG: [[C0:%.+]] = arith.constant 0
>From 73b28d7509e96fe0549a52890bff16a3d2ca6215 Mon Sep 17 00:00:00 2001
From: Hung-Kuan Tseng <p76091014 at gs.ncku.edu.tw>
Date: Fri, 7 Aug 2026 12:53:46 +0800
Subject: [PATCH 2/2] [mlir][arith] Fold ceildivsi with MININT operands
CeilDivSIOp::fold computed the ceiling by negating the operands so that
the division ran on two non-negative values. Negating MININT overflows,
so it gave up on any operand that was MININT, even when the result was
perfectly representable: ceildivsi(-128, 7) : i8 is -18, but did not
fold. The folder's TODO mentions only a MININT dividend; a MININT
divisor was affected too, and accounted for more than half of the
missed cases.
Compute the ceiling without negating anything instead. sdiv truncates
towards zero, which already rounds up whenever the exact quotient is
negative, so the only correction needed is +1 when the operands share a
sign and the division is inexact. This is the shape ExpandOps.cpp
already uses to expand the op.
The only case that now bails is MININT / -1, whose result is not
representable, plus division by zero as before. Exhaustive comparison
against an oracle over every i4 and i8 operand pair agrees on all of
them: for i8, 507 of the 65280 pairs did not fold before and 1 does not
fold now, with no pair folding to a different value in either version.
signedCeilNonnegInputs has no callers left and is removed.
---
mlir/lib/Dialect/Arith/IR/ArithOps.cpp | 74 +++++++------------------
mlir/test/Transforms/constant-fold.mlir | 48 +++++-----------
2 files changed, 36 insertions(+), 86 deletions(-)
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index ff6a5d4a0c29a..412c378cce531 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -912,18 +912,6 @@ Speculation::Speculatability arith::DivSIOp::getSpeculatability() {
return getDivSISpeculatability(getRhs());
}
-//===----------------------------------------------------------------------===//
-// Ceil and floor division folding helpers
-//===----------------------------------------------------------------------===//
-
-static APInt signedCeilNonnegInputs(const APInt &a, const APInt &b,
- bool &overflow) {
- // Returns (a-1)/b + 1
- APInt one(a.getBitWidth(), 1, true); // Signed value 1.
- APInt val = a.ssub_ov(one, overflow).sdiv_ov(b, overflow);
- return val.sadd_ov(one, overflow);
-}
-
//===----------------------------------------------------------------------===//
// CeilDivUIOp
//===----------------------------------------------------------------------===//
@@ -988,56 +976,36 @@ OpFoldResult arith::CeilDivSIOp::fold(FoldAdaptor adaptor) {
return getIntegerAttrOfType(getType(), 1);
// Don't fold if it would overflow or if it requires a division by zero.
- // TODO: This hook won't fold operations where a = MININT, because
- // negating MININT overflows. This can be improved.
bool overflowOrDiv0 = false;
auto result = constFoldBinaryOp<IntegerAttr>(
- adaptor.getOperands(), [&](APInt a, const APInt &b) {
+ adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
if (overflowOrDiv0 || !b) {
overflowOrDiv0 = true;
return a;
}
- if (!a)
- return a;
- // After this point we know that neither a or b are zero.
- unsigned bits = a.getBitWidth();
- APInt zero = APInt::getZero(bits);
- bool aGtZero = a.sgt(zero);
- bool bGtZero = b.sgt(zero);
- if (aGtZero && bGtZero) {
- // Both positive, return ceil(a, b).
- return signedCeilNonnegInputs(a, b, overflowOrDiv0);
- }
-
- // No folding happens if any of the intermediate arithmetic operations
- // overflows.
- bool overflowNegA = false;
- bool overflowNegB = false;
+ // Compute the ceiling without negating either operand, so that MININT
+ // operands still fold whenever the result is representable.
+ //
+ // sdiv truncates towards zero, so it already rounds up whenever the
+ // exact quotient is negative. When the exact quotient is positive, i.e.
+ // when the operands have the same sign, an inexact division has to be
+ // corrected by one. This mirrors the expansion in ExpandOps.cpp.
bool overflowDiv = false;
- bool overflowNegRes = false;
- if (!aGtZero && !bGtZero) {
- // Both negative, return ceil(-a, -b).
- APInt posA = zero.ssub_ov(a, overflowNegA);
- APInt posB = zero.ssub_ov(b, overflowNegB);
- APInt res = signedCeilNonnegInputs(posA, posB, overflowDiv);
- overflowOrDiv0 = (overflowNegA || overflowNegB || overflowDiv);
- return res;
- }
- if (!aGtZero && bGtZero) {
- // A is negative, b is positive, return - ( -a / b).
- APInt posA = zero.ssub_ov(a, overflowNegA);
- APInt div = posA.sdiv_ov(b, overflowDiv);
- APInt res = zero.ssub_ov(div, overflowNegRes);
- overflowOrDiv0 = (overflowNegA || overflowDiv || overflowNegRes);
- return res;
+ APInt quotient = a.sdiv_ov(b, overflowDiv);
+ if (overflowDiv) {
+ // MININT / -1. The exact result is -MININT, which is not
+ // representable.
+ overflowOrDiv0 = true;
+ return a;
}
- // A is positive, b is negative, return - (a / -b).
- APInt posB = zero.ssub_ov(b, overflowNegB);
- APInt div = a.sdiv_ov(posB, overflowDiv);
- APInt res = zero.ssub_ov(div, overflowNegRes);
+ if (a.srem(b).isZero() || a.isNegative() != b.isNegative())
+ return quotient;
- overflowOrDiv0 = (overflowNegB || overflowDiv || overflowNegRes);
- return res;
+ // The correction cannot overflow: it only applies when the exact
+ // quotient is positive and the division is inexact, which bounds the
+ // quotient well below the maximum. Check anyway, at no cost.
+ APInt one(a.getBitWidth(), 1, /*isSigned=*/true);
+ return quotient.sadd_ov(one, overflowOrDiv0);
});
return overflowOrDiv0 ? Attribute() : result;
diff --git a/mlir/test/Transforms/constant-fold.mlir b/mlir/test/Transforms/constant-fold.mlir
index 79382c06b32b7..f3ca18c56baab 100644
--- a/mlir/test/Transforms/constant-fold.mlir
+++ b/mlir/test/Transforms/constant-fold.mlir
@@ -478,35 +478,26 @@ func.func @simple_arith.ceildivsi() -> (i32, i32, i32, i32, i32) {
// -----
-// CHECK-LABEL: func @simple_arith.ceildivsi_overflow
-func.func @simple_arith.ceildivsi_overflow() -> (i8, i16, i32) {
- // The negative values below are MININTs for the corresponding bit-width. The
- // folder will try to negate them (so that the division operates on two
- // positive numbers), but that would cause overflow (negating MININT
- // overflows). Hence folding should not happen and the original ceildivsi is
- // preserved.
-
- // TODO: The folder should be able to fold the following by avoiding
- // intermediate operations that overflow.
-
- // CHECK-DAG: %[[C_1:.*]] = arith.constant 7 : i8
- // CHECK-DAG: %[[MIN_I8:.*]] = arith.constant -128 : i8
- // CHECK-DAG: %[[C_2:.*]] = arith.constant 7 : i16
- // CHECK-DAG: %[[MIN_I16:.*]] = arith.constant -32768 : i16
- // CHECK-DAG: %[[C_3:.*]] = arith.constant 7 : i32
- // CHECK-DAG: %[[MIN_I32:.*]] = arith.constant -2147483648 : i32
-
- // CHECK-NEXT: %[[CEILDIV_1:.*]] = arith.ceildivsi %[[MIN_I8]], %[[C_1]] : i8
+// The dividends below are MININTs for the corresponding bit-width. Every
+// result is representable, so all of them fold.
+
+// CHECK-LABEL: func @simple_arith.ceildivsi_minint_dividend
+// CHECK-DAG: %[[CEILDIV_1:.*]] = arith.constant -18 : i8
+// CHECK-DAG: %[[CEILDIV_2:.*]] = arith.constant -4681 : i16
+// CHECK-DAG: %[[CEILDIV_3:.*]] = arith.constant -306783378 : i32
+// CHECK: return %[[CEILDIV_1]], %[[CEILDIV_2]], %[[CEILDIV_3]]
+func.func @simple_arith.ceildivsi_minint_dividend() -> (i8, i16, i32) {
+ // ceil(-128 / 7) = -18
%0 = arith.constant 7 : i8
%min_int_i8 = arith.constant -128 : i8
%2 = arith.ceildivsi %min_int_i8, %0 : i8
- // CHECK-NEXT: %[[CEILDIV_2:.*]] = arith.ceildivsi %[[MIN_I16]], %[[C_2]] : i16
+ // ceil(-32768 / 7) = -4681
%3 = arith.constant 7 : i16
%min_int_i16 = arith.constant -32768 : i16
%5 = arith.ceildivsi %min_int_i16, %3 : i16
- // CHECK-NEXT: %[[CEILDIV_2:.*]] = arith.ceildivsi %[[MIN_I32]], %[[C_3]] : i32
+ // ceil(-2147483648 / 7) = -306783378
%6 = arith.constant 7 : i32
%min_int_i32 = arith.constant -2147483648 : i32
%8 = arith.ceildivsi %min_int_i32, %6 : i32
@@ -516,21 +507,12 @@ func.func @simple_arith.ceildivsi_overflow() -> (i8, i16, i32) {
// -----
-// The divisor, rather than the dividend, is MININT here. The folder negates a
-// negative divisor, which overflows for MININT, so neither of the first two
-// operations folds even though both results are representable.
-
-// TODO: The folder should be able to fold the following by avoiding
-// intermediate operations that overflow.
+// The divisor, rather than the dividend, is MININT here.
// CHECK-LABEL: func @simple_arith.ceildivsi_minint_divisor
-// CHECK-DAG: %[[MIN_I8:.*]] = arith.constant -128 : i8
-// CHECK-DAG: %[[C_7:.*]] = arith.constant 7 : i8
-// CHECK-DAG: %[[C_M9:.*]] = arith.constant -9 : i8
+// CHECK-DAG: %[[C_0:.*]] = arith.constant 0 : i8
// CHECK-DAG: %[[C_1:.*]] = arith.constant 1 : i8
-// CHECK: %[[DIV_1:.*]] = arith.ceildivsi %[[C_7]], %[[MIN_I8]] : i8
-// CHECK-NEXT: %[[DIV_2:.*]] = arith.ceildivsi %[[C_M9]], %[[MIN_I8]] : i8
-// CHECK-NEXT: return %[[DIV_1]], %[[DIV_2]], %[[C_1]]
+// CHECK: return %[[C_0]], %[[C_1]], %[[C_1]]
func.func @simple_arith.ceildivsi_minint_divisor() -> (i8, i8, i8) {
%min_int_i8 = arith.constant -128 : i8
%0 = arith.constant 7 : i8
More information about the Mlir-commits
mailing list