1

在下面的:

#include <string.h>

struct cpuidOut
{
   long a ;
   long b ;
   long c ;
   long d ;
} ;

void callcpuid( cpuidOut * p, long a )
{
   memset( p, 0xFF, sizeof(*p) ) ;
   p->a = a ;

   __asm__ ( "cpuid"
             : "+a"(p->a), "=b"(p->b), "=c"(p->c), "=d"(p->d)  // output
             :                                                 // no (only) inputs
             : "a", "b", "c", "d"                              // clobbered registers
           ) ;
}

我得到一个编译错误:

t.C:22: error: unknown register name 'd' in 'asm'
t.C:22: error: unknown register name 'c' in 'asm'
t.C:22: error: unknown register name 'b' in 'asm'
t.C:22: error: unknown register name 'a' in 'asm'

(来自 g++ 或 clang++ 的相同类型的错误)

这让我很吃惊,因为我在 gcc 文档的 i386 clobbers 中看到了 a、b、c、d

http://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints

我可以通过明确来修复clobber约束:

"%rax", "%rbx", "%rcx", "%rdx"                  // clobbered registers

但我很惊讶我必须这样做?我只需要它在 x86_64 上工作,但我认为“a”、“b”、“c”、“d”样式约束会更好,以防以后在 i386 上也需要代码。

编辑:我最初发布了错误的asm,经过几次调整以使其正常工作,并在此过程中弄混了。上面的 asm 与我最初的问题一致,但是会导致编译器无法调度 A 寄存器的编译错误。这似乎可行:

void callcpuid( cpuidOut * p, long a, long b )
{
   __asm__ ( "cpuid"
             : "=a"(p->a), "=b"(p->b), "=c"(p->c), "=d"(p->d)  // output
             : "0"(a), "1"(b)                                  // inputs
           ) ;
}

但请注意,它完全避免了对任何 clobber 约束的要求,因为所有 clobber 都是输出。这可能是正确的方法,尽管在阅读 gcc 文档后我仍然感到惊讶,我不能在 clobber 约束中使用通用的 reg 名称“a”、“b”、“c”、“d”,而不是必须使用 "%eax", "%rax", ...

4

2 回答 2

1

这让我很吃惊,因为我在 gcc 文档的 i386 clobbers 中看到了 a、b、c、d

破坏者不是约束。

当您告诉 GCC 为 insn 操作数分配寄存器时,会使用约束,这些约束定义了从中绘制寄存器的可接受的寄存器类。

另一方面,在输入/输出约束不明显的情况下,clobbers 告诉 GCC 有关由 insns 修改的寄存器,例如修改固定寄存器的 insn,这不是它的操作数或当您在内联程序集中使用硬编码的寄存器名称时。

这是必需的,因此在执行内联汇编之前,GCC 可以隐藏值,它恰好保存在被破坏的寄存器中。

PS。对于输入输出操作数,您可以使用"+"修饰符:

void callcpuid( cpuidOut * p, long a, long b )
{
   __asm__ ( "cpuid" : "+a"(p->a), "+b"(p->b), "=c"(p->c), "=d"(p->d)) ;
}

PS。生成的 32 位代码:

movl    (%esi), %eax  ; load p->a
movl    4(%esi), %ebx ; load p->b
cpuid
movl    %ebx, 4(%esi)  ; write back into p->b
movl    (%esp), %ebx   
movl    %eax, (%esi)   ; write back into p->a
movl    %ecx, 8(%esi)  ; write p->c
movl    %edx, 12(%esi) ; write p->d

生成的 64 位代码:

movq    (%rdi), %rax   ; load p->a
movq    8(%rdi), %rbx  ; load p->b
cpuid
movq    %rbx, 8(%rdi)  ; write back p->b
movq    %rax, (%rdi)   ; write back p->a
movq    %rcx, 16(%rdi) ; write p->c
movq    %rdx, 24(%rdi) ; write p->d
于 2012-11-24T18:20:08.323 回答
0

如果您可以等待下一个 GCC 4.8 版本,或者如果您可以使用 GCC 的最新快照(即从 svn 源编译主干),请考虑使用新的内置函数 __builtin_cpu_is__builtin_cpu_supports;

否则,请执行您的建议,例如明确使用clobber 约束。

注意:您所指的链接也适用于未来的 GCC 4.8,而不适用于 4.7 或更早版本

于 2012-11-24T14:52:57.003 回答