如何使用 Clang 11、intel 语法和替换变量进行内联汇编

How to inline-assembly with Clang 11, intel syntax and substitution variables

我费了很大的劲才让它生效:


我试过以下方法:

 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
    ^

显然编译器将 %0 替换为 %register,但仍保持 '%'...


因此我决定将 %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 内联 asm 的 AT&T 语法(或方言替代 add {%1,%0 | %0,%1} 所以它对 GCC 有两种方式。)像 -masm=intel 不要像 GCC 那样用 clang 替换裸寄存器名称。

How to generate assembly code with clang in Intel syntax? 是关于用于 -S output 的语法,与 GCC 不同的是,它与编译器的 inline-asm 输入语法无关. --x86-asm-syntax=intel 的行为没有 改变:它仍然以 Intel 语法输出,并且对内联汇编没有帮助。


您可以滥用%V0%V[i](而不是%0%[i])在template https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers,但这很糟糕,因为它只打印 full 寄存器名称。即使对于选择 EAX 的 32 位 int,它也会打印 RAX 而不是 EAX

(对于 "m" 内存操作数获取 dword ptr [rsp + 16] 或任何编译器选择的寻址模式也不起作用,但总比没有好。尽管 IMO 它并不比仅使用 AT&T 好语法。)


或者您可以选择像 "=a"(var) 这样的硬寄存器,然后直接使用 EAX 而不是 %0。但这更糟,并且破坏了约束系统的一些优化优势。

您的模板中仍然需要 ".intel_syntax noprefix\n",并且您 应该 ".att_syntax" 结束您的模板以切换回 assembler到 AT&T 模式到 assemble 后来编译器生成的 asm。 (如果你想让你的代码与 GCC 一起工作,则需要!clang 的内置 assembler 在汇编之前不会将你的内联 asm 文本合并到一个大的 asm 文本文件中,它直接进入编译器生成指令的机器代码.)


显然告诉编译器它可以使用 "=r" 选择任何寄存器,然后实际使用您自己的硬编码选择,当编译器选择不同时会产生未定义的行为。您将踩到编译器的脚趾并损坏它稍后要使用的值,并让它从错误的寄存器中获取垃圾作为输出。 IDK 为什么你费心把它包括在你的问题中;出于同样相当明显的原因,这将以与 AT&T 语法完全相同的方式中断。