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...