Aarch64 上 C++11 原子的部分重新排序

Posted

技术标签:

【中文标题】Aarch64 上 C++11 原子的部分重新排序【英文标题】:Partial reordering of C++11 atomics on Aarch64 【发布时间】:2016-02-05 06:11:24 【问题描述】:

我在查看 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)。 GCC 没有添加栅栏来防止这种情况 - 这是一小段代码展示了这一点:

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 操作之前和之后的两个操作可以相互重新排序似乎是无效的。如果边界操作都重新排序到操作的中间,然后相互超越,这可能(?)发生。

【问题讨论】:

可能相关:bigflake.com/seq_cst.cpp 显示用于基本原子加载/存储的 x64/arm32/aar​​ch64 的编译器输出。 store(val, std::memory_order_seq_cst) 似乎缺少障碍。这是 android 上的 gcc 4.9。 【参考方案1】:

看来你是对的。至少,非常相似的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 for barrier on aarch64 清楚地解释了这种重新排序是可能的。

【讨论】:

虽然我想不出第一次加载-加载重新排序很重要的情况,但第二次商店购买的重新排序可能很重要 - 类似时代或危险指针的技术通常需要商店购买的重新排序将线程标记为活动并加载指针。在这种情况下,如果第二次加载是一个指针,它可以在将存储提交为“活动”之前加载指针。如果 acq_rel 中的获取和释放仅适用于 rmw 操作的一部分,则这是有效的。这似乎是标准的一个无情的细节,因为它意味着原子在某种意义上显然是非原子的。 在其他弱排序架构上,获取-释放排序使用明确的栅栏(dmb 围绕 ldrex,strex 用于 arm)进行归档,从而防止这种内部重新排序。另外,我仍然看不到允许操作围绕比 acq_rel 更强的 seq_cst 操作重新排序是有效的。 it could load the pointer before committing the store as 'active'. - 再次,“提交”是在stlxr 调用中完成的,无法通过进一步加载重新排序。 cbnz 只是检查提交是否成功,其他线程无法观察到它的行为。至于seq_cst 排序,我相信stlxr 指令还提供给定架构的全局可见性保证。不过,这可能是 Stack Overflow 的另一个问题。 实际上,stlrx 可以通过进一步的加载和存储重新排序 - 我无法获取 arm 文档的链接以正常工作,但他们对此非常明确。 Stlrx 只有 store-release ordering,这意味着以后的 load/store 操作可以在它后面重新排序。如果 stlrx 提供了 store-load 和 store-store ordering,那么这个问题就没有实际意义了。 我认为您想要的文档是:infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/…(解释 LDAR/STLR)。

以上是关于Aarch64 上 C++11 原子的部分重新排序的主要内容,如果未能解决你的问题,请参考以下文章

Windows11 上使用 QEMU 创建 aarch64(ARM64)虚拟机

对于 ARM Aarch64 的 NEON 编码,如何将寄存器推送到堆栈?似乎 STMFD 不是 Aarch64 指令集的一部分?

2017-11-19Linux基础知识:TP-Link WN823N无线网卡(RTL8192EU芯片)的X86-64及AARCH64驱动安装

Linux-我可以在aarch64体系结构上运行arm64二进制文件吗?

有没有人总结过ARMv7和ARMv8的区别

aarch64-linux-gnu-g++ 交叉编译为 arm64 错误