nx_buf_dyn_owned_candidate_t218.nx source
↩ module page · 248 lines · 8496 B
1// nx_buf_dyn.nx -- dynamic growable byte buffer.
2//
3// Every formatter / builder / ELF emitter today picks a fixed
4// upfront cap and bails on overflow. When the cap is wrong the
5// caller has to recompile or re-mmap. This module gives them
6// std::vector-style amortized growth: 2x doubling when full.
7//
8// API:
9// buf = nx_bd_new(initial_cap)
10// nx_bd_byte(buf, b)
11// nx_bd_bytes(buf, src, n)
12// nx_bd_u32(buf, v) -- LE
13// nx_bd_u64(buf, v) -- LE
14// nx_bd_str(buf, s) -- NUL-terminated, NUL not written
15// nx_bd_strz(buf, s) -- NUL-terminated, NUL written
16// nx_bd_clear(buf) -- len=0, keeps capacity
17// buf.buf, buf.len -- direct access for emit
18//
19// Growth: 2x when full, never less than +64. Old bytes are
20// memcpy'd to the new region; the old region is leaked (we don't
21// have munmap discipline yet, and arenas reset wholesale anyway).
22
23// nx_safety_envelope:
24// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
25// sil_target: SIL1
26// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
27// verdict: NOT_YET_EVALUATED
28
29import "syscalls.nx"
30
31struct NxBufDyn {
32 buf: *u8,
33 len: i64,
34 cap: i64,
35}
36
37const NX_BD_BYTES: i64 = 24
38const NX_BD_MIN_CAP: i64 = 64
39const NX_BD_MIN_GROW: i64 = 64
40
41func nx_bd_new(initial_cap: i64) -> *NxBufDyn {
42 let raw: *u8 = sys_mmap(NX_BD_BYTES)
43 let b: *NxBufDyn = raw as *NxBufDyn
44 var c: i64 = initial_cap
45 if c < NX_BD_MIN_CAP { c = NX_BD_MIN_CAP }
46 b.buf = sys_mmap(c)
47 b.len = 0
48 b.cap = c
49 return b
50}
51
52// Ensure room for `extra` more bytes. Doubles cap until it fits.
53func nx_bd_grow(b: *NxBufDyn, extra: i64) -> i64 {
54 if b.len + extra <= b.cap { return 0 }
55 var new_cap: i64 = b.cap * 2
56 if new_cap < b.len + extra {
57 new_cap = b.len + extra + NX_BD_MIN_GROW
58 }
59 let new_buf: *u8 = sys_mmap(new_cap)
60 var i: i64 = 0
61 while i < b.len {
62 new_buf[i] = b.buf[i]
63 i = i + 1
64 }
65 b.buf = new_buf
66 b.cap = new_cap
67 return 0
68}
69
70func nx_bd_byte(b: *NxBufDyn, v: i64) -> i64 {
71 nx_bd_grow(b, 1)
72 b.buf[b.len] = v & 0xFF
73 b.len = b.len + 1
74 return 0
75}
76
77func nx_bd_bytes(b: *NxBufDyn, src: *u8, n: i64) -> i64 {
78 nx_bd_grow(b, n)
79 var i: i64 = 0
80 while i < n {
81 b.buf[b.len + i] = src[i]
82 i = i + 1
83 }
84 b.len = b.len + n
85 return 0
86}
87
88func nx_bd_u16(b: *NxBufDyn, v: i64) -> i64 {
89 nx_bd_byte(b, v & 0xFF)
90 return nx_bd_byte(b, (v >> 8) & 0xFF)
91}
92
93func nx_bd_u32(b: *NxBufDyn, v: i64) -> i64 {
94 nx_bd_byte(b, v & 0xFF)
95 nx_bd_byte(b, (v >> 8) & 0xFF)
96 nx_bd_byte(b, (v >> 16) & 0xFF)
97 return nx_bd_byte(b, (v >> 24) & 0xFF)
98}
99
100func nx_bd_u64(b: *NxBufDyn, v: i64) -> i64 {
101 var i: i64 = 0
102 while i < 8 {
103 nx_bd_byte(b, (v >> (i * 8)) & 0xFF)
104 i = i + 1
105 }
106 return 0
107}
108
109// Append a NUL-terminated string WITHOUT the NUL.
110func nx_bd_str(b: *NxBufDyn, s: *u8) -> i64 {
111 var i: i64 = 0
112 while s[i] != 0 {
113 nx_bd_byte(b, s[i])
114 i = i + 1
115 }
116 return 0
117}
118
119// Append a NUL-terminated string WITH the NUL.
120func nx_bd_strz(b: *NxBufDyn, s: *u8) -> i64 {
121 var i: i64 = 0
122 while s[i] != 0 {
123 nx_bd_byte(b, s[i])
124 i = i + 1
125 }
126 return nx_bd_byte(b, 0)
127}
128
129func nx_bd_clear(b: *NxBufDyn) -> i64 {
130 b.len = 0
131 return 0
132}
133
134// Patch a u32 at a previously-recorded offset (e.g. backpatching
135// a length field whose value wasn't known at the time of writing).
136func nx_bd_patch_u32(b: *NxBufDyn, off: i64, v: i64) -> i64 {
137 if off + 4 > b.len { return -1 }
138 b.buf[off] = v & 0xFF
139 b.buf[off + 1] = (v >> 8) & 0xFF
140 b.buf[off + 2] = (v >> 16) & 0xFF
141 b.buf[off + 3] = (v >> 24) & 0xFF
142 return 0
143}
144
145// ---- self-test ---------------------------------------------------
146
147func main() -> i64 {
148 let b: *NxBufDyn = nx_bd_new(8)
149 if b.cap != NX_BD_MIN_CAP { return __syscall(93, 1, 0, 0, 0, 0, 0) } // promoted to MIN_CAP
150
151 nx_bd_byte(b, 0xAA)
152 nx_bd_byte(b, 0xBB)
153 if b.len != 2 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
154 if b.buf[0] != 0xAA { return __syscall(93, 3, 0, 0, 0, 0, 0) }
155
156 nx_bd_u32(b, 0x12345678)
157 if b.len != 6 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
158 if b.buf[2] != 0x78 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
159 if b.buf[5] != 0x12 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
160
161 // Force growth: write past initial cap.
162 let blob: *u8 = sys_mmap(256)
163 var i: i64 = 0
164 while i < 256 { blob[i] = i; i = i + 1 }
165 nx_bd_bytes(b, blob, 256)
166 if b.len != 262 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
167 if b.cap < 262 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
168 // Old bytes still intact?
169 if b.buf[0] != 0xAA { return __syscall(93, 9, 0, 0, 0, 0, 0) }
170 if b.buf[1] != 0xBB { return __syscall(93, 10, 0, 0, 0, 0, 0) }
171 // First byte of blob copy
172 if b.buf[6] != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
173 if b.buf[6 + 100] != 100 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
174
175 // Backpatch: write a u32 placeholder, then patch.
176 let b2: *NxBufDyn = nx_bd_new(8)
177 nx_bd_u32(b2, 0)
178 nx_bd_byte(b2, 0xFF)
179 nx_bd_patch_u32(b2, 0, 0xCAFEBABE)
180 if b2.buf[0] != 0xBE { return __syscall(93, 13, 0, 0, 0, 0, 0) }
181 if b2.buf[3] != 0xCA { return __syscall(93, 14, 0, 0, 0, 0, 0) }
182 if b2.buf[4] != 0xFF { return __syscall(93, 15, 0, 0, 0, 0, 0) }
183
184 // strz writes the NUL.
185 let s: *u8 = sys_mmap(8)
186 s[0] = 0x68; s[1] = 0x69; s[2] = 0
187 let b3: *NxBufDyn = nx_bd_new(8)
188 nx_bd_strz(b3, s)
189 if b3.len != 3 { return __syscall(93, 16, 0, 0, 0, 0, 0) }
190 if b3.buf[2] != 0 { return __syscall(93, 17, 0, 0, 0, 0, 0) }
191
192 // clear preserves cap.
193 let pre_cap: i64 = b3.cap
194 nx_bd_clear(b3)
195 if b3.len != 0 { return __syscall(93, 18, 0, 0, 0, 0, 0) }
196 if b3.cap != pre_cap { return __syscall(93, 19, 0, 0, 0, 0, 0) }
197
198 return 0
199}
200
201// Owned capture storage is separate from legacy arena-backed NxBufDyn.
202// Caller initializes all fields to zero and retains exclusive ownership. Only
203// this API may replace buf; descriptors and external source spans must stay live.
204// max_bytes=0 applies no caller policy ceiling; i64 extent and kernel admission still apply.
205struct NxBufOwned { buf:*u8, len:i64, cap:i64, }
206const NX_BO_BYTES:i64=24
207const NX_BO_I64_MAX:i64=9223372036854775807
208const NX_BO_INVALID:i64=-100
209const NX_BO_RANGE:i64=-101
210const NX_BO_CAPACITY:i64=-102
211const NX_BO_ALLOCATION:i64=-103
212const NX_BO_ALIAS:i64=-104
213const NX_BO_RELEASE:i64=-105
214func nx_bo_state(b:*NxBufOwned)->i64{
215 let bp:i64=b as i64;if bp<=0||bp>NX_BO_I64_MAX-NX_BO_BYTES{return NX_BO_INVALID}
216 if b.len<0||b.cap<0||b.len>b.cap{return NX_BO_INVALID}
217 let p:i64=b.buf as i64;if b.cap==0{if p!=0||b.len!=0{return NX_BO_INVALID};return 0}
218 if p<=0||b.cap>NX_BO_I64_MAX-p{return NX_BO_RANGE}
219 if bp<p+b.cap&&p<bp+NX_BO_BYTES{return NX_BO_ALIAS};return 0
220}
221func nx_bo_append(b:*NxBufOwned,src:*u8,n:i64,max_bytes:i64)->i64{
222 let valid:i64=nx_bo_state(b);if valid<0{return valid}
223 if n<0||max_bytes<0{return NX_BO_INVALID}
224 if n>NX_BO_I64_MAX-b.len{return NX_BO_RANGE}
225 let need:i64=b.len+n;if max_bytes>0&&need>max_bytes{return NX_BO_CAPACITY}
226 if n==0{return 0}
227 let sp:i64=src as i64;if sp<=0{return NX_BO_INVALID};if n>NX_BO_I64_MAX-sp{return NX_BO_RANGE}
228 let bp:i64=b as i64;let oldp:i64=b.buf as i64
229 if sp<bp+NX_BO_BYTES&&bp<sp+n{return NX_BO_ALIAS}
230 if b.cap>0&&sp<oldp+b.cap&&oldp<sp+n{return NX_BO_ALIAS}
231 if need<=b.cap{var i:i64=0;while i<n{b.buf[b.len+i]=src[i];i=i+1};b.len=need;return 0}
232 var next:i64=need
233 if b.cap>0&&b.cap<=NX_BO_I64_MAX/2{let doubled:i64=b.cap*2;if doubled>next{next=doubled}}
234 if max_bytes>0&&next>max_bytes{next=max_bytes}
235 let fresh:*u8=sys_mmap_try(next);if (fresh as i64)<=0{return NX_BO_ALLOCATION}
236 var i:i64=0;while i<b.len{fresh[i]=b.buf[i];i=i+1}
237 var j:i64=0;while j<n{fresh[b.len+j]=src[j];j=j+1}
238 if b.cap>0{let freed:i64=sys_munmap_direct(b.buf,b.cap);if freed<0{sys_munmap_direct(fresh,next);return NX_BO_RELEASE}}
239 b.buf=fresh;b.len=need;b.cap=next;return 0
240}
241// Successful release zeroes descriptor; repeated release is a no-op.
242// A release refusal retains the descriptor so the caller can report/reconcile it.
243func nx_bo_release(b:*NxBufOwned)->i64{
244 let valid:i64=nx_bo_state(b);if valid<0{return valid}
245 if b.cap==0{return 0}
246 let rc:i64=sys_munmap_direct(b.buf,b.cap);if rc<0{return NX_BO_RELEASE}
247 b.buf=0 as *u8;b.len=0;b.cap=0;return 0
248}