[Mlir-commits] [mlir] [mlir][complex] Fix exp special values for infinite real part (PR #232791)

Chang Yong Lik llvmlistbot at llvm.org
Fri Oct 16 08:01:31 PDT 2026


https://github.com/ahlag created https://github.com/llvm/llvm-project/pull/232791

This PR fixes incorrect handling of complex exponentials involving infinite and NaN components in the `complex.exp` lowering of `ComplexToStandard`. Currently, `exp(-inf ± inf·i)` and `exp(-inf + nan·i)` return `nan + nan·i` instead of the expected signed zeros (`±0 ± 0·i`), while `exp(+inf + inf·i)` returns NaN instead of `±inf + nan·i`. These issues arise from intermediate operations such as `0 * NaN` and `inf * NaN` during complex exponential computation. The fix explicitly handles these special cases to ensure compliance with C99 Annex G.6.3.1 and the Python Array API specification. This addresses the issue reported in [tensorflow#128988](https://github.com/tensorflow/tensorflow/issues/128988) and affects both XLA CPU and GPU backends, which use the same lowering pass.


>From 6183461e74e45f8c9b4195d5869ce59f792e7bdd Mon Sep 17 00:00:00 2001
From: ahlag <theeahlag at gmail.com>
Date: Sat, 17 Oct 2026 00:00:57 +0900
Subject: [PATCH] [mlir][complex] Fix exp special values for infinite real part

complex.exp lowering returned nan + nan*i for exp(-inf +- inf*i) and
exp(-inf + nan*i) instead of +-0 +- 0*i, and NaN instead of +-inf for the
real part of exp(+inf + inf*i), because of 0 * NaN and inf * NaN in
exp(x) * cos(y). Handle these cases as C99 Annex G.6.3.1 and the Python
array API specify.

Fixes https://github.com/tensorflow/tensorflow/issues/128988 for XLA CPU
and GPU, which use this lowering.

Assisted-by: Claude
---
 .../ComplexToStandard/ComplexToStandard.cpp   | 18 ++++++++++
 .../convert-to-standard.mlir                  | 24 ++++++++++++--
 .../Dialect/Complex/CPU/correctness.mlir      | 33 +++++++++++++++++--
 3 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp b/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
index d1cdd8ae17e3f4..9dea16f9da505e 100644
--- a/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
+++ b/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
@@ -361,6 +361,24 @@ struct ExpOpConversion : public OpConversionPattern<complex::ExpOp> {
         arith::SelectOp::create(b, expIsInf, imagOverflow, imagNormal);
     Value resultImag = arith::SelectOp::create(b, yIsZero, zero, imagNonZero);
 
+    // When x is +-inf and y is +-inf or NaN, exp(x) * cos(y) is inf * NaN or
+    // 0 * NaN. C99 Annex G gives exp(-inf + I*y) = +-0 +- 0*I and
+    // exp(+inf + I*y) = +-inf + NaN*I for these y.
+    Value absX = math::AbsFOp::create(b, x);
+    Value absY = math::AbsFOp::create(b, y);
+    Value negInf = arith::ConstantOp::create(
+        b, ET, b.getFloatAttr(ET, APFloat::getInf(floatSemantics, true)));
+    Value yIsNotFinite =
+        arith::CmpFOp::create(b, arith::CmpFPredicate::UGE, absY, inf);
+    Value xIsInf =
+        arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, absX, inf);
+    Value xIsNegInf =
+        arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, x, negInf);
+    Value useExpX = arith::AndIOp::create(b, xIsInf, yIsNotFinite);
+    Value useZeroImag = arith::AndIOp::create(b, xIsNegInf, yIsNotFinite);
+    resultReal = arith::SelectOp::create(b, useExpX, exp, resultReal);
+    resultImag = arith::SelectOp::create(b, useZeroImag, zero, resultImag);
+
     rewriter.replaceOpWithNewOp<complex::CreateOp>(op, type, resultReal,
                                                    resultImag);
     return success();
diff --git a/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir b/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
index 448d82df503202..907595b0a395e9 100644
--- a/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
+++ b/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
@@ -230,7 +230,17 @@ func.func @complex_exp(%arg: complex<f32>) -> complex<f32> {
 // CHECK-DAG: %[[IMAG_OVERFLOW:.*]] = arith.mulf %[[EXP_HALF_SIN]], %[[EXP_HALF]] : f32
 // CHECK-DAG: %[[IMAG_NONZERO:.*]] = arith.select %[[IS_INF]], %[[IMAG_OVERFLOW]], %[[IMAG_NORMAL]] : f32
 // CHECK: %[[RESULT_IMAG:.*]] = arith.select %[[IS_IMAG_ZERO]], %[[ZERO]], %[[IMAG_NONZERO]] : f32
-// CHECK: %[[RESULT:.*]] = complex.create %[[RESULT_REAL]], %[[RESULT_IMAG]] : complex<f32>
+// CHECK-DAG: %[[ABS_REAL:.*]] = math.absf %[[REAL]] : f32
+// CHECK-DAG: %[[ABS_IMAG:.*]] = math.absf %[[IMAG]] : f32
+// CHECK-DAG: %[[NEG_INF:.*]] = arith.constant 0xFF800000 : f32
+// CHECK-DAG: %[[IMAG_NOT_FINITE:.*]] = arith.cmpf uge, %[[ABS_IMAG]], %[[INF]] : f32
+// CHECK-DAG: %[[REAL_IS_INF:.*]] = arith.cmpf oeq, %[[ABS_REAL]], %[[INF]] : f32
+// CHECK-DAG: %[[REAL_IS_NEG_INF:.*]] = arith.cmpf oeq, %[[REAL]], %[[NEG_INF]] : f32
+// CHECK-DAG: %[[USE_EXP_REAL:.*]] = arith.andi %[[REAL_IS_INF]], %[[IMAG_NOT_FINITE]] : i1
+// CHECK-DAG: %[[USE_ZERO_IMAG:.*]] = arith.andi %[[REAL_IS_NEG_INF]], %[[IMAG_NOT_FINITE]] : i1
+// CHECK: %[[SPECIAL_REAL:.*]] = arith.select %[[USE_EXP_REAL]], %[[EXP_REAL]], %[[RESULT_REAL]] : f32
+// CHECK: %[[SPECIAL_IMAG:.*]] = arith.select %[[USE_ZERO_IMAG]], %[[ZERO]], %[[RESULT_IMAG]] : f32
+// CHECK: %[[RESULT:.*]] = complex.create %[[SPECIAL_REAL]], %[[SPECIAL_IMAG]] : complex<f32>
 // CHECK: return %[[RESULT]] : complex<f32>
 
 // -----
@@ -866,7 +876,17 @@ func.func @complex_exp_with_fmf(%arg: complex<f32>) -> complex<f32> {
 // CHECK-DAG: %[[IMAG_OVERFLOW:.*]] = arith.mulf %[[EXP_HALF_SIN]], %[[EXP_HALF]] fastmath<nnan,contract> : f32
 // CHECK-DAG: %[[IMAG_NONZERO:.*]] = arith.select %[[IS_INF]], %[[IMAG_OVERFLOW]], %[[IMAG_NORMAL]] : f32
 // CHECK: %[[RESULT_IMAG:.*]] = arith.select %[[IS_IMAG_ZERO]], %[[ZERO]], %[[IMAG_NONZERO]] : f32
-// CHECK: %[[RESULT:.*]] = complex.create %[[RESULT_REAL]], %[[RESULT_IMAG]] : complex<f32>
+// CHECK-DAG: %[[ABS_REAL:.*]] = math.absf %[[REAL]] : f32
+// CHECK-DAG: %[[ABS_IMAG:.*]] = math.absf %[[IMAG]] : f32
+// CHECK-DAG: %[[NEG_INF:.*]] = arith.constant 0xFF800000 : f32
+// CHECK-DAG: %[[IMAG_NOT_FINITE:.*]] = arith.cmpf uge, %[[ABS_IMAG]], %[[INF]] : f32
+// CHECK-DAG: %[[REAL_IS_INF:.*]] = arith.cmpf oeq, %[[ABS_REAL]], %[[INF]] : f32
+// CHECK-DAG: %[[REAL_IS_NEG_INF:.*]] = arith.cmpf oeq, %[[REAL]], %[[NEG_INF]] : f32
+// CHECK-DAG: %[[USE_EXP_REAL:.*]] = arith.andi %[[REAL_IS_INF]], %[[IMAG_NOT_FINITE]] : i1
+// CHECK-DAG: %[[USE_ZERO_IMAG:.*]] = arith.andi %[[REAL_IS_NEG_INF]], %[[IMAG_NOT_FINITE]] : i1
+// CHECK: %[[SPECIAL_REAL:.*]] = arith.select %[[USE_EXP_REAL]], %[[EXP_REAL]], %[[RESULT_REAL]] : f32
+// CHECK: %[[SPECIAL_IMAG:.*]] = arith.select %[[USE_ZERO_IMAG]], %[[ZERO]], %[[RESULT_IMAG]] : f32
+// CHECK: %[[RESULT:.*]] = complex.create %[[SPECIAL_REAL]], %[[SPECIAL_IMAG]] : complex<f32>
 // CHECK: return %[[RESULT]] : complex<f32>
 
 // -----
diff --git a/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir b/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir
index 29ad9603d1b9b2..e07ba8ee1cd547 100644
--- a/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir
+++ b/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir
@@ -377,13 +377,40 @@ func.func @entry() {
 
     // In this case, the overflow of exp(real_part) is compensated when
     // sin(imag_part) is close to zero, yielding a finite imaginary part.
-    (90.0238094, 5.900613e-39)
+    (90.0238094, 5.900613e-39),
     // CHECK-NEXT:  inf
     // CHECK-NOT:   inf
     // CHECK-NEXT:  7.3746
-  ]> : tensor<3xcomplex<f32>>
+
+    // C99 Annex G special values: exp(-inf + I*y) = +-0 +- 0*I and
+    // exp(+inf + I*y) = +-inf + NaN*I for y = +-inf or NaN, not NaN + NaN*I.
+    // (-inf, inf)
+    (0xFF800000, 0x7F800000),
+    // CHECK-NEXT:  0
+    // CHECK-NEXT:  0
+    // (-inf, -inf)
+    (0xFF800000, 0xFF800000),
+    // CHECK-NEXT:  0
+    // CHECK-NEXT:  0
+    // (-inf, nan)
+    (0xFF800000, 0x7FC00000),
+    // CHECK-NEXT:  0
+    // CHECK-NEXT:  0
+    // (inf, inf)
+    (0x7F800000, 0x7F800000),
+    // CHECK-NEXT:  inf
+    // CHECK-NEXT:  nan
+    // (inf, -inf)
+    (0x7F800000, 0xFF800000),
+    // CHECK-NEXT:  inf
+    // CHECK-NEXT:  nan
+    // (inf, nan)
+    (0x7F800000, 0x7FC00000)
+    // CHECK-NEXT:  inf
+    // CHECK-NEXT:  nan
+  ]> : tensor<9xcomplex<f32>>
   %exp_test_cast = tensor.cast %exp_test
-    :  tensor<3xcomplex<f32>> to tensor<?xcomplex<f32>>
+    :  tensor<9xcomplex<f32>> to tensor<?xcomplex<f32>>
 
   %exp_func = func.constant @exp : (complex<f32>) -> complex<f32>
   call @test_unary(%exp_test_cast, %exp_func)



More information about the Mlir-commits mailing list