如何在x86 ASM中原子地移动64位值?

Sar*_*gis 3 x86 assembly thread-safety

首先,我发现了这个问题:如何以原子方式读取x86 ASM中的值? 但它有点不同,在我的情况下,我想在32位应用程序中原子地分配一个浮点(64位双倍)值.

来自:"英特尔®64和IA-32架构软件开发人员手册,Volume3A"

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

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

实际上是否可以使用一些组装技巧?

Pet*_*des 6

在64位x86 asm中,您可以使用整数mov rax, [rsi],或x87或SSE2. 只要地址是8字节对齐(或在Intel p6和更高版本的CPU上:不跨越缓存行边界),加载或存储将是原子的.


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

所以答案是:不要使用整数寄存器: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
Run Code Online (Sandbox Code Playgroud)

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

使用MMX,movq来往/来自mm0-7作品.


gcc 在32位模式下按优先顺序使用SSE2 movq,SSE1 movlps或x87 fild/ .不幸的是,Clang 即使在SSE2可用时也会使用:LLVM bug 33109..fstpstd::atomic<int64_t>-m32lock cmpxchg8b

某些版本的gcc已配置-msse2为默认情况下启用-m32(在这种情况下,您可以使用-mno-sse2-march=i486查看gcc在没有它的情况下执行的操作).

我将加载和存储函数放在Godbolt编译器资源管理器上,以便通过x87,SSE和SSE2查看来自gcc的asm.来自clang4.0.1和ICC18.

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