C语言 如何将内联程序集写入位旋转

vwhgwdsa  于 2024-01-06  发布在  其他
关注(0)|答案(3)|浏览(113)

我在阅读gcc's guide on extended ASM的时候遇到了一个问题,编译器没有按照我想象的方式解释汇编。我想我应该用一个位旋转指令来尝试,因为这些在C中并不容易获得。
下面是我的C函数:

  1. int rotate_right(int num,int count) {
  2. asm (
  3. "rcr %[value],%[count]"
  4. : [value] "=r" (num)
  5. : [count] "r" (count)
  6. );
  7. return num;
  8. }

字符串
使用x86-64 gcc(trunk)-O 0编译输出:

  1. push rbp
  2. mov rbp, rsp
  3. mov DWORD PTR [rbp-4], edi
  4. mov DWORD PTR [rbp-8], esi
  5. mov eax, DWORD PTR [rbp-8]
  6. rcr eax,eax
  7. mov DWORD PTR [rbp-4], eax
  8. mov eax, DWORD PTR [rbp-4]
  9. pop rbp
  10. ret


我遇到的问题是,GCC将我的内联程序集理解为“将EAX旋转EAX,而不是旋转我想要的count参数。这是我 * 期望得到的:*

  1. push rbp
  2. mov rbp, rsp
  3. mov DWORD PTR [rbp-4], edi
  4. mov DWORD PTR [rbp-8], esi
  5. mov eax, DWORD PTR [rbp-8]
  6. mov ecx, DWORD PTR [rbp-4]
  7. rcr eax,ecx
  8. pop rbp
  9. ret

xkftehaa

xkftehaa1#

num使用+r约束,表明num将被读取而不仅仅是写入。否则gcc将假定num的先前值无关紧要,并仅选择一个未使用的寄存器将输出转储到其中。
您还必须对count使用c约束,因为ror指令的移位量必须在cl中。有关更详细的解释,请参阅other answer
在进行任何内联汇编编程之前,请仔细阅读the manual!要正确理解它有些棘手,并且有许多微妙的细节需要注意。
还要注意的是,即使内联汇编看起来工作正常,也可能是不正确的,例如,由于丢失了clobbers,而这些clobbers恰好不会影响与该特定编译器版本相关的任何内容,在该特定优化级别上,对于该特定版本的代码。所以要格外小心,尽可能避免使用它。
例如,在你的例子中,你可以只使用标准的C旋转习惯用法。只要启用了优化,编译器就会选择它:

  1. #include <limits.h>
  2. int rotate_right(int num,int count) {
  3. return ((unsigned)num >> count | num << CHAR_BIT * sizeof num - count);
  4. }

字符串

wlsrxk51

wlsrxk512#

首先,让我来解决标题中提到的问题。

  1. static inline int ror(int num, int count) {
  2. __asm__ ("ror\t%0, %b1" : "+r"(num) : "c"(count));
  3. return num;
  4. }
  5. ror(int, int):
  6. mov eax, edi
  7. mov ecx, esi
  8. ror eax, cl
  9. ret

字符串
下面我将解释一些细节,但基本上,你必须仔细阅读GCC文档。
引用OP,
我真的发现gcc的内联asm语法比Visual Studio的差得多。这几乎就像GCC试图阻止用户使用汇编语言。
从某种意义上说,它需要更多的时间来学习,但在你了解细节之后,它是各种低级编程和优化的强大工具。
我在实际程序中使用内联汇编的一个例子是使用rcpss指令。有一个Intel内部的指令,但是当前版本的GCC(12.1)在你使用它处理一个float时会产生相当可怕的代码。

  1. static inline float float_recip(float x) {
  2. if (__builtin_constant_p(x)) {
  3. return 1 / x;
  4. }
  5. __asm__ ("rcpss\t%0, %0" : "+x"(x));
  6. return x;
  7. }


这是实际的代码。当x的值在编译时已知时,__builtin_constant_p使常量替换成为可能。我故意将两个操作数相同以避免假依赖问题。
查看程序集在某处被调用时是如何生成的。

  1. float f(float x) {
  2. return float_recip(x) + float_recip(2);
  3. }
  4. f(float):
  5. rcpss xmm0, xmm0
  6. addss xmm0, DWORD PTR .LC0[rip]
  7. ret
  8. .LC0:
  9. .long 1056964608


您可以看到float_recip(2)被替换为0.5f常量,所有不必要的副本都消失了。
你不能用MSVC内联汇编来做这件事,除此之外,它甚至不支持64位。

展开查看全部
ifsvaxew

ifsvaxew3#

与GCC没有区别,用C编写它。

  1. #include <stdint.h>
  2. uint64_t rotl64 ( uint64_t x, int8_t r )
  3. {
  4. return (x << r) | (x >> (64 - r));
  5. }
  6. uint64_t rotl64_assembly(uint64_t num, int8_t count) {
  7. __asm__ ("rol\t%0, %b1" : "+r"(num) : "c"(count));
  8. return num;
  9. }

字符串
测试结果:

  1. rotl64(unsigned long, signed char):
  2. mov rax, rdi
  3. mov ecx, esi
  4. rol rax, cl
  5. ret
  6. rotl64_assembly(unsigned long, signed char):
  7. mov rax, rdi
  8. mov ecx, esi
  9. rol rax, cl
  10. ret


你可以在这里玩:https://godbolt.org/z/3xfs5EhWv

展开查看全部

相关问题