如何根据架构一般指定 AX、EAX 或 RAX?

How to generically specify AX, EAX or RAX depending on architecture?

我正在尝试编写一些适用于 16、32、64、128 位英特尔机器的内联汇编(128 位是在未来的某个时候)。这个想法是使用通用寄存器名称,以便编译器或汇编器在 8086 上选择 AX-m16?),在 i686 上选择 EAX-m32),RAX 在 x86_64 (-m64) 上,依此类推。

我想我可以通过使用 a 注册 axeaxrax 来实现; b 对于 bxebxrbx;等等。但是,我无法在代码中调出 "generic" a 寄存器:

unsigned char* ptr = ...;
size_t size = ...;

__asm__ __volatile__
(
    "xor %a, %a"
    "lea ptr, %b\n"
    "lea size, %c\n"

    "1:\n"
    "movb 0, %b(%a)\n"
    "inc %a\n"
    "loop 1b\n"

    : /* no outputs */
    : "b" (ptr), "c" (size)
    : "a", "b", "c", "cc"
 );

编译器正在抱怨:

error: unknown register name 'a' in asm
    : "a", "b", "c", "cc"

如果我从 clobber 列表中删除 "a"(使其以 "b" 开头),那么我会得到:

error: unknown register name 'b' in asm
    : "b", "c", "cc"

我也试过在 clobber 列表中将其指定为 "%a",但我收到相同的错误消息。

根据 GCC 手册中的 Machine Constraintsaa register。所以我很确定我的名字是正确的。但我也很确定我做错了什么,或者我不太了解事情的大局。

问题:我如何一般指定 Intel 寄存器名称,以便内联汇编 "just works" 与 -m32-m64(甚至-m128那一天什么时候到来)?


OS X 是 10.8.5,x64,已完全修补。汇编程序是:

$ /usr/bin/as -v
Apple Inc version cctools-855, GNU assembler version 1.38

相关:在上面的代码中,我使用 lea 来避免机器字长。例如,我是这样写的,因此生成的代码是 movl size, %%ecx(32 位)或 movq size, %%rcx(64 位)。我不确定它是否是推荐的方法(或者它是否有效,因为我无法 运行 它)。请更正。

我已经尽力了,但不能比这更接近了。

#include <stddef.h>

void f(unsigned char* ptr, size_t size) {

__asm__ __volatile__
(
    "xor %%eax, %%eax\n\t"
    "lea ptr, %0\n\t"
    "lea size, %1\n\t"

    "1:\n\t"
    "movb 0, %0\n\t"
    "inc %%eax\n\t"
    "loop 1\n\t"

    : /* no outputs */
    : "b" (ptr), "c" (size)
    : "0", "1", "%eax", "cc"
 );

}

它与您的略有不同,但显示了正确的路径:显然,无论如何,"a" 不能用于 clobber 列表。所以我就这样做了。

gcc -S x.c -o- 编译这个模块显示我

... [ start of function, irrelevant here ]
#APP
# 5 "x.c" 1
    xor %eax, %eax
    lea ptr, %ebx
    lea size, %ecx
    1:
    movb 0, %ebx
    inc %eax
    loop 1

# 0 "" 2
#NO_APP
... [ end of function, irrelevant here ]

尽管如此,我还是希望能有所帮助。


编辑:根据 GCC 文档,这表明这是非法的。 (虽然我的编译器没有抱怨,不像我链接到的问题。)

所以让我们再试一次:

#include <stddef.h>
#include <stdint.h>

void f(unsigned char* ptr, size_t size) {

uint32_t junk;
size_t countdown;

__asm__ __volatile__
(
    "xor %0, %0\n\t"
    "lea ptr, %2\n\t"
    "lea size, %3\n\t"

    "1:\n\t"
    "movb 0, %2(%0)\n\t"
    "inc %0\n\t"
    "dec %1\n\t"
    "loopnz 1\n\t"

    : "=a" (junk) /* junk output */, "=c" (countdown)
    : "b" (ptr), "c" (size)
    : "cc", "memory"
 );

}

(顺便说一句,我在某处添加了一个 dec %1 和一个 loopnz...)

你就是做不到。 使用特定于体系结构的预定义和复制粘贴。更好地使用编译器内部函数或单独的 asm 文件。

其他一些有用的信息

  • 在苹果上,这种对我有用的预定义宏是 __LP64__ 它设置在 x86_64 架构上 所以你的代码可以看起来像:

     #ifdef __LP64__
         void myfunctionfor64bitArch()
     #else
         void myfunctionfor32bitArch()
     #endif
    

应该还有更正确的用法__x86_64__,但我没试过。

  • 想想你为什么这么想要跨平台汇编器?您的代码不能很大:ABI 不同,请参阅 http://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions 所以您的代码不能以通用形式很长,汇编器太不同了[=​​13=]

  • 在最新版本的 clang Visual-Studio 风格的汇编程序中工作。恕我直言,它更方便。尝试

    __asm
    {
         mov eax, your_variable    ; Get first argument
    }
    

clang 的有趣之处在于它适用于 x64,而在原始工作室中它仅适用于 32 位

您似乎没有在代码中检查是否 (size == 0),所以我将遵循该断言。 __volatile__ 如果任何输出参数被更新并且随后没有被使用,那么 __volatile__ 将是必需的 - 编译器不知道内存 ptr[size] 已经归零,并且看不到任何副作用,它会简单地省略asm 块。

我们使用 'temporary' 个参数,因此我们可以更新值然后丢弃它们。编译器知道临时参数已被修改,并且看到它们再也不会被使用,不需要维护这些寄存器。我建议像这样:

{
    size_t tmp_size = size;

    __asm__ __volatile__ (

        "%=:\n\t" /* generate a unique label. */

        "sub , %0\n\t"
        "movb [=10=], %1(%0)\n\t" /* 0 -> ptr[size - 1] .. ptr[0] */
        "jnz %=b\n\t" /* jump 'back' */

        : "+r" (tmp_size) : "r" (ptr) : "memory", "cc");
}

这也让编译器可以选择寄存器,这是更可取的。我认为这不会为您提供通用的 64 位、32 位(或 16 位)代码。为此,您可能需要查看 operand modifiers。否则,您可能需要在 64 位和 32 位版本的说明中分别添加 'q' 和 'l' 后缀。

顺便说一句,sub/jnz 在现代处理器上通常比 inc/dec(部分标志停止危险)和 loop(复杂微码 'stuff')更好。