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
Please register or sign in to comment