code wiki / (root) / nx_arena_smp_gate.nx

nx_arena_smp_gate.nx

buildroot/runtime/nx_arena_smp_gate.nx

10520 B215 linesdepth 7pulls 11 transitivereach 0 importersview sourcekind gate/prooftopic arena
docsdependenciesstructsconstsfunctions

about

nx_arena_smp_gate.nx -- TWO THREADS MUST NEVER RECEIVE THE SAME POINTER. THE DEFECT, MEASURED 2026-08-25 while shipping structured concurrency. The arena bump allocator inside nx_syscalls advanced its cursor with a plain read-modify-write: let p: i64 = nxa_st[0] nxa_st[0] = p + need Two threads that read the cursor before either wrote it BOTH RECEIVE THE SAME POINTER and then write over one another. Eight pool workers calling a helper that allocates a 16-byte timespec hit exactly this and produced ARENA-OVERRUN prev_alloc_size=16 followed by SIGSEGV. It generalises to EVERY small allocation from more than one thread -- which is why the scoped-spawn child body was deliberately written to allocate nothing, load-bearing rather than incidental. WHY A BARRIER AND NOT A LOST-UPDATE COUNT. The obvious non-vacuity proof is to run an UNLOCKED counter beside the allocator and show it loses updates. That proof is PROBABILISTIC: on a run where the threads happen not to interleave it shows no loss, and the gate would go RED for no reason. A DETECTOR WITH FALSE POSITIVES IS WORSE THAN NONE. So overlap is established DETERMINISTICALLY: every task blocks on an atomic barrier until ALL of them have arrived, and only then allocates. If the barrier is reached, the tasks provably held the allocator at the same time; if it is not, this gate reports the pointer-distinctness result as UNPROVEN rather than GREEN, because an AXIS THAT CANNOT SEE MUST ABSTAIN, NOT ACQUIT. The lost-update count is still MEASURED and PRINTED beside it, as evidence about the machine rather than as a tooth.

dependencies 3 imports · 0 importers

nx_syscalls.nx nx_gate_verdict.nx nx_thread_pool.nx nx_arena_smp_gate.nx

imports: nx_syscalls.nxnx_gate_verdict.nxnx_thread_pool.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main ag_puts sys_write gv_ctr sys_mmap nxa_die sys_write ↻ sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ nx_hw_worker_count nx_hw_cpu_count sys_mmap ↻ sys_munmap sys_mmap ↻ nx_pool_new nx_hw_worker_count ↻ sys_mmap ↻ nx_chan_new sys_mmap ↻ _nx_chan_cell sys_thread_create nx_thread_spawn sys_mmap ↻ nx_thread_spawn_fn sys_mmap ↻ sc_scope_open sc_conf_kidcap lcf_int_of sys_mmap ↻ sys_read_file

structs

none

consts

30const AG_PER: i64 = 256
33const AG_MAXTASKS: i64 = 64
38const AG_BARRIER_SPINS: i64 = NX_MAGIC_2000000000

functions

45func ag_puts(s: *u8) -> i64
called by 2: ag_putnmain calls 1: sys_write
52func ag_putn(v: i64) -> i64
called by 1: main calls 2: ag_putssys_mmap
70func ag_task(id: i64) -> i64
calls 1: sys_mmap
106func main(argc: i64, argv: *i64) -> i64