https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82261

--- Comment #6 from Peter Cordes <pcordes at gmail dot com> ---
(In reply to Paweł Bylica from comment #5)
> The form that is well defined for a shift count of 0 is not turned into
> SHLD/SHRD either. SHLD/SHRD with a count of 0 leave the destination
> unchanged, which is the s == 0 result here:

Using __int128 with masked shift-count is another way to make it well-defined
for a count of 0, and GCC16 does optimize that.   But that requires GNU
extensions for 64-bit funnel-shifts; only narrower types can use pure C.  (Oh,
this is basically the same as what I commented in 2022, and for uint32_t
funnel-shifts GCC still does a horrible job, merging into a single register
instead of "resorting" to SHLD / SHRD.)

unsigned long fshl_wide(unsigned long hi, unsigned long lo, unsigned s)
{
    unsigned __int128 wide = (unsigned __int128)hi << 64 | lo;
  wide <<= (s&63);
  return wide >> 64;
}

unsigned long fshr_wide(unsigned long hi, unsigned long lo, unsigned s)
{
    unsigned __int128 wide = (unsigned __int128)hi << 64 | lo;
  return wide >> (s&63);
}

GCC16.2 -O2 https://godbolt.org/z/1Tqzrz7P4

fshl_wide:
        movq    %rdi, %rax
        movl    %edx, %ecx
        shldq   %cl, %rsi, %rax
        ret
fshr_wide:
        movl    %edx, %ecx
        movq    %rsi, %rax
        shrdq   %cl, %rdi, %rax
        ret

The Godbolt link also includes a test loop over shift counts from 0 to 63,
showing it gives identical results to your ternary version.

Clang optimizes either version, so I'd recommend this if your library already
uses GNU extensions or if you want to put it inside a #ifdef __SIZEOF_INT128__
or use C23 _BitInt(128)

This is a missed-optimization that should be fixed, but as a workaround for
existing GCC versions in the wild...

Reply via email to