我们认为我们正在GCC
体系结构上使用GCC
(或X86_64
兼容)编译器,并且eax
,ebx
,ecx
,edx
和level
是变量(unsigned int
或unsigned int*
)用于指令的输入和输出(例如here)。
asm("CPUID":::);
asm volatile("CPUID":::);
asm volatile("CPUID":::"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)::"memory");
asm volatile("CPUID":"=a"(eax):"0"(level):"memory");
asm volatile("CPUID"::"a"(level):"memory"); // Not sure of this syntax
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
asm("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level));
CPUID
用作serializing instruction的情况下,所有这些调用之间的区别是什么(例如,指令的输出将不执行任何操作) )。 最佳答案
首先,lfence
的序列化程度可能与cpuid
一样强,或者也许没有。如果您关心性能,请检查并查看是否可以找到lfence
足够强大的证据(至少对于您的用例而言)。如果mfence; lfence
和cpuid
都不足以在AMD和Intel上进行序列化,甚至using both mfence
可能也比lfence
更好。 (我不确定,请参阅我的链接评论)。
2.是的,所有没有告诉编译器asm语句写E [A-D] X的代码都是危险的,并且很可能会导致难以调试的怪异现象。 (即,您需要使用(虚拟)输出操作数或Clobbers)。
您需要volatile
,因为您要为序列化的副作用执行asm代码,而不要产生输出。
如果您不希望将CPUID结果用于任何事情(例如,通过序列化和查询来完成双重任务),则应仅将寄存器列为Clobbers(而不是输出),因此不需要任何C变量来保存结果。
// volatile is already implied because there are no output operands
// but it doesn't hurt to be explicit.
// Serialize and block compile-time reordering of loads/stores across this
asm volatile("CPUID"::: "eax","ebx","ecx","edx", "memory");
// the "eax" clobber covers RAX in x86-64 code, you don't need an #ifdef __i386__
I am wondering what would be the difference between all these calls
首先,这些都不是“电话”。它们是asm语句,并内联到使用它们的函数中。 CPUID本身也不是“调用”,尽管我猜您可以将其视为调用CPU内置的微码函数。但是按照这种逻辑,每条指令都是“调用”,例如
mul rcx
在RAX和RCX中接受输入,并在RDX:RAX中返回。前三个(后来的一个没有输出,只是一个
level
输入)通过RDX破坏了RAX,而没有告诉编译器。它将假定那些寄存器仍然保留它们中保存的内容。它们显然无法使用。如果您不使用任何输出,
asm("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
(是不带volatile
的那个)将优化。而且,如果您确实使用它们,它仍然可以从环路中吊起。优化器将非volatile
asm语句视为纯函数,没有副作用。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#index-asm-volatile它有一个内存破坏器,但是(我认为)这并不能阻止它优化,只是意味着,如果它/在何时何地运行,它可能读取/写入的任何变量都将同步到内存,因此内存内容匹配当时的C抽象机。不过,这可能会排除尚未取得地址的当地人。
asm("" ::: "memory")
与std::atomic_thread_fence(std::memory_order_seq_cst)
非常相似,但请注意asm
语句没有输出,因此是隐含的volatile
。这就是为什么它没有被优化,不是因为"memory"
破坏者本身。 带有内存破坏器的(volatile
)asm语句是编译器的障碍,它无法在其上重新排序装入或存储。 优化器根本不在乎第一个字符串文字中的内容,只在乎约束/修饰符,因此
asm volatile("anything" ::: register clobbers, "memory")
也是仅编译时的内存障碍。我假设这是您想要的,序列化一些内存操作。"0"(level)
是第一个操作数("=a"
)的匹配约束。您同样可以编写"a"(level)
,因为在这种情况下,编译器无法选择要选择的寄存器。输出约束只能由eax
满足。您也可以使用"+a"(eax)
作为输出操作数,但随后必须在asm语句之前设置eax=level
。对于x87堆栈,有时需要匹配约束而不是读写操作数。我认为这是一次SO问题。但是除了像这样的怪异事物外,优点是能够对输入和输出使用不同的C变量,或者根本不对输入使用变量。 (例如,文字常量或左值(表达式))。无论如何,告诉编译器提供输入可能会导致额外的指令,例如
level=0
将导致xor
的eax
归零。如果它早先不需要清零寄存器,那将浪费指令。通常,对输入进行异或归零会破坏对先前值的依赖性,但是这里的CPUID的全部要点是它正在序列化,因此无论如何它必须等待所有先前的指令完成。确保eax
尽早准备是没有意义的; 如果您不关心输出,甚至不告诉编译器,您的asm语句将输入。编译器很难或不可能使用没有开销的未定义/未初始化的值;有时,将C变量保留为未初始化状态将导致从堆栈中加载垃圾或将寄存器清零,而不是仅使用寄存器而不先写入寄存器。
关于c++ - 将内联汇编与序列化说明一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48522628/