与常规cmpxchg r/m32, r32 的措辞(具有显式而不是隐式来源)相比,它应该更有意义,尤其是比较手动输入顶部表格中的简短描述。我已经用 dst、src 和隐式进行了注释。请注意,Intel 语法一般为op dst, src。
-
cmpxchg r/m64, r64:比较 RAX (implicit) 和 r/m64 (dst)。如果相等,则设置 ZF 并将 r64 (src) 加载到 r/m64 (dst) 中。否则,清除 ZF 并将 r/m64 (dst) 加载到 RAX (implicit)。
-
cmpxchg16b m128 比较 RDX:RAX 和 m128 (dst)。如果相等,则设置 ZF 并将 RCX:RBX 加载到 m128 (dst) 中。否则,清除 ZF 并将 m128 加载到 RDX:RAX。
是的,没错,英特尔的手册使用“加载”来描述存储到内存。 (对于cmpxchg,目标可以是一个寄存器,这有点合理,对于cmpxchg16b,根本不是。)
但无论如何,记住这些实现会有所帮助:
m64.compare_exchange_strong(expected=RAX, desired=r64);
m128.compare_exchange_strong(expected=RDX:RAX, desired=RCX:RBX);
(就 C++ std::atomic 而言。要真正成为原子,它们需要 lock 前缀,否则它是非原子 RMW。C++ 只会编译为 lock cmpxchg / lock cmpxchg16b,永远不会是 un-与主流编译器锁定cmpxchg。)
目标操作数被写回...回什么?
目标的旧值(刚刚加载)被写回。这意味着cmpxchg16b总是是一个写操作,例如始终将页面的脏标志标记为脏。 (Does cmpxchg write destination cache line on failure? If not, is it better than xchg for spinlock? 询问它是否真的在 CAS 故障时在微架构上弄脏了缓存行。我假设是这样,但尚未检查。)
这对于旧 CPU 上的 lock 前缀在历史上很重要,其中有一个外部 LOCK# 引脚,lock cmpxchg 实际上为整个加载+存储对断言。现代 CPU 只是在持续时间内保持受影响缓存行上的缓存锁,用于可缓存内存上的对齐锁 CAS。这就是为什么手册说“为了简化与处理器总线的接口,目标操作数接收一个写周期而不考虑比较结果。”
如果比较失败,则写回目标操作数;否则,源操作数被写入目标。 (处理器不会在不产生锁定写入的情况下产生锁定读取。)
整个段落是在英特尔编写cmpxchg16b 条目时从cmpxchg 手动条目中复制粘贴的;在 CX16 上下文中不太清楚,因为它有 2 个隐式操作数,而不是显式源和读写 RAX。它没有定义术语“源操作数”。
在描述的前面,它确实为该指令定义了“目标操作数”术语
比较 EDX:EAX 中的 64 位值(或 RDX:RAX 中的 128 位值,如果操作数大小为 128 位)与操作数(目标操作数)
“操作数”表示显式操作数。这显然是什么意思,因为它是唯一可以作为记忆的东西,所以它必须是被比较的东西之一。还有其他线索/原因来自英语的运作方式等等。
所以“目标操作数”确实得到了明确定义,但是在一条总共有 3 个操作数的指令中,不定义它就说“源操作数”是很糟糕的。就像我说的那样,显然是英特尔文档作者复制/粘贴的结果。
这不是一个严重的问题;我们知道指令的基本点,操作部分可以 100% 清楚实际发生的情况。