[Mlir-commits] [mlir] [mlir][math] Fix the sign of zero-base results in the fpowi expansion (PR #237452)
Ben Limpanukorn
llvmlistbot at llvm.org
Fri Oct 30 18:27:29 PDT 2026
https://github.com/benlimpa created https://github.com/llvm/llvm-project/pull/237452
For a constant negative exponent, `--math-expand-ops` expands `math.fpowi` as `1 / x^|n|`. However, it gives a wrong result for `(+0.0)^-1`, `(+0.0)^-2` and `(-0.0)^-2`, which become -inf, but should be +inf.
To avoid dividing by zero, the pass selects +inf if `x^|n|` compares `oeq` to +0.0 and -inf if it compares `oeq` to -0.0 (added in #87081). However, because `oeq` treats +0.0 and -0.0 as equal, both comparisons hold for either zero and -inf is always selected.
This patch replaces the two comparisons with one `oeq` comparison against +0.0 and selects `copysign(+inf, x^|n|)` when it holds. Since `x^|n|` is computed by repeated multiplication, a zero result has the correct sign, e.g. `(-0.0) * (-0.0) = +0.0`.
It also updates the two `fpowi` CHECK sequences in `expand-math.mlir` and adds runtime cases to `test-expand-math-approx.mlir` for +0.0, -0.0 and 2.0 with exponents -1 and -2. Before this change, every zero base printed -inf.
Assisted-by: Claude Code:claude-opus-5-5
>From 4fb4b952276d2d623ecb8ce05be065e23aef5cff Mon Sep 17 00:00:00 2001
From: Ben Limpanukorn <ben at limpanu.com>
Date: Fri, 30 Oct 2026 18:25:03 -0700
Subject: [PATCH] [mlir][math] Fix the sign of zero-base results in the fpowi
expansion
For a constant negative exponent, `--math-expand-ops` expands `math.fpowi` as `1 / x^|n|`. However, it gives a wrong result for `(+0.0)^-1`, `(+0.0)^-2` and `(-0.0)^-2`, which become -inf, but should be +inf.
To avoid dividing by zero, the pass selects +inf if `x^|n|` compares `oeq` to +0.0 and -inf if it compares `oeq` to -0.0 (added in #87081). However, because `oeq` treats +0.0 and -0.0 as equal, both comparisons hold for either zero and -inf is always selected.
This patch replaces the two comparisons with one `oeq` comparison against +0.0 and selects `copysign(+inf, x^|n|)` when it holds. Since `x^|n|` is computed by repeated multiplication, a zero result has the correct sign, e.g. `(-0.0) * (-0.0) = +0.0`.
It also updates the two `fpowi` CHECK sequences in `expand-math.mlir` and adds runtime cases to `test-expand-math-approx.mlir` for +0.0, -0.0 and 2.0 with exponents -1 and -2. Before this change, every zero base printed -inf.
Assisted-by: Claude Code:claude-opus-5-5
---
.../lib/Dialect/Math/Transforms/ExpandOps.cpp | 16 ++------
mlir/test/Dialect/Math/expand-math.mlir | 18 +++-----
.../mlir-runner/test-expand-math-approx.mlir | 41 +++++++++++++++++++
3 files changed, 51 insertions(+), 24 deletions(-)
diff --git a/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp b/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp
index 41816abf073398..593681d3d9f835 100644
--- a/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp
+++ b/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp
@@ -393,30 +393,22 @@ static LogicalResult convertFPowIOp(math::FPowIOp op,
base = arith::MulFOp::create(b, baseType, base, base);
}
- // Make sure not to introduce UB in case of negative power.
+ // Make sure not to introduce UB in case of negative power. A zero `res`
+ // (`oeq` holds for both +0.0 and -0.0) gives an infinity with its sign.
if (isNegative) {
auto &sem = dyn_cast<mlir::FloatType>(getElementTypeOrSelf(baseType))
.getFloatSemantics();
Value zero =
createFloatConst(op->getLoc(), baseType,
APFloat::getZero(sem, /*Negative=*/false), rewriter);
- Value negZero =
- createFloatConst(op->getLoc(), baseType,
- APFloat::getZero(sem, /*Negative=*/true), rewriter);
Value posInfinity =
createFloatConst(op->getLoc(), baseType,
APFloat::getInf(sem, /*Negative=*/false), rewriter);
- Value negInfinity =
- createFloatConst(op->getLoc(), baseType,
- APFloat::getInf(sem, /*Negative=*/true), rewriter);
Value zeroEqCheck =
arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, res, zero);
- Value negZeroEqCheck =
- arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, res, negZero);
+ Value signedInfinity = math::CopySignOp::create(b, posInfinity, res);
res = arith::DivFOp::create(b, baseType, one, res);
- res =
- arith::SelectOp::create(b, op->getLoc(), zeroEqCheck, posInfinity, res);
- res = arith::SelectOp::create(b, op->getLoc(), negZeroEqCheck, negInfinity,
+ res = arith::SelectOp::create(b, op->getLoc(), zeroEqCheck, signedInfinity,
res);
}
diff --git a/mlir/test/Dialect/Math/expand-math.mlir b/mlir/test/Dialect/Math/expand-math.mlir
index 84b0bb0aa314ae..35293c5b75b951 100644
--- a/mlir/test/Dialect/Math/expand-math.mlir
+++ b/mlir/test/Dialect/Math/expand-math.mlir
@@ -633,17 +633,14 @@ func.func @math_fpowi_neg_odd_power(%0 : tensor<8xf32>) -> tensor<8xf32> {
// CHECK-SAME: (%[[ARG0:.*]]: tensor<8xf32>) -> tensor<8xf32> {
// CHECK-DAG: %[[CST1:.*]] = arith.constant dense<1.000000e+00> : tensor<8xf32>
// CHECK-DAG: %[[CST0:.*]] = arith.constant dense<0.000000e+00> : tensor<8xf32>
-// CHECK-DAG: %[[CSTNEG0:.*]] = arith.constant dense<-0.000000e+00> : tensor<8xf32>
// CHECK-DAG: %[[CSTINF:.*]] = arith.constant dense<0x7F800000> : tensor<8xf32>
-// CHECK-DAG: %[[CSTNEGINF:.*]] = arith.constant dense<0xFF800000> : tensor<8xf32>
// CHECK: %[[SQ:.*]] = arith.mulf %[[ARG0]], %[[ARG0]] : tensor<8xf32>
// CHECK: %[[CUBE:.*]] = arith.mulf %[[SQ]], %[[ARG0]] : tensor<8xf32>
// CHECK: %[[CMP0:.*]] = arith.cmpf oeq, %[[CUBE]], %[[CST0]] : tensor<8xf32>
-// CHECK: %[[CMPNEG0:.*]] = arith.cmpf oeq, %[[CUBE]], %[[CSTNEG0]] : tensor<8xf32>
+// CHECK: %[[SIGNEDINF:.*]] = math.copysign %[[CSTINF]], %[[CUBE]] : tensor<8xf32>
// CHECK: %[[INV:.*]] = arith.divf %[[CST1]], %[[CUBE]] : tensor<8xf32>
-// CHECK: %[[UB1:.*]] = arith.select %[[CMP0]], %[[CSTINF]], %[[INV]] : tensor<8xi1>, tensor<8xf32>
-// CHECK: %[[UB2:.*]] = arith.select %[[CMPNEG0]], %[[CSTNEGINF]], %[[UB1]] : tensor<8xi1>, tensor<8xf32>
-// CHECK: return %[[UB2]] : tensor<8xf32>
+// CHECK: %[[RES:.*]] = arith.select %[[CMP0]], %[[SIGNEDINF]], %[[INV]] : tensor<8xi1>, tensor<8xf32>
+// CHECK: return %[[RES]] : tensor<8xf32>
// -----
@@ -656,17 +653,14 @@ func.func @math_fpowi_neg_even_power(%0 : tensor<8xf32>) -> tensor<8xf32> {
// CHECK-SAME: (%[[ARG0:.*]]: tensor<8xf32>) -> tensor<8xf32> {
// CHECK-DAG: %[[CST1:.*]] = arith.constant dense<1.000000e+00> : tensor<8xf32>
// CHECK-DAG: %[[CST0:.*]] = arith.constant dense<0.000000e+00> : tensor<8xf32>
-// CHECK-DAG: %[[CSTNEG0:.*]] = arith.constant dense<-0.000000e+00> : tensor<8xf32>
// CHECK-DAG: %[[CSTINF:.*]] = arith.constant dense<0x7F800000> : tensor<8xf32>
-// CHECK-DAG: %[[CSTNEGINF:.*]] = arith.constant dense<0xFF800000> : tensor<8xf32>
// CHECK: %[[SQ:.*]] = arith.mulf %[[ARG0]], %[[ARG0]] : tensor<8xf32>
// CHECK: %[[PW4:.*]] = arith.mulf %[[SQ]], %[[SQ]] : tensor<8xf32>
// CHECK: %[[CMP0:.*]] = arith.cmpf oeq, %[[PW4]], %[[CST0]] : tensor<8xf32>
-// CHECK: %[[CMPNEG0:.*]] = arith.cmpf oeq, %[[PW4]], %[[CSTNEG0]] : tensor<8xf32>
+// CHECK: %[[SIGNEDINF:.*]] = math.copysign %[[CSTINF]], %[[PW4]] : tensor<8xf32>
// CHECK: %[[INV:.*]] = arith.divf %[[CST1]], %[[PW4]] : tensor<8xf32>
-// CHECK: %[[UB1:.*]] = arith.select %[[CMP0]], %[[CSTINF]], %[[INV]] : tensor<8xi1>, tensor<8xf32>
-// CHECK: %[[UB2:.*]] = arith.select %[[CMPNEG0]], %[[CSTNEGINF]], %[[UB1]] : tensor<8xi1>, tensor<8xf32>
-// CHECK: return %[[UB2]] : tensor<8xf32>
+// CHECK: %[[RES:.*]] = arith.select %[[CMP0]], %[[SIGNEDINF]], %[[INV]] : tensor<8xi1>, tensor<8xf32>
+// CHECK: return %[[RES]] : tensor<8xf32>
// -----
diff --git a/mlir/test/mlir-runner/test-expand-math-approx.mlir b/mlir/test/mlir-runner/test-expand-math-approx.mlir
index 06b3171a2349e7..28d92175f2e80d 100644
--- a/mlir/test/mlir-runner/test-expand-math-approx.mlir
+++ b/mlir/test/mlir-runner/test-expand-math-approx.mlir
@@ -262,6 +262,46 @@ func.func @powf() {
return
}
+// -------------------------------------------------------------------------- //
+// fpowi.
+// -------------------------------------------------------------------------- //
+
+func.func @func_fpowi_neg1_f32(%a : f32) {
+ %n = arith.constant -1 : i32
+ %r = math.fpowi %a, %n : f32, i32
+ vector.print %r : f32
+ return
+}
+
+func.func @func_fpowi_neg2_f32(%a : f32) {
+ %n = arith.constant -2 : i32
+ %r = math.fpowi %a, %n : f32, i32
+ vector.print %r : f32
+ return
+}
+
+func.func @fpowi() {
+ %zero = arith.constant 0.0 : f32
+ %neg_zero = arith.constant -0.0 : f32
+ %two = arith.constant 2.0 : f32
+
+ // CHECK-NEXT: {{^}}inf
+ call @func_fpowi_neg1_f32(%zero) : (f32) -> ()
+ // CHECK-NEXT: {{^}}-inf
+ call @func_fpowi_neg1_f32(%neg_zero) : (f32) -> ()
+ // CHECK-NEXT: {{^}}0.5
+ call @func_fpowi_neg1_f32(%two) : (f32) -> ()
+
+ // CHECK-NEXT: {{^}}inf
+ call @func_fpowi_neg2_f32(%zero) : (f32) -> ()
+ // CHECK-NEXT: {{^}}inf
+ call @func_fpowi_neg2_f32(%neg_zero) : (f32) -> ()
+ // CHECK-NEXT: {{^}}0.25
+ call @func_fpowi_neg2_f32(%two) : (f32) -> ()
+
+ return
+}
+
// -------------------------------------------------------------------------- //
// roundeven.
// -------------------------------------------------------------------------- //
@@ -890,6 +930,7 @@ func.func @main() {
call @exp2f() : () -> ()
call @roundf() : () -> ()
call @powf() : () -> ()
+ call @fpowi() : () -> ()
call @roundeven() : () -> ()
call @sinh() : () -> ()
call @cosh() : () -> ()
More information about the Mlir-commits
mailing list