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

Reply via email to