Commit c900e435 authored by Matthew Fernandez's avatar Matthew Fernandez
Browse files

avoid linking libatomic if possible on ARM64

Double-word atomics are used to implement lock-free reference-counted pointers.¹
Compiler built-ins (the GCC __sync and __atomic built-ins) are used to access
this functionality. It is up to the compiler how to lower these built-ins, and
it chooses between emitting inline instructions or calling into the libatomic
runtime support library.² The implementations in libatomic are typically not
lock-free – they work by using per-variable-instance mutexes – which negates the
performance benefits of the lock-free algorithm we are trying to implement. In
tight code like our scenario, the function call overhead into libatomic is also
a noticeable factor.

It was observed that on ARM64 GCC lowers these __atomic built-ins into libatomic
calls. ARM64 has Load-Linked/Store-Conditional (LL/SC) instructions that can be
used to implement these inline, but GCC has traditionally avoided using these to
back the atomics.³ While compiler developers were debating the utility of these
instructions, ARM introduced “Large System Extensions,” adding a new CASP family
of instructions that more efficiently implements compare-and-swap.

So our aim is to use the CASP instructions where possible. We have the following
matrix:

  ┌──────┬───────────────────────┬───────────────────────┐
  │      │       Clang           │         GCC           │
  ├──────┼───────────────────────┼───────────────────────┤
  │ load │     __atomic          │       __sync          │
  │      │ <armv8.1-a: LL/SC     │ <armv8.1-a: libatomic │
  │      │ ≥armv8.1-a: CASP      │ ≥armv8.1-a: CASP      │
  ├──────┼───────────────────────┼───────────────────────┤
  │ store│     __atomic          │       __sync          │
  │      │ <armv8.1-a: LL/SC     │ <armv8.1-a: libatomic │
  │      │ ≥armv8.1-a: CASP      │ ≥armv8.1-a: CASP      │
  ├──────┼───────────────────────┼───────────────────────┤
  │ CAS  │     __atomic          │       __sync          │
  │      │ <armv8.1-a: libatomic │ <armv8.1-a: libatomic │
  │      │ ≥armv8.1-a: CASP      │ ≥armv8.1-a: CASP      │
  └──────┴───────────────────────┴───────────────────────┘

This seems to result in a near-optimal situation on ≥armv8.1-a. For <armv8.1-a,
the only way to avoid linking against libatomic seems to be resorting to inline
assembly which is not worthwhile.

Reported-by: e69d5a347277e5e7cb23518d93266bdac89a4bad

¹ Specifically “Hazard Pointers” as described in Maged Michael’s “Hazard
  Pointers: Safe Memory Reclamation for Lock-Free Objects” in TPDS 15(8) 2004.
² GCC 10.1 introduced a third option that does a runtime check, similar to GNU
  indirect functions (IFUNCs),
  https://community.arm.com/arm-community-blogs/b/tools-software-ides-blog/posts/making-the-most-of-the-arm-architecture-in-gcc-10.
  However, this is not relevant to us.
³ See https://gcc.gnu.org/pipermail/gcc-help/2017-June.txt for a lengthy debate
  on this and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878 for the
  underlying rationale for avoiding both LL/SC and cmpxchg for backing these
  atomics. As one participant in the first linked discussion accurately
  summarises, “I think what’s happened makes perfect sense at each step of the
  way but has led to an outcome which is crazy.”
parent bc2973a5
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment