On Sat, Sep 12, 2026 at 05:22:47PM +0800, Yuao Ma wrote:
> Jakub recently implemented most of P3793R2; this patch completes it by
> implementing the [simd] support and defining the FTM.
>
> The implementation is straightforward within the existing SIMD
> framework. Built and tested on x86_64-linux. OK for trunk?
I admit I know next to nothing about the libstdc++ simd implementation,
so will defer to Matthias and Jonathan, but I wonder if at least
for the case of scalar __s it shouldn't be implemented pretty much
the same as the scalar std::shl/std::shr, i.e. conditionals on
the __s value vs. number of bits in the element of the _Vp vector
resp. 0 and doing << or >> (i.e. vector shift by scalar).
So, if it is called with constant __s, it will be optimized into
either left or right shift of the vector or 0 or all ones vector
(or, for the 0 vs. all ones one needs to compare all elements against 0
if signed, so blend based on the condition). Without relying on the
compiler SLP optimizing it.
The shl/shr of vector by vector is harder to do fully vectorized, in
that case I'd recommend the if constexpr (is_signed_v <...>) and
probably performing <</>> vector/vector shifts with say masked operand
and then blending everything together based on the comparisons.
In pure generic vectors (as I said, I really don't know what <simd>
uses for abstractions), say
using V = int [[gnu::vector_size (4 * sizeof (int))]];
V shl (V __x, int __s)
{
constexpr auto _Nd = __SIZEOF_INT__ * __CHAR_BIT__;
if (__s < 0)
return __s > -_Nd ? __x >> -__s : static_cast<V>(__x < 0);
return __s < _Nd ? __x << __s : V{};
}
(note __x < 0 for GNU vectors results in 0 or all ones elements depending
on the comparison).
For the other one roughly
V shl (V __x, V __s)
{
constexpr auto _Nd = __SIZEOF_INT__ * __CHAR_BIT__;
V __r = __s < _Nd ? __x << (__s & (_Nd - 1)) : V{};
if constexpr (is_signed_v <decltype(V[0])>)
__r = __s < 0 ? __s > -_Nd ? __x >> ((-__s) & (_Nd - 1)) :
static_cast<V>(__x < 0) : __r;
return __r;
}
The latter is gimplified into
_Nd = 32;
_1 = __s <= { 31, 31, 31, 31 };
_2 = __s & { 31, 31, 31, 31 };
_3 = __x << _2;
__r = VEC_COND_EXPR <_1, _3, { 0, 0, 0, 0 }>;
_4 = __s < { 0, 0, 0, 0 };
_5 = __s >= { -31, -31, -31, -31 };
_6 = -__s;
_7 = _6 & { 31, 31, 31, 31 };
_8 = __x >> _7;
_9 = __x < { 0, 0, 0, 0 };
_10 = VEC_COND_EXPR <_9, { -1, -1, -1, -1 }, { 0, 0, 0, 0 }>;
_11 = VEC_COND_EXPR <_5, _8, _10>;
__r = VEC_COND_EXPR <_4, _11, __r>;
so there are no conditional jumps, everything is done on vectors here
(except the _Nd initialization).
I have no idea if one needs to implement this several times in <simd>
included headers, once generically using lambda as you did for vectors that
can't use gnu::vector_size (if there is something like that,
gnu::vector_size doesn't support e.g. number of elements that isn't power of
two) and once optimized for the generic vectors.
Jakub