code wiki / (root) / nx_buf.nx

nx_buf.nx source

↩ module page · 238 lines · 8113 B

1// nx_buf.nx -- bounded append-only byte buffer with overflow detection. 2// 3// Why this exists: 4// Every diagnostic emitter (nx_log, nx_panic, nx_hex, nx_metrics) 5// currently calls sys_write directly multiple times per record. 6// Three problems with that: 7// 8// 1. Records can be interleaved by other writers between the 9// first and last sys_write of a single logical record. 10// On a single-threaded process today this is rare; on a 11// multi-threaded NishiOS userspace tomorrow, every writer 12// racing to fd=2 will produce shredded log lines. 13// 14// 2. Every sys_write is a syscall. For a hot path (parser 15// guard fires, every fuzz iteration crashing) we pay 16// thousands of context switches that we could batch. 17// 18// 3. There is no centralised "did this record fit?" check. 19// Truncated diagnostic output is worse than dropped -- 20// truncated misleads. 21// 22// nx_buf solves all three: build the record in a fixed-capacity 23// byte buffer, hit overflow visibly (length > cap sets `overflow=1` 24// and stops appending), then flush atomically with one sys_write. 25// 26// Design references: 27// Rust std::io::BufWriter 28// Linux pr_cont() vs pr_info() -- per-record buffering 29// Go bytes.Buffer 30// C stdio fwrite-then-fflush pattern 31// 32// Discipline: 33// * caller picks capacity; nx_buf never reallocates (predictable) 34// * overflow is sticky -- once tripped, stays tripped until clear() 35// * never returns a partial write -- flush is all-or-nothing 36// * append family is intentionally minimal (bytes / cstr / int / 37// hex8); higher-level formatters compose from these. 38 39// nx_safety_envelope: 40// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 41// sil_target: SIL1 42// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 43// verdict: NOT_YET_EVALUATED 44 45import "syscalls.nx" 46const NX_MAGIC_12345: i64 = 12345 47 48struct NxBuf { 49 data: *u8, // backing storage 50 cap: i64, // capacity bytes 51 len: i64, // bytes written so far 52 overflow: i64, // 1 once any append has been refused 53} 54 55const NX_BUF_BYTES: i64 = 32 // 4 fields x 8 56 57// --- construction -------------------------------------------------- 58 59func nx_buf_new(cap: i64) -> *NxBuf { 60 let raw: *u8 = sys_mmap(NX_BUF_BYTES) 61 let b: *NxBuf = raw as *NxBuf 62 b.data = sys_mmap(cap) 63 b.cap = cap 64 b.len = 0 65 b.overflow = 0 66 return b 67} 68 69// --- state ------------------------------------------------------------- 70 71func nx_buf_clear(b: *NxBuf) -> i64 { 72 b.len = 0 73 b.overflow = 0 74 return 0 75} 76 77func nx_buf_len(b: *NxBuf) -> i64 { return b.len } 78func nx_buf_cap(b: *NxBuf) -> i64 { return b.cap } 79func nx_buf_overflow(b: *NxBuf) -> i64 { return b.overflow } 80 81// --- appenders ----------------------------------------------------- 82 83func nx_buf_append_byte(b: *NxBuf, x: i64) -> i64 { 84 if b.overflow != 0 { return -1 } 85 if b.len + 1 > b.cap { b.overflow = 1; return -1 } 86 b.data[b.len] = x 87 b.len = b.len + 1 88 return 0 89} 90 91func nx_buf_append_bytes(b: *NxBuf, src: *u8, n: i64) -> i64 { 92 if b.overflow != 0 { return -1 } 93 if n < 0 { return -1 } 94 if b.len + n > b.cap { b.overflow = 1; return -1 } 95 var i: i64 = 0 96 while i < n { 97 b.data[b.len + i] = src[i] 98 i = i + 1 99 } 100 b.len = b.len + n 101 return 0 102} 103 104// nx_buf_append_cstr -- append NUL-terminated string up to cap bytes. 105// Stops at NUL or overflow, whichever comes first. 106func nx_buf_append_cstr(b: *NxBuf, s: *u8) -> i64 { 107 if b.overflow != 0 { return -1 } 108 var i: i64 = 0 109 while s[i] != 0 { 110 if b.len + 1 > b.cap { 111 b.overflow = 1 112 return -1 113 } 114 b.data[b.len] = s[i] 115 b.len = b.len + 1 116 i = i + 1 117 } 118 return 0 119} 120 121// nx_buf_append_i64 -- decimal, signed. 122func nx_buf_append_i64(b: *NxBuf, v: i64) -> i64 { 123 if b.overflow != 0 { return -1 } 124 if v == 0 { 125 return nx_buf_append_byte(b, 0x30) 126 } 127 var negative: i64 = 0 128 var n: i64 = v 129 if n < 0 { negative = 1; n = -n } 130 131 // Up to 20 digits for i64 (-9223372036854775808 = 19 digits + sign). 132 let tmp: *u8 = sys_mmap(32) 133 var ti: i64 = 0 134 while n > 0 { 135 tmp[ti] = 0x30 + (n - (n / 10) * 10) 136 n = n / 10 137 ti = ti + 1 138 } 139 if negative != 0 { 140 if nx_buf_append_byte(b, 0x2D) < 0 { return -1 } // '-' 141 } 142 var ri: i64 = ti - 1 143 while ri >= 0 { 144 if nx_buf_append_byte(b, tmp[ri]) < 0 { return -1 } 145 ri = ri - 1 146 } 147 return 0 148} 149 150// nx_buf_append_u8_hex -- two hex chars (00..FF) for one byte. 151func nx_buf_append_u8_hex(b: *NxBuf, v: i64) -> i64 { 152 let masked: i64 = v - (v / 256) * 256 153 let hi: i64 = masked / 16 154 let lo: i64 = masked - hi * 16 155 var ch_hi: i64 = 0 156 if hi < 10 { ch_hi = 0x30 + hi } else { ch_hi = 0x41 + (hi - 10) } 157 var ch_lo: i64 = 0 158 if lo < 10 { ch_lo = 0x30 + lo } else { ch_lo = 0x41 + (lo - 10) } 159 if nx_buf_append_byte(b, ch_hi) < 0 { return -1 } 160 if nx_buf_append_byte(b, ch_lo) < 0 { return -1 } 161 return 0 162} 163 164// --- atomic flush -------------------------------------------------- 165 166// nx_buf_flush -- one sys_write with the whole record. 167// Returns bytes written, or -1 on syscall failure. 168// Buffer length is reset to 0 after a successful flush; overflow 169// flag is preserved so callers can detect mid-record truncation 170// across flush boundaries. 171func nx_buf_flush(b: *NxBuf, fd: i64) -> i64 { 172 if b.len == 0 { return 0 } 173 let n: i64 = sys_write(fd, b.data, b.len) 174 if n >= 0 { b.len = 0 } 175 return n 176} 177 178// --- self-test ------------------------------------------------------ 179 180func main() -> i64 { 181 let b: *NxBuf = nx_buf_new(64) 182 183 // Basic byte/string append. 184 nx_buf_append_cstr(b, "hello " as *u8) 185 nx_buf_append_cstr(b, "world" as *u8) 186 if nx_buf_len(b) != 11 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 187 if nx_buf_overflow(b) != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 188 189 // Append i64. 190 nx_buf_clear(b) 191 nx_buf_append_i64(b, 0) 192 if nx_buf_len(b) != 1 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 193 if b.data[0] != 0x30 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 194 195 nx_buf_clear(b) 196 nx_buf_append_i64(b, NX_MAGIC_12345) 197 if nx_buf_len(b) != 5 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 198 if b.data[0] != 0x31 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 199 if b.data[4] != 0x35 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 200 201 nx_buf_clear(b) 202 nx_buf_append_i64(b, -42) 203 if nx_buf_len(b) != 3 { return __syscall(93, 25, 0, 0, 0, 0, 0) } 204 if b.data[0] != 0x2D { return __syscall(93, 26, 0, 0, 0, 0, 0) } 205 if b.data[1] != 0x34 { return __syscall(93, 27, 0, 0, 0, 0, 0) } 206 if b.data[2] != 0x32 { return __syscall(93, 28, 0, 0, 0, 0, 0) } 207 208 // Append hex byte. 209 nx_buf_clear(b) 210 nx_buf_append_u8_hex(b, 0xAB) 211 if nx_buf_len(b) != 2 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 212 if b.data[0] != 0x41 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 213 if b.data[1] != 0x42 { return __syscall(93, 32, 0, 0, 0, 0, 0) } 214 215 nx_buf_clear(b) 216 nx_buf_append_u8_hex(b, 0x05) 217 if b.data[0] != 0x30 { return __syscall(93, 33, 0, 0, 0, 0, 0) } 218 if b.data[1] != 0x35 { return __syscall(93, 34, 0, 0, 0, 0, 0) } 219 220 // Overflow trip + sticky flag. 221 let small: *NxBuf = nx_buf_new(4) 222 nx_buf_append_cstr(small, "hi" as *u8) 223 if nx_buf_overflow(small) != 0 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 224 nx_buf_append_cstr(small, "moremore" as *u8) 225 if nx_buf_overflow(small) == 0 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 226 // Subsequent appends should be refused. 227 let rc: i64 = nx_buf_append_byte(small, 0x41) 228 if rc >= 0 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 229 230 // Flush emits the whole record + resets len. 231 nx_buf_clear(b) 232 nx_buf_append_cstr(b, "flushed-record\n" as *u8) 233 let n: i64 = nx_buf_flush(b, 2) 234 if n != 15 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 235 if nx_buf_len(b) != 0 { return __syscall(93, 51, 0, 0, 0, 0, 0) } 236 237 return 0 238}