与高级语言不同的CPUID用法
Differing CPUID usage from high-level languages
我正在尝试使用需要特定处理器架构的 x86 ASM 函数。我知道我需要在调用“CPUID 标准函数 01H”后检查特定位。下面是来自 CPUID Wikipedia 页面的 C 实现,用于调用 CPUID:
#include <stdio.h>
int main() {
int i;
unsigned int index = 0;
unsigned int regs[4];
int sum;
__asm__ __volatile__(
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"pushq %%rbx \n\t" /* save %rbx */
#else
"pushl %%ebx \n\t" /* save %ebx */
#endif
"cpuid \n\t"
"movl %%ebx ,%[ebx] \n\t" /* write the result into output var */
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"popq %%rbx \n\t"
#else
"popl %%ebx \n\t"
#endif
: "=a"(regs[0]), [ebx] "=r"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
: "a"(index));
for (i=4; i<8; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=12; i<16; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=8; i<12; i++) {
printf("%c" ,((char *)regs)[i]);
}
printf("\n");
}
尽管 Linux kernel 使用以下函数:
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
哪个更好?其他的他们本质上是等价的?
正如 Jester 所说,在 GNU C 中,cpuid.h 内在包装器可能是您最好的选择。
还有 __builtin_cpu_supports("popcnt")
或 "avx"
之类的,它们在您调用 __builtin_cpu_init()
后起作用。不过,仅支持真正主要的功能位。例如,文档没有提及 rdrand 的功能位,因此 __builtin_cpu_supports("rdrand")
可能不起作用。
自定义 inline-assembly 版本:
Linux的实现可以内联,没有浪费指令,而且看起来写得很好,所以没有理由使用其他任何东西。您极有可能会收到有关无法满足 "=b"
约束的投诉;如果是这样,请参阅下面的 clang cpuid.h 的作用。 (但我认为这从来没有必要,而且是文档错误的结果)。
它实际上并不需要 volatile
,但是,如果您将它用于生成的值而不是对管道的序列化效果:运行 具有相同输入的 CPUID 将提供相同的结果,所以我们可以让优化器移动它或将它提升到循环之外。 (所以它运行的次数更少)。这可能没有帮助,因为普通代码首先不会在循环中使用它。
The source for clang's implementation of cpuid.h
does some weird stuff, like preserving %rbx
because apparently some x86-64 environments might not be able to satisfy a constraint that uses %rbx
as an output operand? The comment is /* x86-64 uses %rbx as the base register, so preserve it. */
, but I have no idea what they're talking about. If anything x86-32 PIC code in the SysV ABI uses %ebx
for a fixed purpose (as a pointer to the GOT), but I don't know about anything like that for x86-64. Perhaps that code is motivated by a mistake in the ABI documentation? See HJ Lu's mailing list post about it.
最重要的是,问题中的第一个版本(在main()
内)已损坏,因为它.
要修复它,只需告诉编译器结果将在 ebx 中(使用 "=b"
),让它担心 saving/restoring ebx/rbx 在 start/end的功能。
我正在尝试使用需要特定处理器架构的 x86 ASM 函数。我知道我需要在调用“CPUID 标准函数 01H”后检查特定位。下面是来自 CPUID Wikipedia 页面的 C 实现,用于调用 CPUID:
#include <stdio.h>
int main() {
int i;
unsigned int index = 0;
unsigned int regs[4];
int sum;
__asm__ __volatile__(
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"pushq %%rbx \n\t" /* save %rbx */
#else
"pushl %%ebx \n\t" /* save %ebx */
#endif
"cpuid \n\t"
"movl %%ebx ,%[ebx] \n\t" /* write the result into output var */
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"popq %%rbx \n\t"
#else
"popl %%ebx \n\t"
#endif
: "=a"(regs[0]), [ebx] "=r"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
: "a"(index));
for (i=4; i<8; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=12; i<16; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=8; i<12; i++) {
printf("%c" ,((char *)regs)[i]);
}
printf("\n");
}
尽管 Linux kernel 使用以下函数:
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
哪个更好?其他的他们本质上是等价的?
正如 Jester 所说,在 GNU C 中,cpuid.h 内在包装器可能是您最好的选择。
还有 __builtin_cpu_supports("popcnt")
或 "avx"
之类的,它们在您调用 __builtin_cpu_init()
后起作用。不过,仅支持真正主要的功能位。例如,文档没有提及 rdrand 的功能位,因此 __builtin_cpu_supports("rdrand")
可能不起作用。
自定义 inline-assembly 版本:
Linux的实现可以内联,没有浪费指令,而且看起来写得很好,所以没有理由使用其他任何东西。您极有可能会收到有关无法满足 "=b"
约束的投诉;如果是这样,请参阅下面的 clang cpuid.h 的作用。 (但我认为这从来没有必要,而且是文档错误的结果)。
它实际上并不需要 volatile
,但是,如果您将它用于生成的值而不是对管道的序列化效果:运行 具有相同输入的 CPUID 将提供相同的结果,所以我们可以让优化器移动它或将它提升到循环之外。 (所以它运行的次数更少)。这可能没有帮助,因为普通代码首先不会在循环中使用它。
The source for clang's implementation of cpuid.h
does some weird stuff, like preserving %rbx
because apparently some x86-64 environments might not be able to satisfy a constraint that uses %rbx
as an output operand? The comment is /* x86-64 uses %rbx as the base register, so preserve it. */
, but I have no idea what they're talking about. If anything x86-32 PIC code in the SysV ABI uses %ebx
for a fixed purpose (as a pointer to the GOT), but I don't know about anything like that for x86-64. Perhaps that code is motivated by a mistake in the ABI documentation? See HJ Lu's mailing list post about it.
最重要的是,问题中的第一个版本(在main()
内)已损坏,因为它
要修复它,只需告诉编译器结果将在 ebx 中(使用 "=b"
),让它担心 saving/restoring ebx/rbx 在 start/end的功能。