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}