code wiki / (root) / nx_io.nx

nx_io.nx source

↩ module page · 172 lines · 5107 B

1// nx_io.nx -- buffered fd I/O (line-buffered stdout, full-buffered file). 2// 3// Today every nx_*.nx tool uses raw `sys_write(fd, buf, n)`. For 4// per-character or per-token output (disassembler, hex dumps, log 5// formatters) that's a syscall per byte -- thousands of context 6// switches for one tool invocation. 7// 8// This module wraps each fd in a 4 KiB buffer: 9// io = nx_io_open_fd(fd, mode) 10// nx_io_write(io, "hello", 5) 11// nx_io_putc(io, 0x21) 12// nx_io_flush(io) 13// nx_io_close(io) 14// 15// modes: 16// NX_IO_LINEBUF : flush on '\n' (good for stderr / interactive) 17// NX_IO_FULLBUF : flush only when buffer full or explicit flush 18// 19// Pairs with nx_strfmt (formatter takes an fd today, can take an 20// nx_io once we wire it through) + nx_log + nx_dbg. 21 22// nx_safety_envelope: 23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 24// sil_target: SIL1 25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 26// verdict: NOT_YET_EVALUATED 27 28import "syscalls.nx" 29 30const NX_IO_BUFSZ: i64 = 4096 31const NX_IO_LINEBUF: i64 = 1 32const NX_IO_FULLBUF: i64 = 2 33 34struct NxIoFd { 35 fd: i64, 36 buf: *u8, 37 pos: i64, 38 cap: i64, 39 mode: i64, 40} 41 42const NX_IO_FD_BYTES: i64 = 40 43 44func nx_io_open_fd(fd: i64, mode: i64) -> *NxIoFd { 45 let raw: *u8 = sys_mmap(NX_IO_FD_BYTES) 46 let io: *NxIoFd = raw as *NxIoFd 47 io.fd = fd 48 io.buf = sys_mmap(NX_IO_BUFSZ) 49 io.pos = 0 50 io.cap = NX_IO_BUFSZ 51 io.mode = mode 52 return io 53} 54 55// Drain the buffer to the underlying fd. Returns 0 on success or 56// the negative error from sys_write. 57func nx_io_flush(io: *NxIoFd) -> i64 { 58 if io.pos == 0 { return 0 } 59 let n: i64 = sys_write(io.fd, io.buf, io.pos) 60 if n < 0 { return n } 61 io.pos = 0 62 return 0 63} 64 65// Append one byte; flush if buffer is full or (LINEBUF + byte = '\n'). 66func nx_io_putc(io: *NxIoFd, c: i64) -> i64 { 67 if io.pos >= io.cap { 68 let r: i64 = nx_io_flush(io) 69 if r < 0 { return r } 70 } 71 io.buf[io.pos] = c & 0xFF 72 io.pos = io.pos + 1 73 if io.mode == NX_IO_LINEBUF { 74 if c == 0x0A { return nx_io_flush(io) } 75 } 76 return 0 77} 78 79// Append `n` bytes from `src`. Splits writes that would overflow 80// the buffer into a flush + tail-copy. 81func nx_io_write(io: *NxIoFd, src: *u8, n: i64) -> i64 { 82 var i: i64 = 0 83 while i < n { 84 let space: i64 = io.cap - io.pos 85 if space <= 0 { 86 let r: i64 = nx_io_flush(io) 87 if r < 0 { return r } 88 } else { 89 var chunk: i64 = n - i 90 if chunk > space { chunk = space } 91 var k: i64 = 0 92 while k < chunk { 93 io.buf[io.pos + k] = src[i + k] 94 k = k + 1 95 } 96 io.pos = io.pos + chunk 97 i = i + chunk 98 if io.mode == NX_IO_LINEBUF { 99 // If the chunk we just wrote contained a '\n', 100 // flush. Cheaper than scanning every byte: just 101 // check the new tail. 102 var found: i64 = 0 103 var s: i64 = io.pos - chunk 104 while s < io.pos { 105 if io.buf[s] == 0x0A { found = 1; s = io.pos } 106 else { s = s + 1 } 107 } 108 if found == 1 { 109 let rf: i64 = nx_io_flush(io) 110 if rf < 0 { return rf } 111 } 112 } 113 } 114 } 115 return 0 116} 117 118// Append a NUL-terminated string. 119func nx_io_puts(io: *NxIoFd, s: *u8) -> i64 { 120 var n: i64 = 0 121 while s[n] != 0 { n = n + 1 } 122 return nx_io_write(io, s, n) 123} 124 125// Flush + drop the wrapper. Caller is responsible for closing the 126// underlying fd if they want it closed. 127func nx_io_close(io: *NxIoFd) -> i64 { 128 return nx_io_flush(io) 129} 130 131// ---- self-test --------------------------------------------------- 132 133func main() -> i64 { 134 let io: *NxIoFd = nx_io_open_fd(2, NX_IO_FULLBUF) 135 136 // Write 10 small chunks; nothing should reach fd until flush. 137 var i: i64 = 0 138 while i < 10 { 139 nx_io_putc(io, 0x2E) 140 i = i + 1 141 } 142 if io.pos != 10 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 143 144 nx_io_flush(io) 145 if io.pos != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 146 147 // Big write: should auto-flush partway. 148 let big: *u8 = sys_mmap(NX_IO_BUFSZ + 256) 149 var k: i64 = 0 150 while k < NX_IO_BUFSZ + 256 { 151 big[k] = 0x20 152 k = k + 1 153 } 154 nx_io_write(io, big, NX_IO_BUFSZ + 256) 155 // After auto-flush, the residual should be exactly 256 bytes. 156 if io.pos != 256 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 157 nx_io_flush(io) 158 159 // Linebuf mode: '\n' triggers flush. 160 let io2: *NxIoFd = nx_io_open_fd(2, NX_IO_LINEBUF) 161 nx_io_putc(io2, 0x41) // 'A' 162 nx_io_putc(io2, 0x0A) // '\n' -> auto flush 163 if io2.pos != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 164 165 // puts smoke 166 let s: *u8 = sys_mmap(8) 167 s[0] = 0x68; s[1] = 0x69; s[2] = 0x0A; s[3] = 0 168 nx_io_puts(io2, s) 169 if io2.pos != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 170 171 return 0 172}