code wiki / (root) / nx_atom.nx

nx_atom.nx source

↩ module page · 213 lines · 8454 B

1// nx_atom.nx -- atomic primitives + memory ordering. 2// 3// FOUNDATION LAYER. Every lock-free data structure, every SMP-safe 4// counter, every message queue in NishiOS sits on top of these 5// operations: load, store, CAS, FAA, and memory barriers. 6// 7// Why now (DARPA-class concurrency correctness): 8// 9// The current observability stack (nx_log, nx_trace, nx_metrics, 10// nx_arena, nx_ring, nx_id) is single-threaded-only. The moment 11// NishiOS ships its SMP scheduler, every one of those modules 12// either gains an atomic primitive or silently corrupts. Land 13// the primitive FIRST -- mechanical retrofit, not rescue. 14// 15// seL4 / Nemesis lineage: concurrency correctness is the 16// second-highest cost in kernel verification (after memory 17// safety). seL4's proof rests on a uniprocessor model; the 18// multi-core extension (MCS seL4) required re-proving sizable 19// chunks around synchronisation primitives. Getting the atomic 20// memory model RIGHT AT THE SOURCE LANGUAGE LAYER pays that cost 21// once. 22// 23// MEMORY ORDERING TAXONOMY (RV64A, C11 atomic_*, Rust atomic_*): 24// 25// NX_MO_RELAXED No ordering. Counter bumps. Fastest. 26// NX_MO_CONSUME Data-dependent loads (C11 intent; rarely used). 27// NX_MO_ACQUIRE Load-acquire: no load/store after this op can 28// migrate BEFORE it. Standard lock acquire. 29// NX_MO_RELEASE Store-release: no load/store before this op 30// can migrate AFTER it. Standard lock release. 31// NX_MO_ACQ_REL Both -- used on RMW (read-modify-write) ops. 32// NX_MO_SEQ_CST Sequential consistency: single global order 33// across all seq-cst ops on the machine. Slowest. 34// Default for userland until profiling warrants 35// weaker ordering. 36// 37// RV64A MAPPING (RISC-V A extension) -- emit targets when nxc2 grows 38// inline asm: 39// 40// load-acquire lr.d / ld + fence r,rw (aq bit on lr.d) 41// store-release sd + fence rw,w / sc.d (rl bit on sc.d) 42// CAS lr.d + sc.d loop 43// FAA amoadd.d (acq/rel via aq/rl) 44// Fence fence [pred], [succ] 45// 46// x86 TSO needs fewer fences; ARM weak-memory needs more. This 47// module is the abstraction point so user code writes once. 48// 49// WHAT WE SHIP TODAY: 50// 51// * Ordering enum (stable across future backends) 52// * Public API: load / store / cas / faa / fence (i64 cell) 53// * Inline-asm stubs with TODO markers -- NishiLang doesn't yet 54// have an __asm intrinsic; when it lands these emit real RV64A 55// * Convenience: inc, dec, try_lock, unlock spinlock pattern 56// * Property tests: CAS observe-then-write, FAA returns-prior, 57// spinlock take/release discipline 58// 59// WHAT WE DO NOT YET SHIP: 60// 61// * Real HW emit -- compiler change needed (__atomic_cas_i64 etc. 62// intrinsic that lowers to lr.d/sc.d). Tracked Q7 cluster. 63// * Per-ordering cost model for the regalloc. Future commit. 64// * SEQ_CST optimisation (store-load barrier elision when prev 65// op is already a release). When the cost model lands. 66// 67// REFERENCES: 68// 69// * C11 6.7.2.4 _Atomic; 7.17 <stdatomic.h> 70// * Rust std::sync::atomic (2015 stabilisation) 71// * Boehm-Adve "Foundations of the C++ Concurrency Memory Model" 72// PLDI 2008 73// * Lea & Manson JSR-133 Java Memory Model 74// * McKenney "Memory Barriers: a Hardware View for Software 75// Hackers" Linux kernel primer 76// * Sewell et al. "x86-TSO: A Rigorous and Usable Programmer's 77// Model for x86 Multiprocessors" CACM 2010 78// * RISC-V Unprivileged ISA, Chapter 14 (A extension) 79// * Klein et al. seL4 MCS extension 2018 (SMP proof experience) 80 81// nx_safety_envelope: 82// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 83// sil_target: SIL1 84// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 85// verdict: NOT_YET_EVALUATED 86 87import "nx_syscalls.nx" 88 89// ---- memory ordering constants ------------------------------------ 90 91const NX_MO_RELAXED: i64 = 0 92const NX_MO_CONSUME: i64 = 1 93const NX_MO_ACQUIRE: i64 = 2 94const NX_MO_RELEASE: i64 = 3 95const NX_MO_ACQ_REL: i64 = 4 96const NX_MO_SEQ_CST: i64 = 5 97 98// ---- core primitives (stub lowering; HW emit TODO) ---------------- 99 100// Ordered load of 8 bytes. Lowers to `lr.d.aq` (ACQUIRE/SEQ_CST) or 101// plain `ld` (RELAXED) on RV64A; plain `movq` on x86 TSO. Real HW 102// emit since 2026-05-15. 103func nx_atom_load_i64(addr: *i64, mo: i64) -> i64 { 104 return __atomic_load_i64(addr, mo) 105} 106 107// Ordered store of 8 bytes. Lowers to `amoswap.d.rl x0, val, (addr)` 108// for RELEASE/SEQ_CST on RV64A; `xchgq` on SEQ_CST x86, `movq` else. 109func nx_atom_store_i64(addr: *i64, val: i64, mo: i64) -> i64 { 110 __atomic_store_i64(addr, val, mo) 111 return 0 112} 113 114// Compare-and-swap: if *addr == expected, write new and return 1; 115// else leave *addr alone, return 0. Lowers to a `lr.d.aq + sc.d.rl` 116// retry loop on RV64A; single `lock cmpxchgq` + setz on x86. 117func nx_atom_cas_i64(addr: *i64, expected: i64, new_val: i64, mo: i64) -> i64 { 118 return __atomic_cas_i64(addr, expected, new_val, mo) 119} 120 121// Fetch-and-add: returns the OLD value; atomically adds delta. 122// Single-instruction `amoadd.d.aqrl` on RV64A; `lock xaddq` on x86. 123func nx_atom_faa_i64(addr: *i64, delta: i64, mo: i64) -> i64 { 124 return __atomic_faa_i64(addr, delta, mo) 125} 126 127// Standalone fence. Lowers to `fence rw, rw` on RV64 SEQ_CST, 128// `fence r, rw` on ACQUIRE, `fence rw, w` on RELEASE; `mfence` on 129// x86 SEQ_CST/ACQ_REL. RELAXED is a no-op. 130func nx_atom_fence(mo: i64) -> i64 { 131 __atomic_fence(mo) 132 return 0 133} 134 135// ---- convenience primitives built on core ------------------------- 136 137// Increment, returning the new value. 138func nx_atom_inc_i64(addr: *i64) -> i64 { 139 let old: i64 = nx_atom_faa_i64(addr, 1, NX_MO_SEQ_CST) 140 return old + 1 141} 142 143// Decrement, returning the new value. 144func nx_atom_dec_i64(addr: *i64) -> i64 { 145 let old: i64 = nx_atom_faa_i64(addr, -1, NX_MO_SEQ_CST) 146 return old - 1 147} 148 149// Spinlock try-take (0=free, 1=taken). Returns 1 if acquired. 150func nx_atom_try_lock_i64(addr: *i64) -> i64 { 151 return nx_atom_cas_i64(addr, 0, 1, NX_MO_ACQUIRE) 152} 153 154// Spinlock release. 155func nx_atom_unlock_i64(addr: *i64) -> i64 { 156 nx_atom_store_i64(addr, 0, NX_MO_RELEASE) 157 return 0 158} 159 160// ---- self-test ---------------------------------------------------- 161 162func main() -> i64 { 163 let cell_raw: *u8 = sys_mmap(16) 164 let cell: *i64 = cell_raw as *i64 165 *cell = 0 166 167 // Load/store round-trip. 168 nx_atom_store_i64(cell, 42, NX_MO_RELAXED) 169 if nx_atom_load_i64(cell, NX_MO_RELAXED) != 42 { 170 return __syscall(93, 10, 0, 0, 0, 0, 0) 171 } 172 173 // FAA returns OLD, increments. 174 let old1: i64 = nx_atom_faa_i64(cell, 5, NX_MO_SEQ_CST) 175 if old1 != 42 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 176 if *cell != 47 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 177 178 // CAS success: observes old, writes new. 179 let r_ok: i64 = nx_atom_cas_i64(cell, 47, 100, NX_MO_SEQ_CST) 180 if r_ok != 1 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 181 if *cell != 100 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 182 183 // CAS failure: leaves cell, returns 0. 184 let r_fail: i64 = nx_atom_cas_i64(cell, 999, 123, NX_MO_SEQ_CST) 185 if r_fail != 0 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 186 if *cell != 100 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 187 188 // Inc / dec. 189 nx_atom_store_i64(cell, 10, NX_MO_RELAXED) 190 let after_inc: i64 = nx_atom_inc_i64(cell) 191 if after_inc != 11 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 192 if *cell != 11 { return __syscall(93, 51, 0, 0, 0, 0, 0) } 193 let after_dec: i64 = nx_atom_dec_i64(cell) 194 if after_dec != 10 { return __syscall(93, 52, 0, 0, 0, 0, 0) } 195 196 // Spinlock discipline. 197 let lock_raw: *u8 = sys_mmap(16) 198 let lock: *i64 = lock_raw as *i64 199 *lock = 0 200 let got_a: i64 = nx_atom_try_lock_i64(lock) 201 if got_a != 1 { return __syscall(93, 60, 0, 0, 0, 0, 0) } 202 let got_b: i64 = nx_atom_try_lock_i64(lock) 203 if got_b != 0 { return __syscall(93, 61, 0, 0, 0, 0, 0) } 204 nx_atom_unlock_i64(lock) 205 let got_c: i64 = nx_atom_try_lock_i64(lock) 206 if got_c != 1 { return __syscall(93, 62, 0, 0, 0, 0, 0) } 207 208 // Ordering constants stable + distinct. 209 if NX_MO_RELAXED != 0 { return __syscall(93, 70, 0, 0, 0, 0, 0) } 210 if NX_MO_SEQ_CST != 5 { return __syscall(93, 71, 0, 0, 0, 0, 0) } 211 212 return 0 213}