ARM STLR 内存排序语义

问题描述

我正在为 ARM STLR 的确切语义而苦苦挣扎。

根据文档,它具有发布语义。所以如果你有 STLR 存储,你会得到:

[StoreStore][LoadStore]
X=r1

其中 X 是内存,r1 是一些寄存器。

问题在于发布存储和获取加载,无法提供顺序一致性:

[StoreStore][LoadStore]
X=r1
r2=Y
[LoadLoad][LoadStore]

在上述情况下,允许重新排序 X=r1 和 r2=Y。为了使这个顺序一致,需要添加一个 [StoreLoad]:

[StoreStore][LoadStore]
X=r1
[StoreLoad]
r2=Y
[LoadLoad][LoadStore]

你通常在商店里这样做,因为加载更频繁。

在 X86 上,普通存储是发布存储,普通加载是获取加载。 [StoreLoad] 可以通过 MFENCE 或使用 LOCK ADDL %(RSP),0 来实现,就像在 Hotspot JVM 中所做的那样。

查看 ARM 文档时,似乎 LDAR 已获得语义;所以这将是 [LoadLoad][LoadStore]。

但是 STLR 的语义是模糊的。当我使用 memory_order_seq_cst 编译 C++ atomic 时,只有一个 STLR;没有 DMB。因此,STLR 似乎比发布存储具有更强的内存排序保证。在我看来,在栅栏级别上,STLR 等效于:

 [StoreStore][LoadStore]
 X=r1
 [StoreLoad]

有人可以解释一下吗?

解决方法

我只是在学习这方面的知识,所以请谨慎对待。但我的理解是,在 ARMv8/AArch64 中,STLR/LDAR 确实提供了超出通常的发布/获取定义的附加语义,但不如您的建议那么强。即,发布存储 STLR 确实与按程序顺序跟随它的获取加载 LDAR 具有顺序一致性,但与普通的 LDR 加载不具有一致性。

来自 ARMv8 架构参考手册,B2.3.7,“加载-获取、加载-获取PC 和存储-发布”:

在 Store-Release 之后按程序顺序出现 Load-Acquire 时,由 Store-Release 指令被每个 PE 观察到需要 PE 连贯地观察访问的程度, 在 Load-Acquire 指令产生的内存访问被那个 PE 观察到之前, 要求PE一致地观察访问。

从 B2.3.2 开始,“顺序关系”:

读或写 RW1 是 Barrier-ordered-before 读或写 RW2 相同的观察者当且仅当 RW1 出现在 RW2 之前的程序顺序和以下任何一个 适用情况:[...] RW1 是由具有释放语义的指令生成的写 W1,而 RW2 是读 R2 由具有 Acquire 语义的指令生成。

作为测试,我借用了 a C++ implementation of Peterson's locking algorithm by LWimsey。使用 clang 11.0 on godbolt,您可以看到即使请求顺序一致性,编译器仍然生成 STLR,LDAR 来获取锁(程序集的第 18-19 行),没有 DMB。我运行了一段时间(Raspberry Pi 4B、Cortex A72、4 核)并且没有违规。

然而,与您的想法相反,STLR 仍然可以相对于它后面的普通(非获取)加载重新排序,因此它没有隐式地具有完整的 StoreLoad 栅栏。我修改了 LWimsey 的程序,改为使用 STLR,LDR,在添加了一些额外的垃圾来引发竞争后,我能够看到锁违规。

同样,LDAR 可以相对于它之前的普通(非发布)商店重新排序。我同样能够在测试程序中使用 STR,LDAR 获得锁违规。