[Mlir-commits] [mlir] [mlir][math] Fix the sign of zero-base results in the fpowi expansion (PR #237452)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Oct 30 18:28:22 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-math

Author: Ben Limpanukorn (benlimpa)

<details>
<summary>Changes</summary>

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

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


3 Files Affected:

- (modified) mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp (+4-12) 
- (modified) mlir/test/Dialect/Math/expand-math.mlir (+6-12) 
- (modified) mlir/test/mlir-runner/test-expand-math-approx.mlir (+41) 


``````````diff
diff --git a/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp b/mlir/lib/Dialect/Math/Transforms/ExpandOps.cpp
index 41816abf07339..593681d3d9f83 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 84b0bb0aa314a..35293c5b75b95 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 06b3171a2349e..28d92175f2e80 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() : () -> ()

``````````

</details>


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


More information about the Mlir-commits mailing list