如何根据架构一般指定 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
注册 ax
、eax
或 rax
来实现; b
对于 bx
、ebx
或 rbx
;等等。但是,我无法在代码中调出 "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 Constraints,a
是 a 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')更好。
我正在尝试编写一些适用于 16、32、64、128 位英特尔机器的内联汇编(128 位是在未来的某个时候)。这个想法是使用通用寄存器名称,以便编译器或汇编器在 8086 上选择 AX
(-m16
?),在 i686 上选择 EAX
(-m32
),RAX
在 x86_64 (-m64
) 上,依此类推。
我想我可以通过使用 a
注册 ax
、eax
或 rax
来实现; b
对于 bx
、ebx
或 rbx
;等等。但是,我无法在代码中调出 "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 Constraints,a
是 a 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')更好。