
This patch adds an option to select the method for computing complex number division. It uses `LoweringOptions` to determine whether to lower complex division to a runtime function call or to MLIR's `complex.div`, and `CodeGenOptions` to select the computation algorithm for `complex.div`. The available option values and their corresponding algorithms are as follows: - `full`: Lower to a runtime function call. (Default behavior) - `improved`: Lower to `complex.div` and expand to Smith's algorithm. - `basic`: Lower to `complex.div` and expand to the algebraic algorithm. See also the discussion in the following discourse post: https://discourse.llvm.org/t/optimization-of-complex-number-division/83468 --------- Co-authored-by: Tarun Prabhu <tarunprabhu@gmail.com>
100 lines
4.3 KiB
Markdown
100 lines
4.3 KiB
Markdown
# 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.
|
|
|
|
The flang driver option `-fcomplex-arithmetic=` allows you to select whether
|
|
complex number division is lowered to function calls or to the `complex.div`
|
|
operation in the MLIR complex dialect. To avoid the ABI issues mentioned above,
|
|
the choice of function calls or the `complex.div` operation is made during the
|
|
lowering phase. The behavior of this option is as follows:
|
|
|
|
- `basic`: Lowers to `complex.div` and is converted to algebraic formulas. No
|
|
special handling to avoid overflow. NaN and infinite values are not handled.
|
|
- `improved`: Lowers to `complex.div` and is converted to Smith's algorithm. See
|
|
SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962). This
|
|
value offers improved handling for overflow in intermediate calculations, but
|
|
overflow may occur. NaN and infinite values are handled.
|
|
- `full`: Lowers to a call to runtime library functions. Overflow and non-finite
|
|
values are handled by the library implementation. This is the default value.
|
|
|
|
While [the same option in clang][2] allows specifying `promoted`, this is not
|
|
implemented in Flang. Also, in the case of `improved`, clang does not handle NaN
|
|
and infinite values, but Flang does. These behavioral differences arise because
|
|
the transformation of complex division calculations depends on the implementation
|
|
of ComplexToStandard, which may change in the future.
|
|
|
|
[1]: https://discourse.llvm.org/t/rfc-change-lowering-of-fortran-math-intrinsics/63971
|
|
[2]: https://clang.llvm.org/docs/UsersManual.html#cmdoption-fcomplex-arithmetic
|