|
CX Framework
Cross-platform C utility framework
|
C11-style atomic types and operations, with one consistent spelling across compilers and platforms.
Wrap a type in atomic(type) to declare an atomic variable or struct field:
The available type tokens are: ptr (a void*), bool, size (size_t), intptr, uintptr, int8, int16, int32, int64, uint8, uint16, uint32, and uint64.
An atomic variable must not be read or written directly - always go through the operations below, which take the same type token used to declare it.
Every operation takes a memory order, written as a bare name (the operation macro adds the required prefix internally): Relaxed, Acquire, Release, AcqRel, or SeqCst. These match the C11 memory order semantics of the same names - Relaxed only guarantees atomicity of the operation itself, Acquire/Release establish a happens-before relationship between a store and a later load of the same value, AcqRel combines both for read-modify-write operations, and SeqCst adds a single total order across all SeqCst operations for cases that need the strongest guarantee.
atomicLoad(type, atomic_ptr, order) reads the current value.atomicStore(type, atomic_ptr, val, order) writes a new value.atomicExchange(type, atomic_ptr, val, order) writes a new value and returns the value that was replaced.atomicCompareExchange(type, weak|strong, atomic_ptr, expected_ptr, desired, success_order, fail_order) writes desired only if the current value equals *expected_ptr, returning true on success. On failure, *expected_ptr is updated to the actual current value, so a failed call can typically be retried directly with the same variables. Use strong unless the operation is already inside a retry loop, where weak allows a cheaper implementation that may fail even when the comparison would have succeeded.atomicFetchAdd, atomicFetchSub, atomicFetchAnd, atomicFetchOr, and atomicFetchXor (all (type, atomic_ptr, val, order)) apply the operation and return the value from before it was applied. These are only available for the integer types, not ptr or bool.atomicFence(order) issues a standalone memory fence.atomicInit(val) produces a static initializer for an atomic variable, for use where a function call isn't allowed: weak the same as strong, so don't rely on being able to observe a spurious weak failure - only on the fact that a strong failure is never spurious.atomic(int64) and atomic(uint64) work on 32-bit platforms as well as 64-bit ones. On 32-bit x86 specifically, the read-modify-write operations (atomicExchange, atomicCompareExchange, and the atomicFetch* family) are implemented with a compare-and-swap retry loop rather than a single instruction, since the platform exposes no native 8-byte instruction for them. atomicLoad and atomicStore use a fast path where available. Expect 64-bit atomics to be slower than other sizes on 32-bit x86, and avoid them on hot paths that must also run there.