这个比较交换函数中的内联汇编是如何工作的? (ARM 上的 %H 修饰符)

How does the inline assembly in this compare-exchange function work? (%H modifier on ARM)

static inline unsigned long long __cmpxchg64(unsigned long long *ptr,unsigned long long old,unsigned long long new)
{
    unsigned long long oldval;
    unsigned long res;
    prefetchw(ptr);
    __asm__ __volatile__(
"1: ldrexd      %1, %H1, [%3]\n"
"   teq     %1, %4\n"
"   teqeq       %H1, %H4\n"
"   bne     2f\n"
"   strexd      %0, %5, %H5, [%3]\n"
"   teq     %0, #0\n"
"   bne     1b\n"
"2:"
    : "=&r" (res), "=&r" (oldval), "+Qo" (*ptr)
    : "r" (ptr), "r" (old), "r" (new)
    : "cc");
    return oldval;
}

我在 gnu 手册(扩展 extended-asm)中发现 '%H1' 中的 'H' 表示 'Add 8 bytes to an offsettable memory reference'。

但我认为如果我想将双字长数据加载到oldval(一个long long值),应该将4个字节添加到'%1',即oldval的低32位作为高32位有点旧。那么我的错误是什么?

I find in gnu manual(extend extended-asm) that 'H' in '%H1' means 'Add 8 bytes to an offsettable memory reference'.

That table of template modifiers 仅适用于 x86。不适用于ARM。

不幸的是,ARM 的模板修饰符没有记录在 GCC 手册中,但它们在 armclang manual 中定义,据我所知,GCC 符合这些定义。所以这里的H模板修饰符的正确含义是:

The operand must use the r constraint, and must be a 64-bit integer or floating-point type. The operand is printed as the highest-numbered register holding half of the value.

现在这是有道理的。 inline asm 的操作数 1 是 oldval,类型为 unsigned long long,64 位,因此编译器会为其分配两个连续的 32 位通用寄存器。假设它们是 r4r5,如 this compiled output. Then %1 will expand to r4, and %H1 will expand to r5, which is exactly what the ldrexd instruction needs. Likewise, %4, %H4 expanded to r2, r3, and %5, %H5 expanded to fp, ip, which are alternative names for r11, r12.

frant 的回答解释了 compare-exchange 应该做什么。 (拼写 cmpxchg 可能来自 x86 compare-exchange 指令的助记符。)如果您现在通读代码,您应该会发现它确实是这样做的。如果 old*ptr 不相等,ldrexdstrexd 之间的 teq; teqeq; bne 将中止存储。如果独占存储失败,strexd 之后的 teq; bne 将导致重试,如果有对 *ptr 的干预访问(由另一个核心、中断处理程序等),就会发生这种情况。这样就保证了原子性。