x86_64 上的原子 16 字节操作

Atomic 16-Byte operations on x86_64

下面的16字节原子操作是否正确执行?有没有更好的选择?

typedef struct {
    uintptr_t low;
    uintptr_t high;
} uint128_atomic;


uint128_atomic load_relaxed(uint128_atomic const *atomic)
{
    uint128_atomic ret;
    asm volatile("xor %%eax, %%eax\n"
                 "xor %%ebx, %%ebx\n"
                 "xor %%ecx, %%ecx\n"
                 "xor %%edx, %%edx\n"
                 "lock; cmpxchg16b %1"
                 : "=A"(ret)
                 : "m"(*atomic)
                 : "cc", "rbx", "rcx");
    return ret;
}

bool cmpexch_weak_relaxed(
    uint128_atomic *atomic,
    uint128_atomic *expected,
    uint128_atomic desired)
{
    bool matched;
    uint128_atomic e = *expected;
    asm volatile("lock; cmpxchg16b %1\n"
                 "setz %0"
                 : "=q"(matched), "+m"(atomic->ui)
                 : "a"(e.low), "d"(e.high), "b"(desired.low), "c"(desired.high)
                 : "cc");
    return matched;
}

void store_relaxed(uint128_atomic *atomic, uint128_atomic val)
{
    uint128_atomic old = *atomic;
    asm volatile("lock; cmpxchg16b %0"
                 : "+m"(*atomic)
                 : "a"(old.low), "d"(old.high), "b"(val.low), "c"(val.high)
                 : "cc");
}

有关完整的工作示例,请查看:

https://godbolt.org/g/CemfSg

可以在此处找到更新的实现:https://godbolt.org/g/vGNQG5

为什么不直接使用 C11 原子内在函数?

#include <stdatomic.h>

inline __uint128_t load_relaxed(_Atomic __uint128_t *obj)
{
  return atomic_load_explicit(obj, memory_order_relaxed);
}

inline _Bool cmpexch_weak_relaxed(_Atomic __uint128_t *obj,
                                  __uint128_t *expected,
                                  __uint128_t desired)
{
  return atomic_compare_exchange_weak_explicit(obj, expected, desired,
    memory_order_relaxed, memory_order_relaxed);
}

这会使用 clang 4.0.1 和 -march=native 编译成 more-or-less 您编写的程序集。但是,与您编写的不同,编译器实际上了解发生了什么,因此代码生成 around 这些函数将是正确的。据我所知,没有办法注释 GNU-style 程序集插入以告诉编译器它具有原子操作的语义。

不,您需要 cmpexch_weak_relaxed 和 store_relaxed 中的“+a”和“+d”。

除此之外,我没有发现任何问题。 (我比较了我自己在工作软件中的实现。)

就改进而言,我建议

uint128_atomic load_relaxed(uint128_atomic const *atomic)
{
    uint128_atomic ret = { 0, 0 };
    asm volatile("lock; cmpxchg16b %1"
                 : "+A"(ret)
                 : "m"(*atomic), "b"(0), "c"(0)
                 : "cc");
    return ret;
}

(我看到 David Wohlferd 在评论中也提出了这个建议。)

在应用了 @PeterCordes, @David Wohlferd and @prl 的所有建议后,我想出了以下实现。非常感谢!

struct _uint128_atomic {
    volatile uint64_t low;
    volatile uint64_t high;
} __attribute__((aligned(16)));
typedef struct _uint128_atomic uint128_atomic;


bool
cmpexch_weak_relaxed(
    uint128_atomic *atomic,
    uint128_atomic *expected,
    uint128_atomic desired)
{
    bool matched;
    uint128_atomic e = *expected;
    asm volatile("lock cmpxchg16b %1"
                 : "=@ccz"(matched), "+m"(*atomic), "+a"(e.low), "+d"(e.high)
                 : "b"(desired.low), "c"(desired.high)
                 : "cc");
    if (!matched)
        *expected = e;
    return matched;
}


uint128_atomic
load_relaxed(uint128_atomic const *atomic)
{
    uint128_atomic ret = {0, 0};
    asm volatile("lock cmpxchg16b %1"
                 : "+A"(ret)
                 : "m"(*atomic), "b"(0), "c"(0)
                 : "cc");
    return ret;
}


void
store_relaxed(uint128_atomic *atomic, uint128_atomic val)
{
    uint128_atomic old = *atomic;
    while (!cmpexch_weak_relaxed(atomic, &old, val))
        ;
}

请记住,该实现是特定于 GCC 的,不适用于 clangclang 中 GCC 内联汇编的实现充其量是次优的,最坏的情况下是垃圾。 GCC 实现也可以在 Godbolt 的 Compiler Explorer here 上找到。 可以找到一个次优但有效的 clang 实现 here.