用 C 语言实现 SHLD/SHRD 指令

xiv*_*r77 5 c x86 assembly compiler-optimization bigint

我正在尝试在不使用内联汇编的情况下有效地实现SHLDSHRD说明x86

uint32_t shld_UB_on_0(uint32_t a, uint32_t b, uint32_t c) {
    return a << c | b >> 32 - c;
}
Run Code Online (Sandbox Code Playgroud)

似乎有效,但当c == 0第二个移位的操作数变为时会调用未定义的行为32SHLD第三个操作数的实际指令0被明确定义为不执行任何操作。(https://www.felixcloutier.com/x86/shld

uint32_t shld_broken_on_0(uint32_t a, uint32_t b, uint32_t c) {
    return a << c | b >> (-c & 31);
}
Run Code Online (Sandbox Code Playgroud)

不会调用未定义的行为,但当c == 0结果是a | b而不是a.

uint32_t shld_safe(uint32_t a, uint32_t b, uint32_t c) {
    if (c == 0) return a;
    return a << c | b >> 32 - c;
}
Run Code Online (Sandbox Code Playgroud)

做了预期的事情,但gcc现在放了一个je. clang另一方面,它足够聪明,可以将其转换为单个shld指令。

有没有什么方法可以在不使用内联汇编的情况下正确高效地实现呢?

为什么gcc这么努力却不放shld呢?该shld_safe尝试被gcc11.2 -O3 翻译为(Godbolt):

shld_safe:
        mov     eax, edi
        test    edx, edx
        je      .L1
        mov     ecx, 32
        sub     ecx, edx
        shr     esi, cl
        mov     ecx, edx
        sal     eax, cl
        or      eax, esi
.L1:
        ret
Run Code Online (Sandbox Code Playgroud)

当 clang 这样做时,

shld_safe:
        mov     ecx, edx
        mov     eax, edi
        shld    eax, esi, cl
        ret
Run Code Online (Sandbox Code Playgroud)

小智 1

据我使用 gcc 9.3 (x86-64) 进行测试,它将以下代码转换为shldqshrdq

uint64_t shldq_x64(uint64_t low, uint64_t high, uint64_t count) {
  return (uint64_t)(((((unsigned __int128)high << 64) | (unsigned __int128)low) << (count & 63)) >> 64);
}

uint64_t shrdq_x64(uint64_t low, uint64_t high, uint64_t count) {
  return (uint64_t)((((unsigned __int128)high << 64) | (unsigned __int128)low) >> (count & 63));
}
Run Code Online (Sandbox Code Playgroud)

另外,gcc -m32 -O3将以下代码翻译为shldshrd。(不过,我还没有使用 gcc (i386) 进行测试。)

uint32_t shld_x86(uint32_t low, uint32_t high, uint32_t count) {
  return (uint32_t)(((((uint64_t)high << 32) | (uint64_t)low) << (count & 31)) >> 32);
}

uint32_t shrd_x86(uint32_t low, uint32_t high, uint32_t count) {
  return (uint32_t)((((uint64_t)high << 32) | (uint64_t)low) >> (count & 31));
}
Run Code Online (Sandbox Code Playgroud)

(我刚刚阅读了 gcc 代码并编写了上述函数,即我不确定它们是您期望的函数。)