Dealing with AVX and AVX512
AVX is a bit easier for PowerISA and the ELF V2 ABI. First we have
lots (64) of vector registers and a superscalar vector pipeline (can execute
two or more independent 128-bit vector operations concurrently). Second the ELF
V2 ABI was designed to pass and return larger aggregates in vector
registers:
Up to 12 qualified vector arguments can be passed in
v2–v13.
A qualified vector argument corresponds to:
A vector data type
A member of a homogeneous aggregate of multiple like data types
passed in up to eight vector registers.
Homogeneous floating-point or vector aggregate return values
that consist of up to eight registers with up to eight elements will
be returned in floating-point or vector registers that correspond to
the parameter registers that would be used if the return value type
were the first input parameter to a function.
So the ABI allows for passing up to three structures each
representing 512-bit vectors and returning such (512-bit) structures all in VMX
registers. This can be extended further by spilling parameters (beyond 12 X
128-bit vectors) to the parameter save area, but we should not need that, as
most intrinsics only use 2 or 3 operands.. Vector registers not needed for
parameter passing, along with an additional 8 volatile vector registers, are
available for scratch and local variables. All can be used by the application
without requiring register spill to the save area. So most intrinsic operations
on 256- or 512-bit vectors can be held within existing PowerISA vector
registers.
For larger functions that might use multiple AVX 256 or 512-bit
intrinsics and, as a result, push beyond the 20 volatile vector registers, the
compiler will just allocate non-volatile vector registers by allocating a stack
frame and spilling non-volatile vector registers to the save area (as needed in
the function prologue). This frees up to 64 vectors (32 x 256-bit or 16 x
512-bit structs) for code optimization.
Based on the specifics of our ISA and ABI we will not use
__vector_size__ (32) or (64) in the PowerPC implementation of
__m256 and __m512
types. Instead we will typedef structs of 2 or 4 vector (__vector) fields. This
allows efficient handling of these larger data types without requiring new GCC
language extensions or vector builtins. For example:
This requires a different syntax for operations
where the 128-bit vector chunks are explicitly referenced.
For example:
But this creates a new issue because
the C language does not allow direct casts between structs.
This can be an issue where the intrinsic interface type is not the correct type for the operation.
For example AVX2 integer operations:
For the AVX2 intrinsic _mm256_add_epi16
we need to cast the input vectors
of 64-bit long long (__m256i) into vectors of 16-bit short
(__v16hi) before the overloaded add operations.
Here we need to use a pointer reference cast.
For example:
As this and related examples are inlined,
we expect the compiler to recognize this
is a "nop cast" and avoid generating any additional instructions.
In the end we should try
to use the same type names and definitions as the
GCC X86 intrinsic headers where possible. Where that is not possible we can
define new typedefs that provide the best mapping to the underlying PowerISA
hardware.