简体   繁体   English

为什么 `asm volatile(""::: "memory")` 可以用作编译器屏障?

[英]Why can `asm volatile("" ::: "memory")` serve as a compiler barrier?

It is known that asm volatile (""::: "memory") can serve as a compiler barrier to prevent compiler from reordering assembly instructions across it.众所周知, asm volatile (""::: "memory")可以用作编译器屏障,以防止编译器重新排序汇编指令。 For example, it is mentioned in https://preshing.com/20120625/memory-ordering-at-compile-time/ , section "Explicit Compiler Barriers".例如,在https://preshing.com/20120625/memory-ordering-at-compile-time/的“显式编译器障碍”一节中提到了它。

However, all the articles I can find only mention the fact that asm volatile (""::: "memory") can serve as a compiler barrier without giving a reason why the "memory" clobber can effectively form a compiler barrier.但是,我能找到的所有文章都只提到asm volatile (""::: "memory")可以用作编译器屏障,而没有给出"memory" clobber 可以有效地形成编译器屏障的原因。 The GCC online documentation only says that all the special clobber "memory" does is tell the compiler that the assembly code may potentially perform memory reads or writes other than those specified in operands lists. GCC 在线文档只说所有特殊的破坏者"memory"所做的就是告诉编译器汇编代码可能会执行 memory 读取或写入操作数列表中指定的操作以外的操作。 But how does such a semantic cause compiler to stop any attempt to reorder memory instructions across it?但是这样的语义是如何导致编译器停止对 memory 指令重新排序的尝试呢? I tried to answer myself but failed, so I ask here: why can asm volatile (""::: "memory") serve as a compiler barrier, based on the semantics of "memory" clobber?我试图回答自己但失败了,所以我在这里问:为什么asm volatile (""::: "memory")可以作为编译器屏障,基于"memory" clobber 的语义? Please note that I am asking about "compiler barrier" (in effect at compile-time), not stronger "memory barrier" (in effect at run-time).请注意,我问的是“编译器屏障”(在编译时有效),而不是更强的“内存屏障”(在运行时有效)。 For convenience, I excerpt the semantics of "memory" clobber in GCC online doc below:为方便起见,我在下面的 GCC 在线文档中摘录了"memory"破坏者的语义:

The "memory" clobber tells the compiler that the assembly code performs memory reads or writes to items other than those listed in the input and output operands (for example, accessing the memory pointed to by one of the input parameters). "memory" clobber 告诉编译器,汇编代码执行 memory 读取或写入输入和 output 操作数中列出的项目以外的项目(例如,访问由输入参数之一指向的 ZCD69B4957F06CD000D818D7BF3D619)。 To ensure memory contains correct values, GCC may need to flush specific register values to memory before executing the asm .为确保 memory 包含正确的值,GCC 可能需要在执行asm之前将特定寄存器值刷新到 memory。 Further, the compiler does not assume that any values read from memory before an asm remain unchanged after that asm ;此外,编译器不假定在asm之前从 memory 读取的任何值在asm之后保持不变; it reloads them as needed.它会根据需要重新加载它们。 Using the "memory" clobber effectively forms a read/write memory barrier for the compiler.有效地使用"memory"破坏器 forms 为编译器提供读/写 memory 屏障。

If a variable is potentially read or written, it matters what order that happens in. The point of a "memory" clobber is to make sure the reads and/or writes in an asm statement happen at the right point in the program's execution.如果一个变量可能被读取或写入,那么它发生的顺序很重要。 "memory"破坏器的目的是确保asm语句中的读取和/或写入发生在程序执行的正确位置。

Any read of a C variable's value that happens in the source after an asm statement must be after the memory-clobbering asm statement in the compiler-generated assembly output for the target machine, otherwise it might be reading a value before the asm statement would have changed it.asm语句之后发生在源中的 C 变量值的任何读取必须在目标计算机的编译器生成的程序集 output 中的内存破坏asm语句之后,否则它可能正在读取 asm 语句之前的值改变了它。

Any read of a C var in the source before an asm statement similarly must stay sequenced before, otherwise it might incorrectly read a modified value.asm语句之前对源中的 C var 的任何读取同样必须保持之前的顺序,否则它可能会错误地读取修改后的值。

Similar reasoning applies to assignments to (writes of) C variables before/after any asm statement with a "memory" clobber.类似的推理适用于在任何带有"memory" clobber 的asm语句之前/之后对 C 变量的赋值(写入)。 Just like a function call to an "opaque" function, one who's definition the compiler can't see.就像 function 调用“不透明” function 一样,编译器无法看到的定义。

