| # Complex Operations | |
| 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. If you specify | |
| `-ffast-math`, the lowering is the same as specifiying | |
| `-fcomplex-arithmetic=basic`. | |
| [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 |