这个问题紧随其后,考虑了一个符合GCC
的编译器和一个x86-64
架构。
我想知道下面的option 1
、option 2
和option 3
之间是否有任何区别。在所有情况下,结果是相同的,还是不同的。如果是这样,会有什么区别?
// Option 1
asm volatile(:::"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):);
和
// Option 2
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):);
asm volatile(:::"memory");
和
// Option 3
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
选项 1 和 2 将允许 CPUID 本身使用不相关的非volatile
加载/存储(在一个方向或另一个方向)重新排序。 这很可能不是您想要的。
你可以在 CPUID 的两侧设置一个内存屏障,但最好让 CPUID 本身成为内存屏障。
正如Jester指出的那样,选项1将强制从内存中重新加载level
,如果它曾经在函数之外传递过其地址,或者它已经是全局或static
。
(或者无论决定 C 变量是否可以由使用"memory"
clobber 的 asm 读取或写入的确切标准是什么。 我认为这与优化器用来决定变量是否可以通过对不透明函数的非内联函数调用保存在寄存器中的内容基本相同,因此没有在任何地方传递地址的纯局部变量,并且不是 asm 语句的输入,仍然可以存在于寄存器中)。
例如(Godbolt编译器资源管理器):
void foo(int level){
int eax, ebx, ecx, edx;
asm volatile("":::"memory");
asm volatile("CPUID"
: "=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)
: "0"(level)
:
);
}
# x86-64 gcc7.3 -O3 -fverbose-asm
pushq %rbx # # rbx is call-preserved, but we clobber it.
movl %edi, %eax # level, eax
CPUID
popq %rbx #
ret
请注意缺少函数 arg 的溢出/重新加载。
通常我会使用英特尔语法,但是对于内联asm,最好始终使用AT&T,除非您完全讨厌AT&T语法或不知道它。
即使它在内存中启动(i386 System V 调用约定,带有堆栈参数),编译器仍然决定没有其他任何东西(包括带有内存 clobber 的asm
语句)可以引用它。 但是我们如何区分延迟负载呢? 在屏障之前修改函数参数,然后在以下之后使用它:
void modify_level(int level){
level += 1; // modify level before the barrier
int eax, ebx, ecx, edx;
asm volatile("#mem barrier here":::"memory");
asm volatile("CPUID" // then read it after
: "=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)
: "0"(level):);
}
gcc -m32 -O3 -fverbose-asm
的 asm 输出为:
modify_level(int):
pushl %ebx #
#mem barrier here
movl 8(%esp), %eax # level, tmp97
addl $1, %eax #, level
CPUID
popl %ebx #
ret
请注意,编译器允许level++
跨内存屏障重新排序,因为它是一个局部变量。
Godbolt 过滤手写的 asm 注释以及编译器生成的仅 asm 注释行。 我禁用了评论过滤器并找到了内存屏障。 您可能希望删除 -fverbose-asm 以减少噪音。 或者使用非注释字符串作为 mem 屏障:如果您只是查看编译器的 asm 输出,则不必组装它。 (除非您使用的是内置汇编程序的 clang)。
顺便说一句,您问题的原始版本没有编译:您省略了空字符串作为 asm 模板。asm(:::"memory")
. 输出、输入和 clobber 部分可以为空,但 asm 指令字符串不是可选的。
有趣的事实,您可以将asm注释放在字符串中:
asm volatile("# memory barrier here":::"memory");
GCC 在写入 ASM 输出时会填充字符串模板中的任何%whatever
内容,因此您甚至可以执行诸如"CPUID # %%0 was in %0"
之类的操作,并查看 GCC 为您的"虚拟"参数选择了什么,否则这些参数在 ASM 模板中未提及。 (当您给 asm 语句一个指针时,虚拟内存输入/输出操作数告诉编译器您读/写哪个内存而不是使用"memory"
clobber 更有趣。