[Mlir-commits] [mlir] [mlir][complex] Fix log1p(-1 + 0i) returning 0 (PR #233043)
Chang Yong Lik
llvmlistbot at llvm.org
Sat Oct 17 00:32:58 PDT 2026
https://github.com/ahlag created https://github.com/llvm/llvm-project/pull/233043
The `complex.log1p` lowering in `ComplexToStandard` computes the real part as `log(max) + 0.5 * log1p((min / max)^2)`, where `min` and `max` are `|real + 1|` and `|imag|`. When that is NaN, it falls back to `min`. For `log1p(-1 + 0i)` both are 0, so `min / max = 0 / 0 = NaN`, and the fallback returns `0 + 0i` instead of `-inf + 0i`.
This patch falls back to `log(max)` instead. That is `-inf` for the zero case and still `inf` when both are inf, which is the case the fallback was written for. Other NaN results come from NaN inputs, where `max` and `min` are both NaN, so they are unchanged.
This fixes [tensorflow#128983](https://github.com/tensorflow/tensorflow/issues/128983) for XLA, which lowers complex `log-plus-one` through this pass. With tf-nightly 2.23.0-dev20261014 on macOS arm64:
```
inputs: -1+0j, -1-0j, inf+inf*j, -inf+inf*j
XLA: [ 0.+0.j 0.-0.j inf+0.7853982j inf+2.3561945j]
numpy: [-inf+0.j -inf-0.j inf+0.7853982j inf+2.3561945j]
```
With this patch, the lowered code matches NumPy on these inputs.
Tests:
- `convert-to-standard.mlir`: updated the select in the two log1p checks.
- `Integration/Dialect/Complex/CPU/correctness.mlir`: added log1p cases, including the inf/inf ones to show they don't change. Integration tests are not enabled in my build, so I ran its RUN pipeline by hand: it passes with the fix and fails without it.
AI use: I used Claude to help write the patch and tests; I reviewed and tested the change.
>From 96eb35f368be3de544982f4f4e3aa93499bec0f2 Mon Sep 17 00:00:00 2001
From: ahlag <theeahlag at gmail.com>
Date: Sat, 17 Oct 2026 16:32:46 +0900
Subject: [PATCH] [mlir][complex] Fix log1p(-1 + 0i) returning 0
The complex.log1p lowering computes the real part as
log(max) + 0.5 * log1p((min / max)^2), where min and max are |real + 1|
and |imag|, and falls back to min when that is NaN. For log1p(-1 + 0i)
both are 0, so min / max = 0 / 0 = NaN and the fallback returned 0
instead of -inf. Fall back to log(max) instead, which is -inf here and
still inf when both are inf, the case the fallback was written for.
Fixes https://github.com/tensorflow/tensorflow/issues/128983 for XLA CPU
and GPU, which use this lowering.
Assisted-by: Claude
---
.../ComplexToStandard/ComplexToStandard.cpp | 5 ++-
.../convert-to-standard.mlir | 4 +-
.../Dialect/Complex/CPU/correctness.mlir | 37 +++++++++++++++++++
3 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp b/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
index d1cdd8ae17e3f4..db32f12c737049 100644
--- a/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
+++ b/mlir/lib/Conversion/ComplexToStandard/ComplexToStandard.cpp
@@ -522,11 +522,14 @@ struct Log1pOpConversion : public OpConversionPattern<complex::Log1pOp> {
Value r = arith::AddFOp::create(
b, arith::MulFOp::create(b, half, logOfSqrtPart, fmfWithNaNInf),
logOfMaxAbsOfRealPlusOneAndImag, fmfWithNaNInf);
+ // r is NaN when min/max is 0/0 or inf/inf, i.e. for log1p(-1 + 0i) and
+ // when both |real + 1| and |imag| are inf. log(max) is then the answer:
+ // -inf and inf respectively.
Value resultReal = arith::SelectOp::create(
b,
arith::CmpFOp::create(b, arith::CmpFPredicate::UNO, r, r,
fmfWithNaNInf),
- minAbs, r);
+ logOfMaxAbsOfRealPlusOneAndImag, r);
Value resultImag = math::Atan2Op::create(b, imag, realPlusOne, fmf);
rewriter.replaceOpWithNewOp<complex::CreateOp>(op, type, resultReal,
resultImag);
diff --git a/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir b/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
index 448d82df503202..a28c765ce91ea7 100644
--- a/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
+++ b/mlir/test/Conversion/ComplexToStandard/convert-to-standard.mlir
@@ -339,7 +339,7 @@ func.func @complex_log1p(%arg: complex<f32>) -> complex<f32> {
// CHECK: %[[HALF_LOG_SQ:.*]] = arith.mulf %cst, %[[LOG_SQ]] : f32
// CHECK: %[[R:.*]] = arith.addf %[[HALF_LOG_SQ]], %[[LOG_1]] : f32
// CHECK: %[[ISNAN:.*]] = arith.cmpf uno, %[[R]], %[[R]] : f32
-// CHECK: %[[RESULT_REAL:.*]] = arith.select %[[ISNAN]], %[[MIN]], %[[R]] : f32
+// CHECK: %[[RESULT_REAL:.*]] = arith.select %[[ISNAN]], %[[LOG_1]], %[[R]] : f32
// CHECK: %[[RESULT_IMAG:.*]] = math.atan2 %[[IMAG]], %[[REAL_PLUS_ONE]] : f32
// CHECK: %[[RESULT:.*]] = complex.create %[[RESULT_REAL]], %[[RESULT_IMAG]] : complex<f32>
// CHECK: return %[[RESULT]] : complex<f32>
@@ -926,7 +926,7 @@ func.func @complex_log1p_with_fmf(%arg: complex<f32>) -> complex<f32> {
// CHECK: %[[HALF_LOG_SQ:.*]] = arith.mulf %cst, %[[LOG_SQ]] fastmath<contract> : f32
// CHECK: %[[R:.*]] = arith.addf %[[HALF_LOG_SQ]], %[[LOG_1]] fastmath<contract> : f32
// CHECK: %[[ISNAN:.*]] = arith.cmpf uno, %[[R]], %[[R]] fastmath<contract> : f32
-// CHECK: %[[RESULT_REAL:.*]] = arith.select %[[ISNAN]], %[[MIN]], %[[R]] : f32
+// CHECK: %[[RESULT_REAL:.*]] = arith.select %[[ISNAN]], %[[LOG_1]], %[[R]] : f32
// CHECK: %[[RESULT_IMAG:.*]] = math.atan2 %[[IMAG]], %[[REAL_PLUS_ONE]] fastmath<nnan,contract> : f32
// CHECK: %[[RESULT:.*]] = complex.create %[[RESULT_REAL]], %[[RESULT_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..120dcf916af835 100644
--- a/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir
+++ b/mlir/test/Integration/Dialect/Complex/CPU/correctness.mlir
@@ -44,6 +44,11 @@ func.func @rsqrt(%arg: complex<f32>) -> complex<f32> {
func.return %sqrt : complex<f32>
}
+func.func @log1p(%arg: complex<f32>) -> complex<f32> {
+ %log1p = complex.log1p %arg : complex<f32>
+ func.return %log1p : complex<f32>
+}
+
func.func @conj(%arg: complex<f32>) -> complex<f32> {
%conj = complex.conj %arg : complex<f32>
func.return %conj : complex<f32>
@@ -362,6 +367,38 @@ func.func @entry() {
call @test_element_f64(%abs_test_cast, %abs_func)
: (tensor<?xcomplex<f64>>, (complex<f64>) -> f64) -> ()
+ // complex.log1p test
+ %log1p_test = arith.constant dense<[
+ (1.0, 2.0),
+ // CHECK: 1.0397
+ // CHECK-NEXT: 0.7853
+
+ // log1p(-1 + 0i) = log(0 + 0i) = -inf + 0i. Both |real + 1| and |imag|
+ // are 0 here, so their ratio is 0 / 0 = NaN.
+ (-1.0, 0.0),
+ // CHECK-NEXT: -inf
+ // CHECK-NEXT: {{^}}0{{$}}
+ (-1.0, 0x80000000),
+ // CHECK-NEXT: -inf
+ // CHECK-NEXT: {{^}}-0{{$}}
+
+ // Both |real + 1| and |imag| are inf, their ratio is inf / inf = NaN.
+ // (inf, inf)
+ (0x7F800000, 0x7F800000),
+ // CHECK-NEXT: inf
+ // CHECK-NEXT: 0.7853
+ // (-inf, inf)
+ (0xFF800000, 0x7F800000)
+ // CHECK-NEXT: inf
+ // CHECK-NEXT: 2.3561
+ ]> : tensor<5xcomplex<f32>>
+ %log1p_test_cast = tensor.cast %log1p_test
+ : tensor<5xcomplex<f32>> to tensor<?xcomplex<f32>>
+
+ %log1p_func = func.constant @log1p : (complex<f32>) -> complex<f32>
+ call @test_unary(%log1p_test_cast, %log1p_func)
+ : (tensor<?xcomplex<f32>>, (complex<f32>) -> complex<f32>) -> ()
+
// complex.exp test
%exp_test = arith.constant dense<[
(1.0, 2.0),
More information about the Mlir-commits
mailing list