lib/memio+shlex+getopt: use os.alloc / os.free; drop @symbol bindings

Migrate the three modules that still carried private
@symbol("rt_alloc") / @symbol("rt_free") bindings onto the public
lib/os.alloc / lib/os.free surface that landed in 87c0883.

memio: 1 alloc (grow) + 2 free (grow's old-buffer drop, dynamicclose).
shlex: 1 alloc (dupstr) + 2 free (freepartial: element strs + slice
header). getopt: 1 alloc (tryparse) + 2 free (tryparse + finish).
ABI identity holds — same rt syms, same shapes, just routed through
the public surface.

rt_ensure stays inline in shlex + getopt; the slice-growth helper
isn't part of os and has no stdlib facade. Comments explain why.

Header rationale comments updated: dropped the now-stale
"lib/io ↔ lib/os C-symbol collision" framing on shlex's inlined
dupstr (that was a pre-#9 concern); reworded shlex's OOM trailer to
match lib/os.ww's documented contract (poisonous pointer, not nil,
fault on deref); fixed memio's dynamicfrom doc to reference
[[os.free]] instead of the retired rt_free name.

980_memio_run / 973_shlex_run / 982_getopt_run all green; bootstrap
byte-identical.
This commit is contained in:
2026-05-16 02:03:18 +09:00
parent 87c088359d
commit db2b05bbe5
3 changed files with 27 additions and 44 deletions

View File

@@ -27,13 +27,7 @@
// their dependencies do.
use io;
// Direct rt_alloc/rt_free bindings — the runtime heap primitives
// memio.dynamic / dynamicfrom need to own the backing buffer. lib/os
// holds the same FFI shape but doesn't export it; same pattern as
// rt/ensure.ww.
@symbol("rt_alloc") fn rtalloc(n: u64) *void;
@symbol("rt_free") fn rtfree(p: *void, n: u64) void;
use os;
// state — memio's per-stream bookkeeping. The caller owns the slot
// and passes its address into a constructor. `ptr/len/cap` are the
@@ -75,7 +69,7 @@ export fn dynamic(m: *state, s: *io.stream) void = {
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership of the slice transfers to the stream; [[io.close]] frees
// it. The slice must come from the runtime allocator: close calls
// rt_free with `m.cap` bytes, which is taken from `buf.cap` (the
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
// slice's allocated capacity), not its logical length. Passing a
// half-filled append slice (len < cap) and using only `buf.len` here
// would under-free on close.
@@ -182,7 +176,7 @@ fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
fn dynamicclose(s: *io.stream) (void | io.closed) = {
let m: *state = s.ctx: *state;
if (m.cap > 0) { rtfree(m.ptr: *void, m.cap: u64); };
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
m.len = 0;
m.cap = 0;
@@ -200,13 +194,13 @@ fn grow(m: *state, need: i32) void = {
let newcap: i32 = m.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
let nbuf: *u8 = rtalloc(newcap: u64): *u8;
let nbuf: *u8 = os.alloc(newcap: u64): *u8;
let i: i32 = 0;
for (i < m.len) {
nbuf[i] = m.ptr[i];
i += 1;
};
if (m.cap > 0) { rtfree(m.ptr: *void, m.cap: u64); };
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nbuf;
m.cap = newcap;
};