【问题标题】:How do I atomically move a 64bit value in x86 ASM?如何在 x86 ASM 中以原子方式移动 64 位值?
【发布时间】:2018-06-11 07:27:02
【问题描述】:

首先,我发现了这个问题:How do I atomically read a value in x86 ASM? 但它有点不同,在我的例子中,我想在 32 位应用程序中自动分配一个浮点(64 位双精度)值。

来自:“英特尔® 64 和 IA-32 架构软件开发人员手册,第 3A 卷”

奔腾处理器(以及之后的更新处理器)保证以下额外的内存操作将始终以原子方式执行:

读取或写入在 64 位边界上对齐的四字

真的可以使用一些组装技巧吗?

【问题讨论】:

  • 提示:阅读内存屏障/内存栅栏。顺便提一句;为什么这个问题被标记为“c++”?
  • 您是在问如何让四字在内存中适当对齐,还是在问如何在内存中存储四字?
  • 我需要使每个操作都成为原子操作。我不在乎值在我读取和写入之间是否发生变化,但我希望a = b; a 始终包含 b 值的任何状态的正确值
  • 唯一的“窍门”是读写需要64位,所以可以使用x87指令或者MMX指令或者SSE指令。

标签: assembly x86 thread-safety atomic


【解决方案1】:

在 64 位 x86 asm 中,您可以使用整数 mov rax, [rsi],或者 x87 或 SSE2。 As long as the address is 8-byte aligned (or on Intel P6 and later CPUs: doesn't cross a cache-line boundary) the load or store will be atomic.

请注意,AMD 和 Intel 的共同基线仍然只有 8 字节对齐; 英特尔保证未对齐但未跨缓存行拆分的可缓存负载的原子性。 (AMD 可能会保证一些更宽的边界,或者至少在实践中对一些后来的 CPU 这样做)。


在 32 位 x86 asm 中,仅使用整数寄存器的唯一选择是 lock cmpxchg8b,但这对于纯加载或纯存储来说很糟糕。 (您可以通过设置 expected=desired = 0 将其用作负载,但只读内存除外)。 (gcc/clang 在 64 位模式下将 lock cmpxchg16b 用于 atomic<struct_16_bytes>,但一些编译器只是选择使 16 字节对象不是无锁的。)

所以答案是:不要使用整数 regsfild qword / fistp qword 可以复制任何位模式而无需更改它。 (只要 x87 精度控制设置为全 64 位尾数)。这对于 Pentium 及更高版本上的对齐地址是原子的。

在现代 x86 上,使用 SSE2 movq 加载或存储。例如

; atomically store edx:eax to qword [edi], assuming [edi] is 8-byte aligned
movd   xmm0, eax
pinsrd xmm0, edx            ; SSE4.1
movq   [edi], xmm0

只有 SSE1 可用,请使用 movlps。 (对于负载,您可能希望使用 xorps 打破对 xmm 寄存器旧值的错误依赖)。

使用 MMX,movqmm0-7 之间可以正常工作。


gcc 使用 SSE2 movq、SSE1 movlps 或 x87 fild/fstp,在 32 位模式下使用 std::atomic<int64_t> 的优先顺序。即使 SSE2 可用,Clang -m32 也不幸使用 lock cmpxchg8bLLVM bug 33109。 .

某些版本的 gcc 被配置为默认开启 -msse2,即使使用 -m32(在这种情况下,您可以使用 -mno-sse2-march=i486 来查看 gcc 在没有它的情况下会做什么)。

我输入load and store functions on the Godbolt compiler explorer 以查看来自 gcc 的带有 x87、SSE 和 SSE2 的 asm。并且来自 clang4.0.1 和 ICC18。

gcc 作为 int->xmm 或 xmm->int 的一部分在内存中反弹,即使 SSE4 (pinsrd / pextrd) 可用。这是一个错过的优化 (gcc bug 80833)。在 64 位模式下,它支持带有 -mtune=intel-mtune=haswell 的 ALU movd + pinsrd / pextrd,但显然不是在 32 位模式下或不适用于此用例(XMM 中的 64 位整数而不是正确的矢量化)。无论如何,请记住只有来自atomic<long long> shared 的加载或存储必须是原子的,堆栈的其他加载/存储是私有的。


在 MSVC 中,在更高版本的 Visual C++ 2019 中有一个 __iso_volatile_load64 内部函数,可以编译为适当的指令序列。

【讨论】:

  • 为了存储,它会发出一个cmpxchg8b 循环。至少我最后一次测试。
  • @fuz:您可能正在查看 clang,不幸的是,它确实做到了。或者您正在查看 RWM 操作,而不是纯加载或纯存储。用一个神螺栓链接更新了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-05
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-05
  • 2012-07-01
相关资源
最近更新 更多