atomics¶
lockfree/atomics is the umbrella's atomics facade. It provides the
typed atomic primitives used internally by the queue bodies and the
SMR layer: relaxed/acquire/release/seq-cst loads and stores, CAS,
fetch-add, and the bounded back-off loop used on contention.
Downstream code generally does not need to import this directly — the queue and SMR APIs encapsulate the atomic operations. It is documented here for callers writing additional lock-free primitives against the same memory-ordering discipline used by the umbrella.
See also¶
lockfree/atomics/dsl— the macros used to declare atomic fields and load/store sites with explicit memory ordering.lockfree/atomics/backoff— the spin/yield/back-off loop used by contention paths.
atomics
¶
debra/atomics
Custom atomics for nebr (and lockfree).
Goals over std/atomics:
* Reject Atomic[ref T] at compile time (no silent spinlock).
* Require Atomic[T] to be lock-free at compile time.
* Validate MemoryOrder per op at compile time.
* Statically assert alignof(Atomic[T]) >= sizeof(T) so a target
where T's natural alignment is insufficient (e.g. uint64 on
some 32-bit ABIs) fails to compile rather than silently downgrading
to a non-lock-free split-lock object. The assert is the safety
boundary; we cannot force alignment higher than the type's natural
alignment from generic code (see Atomic[T] doc), so we trap the
mismatch instead.
Design doc: docs/design/2026-04-25-custom-atomics.md
User guide: see docs/guide/atomics.md for a side-by-side comparison with std/atomics, the DWCAS surface overview, memory-order policy, cross-compiler/arch compatibility matrix, and an LCRQ-style worked example.
Backend strategy: wrap GCC/Clang __atomic_*_n builtins for the
unix toolchains (gcc, clang, llvm_gcc, nintendoswitch), and MSVC's
_Interlocked* intrinsics family (declared in <intrin.h>) for vcc.
The MSVC arms cover the full 1-, 2-, 4-, and 8-byte Atomic[T]
surface plus AtomicFlag, fences, and the 16-byte DWCAS via
_InterlockedCompareExchange128.
DWCAS (16-byte) emit logic adapted from atomic128 (https://github.com/patternnoster/atomic128) by patternnoster, MIT licensed. See atomic128_ref.hpp for the C++ reference implementation that documents the GCC __sync vs __atomic footgun this code works around. Pinned upstream commit: d45ba3d348a9620a25552f9cf50dc7ccef05ef90. See THIRD_PARTY_LICENSES.md for the verbatim MIT text.
MemoryOrder
¶
type MemoryOrder = enum
Specifies how non-atomic operations can be reordered around atomic
operations. Ordinals match GCC's __ATOMIC_* so ord(order) is
passed directly to the builtins.
Values
-
moRelaxed– No ordering constraints. -
moConsume– Accepted, mapped to moAcquire (mirrors std). -
moAcquire– No reordering of subsequent loads/stores before. -
moRelease– No reordering of preceding loads/stores after. -
moAcquireRelease– Both acquire and release on RMW. -
moSequentiallyConsistent– Single total order across all SC ops.
cacheLineAligned ¶
template cacheLineAligned(decl: untyped)
Drop-in {.align: CacheLineBytes.} shorthand.
Parameters
-
decl(untyped)
Pair
¶
type Pair[A, B] = object
16-byte-aligned object wrapper for DWCAS. Both halves must satisfy
supportsCopyMem, each must be <= 8 bytes, and sizeof(A) + sizeof(B)
must equal 16. Domain-neutral field names: first and second.
Conventional LCRQ usage spells Pair[uint64, T] where first is a
monotonically-bumped sequence counter (ABA defense) and second is the
payload. Generation overflow is documented as impossible in practical
lifetimes (uint64 monotonicity).
Field-level {.align: 16.} on first* elevates the whole object to
16-byte alignment per Nim 2.2.10 align-pragma scope rules; the
object-level form (type Pair {.align: 16.} = object ...) is rejected.
ptr T fields are explicitly opt-out of ARC: Pair makes no claim
on the lifetime of whatever a contained ptr points at.
See docs/guide/atomics.md §7 for an LCRQ-style worked example
using Pair[uint64, ptr Node].
Fields
-
secondB
hasManagedFields compileTime ¶
proc hasManagedFields(T: typedesc): bool
enforceDwcasConstraints ¶
template enforceDwcasConstraints(A, B: typedesc)
Gate 2 (Pair shape) + Gate 4 (lock-free) for Atomic[Pair[A, B]].
Checks sizeof(A) + sizeof(B) == 16 rather than sizeof(Pair[A, B]) == 16:
the field-level {.align: 16.} on first pads any undersized Pair up to
16 bytes, so the outer sizeof would silently mask half-size mismatches
(e.g. Pair[uint64, uint32] has 12 bytes of payload + 4 padding). DWCAS
requires both halves live (cmpxchg16b / casp compares all 128 bits), so
the payload sum is the real safety invariant.
Parameters
-
A(typedesc) -
B(typedesc)
Atomic
¶
type Atomic[T] = object
Atomic wrapper for T. Lock-free on this target; rejects
ref T.
Supports 1-, 2-, 4-, and 8-byte types only. 16-byte types
(__int128, double-quadword pointers) require a different code
path (cmpxchg16b on x86_64, casp on aarch64) and are not
currently provided. Such instantiations fail with a compile-time
{.error.} from the underlying nonAtomicType template.
Note on alignment: Nim's field-level {.align: ...} pragma
cannot reference sizeof(T) from a generic context (as of
Nim 2.2.6 it triggers sizeof requires .importc types to be
.completeStruct). We therefore rely on T's natural alignment
— which is >= sizeof(T) for the primitives we ship on every
64-bit ABI we currently target, but is not universally true.
Notably, on i386 System V alignof(uint64) == 4, which would
yield a split-lock object that is not always-lock-free.
enforceAtomicConstraints therefore fires
static: assert alignof(Atomic[T]) >= sizeof(T) per instantiation;
a target where natural alignment is insufficient fails to compile
rather than silently producing a non-lock-free object. If we ever
need to support such a target, the fix is a per-size specialisation
that boxes T in a struct with an explicit {.align: 8.} (or
similar) field; the generic path cannot do better today.
nonAtomicType ¶
template nonAtomicType(T: typedesc): typedesc
load inline ¶
proc load(loc: var Atomic[T]; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
order(static MemoryOrder)
Returns
T
store inline ¶
proc store(loc: var Atomic[T]; desired: T; order: static MemoryOrder = moSequentiallyConsistent)
Parameters
-
loc(var Atomic[T]) -
desired(T) -
order(static MemoryOrder)
exchange inline ¶
proc exchange(loc: var Atomic[T]; desired: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
desired(T) -
order(static MemoryOrder)
Returns
T
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[T]; expected: var T; desired: T; success: static MemoryOrder; failure: static MemoryOrder): bool
Strong CAS. On success, swaps desired into loc. On failure,
overwrites expected with the current value of loc.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[T]; expected: var T; desired: T; success: static MemoryOrder; failure: static MemoryOrder): bool
Weak CAS. May fail spuriously on platforms (notably ARM LL/SC)
even when current value equals expected. Cheaper inside a loop.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[T]; expected: var T; desired: T; order: static MemoryOrder): bool
Strong CAS, single-order form. Failure order is derived from
success per C11 (drop the release component): moRelease ->
moRelaxed, moAcquireRelease -> moAcquire, otherwise
unchanged. Use the five-arg form to spell the failure order
explicitly.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
order(static MemoryOrder)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[T]; expected: var T; desired: T; order: static MemoryOrder): bool
Weak CAS, single-order form. Failure order is derived from
success per compareExchangeStrong's single-order overload.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
order(static MemoryOrder)
Returns
bool
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[T]; expected: var T; desired: T): bool
Strong CAS, default-order form. Equivalent to passing
moSequentiallyConsistent for both success and failure.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[T]; expected: var T; desired: T): bool
Weak CAS, default-order form. Equivalent to passing
moSequentiallyConsistent for both success and failure.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T)
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[T]; expected: var T; desired: T; success: static MemoryOrder; failure: static MemoryOrder): bool
Strong compare-and-exchange. Unsuffixed-name alias for
compareExchangeStrong, matching the spelling used by clients
migrating from std/atomics.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[T]; expected: var T; desired: T; order: static MemoryOrder): bool
Strong CAS, single-order form. Failure order is derived from
success per C11 (drop the release component). Alias for
compareExchangeStrong.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T) -
order(static MemoryOrder)
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[T]; expected: var T; desired: T): bool
Strong CAS, default-order form. Equivalent to passing
moSequentiallyConsistent for both success and failure. Alias
for compareExchangeStrong.
Parameters
-
loc(var Atomic[T]) -
expected(var T) -
desired(T)
Returns
bool
fetchAdd inline ¶
proc fetchAdd(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
fetchSub inline ¶
proc fetchSub(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
fetchAnd inline ¶
proc fetchAnd(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
fetchOr inline ¶
proc fetchOr(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
fetchXor inline ¶
proc fetchXor(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
fetchAdd inline ¶
proc fetchAdd(loc: var Atomic[T]; v: T; order: static MemoryOrder = moSequentiallyConsistent): T
Atomically add v to the float value at loc and return the
previous value. Implemented as a compareExchangeWeak CAS-loop
because GCC's __atomic_fetch_add_n is integer-only.
Semantics: the read-add-CAS cycle is repeated until the CAS
succeeds, so the returned old value and the new stored value
together reflect a coherent atomic update of loc. Under
contention, the loop may iterate multiple times.
order is applied to the successful CAS; the failure order is
derived per C11 (drops the release component).
Bit-pattern fidelity caveat: fetchAdd performs an IEEE-754
float addition; unlike load/store/exchange/compareExchange*
(pure bitwise transfer), the bit-pattern guarantees do NOT apply
to the new stored value. Specifically:
- The returned old value IS bit-exact: it comes from a relaxed
load before the add and reflects the pre-RMW storage bits
verbatim (so e.g. a NaN payload in
locis preserved in the return value). - The new stored value is
old + vcomputed by the FPU, which means: NaN payloads are not preserved across the add (e.g.NaN_payload_A + 1.0yields a quiet NaN with implementation- defined payload, notpayload_A); denormal results may flush to zero if FTZ/DAZ is enabled in the calling thread's FPU state; the rounding mode is whatever the FPU is currently set to (round-to-nearest by default; modifiable viafesetroundon x86 or FPCR on ARM); and overflow produces +/-Inf.
The CAS-loop preserves atomicity; these caveats are inherent to float arithmetic, not to this implementation.
Parameters
-
loc(var Atomic[T]) -
v(T) -
order(static MemoryOrder)
Returns
T
threadFence inline ¶
proc threadFence(order: MemoryOrder)
Full memory fence between threads. All memory orders are valid.
Parameters
-
order(MemoryOrder)
signalFence inline ¶
proc signalFence(order: MemoryOrder)
Compiler-only fence. Prevents the compiler from reordering across
the fence; emits no CPU instructions. All memory orders are valid.
Parameters
-
order(MemoryOrder)
AtomicFlag
¶
type AtomicFlag = distinct uint8
Boolean flag with testAndSet / clear semantics. Underlying
byte must be 0 or 1; __atomic_test_and_set is
implementation-defined for any other value, so do not poke the
raw uint8 directly.
testAndSet inline ¶
proc testAndSet(loc: var AtomicFlag; order: static MemoryOrder = moSequentiallyConsistent): bool
Atomically set the flag and return its previous value.
Parameters
-
loc(var AtomicFlag) -
order(static MemoryOrder)
Returns
bool
clear inline ¶
proc clear(loc: var AtomicFlag; order: static MemoryOrder = moSequentiallyConsistent)
Atomically reset the flag to false. order must not be
moAcquire / moAcquireRelease / moConsume.
Parameters
-
loc(var AtomicFlag) -
order(static MemoryOrder)
dwcasLoad ¶
template dwcasLoad(loc: var Atomic[Pair[A, B]]; order: static MemoryOrder): Pair[A, B]
Low-level 16-byte atomic load emit. Backend-dispatched by compiler:
gcc → __sync_val_compare_and_swap on __int128 (inlines
cmpxchg16b on x86_64 with -mcx16 and casp on aarch64 with
-march=armv8.1-a+lse); clang / llvm_gcc → __atomic_load_n on
__int128. Callers should prefer load(Atomic[Pair[A, B]])
which carries the per-callsite memory-order validation and
seq_cst-upgrade warning. Exposed for testing and for callers
that have already validated the order policy externally.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
order(static MemoryOrder)
Returns
Pair[A, B]
load inline ¶
proc load(loc: var Atomic[Pair[A, B]]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
16-byte atomic load via DWCAS substrate. Returns the current value
of loc as a Pair[A, B]. Always seq_cst at the instruction level;
sub-seq_cst order values are accepted but upgraded with a compile-
time warning.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
order(static MemoryOrder)
Returns
Pair[A, B]
dwcasStore ¶
template dwcasStore(loc: var Atomic[Pair[A, B]]; desired: Pair[A, B]; order: static MemoryOrder)
Low-level 16-byte atomic store emit. Backend-dispatched (see
dwcasLoad). Prefer store(Atomic[Pair[A, B]]) for the
validated, warning-emitting surface.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
store inline ¶
proc store(loc: var Atomic[Pair[A, B]]; desired: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent)
16-byte atomic store via DWCAS substrate. Always seq_cst at the
instruction level; sub-seq_cst order values emit a compile-time
warning.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
dwcasExchange ¶
template dwcasExchange(loc: var Atomic[Pair[A, B]]; desired: Pair[A, B]; order: static MemoryOrder): Pair[A, B]
Low-level 16-byte atomic exchange emit. Backend-dispatched
(see dwcasLoad). Prefer exchange(Atomic[Pair[A, B]]) for
the validated, warning-emitting surface.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
exchange inline ¶
proc exchange(loc: var Atomic[Pair[A, B]]; desired: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
16-byte atomic exchange via DWCAS substrate. Atomically replaces the
value at loc with desired and returns the prior value. Always
seq_cst at the instruction level; sub-seq_cst order values emit a
compile-time warning.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
dwcasCasStrong ¶
template dwcasCasStrong(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; success: static MemoryOrder; failure: static MemoryOrder): bool
Low-level 16-byte strong CAS emit. Backend-dispatched (see
dwcasLoad). On gcc maps to __sync_val_compare_and_swap
(always-strong); on clang / llvm_gcc maps to
__atomic_compare_exchange_n with the weak flag = 0. Prefer
compareExchangeStrong(Atomic[Pair[A, B]]) for the validated,
warning-emitting surface.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; success: static MemoryOrder; failure: static MemoryOrder): bool
Strong 16-byte CAS via DWCAS. On success, swaps desired into
loc and returns true; expected is unchanged. On failure,
overwrites expected with the current value of loc and returns
false. Always seq_cst at the instruction level; sub-seq_cst
success/failure values emit a compile-time warning.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; order: static MemoryOrder): bool
Strong 16-byte CAS, single-order form. Failure order is derived
from order per C11 (drop the release component).
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
Returns
bool
compareExchangeStrong inline ¶
proc compareExchangeStrong(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]): bool
Strong 16-byte CAS, default-order form. Equivalent to passing
moSequentiallyConsistent for both success and failure.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B])
Returns
bool
dwcasCasWeak ¶
template dwcasCasWeak(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; success: static MemoryOrder; failure: static MemoryOrder): bool
Low-level 16-byte weak CAS emit. Backend-dispatched (see
dwcasLoad). On gcc, __sync_val_compare_and_swap is always-strong
on both x86_64 (cmpxchg16b) and aarch64+LSE (casp), so the
weak/strong distinction is a no-op; on clang / llvm_gcc maps to
__atomic_compare_exchange_n with weak=1, where ARMv8.0 LL/SC
(no LSE) stlxp may genuinely fail spuriously, while aarch64
FEAT_LSE / LSE2 (caspal, objdump-verified on Apple Silicon)
is always-strong. Prefer
compareExchangeWeak(Atomic[Pair[A, B]]) for the validated,
warning-emitting surface.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; success: static MemoryOrder; failure: static MemoryOrder): bool
Weak 16-byte CAS via DWCAS. May fail spuriously only on ARMv8.0
LL/SC cores (no LSE), where stlxp is permitted to fail without
contention. On all other supported targets weak and strong are
equivalent: x86_64 (cmpxchg16b) is always-strong; arm64 with
FEAT_LSE / LSE2 (Apple Silicon, modern server chips) emits
caspal for both procs (objdump-verified on Apple Silicon).
Default to compareExchangeStrong; reach for Weak only after
measuring a contention win on an ARMv8.0 target.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; order: static MemoryOrder): bool
Weak 16-byte CAS, single-order form. Failure order is derived
from order per C11 (drop the release component).
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
Returns
bool
compareExchangeWeak inline ¶
proc compareExchangeWeak(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]): bool
Weak 16-byte CAS, default-order form. Equivalent to passing
moSequentiallyConsistent for both success and failure. Mirrors
the compareExchangeStrong default-order overload so that the
two overload sets match shape-for-shape — relevant for callers
migrating from std/atomics, whose compareExchange* procs all
accept the no-order form.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B])
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; success: static MemoryOrder; failure: static MemoryOrder): bool
Strong 16-byte CAS. Unsuffixed-name alias for
compareExchangeStrong, matching the spelling used by clients
migrating from std/atomics.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
success(static MemoryOrder) -
failure(static MemoryOrder)
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]; order: static MemoryOrder): bool
Strong 16-byte CAS, single-order form. Alias for
compareExchangeStrong.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B]) -
order(static MemoryOrder)
Returns
bool
compareExchange inline ¶
proc compareExchange(loc: var Atomic[Pair[A, B]]; expected: var Pair[A, B]; desired: Pair[A, B]): bool
Strong 16-byte CAS, default-order form. Alias for
compareExchangeStrong.
ABA / aliasing note: expected and desired MUST be distinct
memory locations. On CAS failure, expected is overwritten in-place
with the current value of loc; on success, expected is unchanged.
Passing the same var location for both expected and desired is
a defined-but-confusing pattern (the desired payload is read first,
then expected is overwritten — but if expected and desired
alias, the post-CAS desired is undefined). Callers MUST NOT alias
them. The 1/2/4/8-byte CAS surface has the same contract; the
16-byte ops re-document it here because the wider value makes
accidental aliasing more tempting in LCRQ-style consumer code.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
expected(var Pair[A, B]) -
desired(Pair[A, B])
Returns
bool
fetchAdd inline ¶
proc fetchAdd(loc: var Atomic[Pair[A, B]]; delta: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
Atomically add delta.first to loc.first and delta.second to
loc.second, returning the prior 128-bit pair. Both halves are
updated as a single atomic transaction via a 16-byte CAS-loop
(cmpxchg16b / casp / _InterlockedCompareExchange128).
Half-type overflow behavior follows the underlying integer type
(SomeInteger). Unsigned halves wrap modulo 2^(sizeof(half)*8).
Signed halves do NOT silently wrap: the half-add prev.first +
delta.first is an ordinary checked Nim addition, so under default
builds (overflow checks on) a signed-half overflow raises
OverflowDefect — aborting the CAS loop rather than completing the
"atomic transaction". Signed halves only wrap two's-complement
under -d:danger or an explicit {.push overflowChecks: off.}.
For counters that must never trap (e.g. an LCRQ generation tag),
use an unsigned half-type.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
delta(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
fetchSub inline ¶
proc fetchSub(loc: var Atomic[Pair[A, B]]; delta: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
Componentwise atomic subtract. See fetchAdd for transactional
semantics and overflow behavior.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
delta(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
fetchAnd inline ¶
proc fetchAnd(loc: var Atomic[Pair[A, B]]; mask: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
Componentwise atomic bitwise AND. See fetchAdd for transactional
semantics.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
mask(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
fetchOr inline ¶
proc fetchOr(loc: var Atomic[Pair[A, B]]; mask: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
Componentwise atomic bitwise OR. See fetchAdd for transactional
semantics.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
mask(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
fetchXor inline ¶
proc fetchXor(loc: var Atomic[Pair[A, B]]; mask: Pair[A, B]; order: static MemoryOrder = moSequentiallyConsistent): Pair[A, B]
Componentwise atomic bitwise XOR. See fetchAdd for transactional
semantics.
Parameters
-
loc(var Atomic[Pair[A, B]]) -
mask(Pair[A, B]) -
order(static MemoryOrder)
Returns
Pair[A, B]
dwcasOrderRelaxedCAS ¶
template dwcasOrderRelaxedCAS(body: untyped): untyped
Wraps a DWCAS call site, suppressing the moSeqCst-upgrade
warning emitted by the 16-byte ops (load / store / exchange /
compareExchange*) for that site only. Use when the upgrade is
intentional and audited (e.g. the LCRQ producer publish CAS
passing moRelease / moRelaxed). The warning continues to
fire at unwrapped call sites.
See docs/design/2026-04-25-custom-atomics.md §3 and the
user guide docs/guide/atomics.md §5 (Memory-order policy) for
the memory-model rationale.
Parameters
-
body(untyped)
Returns
untyped