Background
The elementwise builtins expose ways of performing certain operations on each element of a vector. They essentially expose various LLVM intrinsics: __builtin_elementwise_abs(float2) → llvm.fabs.v2f32, for example.
In my testing of these builtins with scalar integer types, the behaviour is often confusing and sometimes incorrect.
The first thing I noticed was that scalar char and short types were being promoted to int. For some operations this is “fine” as the operation is the same whether or not it’s been promoted/truncated. With optimizations, the promotions and truncations are often eliminated and the intrinsic is performed in the intended type.
However, for other operations, this promotion behaviour results in subtly wrong behaviour:
__builtin_elementwise_bitreverse((char)0b10010101); // returns -1
__builtin_elementwise_popcount((char)0b10010101); // returns 28
__builtin_elementwise_add_sat((char)127, (char)2); // returns 129 (int) or -127 (char)
All of the above are a result of the operation being sign-extended to int.
Secondly, what could a user reasonably expect from __builtin_elementwise_add_sat(unsigned char, unsigned char)? Currently, it is lowered to the signed llvm.sadd.sat.i32 which I don’t think is correct, nor intuitive.
The behaviour also when mixing types of different signedness is also unintuitive. What should __builtin_elementwise_add_sat(char, unsigned char) do? Currently it’s promoting both to int, but (skipping ahead a bit) if we didn’t promote to int, we need to decide upon a sensible behaviour here.
Note also that the documentation specifies that:
For scalar types, consider the operation applied to a vector with a single element.
This is not correct since with vector types (at least, ext_vector_type vector types) there is no promotion that takes place. There is also no mixing of signs allowed:
typedef __attribute__((ext_vector_type(16))) char char16;
typedef __attribute__((ext_vector_type(16))) unsigned char uchar16;
__builtin_elementwise_add_sat((char16)127, (char16)2); // <16 x i8> splat (i8 127)
__builtin_elementwise_add_sat((char16)127, (uchar16)2); // error
Proposal
Therefore I propose that these builtins stop promoting scalar integer types entirely. The operation is kept at the type the user requested. Luckily, we could point to the documentation we’ve already got which says scalars behave like vectors.
I would also propose that we stop accepting scalar integers with mixed signs, for the same reason.
In the PR which spawned this RFC, @efriedma-quic says that searching GitHub for users of these builtins shows one use, which is only using the builtins with vector types. Therefore we don’t expect this change to affect many users.