Luce Base
LuceEngineeringLuciaOS

15. Atomics and volatile

15.1 Atomic types#

var ready: @bool
var hits: @u64
var head: @Node*?

hits += 1                                       # checked atomic add: traps on overflow
hits +%= 1                                      # C11 fetch-add, wrapping
ready = true                                    # store, seq_cst
if ready: ...                                   # load, seq_cst
let previous = hits.fetch_add(1, order = .relaxed)
ready.store(true, order = .release)
while not ready.load(order = .acquire): ...
match head.compare_exchange(expected = old, desired = new, success = .acq_rel, failure = .acquire):
    .none: ...                                  # exchanged
    .some(observed): ...                        # not exchanged; observed is the current value
atomic.fence(.release)
  • Plain reads and = are sequentially consistent loads and stores.
  • += and -= are sequentially consistent read-modify-write operations that trap on overflow, implemented as a compare-exchange loop, so the overflow rule holds for atomics too. +%= and -%= are C11's wrapping fetch_add and fetch_sub. |=, &=, ^= are fetch_or, fetch_and, fetch_xor.
  • The methods load, store, swap, fetch_add, fetch_sub, fetch_or, fetch_and, fetch_xor (all wrapping) take an Ordering: relaxed, acquire, release, acq_rel, seq_cst. compare_exchange(expected, desired, success, failure) -> T? answers none when it exchanged and the observed value when it did not, which is C11's write-back of expected as a value; failure may not be release or acq_rel and may not be stronger than success. compare_exchange_weak may fail spuriously. atomic.fence(order) is a standalone fence.
  • Every @T is lock-free by construction. There is no atomic struct.
  • Semantics are the C11 memory model.

Why on the type. Atomicity is a property of the location. If the marker were on the access, forgetting it once would be a plain access to the same location, which is a data race by definition; with it on the type, every access is atomic and the compiler can prove no mixed access exists. Refusing atomic structs refuses the lock C11 hides behind _Atomic struct.

15.2 volatile#

A load or store through volatile T* is an observable effect: never elided, merged, reordered with another volatile access or an asm block, or widened. It says nothing about other threads. Hardware registers use volatile; shared memory uses @.