nx_scratch.nx
buildroot/runtime/nx_scratch.nx
about
nx_scratch.nx -- SOVEREIGN SCRATCH ARENA. The god-level fix for the unfreed-mmap class.
THE ROOT DEFECT, stated plainly: NishiLang has no scratch allocator. `sys_mmap` is a raw
page-granular syscall with NO allocator and NO free behind it, so every organ that needs a
temporary buffer hand-rolls `sys_mmap(N)` -- and every one of them re-derives the same bug. That
is 455 occurrences across ~210 files. Fixing them one at a time is 455 chances to get it wrong;
fixing the missing primitive is one chance to get it right, inherited by everything below.
MEASURED cost of the status quo (nx_mmapleak_gate): an unpaired scratch mmap leaks a full
4096-byte page per call -- even sys_mmap(4) -- so 2000 calls grow a process by 4000 pages. In a
supervised daemon that is unbounded: nx_seed_announce_all reached 8.8 GB committed and drove the
build host into swap.
MODEL: one process-lifetime arena, bump-allocated, reset at the top of each work iteration
(per request, per sweep, per connection). Scratch is by definition dead at the end of an
iteration, so a reset is the whole free.
★FAIL-SAFE BY CONSTRUCTION: on overflow nxs_alloc does NOT return null and does NOT truncate --
it falls back to exactly the sys_mmap the caller performed BEFORE adopting this library, and
counts it. So adoption can never make a caller worse or introduce a null it does not check, and
the arena gets sized from evidence (nxs_overflows) instead of guesswork. Refuse-and-report, never
silently shrink.
⚠CONTRACT: memory returned by nxs_alloc is INVALID after the next nxs_reset(). Never hold it
across an iteration boundary, and never use it for state that must outlive the request.
license_tier: ORIGINAL layer: runtime-core module: nishi-core.runtime.scratch
dependencies 1 imports · 1 importers
imports: nx_syscalls.nx
imported by: nx_scratch_gate.nx
structs
| none |
consts
| 29 | const NXS_CAP_DEFAULT: i64 = 1048576 |
| 30 | const NXS_ALIGN: i64 = 16 |
functions
| 39 | func nxs_init(cap: i64) -> i64 |
| 50 | func nxs_reset() -> i64 called by 1: main |
| 56 | func nxs_alloc(n: i64) -> *u8 |
| 72 | func nxs_alloc0(n: i64) -> *u8 calls 1: nxs_alloc |
| 79 | func nxs_used() -> i64 { return NXS_OFF } |
| 80 | func nxs_cap_bytes() -> i64 { return NXS_CAP } called by 1: main |
| 81 | func nxs_hwm() -> i64 { return NXS_HWM } called by 1: main |
| 82 | func nxs_overflows() -> i64 { return NXS_OVF } called by 1: main |