No reads or writes can reorder with the barrier in either direction, therefore no operation before the barrier can reorder with any operation after the barrier, or vice versa.任何读取或写入都不能与屏障在任一方向上重新排序,因此屏障之前的任何操作都不能与屏障之后的任何操作重新排序,反之亦然。


Another way to look at it: the actual machine memory contents must match the C abstract machine at that point.另一种看待它的方式:实际机器 memory 的内容必须与 C 抽象机相匹配。 The compiler-generated asm has to respect that, by storing any variable values from registers to memory before the start of an asm("":::"memory") statement, and afterwards it has to assume that any registers that had copies of variable values might not be up to date anymore.编译器生成的 asm 必须尊重这一点,通过在asm("":::"memory")语句开始之前将任何变量值从寄存器存储到 memory 语句,然后它必须假设任何具有副本的寄存器变量值可能不再是最新的。 So they have to be reloaded if they're needed.因此,如果需要,必须重新加载它们。

This reads-everything / writes-everything assumption for the "memory" clobber is what keeps the asm statement from reordering at all at compile time wrt.对于"memory" clobber,这种读取所有内容/写入所有内容的假设是使asm语句在编译时完全不会重新排序的原因。 all accesses, even non- volatile ones.所有访问,甚至volatile访问。 The volatile is already implicit from being an asm() statement with no "=..." output operands, and is what stops it from being optimized away entirely (and with it the memory clobber). volatile已经是一个没有"=..." output 操作数的asm()语句隐含,并且是阻止它被完全优化的原因(以及 memory clobber)。


Note that only potentially "reachable" C variables are affected.请注意,只有可能“可达”的 C 变量会受到影响。 For example, escape analysis can still let the compiler keep a local int i in a register across a "memory" clobber, as long as the asm statement itself doesn't have the address as an input.例如,只要 asm 语句本身没有地址作为输入,转义分析仍然可以让编译器将本地int i保存在跨越"memory" clobber 的寄存器中。

Just like a function call: for (int i=0;i<10;i++) {foobar("%d\n", i);} can keep the loop counter in a register, and just copy it to the 2nd arg-passing register for foobar every iteration.就像 function 调用一样: for (int i=0;i<10;i++) {foobar("%d\n", i);}可以将循环计数器保存在寄存器中,然后将其复制到第二个参数- 每次迭代都为 foobar 传递寄存器。 There's no way foobar can have a reference to i because its address hasn't been stored anywhere or passed anywhere. foobar 不可能有对i的引用,因为它的地址没有存储在任何地方或传递到任何地方。

(This is fine for the memory barrier use-case; no other thread could have its address either.) (这对于 memory 屏障用例来说很好;也没有其他线程可以拥有它的地址。)


Related:有关的:

I'll add that : memory is only a compiler directive.我要补充一点: memory只是一个编译器指令。 A speculative processor may reorder instructions.推测处理器可以重新排序指令。 To prevent this an explicit memory barrier call is necessary.为了防止这种情况发生,需要明确的 memory 屏障调用。 See Linux doc on memory barriers.请参阅 memory 屏障上的 Linux 文档。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 GCC内存屏障__sync_synchronize vs asm volatile(“”:::“memory”) - GCC memory barrier __sync_synchronize vs asm volatile(“”: : :“memory”) 中断的易失性与内存屏障 - volatile vs memory barrier for interrupts 为什么 glibc 中 x86 的读写屏障不使用 __volatile asm? - Why do read and write barrier for x86 in glibc not use __volatile asm? 内联 asm 编译器屏障(内存破坏)算作外部函数还是静态函数调用? - Does the inline asm compiler barrier (memory clobber) count as an external function, or as static function call? asm,asm volatile和clobbering memory之间的区别 - The difference between asm, asm volatile and clobbering memory WaitForSingleObject是否可用作内存屏障? - Does WaitForSingleObject Serve as a Memory Barrier? memory barrier 和 volatile 是否足以避免数据竞争? - Is a memory barrier AND volatile ENOUGH to avoid a data race? 单个变量是否存在编译器内存障碍? - Is there a compiler memory barrier for a single variable? Windows上的`__asm nop`是否等同于GCC编译器的`asm volatile(“ nop”);`? - Is `__asm nop` the Windows equivalent of `asm volatile(“nop”);` from GCC compiler asm volatile(“ :::” memory“)的寿命是多少? - What is the life-time of asm volatile(“” ::: “memory”)?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM