如何在 C 中编写旋转代码以编译成 `ror` x86 指令?

How to write rotate code in C to compile into the `ror` x86 instruction?

我有一些代码可以轮换我的数据。我知道 GAS 语法有一个可以循环整个字节的汇编指令。然而,当我尝试遵循 Best practices for circular shift (rotate) operations in C++ 上的任何建议时,我的 C 代码编译成至少 5 条指令,这些指令占用了三个寄存器——即使在使用 -O3 编译时也是如此。也许这些是 C++ 中的最佳实践,而不是 C 中的最佳实践?

无论哪种情况,我如何强制 C 使用 ROR x86 指令来循环我的数据?

未被编译为旋转指令的精确代码行是:

value = (((y & mask) << 1 ) | (y >> (size-1))) //rotate y right 1
       ^ (((z & mask) << n ) | (z >> (size-n))) // rotate z left by n
// size can be 64 or 32, depending on whether we are rotating a long or an int, and 
// mask would be 0xff or 0xffffffff, accordingly

我不介意使用 __asm__ __volatile__ 进行此旋转,如果这是我必须做的。但我不知道如何正确地这样做。

你的宏为我编译成一条 ror 指令...具体来说,我编译了这个测试文件:

#define ROR(x,y) ((unsigned)(x) >> (y) | (unsigned)(x) << 32 - (y))
unsigned ror(unsigned x, unsigned y)
{
    return ROR(x, y);
}

作为 C,使用 gcc 6,-O2 -S,这是我得到的程序集:

    .file   "test.c"
    .text
    .p2align 4,,15
    .globl  ror
    .type   ror, @function
ror:
.LFB0:
    .cfi_startproc
    movl    %edi, %eax
    movl    %esi, %ecx
    rorl    %cl, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   ror, .-ror
    .ident  "GCC: (Debian 6.4.0-1) 6.4.0 20170704"
    .section    .note.GNU-stack,"",@progbits

请尝试做同样的事情,并报告你得到的程序集。如果您的测试程序与我的有很大不同,请告诉我们它有何不同。如果您使用不同的编译器或不同版本的 GCC,请准确告诉我们是哪一个。

顺便说一句,当我编译 accepted answer for "Best practices for circular shift (rotate) operations in C++" 中的代码时,我得到了与 C.

相同的汇编输出

您可能需要更具体地说明您正在旋转的整数类型/宽度,以及您是固定旋转还是可变旋转。 ror{b,w,l,q}(8、16、32、64 位)具有 (1)imm8%cl 寄存器的形式。例如:

static inline uint32_t rotate_right (uint32_t u, size_t r)
{
    __asm__ ("rorl %%cl, %0" : "+r" (u) : "c" (r));
    return u;
}

我还没有测试过这个,它只是在我的脑海中。而且我确信可以使用多个约束语法来优化使用常量 (r) 值的情况,因此 %e/rcx 保持不变。


如果您使用的是最新版本的 gcc 或 clang(甚至是 icc)。内在函数 header <x86intrin.h>,可以提供 __ror{b|w|d|q} 内在函数。我没试过。

你的编译器几岁了?正如我在 linked 问题中指出的那样,UB 安全变量计数旋转习惯用法(带有额外的计数掩码)混淆了旧的编译器,例如 4.9 之前的 gcc。由于您没有屏蔽班次计数,因此应该可以使用更旧的 gcc 来识别它。

你的大表达式可能会混淆编译器。为旋转写一个内联函数,然后调用它,比如

value = rotr32(y & mask, 1) ^ rotr32(z & mask, n);

更具可读性,并且可能有助于阻止编译器尝试以错误的顺序执行操作并在将其识别为轮换之前破坏习语。


Maybe those are best practices in C++, and not in C?

我对 linked 问题的回答清楚地表明这是 C 和 C++ 的最佳实践。它们是不同的语言,但根据我的测试,它们为此完全重叠。

这是 the Godbolt link 的一个版本,使用 -xc 编译为 C,而不是 C++。我在原始问题的 link 中有几个 C++isms,用于试验旋转计数的整数类型。

与来自最佳实践答案的原始 link 一样,它有一个使用 x86 内在函数(如果可用)的版本。 clang 似乎没有在 x86intrin.h 中提供任何内容,但其他编译器有 _rotl / _rotr 用于 32 位旋转,其他大小可用。

实际上,我在最佳实践问题的答案中详细讨论了旋转内在函数,而不仅仅是在 godbolt link 中。除了代码块之外,您是否甚至阅读了那里的答案? (如果你这样做了,你的问题并没有反映出来。)


使用内在函数,或您自己的内联函数中的习惯用法,比使用内联汇编 好得多。除其他外,Asm 击败了持续传播。此外,如果您使用 -march=haswell-mbmi2 编译,编译器可以使用 BMI2 rorx dst, src, imm8 通过一条指令进行复制和旋转。编写一个可以使用 rorx 进行立即计数旋转但使用 ror r32, cl 进行可变计数旋转的内联 asm 旋转要困难得多。您可以尝试使用 _builtin_constant_p(),但 clang 会在内联之前对其进行评估,因此对于元编程样式选择要使用的代码基本上没有用。虽然它适用于 gcc。但是最好不要使用内联 asm,除非你已经用尽所有其他途径(比如询问 SO)来避免它。 https://gcc.gnu.org/wiki/DontUseInlineAsm

有趣的事实:gcc x86intrin.h 中的旋转函数 只是使用 gcc 识别的旋转习惯用法的纯 C。除了 16 位循环,他们使用 __builtin_ia32_rolhi.

最佳方式:

#define rotr32(x, n) (( x>>n  ) | (x<<(64-n)))
#define rotr64(x, n) (( x>>n  ) | (x<<(32-n)))

更通用:

#define rotr(x, n) (( x>>n  ) | (x<<((sizeof(x)<<3)-n)))

它使用与下面的 asm 版本完全相同的代码编译(在 GCC 中)。

对于 64 位:

__asm__ __volatile__("rorq %b1, %0" : "=g" (u64) : "Jc" (cShift), "0" (u64));

static inline uint64_t CC_ROR64(uint64_t word, int i)
{
    __asm__("rorq %%cl,%0"
        :"=r" (word)
        :"0" (word),"c" (i));
    return word;
}