nx_buf_dyn.nx source
↩ module page · 199 lines · 5955 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}