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

时间:2017-07-14 17:39:51

标签: c assembly x86 gas

我有一些代码可以旋转我的数据。我知道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__进行旋转,如果这是我必须做的事情。但我不知道如何正确地做到这一点。

4 个答案:

答案 0 :(得分:7)

你的宏为我编译了一条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。

答案 1 :(得分:3)

你的编译器多大了?正如我在链接问题中所指出的那样,UB安全变量计数旋转习语(带有额外的&amp;掩盖计数)会混淆旧的编译器,如4.9之前的gcc。由于您没有屏蔽移位计数,因此应该使用更旧的gcc识别它。

您的大表达可能会让编译器感到困惑。为旋转编写内联函数,并调用它,如

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

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

  

也许那些是C ++的最佳实践,而不是C?

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

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

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

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

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

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

答案 2 :(得分:0)

您可能需要更加具体一点 整体类型/宽度,以及是否有固定或可变旋转。 II = 4501 + JJ*10 (8,16,32,64位)具有ror{b,w,l,q}(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)不会被单独使用。

如果您使用的是最新版本的gcc或clang(甚至是icc)。内在函数标头%e/rcx可能提供<x86intrin.h>内在函数。我还没试过。

答案 3 :(得分:-2)

最佳方式:

#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;
}