code wiki / (root) / nx_arena_scope.nx

nx_arena_scope.nx source

↩ module page · 186 lines · 6778 B

1// nx_arena_scope.nx -- mark/reset-to-mark wrapper for nx_arena. 2// 3// First substrate primitive shipped under NISHI_PREEMPTIVE_BUG_ 4// ABSORPTION.md. Closes the arena-daemon-memory-growth issue 5// named in NISHI_HONEST_TRADE_OFFS.md section 2. 6// 7// Why this primitive exists: 8// 9// nx_arena already ships full-reset via nx_arena_reset(a). That's 10// sufficient for batch programs (parse pass + reset at end of 11// pass). Long-running daemons (the IoT hub, the video-call server, 12// the browser tab) need SCOPED reset -- a tick of the loop should 13// release everything allocated DURING the tick, but preserve the 14// daemon's long-lived state. 15// 16// Scope pattern (replaces full-reset for nested workloads): 17// 18// let mark: i64 = nx_arena_mark(arena) 19// do_work(arena, ...) // many arena_allocs 20// nx_arena_reset_to_mark(arena, mark) // rewinds to mark 21// 22// Reset-to-mark is O(1) (just rewinds the bump pointer). 23// Caller's stack-discipline determines nesting depth. 24// 25// Sealed-enum verdict for the ceiling-aware variant: 26// NX_ARENA_OK allocation succeeded 27// NX_ARENA_OOM_CEILING refused because would exceed ceiling 28// NX_ARENA_OOM_CAP refused because would exceed arena cap 29// NX_ARENA_BAD_MARK reset-to-mark called with invalid mark 30// NX_ARENA_BAD_ARG null pointer / negative size 31// 32// nx_capability_claims: 33// needs: [pointer_arithmetic] 34// provides: [arena_mark, arena_reset_to_mark, arena_ceiling_check] 35// safety: [no_unchecked_deref, no_floating_point, no_syscall, 36// bit_equal_reproducible, kind_isolated] 37// verdict: [sealed_enum_5_state, no_silent_failure] 38// license: ORIGINAL 39// kind: racing_crew_specialist 40// sss: [S0, S6, S7] (closes arena issue in SSS table row 1) 41// 42// Composition: caller threads sys_mmap (no syscall imports needed 43// in this file -- pure pointer arithmetic over an existing NxArena). 44 45// Import types-only (no syscall import) so callers can compose this 46// primitive with any syscall ABI layer. Four-pillar FIX 2026-05-16 47// for the F-meta-4 cross-target portability gap. 48// nx_safety_envelope: 49// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 50// sil_target: SIL1 51// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 52// verdict: NOT_YET_EVALUATED 53 54import "nx_arena_types.nx" 55 56// ---- Sealed enum: arena verdict ------------------------------------ 57 58const NX_ARENA_OK: i64 = 0 59const NX_ARENA_OOM_CEILING: i64 = 1 60const NX_ARENA_OOM_CAP: i64 = 2 61const NX_ARENA_BAD_MARK: i64 = 3 62const NX_ARENA_BAD_ARG: i64 = 4 63const NX_ARENA_N: i64 = 5 64 65func nx_arena_verdict_is_valid(v: i64) -> i64 { 66 if v < 0 { return 0 } 67 if v >= NX_ARENA_N { return 0 } 68 return 1 69} 70 71// ---- Mark / reset-to-mark ------------------------------------------ 72// 73// Marks are i64 offsets into the arena's backing storage. Caller 74// holds the mark on its own stack frame; reset rewinds the arena's 75// bump pointer to that offset. No allocation in mark; nothing 76// freed in reset (just rewound -- next alloc reclaims). 77 78func nx_arena_mark(a: *NxArena) -> i64 { 79 if a == (0 as *NxArena) { return -1 } 80 return a.off 81} 82 83// Reset the bump pointer to a previously-taken mark. Returns 84// sealed verdict via out parameter. Refuses (BAD_MARK) if the 85// mark is out of range OR less than zero OR greater than current 86// off (would expose uninitialised memory). 87func nx_arena_reset_to_mark(a: *NxArena, mark: i64, 88 out_verdict: *i64) -> i64 { 89 if a == (0 as *NxArena) { 90 *out_verdict = NX_ARENA_BAD_ARG 91 return -1 92 } 93 if mark < 0 { 94 *out_verdict = NX_ARENA_BAD_MARK 95 return -1 96 } 97 if mark > a.off { 98 // Would forward the bump pointer + expose stale bytes; refuse. 99 *out_verdict = NX_ARENA_BAD_MARK 100 return -1 101 } 102 if mark > a.cap { 103 *out_verdict = NX_ARENA_BAD_MARK 104 return -1 105 } 106 a.off = mark 107 *out_verdict = NX_ARENA_OK 108 return 0 109} 110 111// ---- Ceiling-aware alloc ------------------------------------------- 112// 113// Caller specifies a ceiling (bytes that this allocation must NOT 114// push the arena past). Used by daemon tick-loop to enforce a 115// per-tick memory budget INSIDE the arena, separate from the 116// arena's overall cap. 117// 118// Returns: 119// - non-null *u8 + verdict OK on success 120// - null + verdict OOM_CEILING when ceiling would be exceeded 121// - null + verdict OOM_CAP when arena cap would be exceeded 122// - null + verdict BAD_ARG when n <= 0 or align <= 0 123 124func nx_arena_alloc_under_ceiling(a: *NxArena, n: i64, align: i64, 125 ceiling: i64, 126 out_verdict: *i64) -> *u8 { 127 if a == (0 as *NxArena) { 128 *out_verdict = NX_ARENA_BAD_ARG 129 return 0 as *u8 130 } 131 if n <= 0 { 132 *out_verdict = NX_ARENA_BAD_ARG 133 return 0 as *u8 134 } 135 if align <= 0 { 136 *out_verdict = NX_ARENA_BAD_ARG 137 return 0 as *u8 138 } 139 // Project the post-alloc offset. Aligning up first matches 140 // nx_arena_alloc's own discipline so the ceiling check sees 141 // the same address space. 142 let aligned: i64 = nx_arena_align_up(a.off, align) 143 let projected: i64 = aligned + n 144 if projected > ceiling { 145 *out_verdict = NX_ARENA_OOM_CEILING 146 a.n_oom = a.n_oom + 1 147 return 0 as *u8 148 } 149 if projected > a.cap { 150 *out_verdict = NX_ARENA_OOM_CAP 151 a.n_oom = a.n_oom + 1 152 return 0 as *u8 153 } 154 *out_verdict = NX_ARENA_OK 155 return nx_arena_alloc(a, n, align) 156} 157 158// ---- Ceiling check (no alloc) -------------------------------------- 159// 160// Useful when daemon wants to peek "would the next N-byte alloc 161// fit under ceiling C?" without performing the allocation. Pure 162// observer; no mutation. 163 164func nx_arena_would_fit(a: *NxArena, n: i64, align: i64, 165 ceiling: i64) -> i64 { 166 if a == (0 as *NxArena) { return 0 } 167 if n <= 0 { return 0 } 168 let aligned: i64 = nx_arena_align_up(a.off, align) 169 let projected: i64 = aligned + n 170 if projected > ceiling { return 0 } 171 if projected > a.cap { return 0 } 172 return 1 173} 174 175// ---- Scope helper macro (in primitive form) ----------------------- 176// 177// Returns 1 if the arena has at least `bytes` headroom (free space) 178// from the current bump position to its cap. Different from 179// would_fit because it ignores alignment + uses cap not ceiling. 180// Caller pattern: pre-check headroom before entering a known-large 181// workload; bail with sealed verdict if insufficient. 182 183func nx_arena_headroom_bytes(a: *NxArena) -> i64 { 184 if a == (0 as *NxArena) { return 0 } 185 return a.cap - a.off 186}