llvm/flang/docs/ComplexOperations.md

# Complex Operations

```{contents}
---
local:
---
```

Fortran includes support for complex number types and a set of operators and
intrinsics that work on these types. Some of those operations are complicated
and require runtime function calls to implement.

This document outlines a design for generating these operations using the MLIR
complex dialect while avoiding cross-platform ABI issues.

## FIR Representation

MLIR contains a complex dialect, similar to the Math dialect also used for
lowering some integer and floating point operations in Flang. Conversion between
fir.complex types and MLIR complex types is supported.

As a result at the FIR level, complex operations can be represented as
conversions from the fir.complex type to the equivalent MLIR complex type, use
of the MLIR operation and a conversion back.

This is similar to the way the math intrinsics are lowered, as proposed [here][1]

**Fortran**
```fortran
function pow_self(c)
  complex, intent(in) :: c
  complex :: pow_self
  pow_self = c ** c
end function pow_self
```

**FIR**
```
func.func @_QPpow_self(%arg0: !fir.ref<!fir.complex<4>>) -> !fir.complex<4> {
    %0 = fir.alloca !fir.complex<4>
    %1 = fir.load %arg0 : !fir.ref<!fir.complex<4>>
    %2 = fir.load %arg0 : !fir.ref<!fir.complex<4>>
    %3 = fir.convert %1 : (!fir.complex<4>) -> complex<f32>
    %4 = fir.convert %2 : (!fir.complex<4>) -> complex<f32>
    %5 = complex.pow %3, %4 : complex<f32>
    %6 = fir.convert %5 : (complex<f32>) -> !fir.complex<4>
    fir.store %6 to %0 : !fir.ref<!fir.complex<4>>
    %7 = fir.load %0 : !fir.ref<!fir.complex<4>>
    return %7 : !fir.complex<4>
  }
```

Some operations are currently missing in the MLIR complex dialect that we would
want to use here, such as powi and the hyperbolic trigonometry functions.
For the missing operations we call directly to libm where possible, for powi
we provide an implementation in the flang runtime.

## Lowering

The MLIR complex dialect supports lowering either by emitting calls to the
complex functions in libm (ComplexToLibm), or through lowering to the standard
dialect (ComplexToStandard). However, as MLIR has no target awareness, the
lowering to libm functions suffers from ABI incompatibilities on some platforms.
As such the custom lowering to the standard dialect is used. This may be
something to revisit in future if performance could be improved by using the
libm functions.

Similarly to the numerical lowering through the math dialect, certain MLIR
optimisations could violate the precise floating point model, so when that is
requested lowering manually emits calls to libm, rather than going through the
MLIR complex dialect.

The ComplexToStandard dialect does still call into libm for some floating
point math operations, however these don't have the same ABI issues as the
complex libm functions.

[1]: https://discourse.llvm.org/t/rfc-change-lowering-of-fortran-math-intrinsics/63971