如何在 x86 ASM 中以原子方式移动 64 位值?
How do I atomically move a 64bit value in x86 ASM?
首先,我发现了这个问题:How do I atomically read a value in x86 ASM?
但这有点不同,在我的例子中,我想在 32 位应用程序中自动分配一个浮点数(64 位双精度)值。
来自:“英特尔® 64 位和 IA-32 架构软件开发人员手册,第 3A 卷”
The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically:
Reading or writing a quadword aligned on a 64-bit boundary
是否真的可以使用一些汇编技巧?
在 64 位 x86 asm 中,您可以使用整数 mov rax, [rsi]
,或者 x87 或 SSE2。 .
请注意,AMD 和 Intel 的共同基线仍然只是 8 字节对齐; 仅 英特尔保证未对齐但未跨缓存行拆分的可缓存加载的原子性。 (AMD 可能会保证某些具有更宽边界的东西,或者至少在实践中为某些后来的 CPU 做到这一点)。
在 32 位 x86 asm 中,仅使用整数寄存器的唯一选择是 lock cmpxchg8b
,但这对于纯加载或纯存储来说很糟糕。 (您可以通过设置 expected=desired = 0 将其用作负载,但只读内存除外)。 (gcc/clang 在 64 位模式下将 lock cmpxchg16b
用于 atomic<struct_16_bytes>
,但一些编译器只是选择使 16 字节对象不是无锁的。)
所以答案是:不要使用整数 regs:fild 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,movq
to/from mm0-7
有效。
gcc 按照优先顺序使用 SSE2 movq
、SSE1 movlps
或 x87 fild
/fstp
std::atomic<int64_t>
在 32 位模式下。不幸的是,即使 SSE2 可用,Clang -m32
也使用 lock cmpxchg8b
:LLVM 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
的加载或存储必须是原子的,其他 loads/stores 到堆栈是私有的。
在 MSVC 中,有一个 __iso_volatile_load64
intrinsic in later versions of Visual C++ 2019 that can compile to a appropriate sequence说明。
首先,我发现了这个问题:How do I atomically read a value in x86 ASM? 但这有点不同,在我的例子中,我想在 32 位应用程序中自动分配一个浮点数(64 位双精度)值。
来自:“英特尔® 64 位和 IA-32 架构软件开发人员手册,第 3A 卷”
The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically:
Reading or writing a quadword aligned on a 64-bit boundary
是否真的可以使用一些汇编技巧?
在 64 位 x86 asm 中,您可以使用整数 mov rax, [rsi]
,或者 x87 或 SSE2。
请注意,AMD 和 Intel 的共同基线仍然只是 8 字节对齐; 仅 英特尔保证未对齐但未跨缓存行拆分的可缓存加载的原子性。 (AMD 可能会保证某些具有更宽边界的东西,或者至少在实践中为某些后来的 CPU 做到这一点)。
在 32 位 x86 asm 中,仅使用整数寄存器的唯一选择是 lock cmpxchg8b
,但这对于纯加载或纯存储来说很糟糕。 (您可以通过设置 expected=desired = 0 将其用作负载,但只读内存除外)。 (gcc/clang 在 64 位模式下将 lock cmpxchg16b
用于 atomic<struct_16_bytes>
,但一些编译器只是选择使 16 字节对象不是无锁的。)
所以答案是:不要使用整数 regs:fild 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,movq
to/from mm0-7
有效。
gcc 按照优先顺序使用 SSE2 movq
、SSE1 movlps
或 x87 fild
/fstp
std::atomic<int64_t>
在 32 位模式下。不幸的是,即使 SSE2 可用,Clang -m32
也使用 lock cmpxchg8b
:LLVM 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
的加载或存储必须是原子的,其他 loads/stores 到堆栈是私有的。
在 MSVC 中,有一个 __iso_volatile_load64
intrinsic in later versions of Visual C++ 2019 that can compile to a appropriate sequence说明。