我很难让它发挥作用:
我尝试过以下方法:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntax\n"
"xor eax, eax \n"
"inc eax \n"
"myloop: \n"
"shr %0, 1 \n"
"adc eax, eax \n"
"jnc short myloop \n"
"mov %1, %0 \n"
: [i] "=r"(i), [n] "=r"(n));;
return n;
}
我会得到:
Line 11: Char 14: error: unknown token in expression
"shr %0, 1 \n"
^
<inline asm>:5:5: note: instantiated into assembly here
shr %edx, 1
^
显然编译器用 %register
替换了 %0
,但仍然保留 '%'
...
因此,我决定将 %0
替换为 edx
,将 %1
替换为 ecx
:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntax\n"
"xor eax, eax \n"
"inc eax \n"
"myloop: \n"
"shr edx, 1 \n"
"adc eax, eax \n"
"jnc short myloop \n"
"mov ecx, edx \n"
: [i] "=r"(i), [n] "=r"(n));;
return n;
}
并得到结果错误:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==31==ERROR: AddressSanitizer: SEGV on unknown address 0x0001405746c8 (pc 0x00000034214d bp 0x7fff1363ed90 sp 0x7fff1363ea20 T0)
==31==The signal is caused by a READ memory access.
#1 0x7f61ff3970b2 (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
AddressSanitizer can not provide additional info.
==31==ABORTING
我怀疑编译器优化了东西并内联了被调用的函数(所以不是ret),但仍然不知道我该怎么做。
注意:我无法将编译器从 clang 更改为 gcc,因为它不是我,而是使用 clang 11 的远程服务器。我也已经 read this link但它已经很老了(2013 年),如果从那时起一切都没有改变,我会感到惊讶。
编辑:根据 Peter Cordes 的出色回答,我能够让它工作得更好一点:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntax noprefix\n"
"xor rax,rax \n"
"inc rax \n"
"myloop: \n"
"shr %V0, 1 \n"
"adc eax, eax \n"
"jnc short myloop \n"
"mov %V0, rax \n"
".att_syntax"
: [i] "=r"(i));;
return i;
}
但是有两件事:
1/我必须将 eax
更改为 rax
,因为 %V0
需要 64 位 (r13
),这很奇怪,因为 i
应该只占 32 位 (uint32_t)。
2/我没有得到所需的输出:
input is : 00000010100101000001111010011100
output is: 93330624 (00000101100100000001110011000000)
expected: 964176192 (00111001011110000010100101000000)
注意:我测试了 "mov %V0, 1\n"
并正确地得到了 1
作为输出,这证明了替换在某种程度上是有效的。
最佳答案
我不知道有什么好的方法可以做到这一点,我推荐 GNU C 内联汇编的 AT&T 语法(或方言替代add {%1,%0 | %0,%1}
所以它对 GCC 来说是双向的。)像 -masm=intel
这样的选项不会像 GCC 那样用 clang 来替换裸寄存器名称。
(更新:clang 14 更改:How to set gcc or clang to use Intel syntax permanently for inline asm() statements?)
How to generate assembly code with clang in Intel syntax?是关于用于 -S
output 的语法,与 GCC 不同的是,它没有连接到编译器的内联 asm 输入的语法。 --x86-asm-syntax=intel
的行为没有改变:它仍然以 Intel 语法输出,并且不能帮助您使用内联汇编。
您可以滥用%V0
或%V[i]
(而不是%0
或%[i ]
)在模板中打印“裸”完整注册名称 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers ,但这很糟糕,因为它只打印完整寄存器名称。即使对于选择 EAX 的 32 位 int,它也会打印 RAX 而不是 EAX。
(它也不适用于 "m"
内存操作数来获取 dword ptr [rsp + 16]
或任何编译器选择的寻址模式,但它更好总比没有好。尽管在我看来,这并不比仅使用 AT&T 语法更好。)
或者您可以选择像 "=a"(var)
这样的硬寄存器,然后显式使用 EAX 而不是 %0
。但这更糟糕,并且抵消了约束系统的一些优化优势。
您的模板中仍然需要“.intel_syntax noprefix\n”
,并且您应该以“.att_syntax”结束模板
将汇编器切换回 AT&T 模式以汇编稍后编译器生成的 asm。 (如果您希望代码与 GCC 一起工作,则需要!clang 的内置汇编器在汇编之前不会将内联 asm 文本合并到一个大的 asm 文本文件中,它会直接转为编译器生成的指令的机器代码。)
显然告诉编译器它可以使用 "=r"
选择任何寄存器,然后实际使用您自己的硬编码选择,当编译器选择不同时,将创建未定义的行为。您将踩到编译器的脚趾并破坏它稍后想要使用的值,并让它从错误的寄存器中获取垃圾作为输出。我不知道你为什么费心在你的问题中包含这一点;由于同样相当明显的原因,这会以与 AT&T 语法完全相同的方式中断。
关于x86 - 如何使用 Clang 11、intel 语法和替换变量进行内联汇编,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66532417/