2026-08-19
x86 does atomics with a single instruction: LOCK CMPXCHG holds the cache line in exclusive state for the duration of the read-modify-write. ARM took a fundamentally different path — load-linked / store-conditional (LL/SC). Instead of one indivisible instruction, you get two cooperating ones: LDXR (Load Exclusive Register) and STXR (Store Exclusive Register).
The mechanism: LDXR X0, [X1] reads a word and tells the CPU's exclusive monitor to watch that address. You do arbitrary computation. Then STXR W2, X3, [X1] attempts the store — but only succeeds if nothing else touched that cache line since your LDXR. Success returns 0 in W2; failure returns 1, and you retry the loop.
A typical atomic increment on AArch64:
1: ldxr x0, [x1] — load with exclusive reservationadd x0, x0, #1 — modifystxr w2, x0, [x1] — try to commitcbnz w2, 1b — retry if the monitor was clearedWhat clears the monitor? Another core writing that cache line (via coherence traffic), a context switch, an exception, another LDXR from the same core, or exceeding the reservation granule (typically the cache line, 64B). Even a random interrupt between your LDXR and STXR will cause the STXR to fail — the kernel's exception entry clears the monitor.
Why this matters practically: LL/SC is optimistic. Uncontended atomics never bus-lock anything; only the coherence protocol's normal invalidations matter. But under heavy contention, LL/SC can livelock — every core keeps invalidating every other core's reservation. This is why ARMv8.1 added LSE (Large System Extensions) with true atomic instructions like CAS, LDADD, SWP. On a Graviton3 or Apple M-series, LSE atomics can be 4-10x faster than LDXR/STXR loops on a contended counter.
Rule of thumb: An uncontended LDXR/STXR pair costs ~3-5 ns. Under N-core contention on a hot line, throughput collapses to roughly 1 / (N × cache_miss_latency) ≈ one atomic per 100 ns per core at N=16. If you see ldxr/stxr in a hot profile on modern ARM, check whether glibc was built with -moutline-atomics or your compiler emitted the casal family instead.
Real-world gotcha: The Linux kernel's arch/arm64/include/asm/atomic_ll_sc.h keeps LL/SC as a fallback for pre-v8.1 CPUs, but production binaries increasingly ship casal unconditionally. AWS Graviton2 was the last major server CPU where the choice mattered performance-wise.
