Hey all,
I volunteered to put together a proposal regard complex in LLVM.
Consider the following to be a strawman meant to spark discussion. It's
based on real-world experience with complex but is not expected to cover
all use-cases.
Proposal to Support Complex Operations in LLVM
----------------------------------------------
Abstract
Several vendors and individuals have proposed first-class complex
support in LLVM. Goals of this proposal include better optimization,
diagnostics and general user experience.
Introduction and Motivation
Recently the topic of complex numbers arose on llvm-dev with several
developers expressing a desire for first-class IR support for complex
[1] [2]. Interest in complex numbers in LLVM goes back much further
[3].
Currently clang chooses to represent standard types like "double
complex" and "std::complex<float>" as structure types containing two
scalar fields, for example {double, double}.
To supplement this, on some ABIs (e.g., PPC64 ELFv2), Clang represents
complex numbers as two-element arrays. This makes backend pattern
matching even more fragile because the representation is different for
different ABIs. Having an IR type would resolve this issue and making
writing IR-level transformations that operate on complex numbers more
robust.
Consequently, arrays of
complex type are represented as, for example, [8 x {double, double}].
This has consequences for how clang converts complex operations to LLVM
IR. In general, clang emits loads of the individual real and imaginary
parts and feeds them into arithmetic operations. Vectorization results
in many shufflevector operations to massage the data into sequences
suitable for vector arithmetic.
All of the real/imaginary data manipulation obscures the underlying
arithmetic. It makes it difficult to reason about the algebraic
properties of expressions. For expressiveness and optimization ability,
it will be nice to have a higher-level representation for complex in
LLVM IR. In general, it is desirable to defer lowering of complex until
the optimizer has had a reasonable chance to exploit its properties.
I think that it's really important that we're specific about the goals
here. Exactly what kinds of optimizations are we aiming to (more-easily)
enable? There certainly exists hardware with instructions that help
vectorize complex multiplication, for example, and having a builtin
complex type would make writing patterns for those instructions easier
(as opposed to trying to build matching into the SLP vectorizer or
elsewhere). This probably makes constant-folding calls to complex libm
functions easier.
Does this make loop vectorization easier or harder? Do you expect the
vectorizer to form vectors of these complex types?
First-class support for complex can also improve the user experience.
Diagnostics could express concepts in the complex domain instead of
referring to expressions containing shuffles and other low-level data
manipulation. Users that wish to examine IR directly will see much less
gobbbledygook and can more easily reason about the IR.
Types
This proposal introduces new Single Value types to represent complex
numbers.
c32 - like float complex or std::complex<float>
c64 - like double complex or std::complex<double>
We defer a c128 type (like std::complex<long double>) for a future
RFC.
Why? I'd prefer we avoid introducing even more special cases. Is there
any reason why we should not define "complex <scalar type>", or to be
more restrictive, "complex <floating-point type>"? I really don't like
the idea of excluding 128-bit complex types, and I think that we can
have a generic facility.
-Hal