[Mlir-commits] [mlir] [mlir][arith] Fix muli divisibility inference under wraparound (PR #237342)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Oct 30 11:53:08 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: edisongz

<details>
<summary>Changes</summary>

Wrapping only preserves the power-of-two factor of the operands'
divisor product. Keep the full unsigned/signed divisor only under
`nuw`/`nsw`, otherwise reduce to the largest power-of-two divisor
(`v & -v`). Compute the product with APInt to avoid uint64_t overflow.

Fixes [#<!-- -->233210](https://github.com/llvm/llvm-project/issues/233210).

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


2 Files Affected:

- (modified) mlir/lib/Dialect/Arith/IR/InferIntDivisibilityOpInterfaceImpl.cpp (+20-2) 
- (modified) mlir/test/Analysis/DataFlow/integer-divisibility.mlir (+60) 


``````````diff
diff --git a/mlir/lib/Dialect/Arith/IR/InferIntDivisibilityOpInterfaceImpl.cpp b/mlir/lib/Dialect/Arith/IR/InferIntDivisibilityOpInterfaceImpl.cpp
index b23ee108ca4a3d..535981bd2cf421 100644
--- a/mlir/lib/Dialect/Arith/IR/InferIntDivisibilityOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Arith/IR/InferIntDivisibilityOpInterfaceImpl.cpp
@@ -42,6 +42,16 @@ inferBinaryGCDResultDivisibility(OpTy op, ArrayRef<IntegerDivisibility> argDivs,
   setResultDivs(op.getResult(), lhsDiv.getUnion(rhsDiv));
 }
 
+// Overflow-free product of two divisors; falls back to the largest
+// power-of-two factor when the product exceeds uint64_t.
+static uint64_t mulDivisibilities(uint64_t a, uint64_t b) {
+  APInt product = APInt(128, a) * APInt(128, b);
+  if (product.getActiveBits() <= 64)
+    return product.getZExtValue();
+  unsigned trailingZeros = product.countr_zero();
+  return uint64_t(1) << (trailingZeros >= 63 ? 63 : trailingZeros);
+}
+
 void ConstantOp::inferResultDivisibility(ArrayRef<IntegerDivisibility> argDivs,
                                          SetIntDivisibilityFn setResultDivs) {
   auto constAttr = dyn_cast_if_present<IntegerAttr>(getValue());
@@ -88,8 +98,16 @@ void MulIOp::inferResultDivisibility(ArrayRef<IntegerDivisibility> argDivs,
   auto lhsDivisibility = getDivisibilityOfOperand(getLhs(), argDivs[0]);
   auto rhsDivisibility = getDivisibilityOfOperand(getRhs(), argDivs[1]);
 
-  uint64_t mulUDiv = lhsDivisibility.udiv() * rhsDivisibility.udiv();
-  uint64_t mulSDiv = lhsDivisibility.sdiv() * rhsDivisibility.sdiv();
+  uint64_t fullUDiv =
+      mulDivisibilities(lhsDivisibility.udiv(), rhsDivisibility.udiv());
+  uint64_t fullSDiv =
+      mulDivisibilities(lhsDivisibility.sdiv(), rhsDivisibility.sdiv());
+
+  // `v & -v` is the largest power-of-two divisor (all that wrapping keeps);
+  // nuw/nsw keep the full unsigned/signed divisor. Maps the 1/0 sentinels to
+  // themselves.
+  uint64_t mulUDiv = hasNoUnsignedWrap() ? fullUDiv : (fullUDiv & -fullUDiv);
+  uint64_t mulSDiv = hasNoSignedWrap() ? fullSDiv : (fullSDiv & -fullSDiv);
 
   setResultDivs(getResult(), ConstantIntDivisibility(mulUDiv, mulSDiv));
 }
diff --git a/mlir/test/Analysis/DataFlow/integer-divisibility.mlir b/mlir/test/Analysis/DataFlow/integer-divisibility.mlir
index 5db5c679eac829..89a512495b491e 100644
--- a/mlir/test/Analysis/DataFlow/integer-divisibility.mlir
+++ b/mlir/test/Analysis/DataFlow/integer-divisibility.mlir
@@ -150,3 +150,63 @@ func.func @affine_apply_mul_plus_const(%arg0 : index) -> index {
   %1 = "test.int_divisibility"(%0) : (index) -> index
   return %1 : index
 }
+
+// -----
+
+// CHECK-LABEL: @muli_wrapping
+func.func @muli_wrapping(%arg0 : i64) -> i64 {
+  %c6 = arith.constant 6 : i64
+  %0 = arith.muli %arg0, %c6 : i64
+  // 6 has power-of-two factor 2 only, so wrapping preserves just that.
+  // CHECK: divisibility = "udiv = 2, sdiv = 2"
+  %1 = "test.int_divisibility"(%0) : (i64) -> i64
+  return %1 : i64
+}
+
+// -----
+
+// CHECK-LABEL: @muli_nuw
+func.func @muli_nuw(%arg0 : i64) -> i64 {
+  %c6 = arith.constant 6 : i64
+  %0 = arith.muli %arg0, %c6 overflow<nuw> : i64
+  // nuw preserves the full unsigned divisor 6; signed still wraps to 2.
+  // CHECK: divisibility = "udiv = 6, sdiv = 2"
+  %1 = "test.int_divisibility"(%0) : (i64) -> i64
+  return %1 : i64
+}
+
+// -----
+
+// CHECK-LABEL: @muli_nsw
+func.func @muli_nsw(%arg0 : i64) -> i64 {
+  %c6 = arith.constant 6 : i64
+  %0 = arith.muli %arg0, %c6 overflow<nsw> : i64
+  // nsw preserves the full signed divisor 6; unsigned still wraps to 2.
+  // CHECK: divisibility = "udiv = 2, sdiv = 6"
+  %1 = "test.int_divisibility"(%0) : (i64) -> i64
+  return %1 : i64
+}
+
+// -----
+
+// CHECK-LABEL: @muli_nuw_nsw
+func.func @muli_nuw_nsw(%arg0 : i64) -> i64 {
+  %c6 = arith.constant 6 : i64
+  %0 = arith.muli %arg0, %c6 overflow<nsw, nuw> : i64
+  // With both no-wrap flags the full divisor 6 survives in both interpretations.
+  // CHECK: divisibility = "udiv = 6, sdiv = 6"
+  %1 = "test.int_divisibility"(%0) : (i64) -> i64
+  return %1 : i64
+}
+
+// -----
+
+// CHECK-LABEL: @muli_power_of_two
+func.func @muli_power_of_two(%arg0 : i64) -> i64 {
+  %c4 = arith.constant 4 : i64
+  %0 = arith.muli %arg0, %c4 : i64
+  // 4 is already a power of two, so wrapping preserves it unchanged.
+  // CHECK: divisibility = "udiv = 4, sdiv = 4"
+  %1 = "test.int_divisibility"(%0) : (i64) -> i64
+  return %1 : i64
+}

``````````

</details>


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


More information about the Mlir-commits mailing list