我正在查看compiler output of rmw atomics from gcc并注意到一些奇怪的事情 - 在Aarch64上,诸如fetch_add之类的rmw操作可以通过宽松的加载进行部分重新排序。
在Aarch64上,可能会为value.fetch_add(1, seq_cst)
.L1:
ldaxr x1, [x0]
add x1, x1, 1
stlxr w2, x1, [x0]
cbnz L1
然而,在ldaxr之前发生的加载和存储可能会重新排序经过stlxr之后发生的加载和加载/存储(参见here)。海湾合作委员会没有添加围栏以防止这种情况 - 这里有一小段代码证明了这一点:
void partial_reorder(std::atomic<uint64_t> loader, std::atomic<uint64_t> adder) {
loader.load(std::memory_order_relaxed); // can be reordered past the ldaxr
adder.fetch_add(1, std::memory_order_seq_cst);
loader.load(std::memory_order_relaxed); // can be reordered past the stlxr
}
生成
partial_reorder(std::atomic<int>, std::atomic<int>):
ldr w2, [x0] @ reordered down
.L2:
ldaxr w2, [x1]
add w2, w2, 1
stlxr w3, w2, [x1]
cbnz w3, .L2
ldr w0, [x0] @ reordered up
ret
实际上,可以使用RMW操作对负载进行部分重新排序 - 它们位于中间。
那么,最重要的是什么?我在问什么?
原子操作可以被这样整除,这似乎很奇怪。我无法在标准中发现任何阻止这种情况的内容,但我相信有一系列规则暗示操作是不可分割的。
看起来这并不尊重获取订购。如果我在此操作之后直接执行加载,我可以在fetch_add和后面的操作之间看到存储加载或存储存储重新排序,这意味着稍后的内存访问至少部分地在获取操作后重新排序。同样,我无法在标准中明确地发现任何不允许和获取的内容是加载顺序,但我的理解是,获取操作适用于整个操作而不仅仅是部分操作。类似的情况可以适用于释放某些内容经过ldaxr重新排序的内容。
这个可能会更多地延伸排序定义,但是seq_cst操作之前和之后的两个操作可以重新排序,看起来无效。如果边界操作每个都重新排序到操作的中间,然后相互经过,则可能发生(?)。
答案 0 :(得分:4)
看起来你是对的。至少,非常相似bug for gcc已被接受并修复。
他们提供此代码:
.L2:
ldaxr w1, [x0] ; load-acquire (__sync_fetch_and_add)
add w1, w1, 1
stlxr w2, w1, [x0] ; store-release (__sync_fetch_and_add)
cbnz w2, .L2
因此可以使用ldaxr
对以前的操作进行重新排序,并且可以使用stlxr
对其进行重新排序,从而打破C ++ 11验证。 Documentation对于aarch64的障碍清楚地解释说,这样的重新排序是可能的。
答案 1 :(得分:0)
我在这里看不出任何错误。第二个示例与第一个部分几乎相同,其中一个负载前置并附加了负载。所以它完全遵循你在C代码中写的内容。我在这里看不到任何重新排序。