[Mlir-commits] [mlir] [mlir][arith] Only fold truncf(sitofp(x)) to sitofp(x) when the first conversion is exact (PR #233536)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Oct 19 11:51:47 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-arith

Author: Stanislav Bardyuk (kodlan)

<details>
<summary>Changes</summary>

The canonicalization patterns that turn truncf(sitofp(x)) into sitofp(x) (and the uitofp variant) assume the intermediate conversion is exact. When the integer is wider than the intermediate float's mantissa, the original program rounds twice and the folded one rounds once, so the results can differ. For i64 -> f64 -> f32 with x = 2^60 + 2^36 + 1 the program prints 0x5D800000 before canonicalization and 0x5D800001 after.

Add a constraint that every value of the integer type converts to the intermediate type exactly: the magnitude bits (width minus one for sitofp, the full width for uitofp) must fit the mantissa, which is the rule InstCombine uses for fptrunc(sitofp x), and the value must also fit the exponent range so the tiny float types cannot overflow. Narrow cases like i32 -> f64 -> f32 keep folding; wide ones keep both conversions.

Fixes #<!-- -->233369

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


3 Files Affected:

- (modified) mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td (+14-4) 
- (modified) mlir/lib/Dialect/Arith/IR/ArithOps.cpp (+13) 
- (modified) mlir/test/Dialect/Arith/canonicalize.mlir (+80) 


``````````diff
diff --git a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
index ac6ccebc56b0a..7f400c5c9352b 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
+++ b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
@@ -521,17 +521,27 @@ def TruncIShrSIToTrunciShrUI :
 // TruncFOp
 //===----------------------------------------------------------------------===//
 
-// truncf(sitofp(x)) -> sitofp(x) if default rounding mode.
+// An inexact intermediate conversion rounds twice and may change the result.
+def IsExactSIToFP :
+    Constraint<CPred<"isExactIntToFPCast($0, $1, /*isSigned=*/true)">,
+               "exact sitofp">;
+def IsExactUIToFP :
+    Constraint<CPred<"isExactIntToFPCast($0, $1, /*isSigned=*/false)">,
+               "exact uitofp">;
+
+// truncf(sitofp(x)) -> sitofp(x) if default rounding mode and sitofp is exact.
 def TruncFSIToFPToSIToFP :
     Pat<(Arith_TruncFOp:$tr (Arith_SIToFPOp:$fp $x), $rmf, $fmf),
         (Arith_SIToFPOp $x),
-        [(Constraint<CPred<"$0 == nullptr">, "default rounding mode"> $rmf)]>;
+        [(Constraint<CPred<"$0 == nullptr">, "default rounding mode"> $rmf),
+         (IsExactSIToFP $x, $fp)]>;
 
-// truncf(uitofp(x)) -> uitofp(x) if default rounding mode.
+// truncf(uitofp(x)) -> uitofp(x) if default rounding mode and uitofp is exact.
 def TruncFUIToFPToUIToFP :
     Pat<(Arith_TruncFOp:$tr (Arith_UIToFPOp:$fp $x, $nneg), $rmf, $fmf),
         (Arith_UIToFPOp $x, $nneg),
-        [(Constraint<CPred<"$0 == nullptr">, "default rounding mode"> $rmf)]>;
+        [(Constraint<CPred<"$0 == nullptr">, "default rounding mode"> $rmf),
+         (IsExactUIToFP $x, $fp)]>;
 
 //===----------------------------------------------------------------------===//
 // SIToFPOp
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index 7ed50c6ab787c..e3b85eee622cf 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -196,6 +196,19 @@ static int64_t getScalarOrElementWidth(Value value) {
   return getScalarOrElementWidth(value.getType());
 }
 
+/// Returns true if every value of `intValue`'s integer type converts exactly.
+static bool isExactIntToFPCast(Value intValue, Value fpValue, bool isSigned) {
+  auto fpTy = dyn_cast<FloatType>(getElementTypeOrSelf(fpValue.getType()));
+  int64_t intWidth = getScalarOrElementWidth(intValue);
+  if (!fpTy || intWidth <= 0)
+    return false;
+  // The magnitude bits must fit the mantissa and the exponent range.
+  int64_t magnitudeBits = intWidth - (isSigned ? 1 : 0);
+  return magnitudeBits <= fpTy.getFPMantissaWidth() &&
+         intWidth - 1 <=
+             APFloat::semanticsMaxExponent(fpTy.getFloatSemantics());
+}
+
 static FailureOr<APInt> getIntOrSplatIntValue(Attribute attr) {
   APInt value;
   if (matchPattern(attr, m_ConstantInt(&value)))
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index 8ea3bc61d979a..328b43cb4636f 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -1505,6 +1505,86 @@ func.func @truncUitofp_nneg(%arg0: i32) -> f32 {
   return %trunc : f32
 }
 
+// i64 does not fit the f64 mantissa, so the two roundings must be kept.
+// CHECK-LABEL: @truncSitofpInexact
+//       CHECK:     %[[SITOFP:.*]] = arith.sitofp %[[ARG0:.*]] : i64 to f64
+//       CHECK:     %[[TRUNCF:.*]] = arith.truncf %[[SITOFP]] : f64 to f32
+//       CHECK:     return %[[TRUNCF]]
+func.func @truncSitofpInexact(%arg0: i64) -> f32 {
+  %sitofp = arith.sitofp %arg0 : i64 to f64
+  %trunc = arith.truncf %sitofp : f64 to f32
+  return %trunc : f32
+}
+
+// CHECK-LABEL: @truncUitofpInexact
+//       CHECK:     %[[UITOFP:.*]] = arith.uitofp %[[ARG0:.*]] : i64 to f64
+//       CHECK:     %[[TRUNCF:.*]] = arith.truncf %[[UITOFP]] : f64 to f32
+//       CHECK:     return %[[TRUNCF]]
+func.func @truncUitofpInexact(%arg0: i64) -> f32 {
+  %uitofp = arith.uitofp %arg0 : i64 to f64
+  %trunc = arith.truncf %uitofp : f64 to f32
+  return %trunc : f32
+}
+
+// A signed i54 has 53 magnitude bits, which exactly fit the f64 mantissa.
+// CHECK-LABEL: @truncSitofpExactBoundary
+//       CHECK:     %[[SITOFP:.*]] = arith.sitofp %[[ARG0:.*]] : i54 to f32
+//       CHECK-NOT: truncf
+//       CHECK:     return %[[SITOFP]]
+func.func @truncSitofpExactBoundary(%arg0: i54) -> f32 {
+  %sitofp = arith.sitofp %arg0 : i54 to f64
+  %trunc = arith.truncf %sitofp : f64 to f32
+  return %trunc : f32
+}
+
+// CHECK-LABEL: @truncSitofpInexactBoundary
+//       CHECK:     arith.sitofp %{{.*}} : i55 to f64
+//       CHECK:     arith.truncf
+func.func @truncSitofpInexactBoundary(%arg0: i55) -> f32 {
+  %sitofp = arith.sitofp %arg0 : i55 to f64
+  %trunc = arith.truncf %sitofp : f64 to f32
+  return %trunc : f32
+}
+
+// An unsigned i53 fits the f64 mantissa, an unsigned i54 does not.
+// CHECK-LABEL: @truncUitofpExactBoundary
+//       CHECK:     %[[UITOFP:.*]] = arith.uitofp %[[ARG0:.*]] : i53 to f32
+//       CHECK-NOT: truncf
+//       CHECK:     return %[[UITOFP]]
+func.func @truncUitofpExactBoundary(%arg0: i53) -> f32 {
+  %uitofp = arith.uitofp %arg0 : i53 to f64
+  %trunc = arith.truncf %uitofp : f64 to f32
+  return %trunc : f32
+}
+
+// CHECK-LABEL: @truncUitofpInexactBoundary
+//       CHECK:     arith.uitofp %{{.*}} : i54 to f64
+//       CHECK:     arith.truncf
+func.func @truncUitofpInexactBoundary(%arg0: i54) -> f32 {
+  %uitofp = arith.uitofp %arg0 : i54 to f64
+  %trunc = arith.truncf %uitofp : f64 to f32
+  return %trunc : f32
+}
+
+// CHECK-LABEL: @truncSitofpExactVector
+//       CHECK:     %[[SITOFP:.*]] = arith.sitofp %[[ARG0:.*]] : vector<2xi32> to vector<2xf32>
+//       CHECK-NOT: truncf
+//       CHECK:     return %[[SITOFP]]
+func.func @truncSitofpExactVector(%arg0: vector<2xi32>) -> vector<2xf32> {
+  %sitofp = arith.sitofp %arg0 : vector<2xi32> to vector<2xf64>
+  %trunc = arith.truncf %sitofp : vector<2xf64> to vector<2xf32>
+  return %trunc : vector<2xf32>
+}
+
+// CHECK-LABEL: @truncSitofpInexactVector
+//       CHECK:     arith.sitofp %{{.*}} : vector<2xi64> to vector<2xf64>
+//       CHECK:     arith.truncf
+func.func @truncSitofpInexactVector(%arg0: vector<2xi64>) -> vector<2xf32> {
+  %sitofp = arith.sitofp %arg0 : vector<2xi64> to vector<2xf64>
+  %trunc = arith.truncf %sitofp : vector<2xf64> to vector<2xf32>
+  return %trunc : vector<2xf32>
+}
+
 // CHECK-LABEL: @sitofpExtsi
 //       CHECK:     %[[SITOFP:.*]] = arith.sitofp %[[ARG0:.*]] : i8 to bf16
 //       CHECK:     return %[[SITOFP]]

``````````

</details>


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


More information about the Mlir-commits mailing list