diff --git a/lib/getopt/getopt.ww b/lib/getopt/getopt.ww index ff14ff8b..ae10c268 100644 --- a/lib/getopt/getopt.ww +++ b/lib/getopt/getopt.ww @@ -91,18 +91,15 @@ // }; // defer getopt.finish(&cmd); +use os; use strings; -// Direct rt_free / rt_ensure bindings rather than `use os;` — os -// exports read/write/close, which collide with io.read/write/close -// in callers that mix both (task #7). Same pattern as lib/memio. -// // rt_ensure is the runtime slice-growth helper invoked by the // `append(s, v)` builtin. We bind it directly because the builtin's // expansion stores only 8 bytes of the new element (cgen emits a // single MOVQ), losing the `value: str` half of an [[option]]. // [[appendoption]] grows manually and stores both fields via *option. -@symbol("rt_free") fn rtfree(p: *void, n: u64) void; +// No public stdlib facade exposes rt_ensure, hence the direct @symbol. @symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; // helpkind — which slot of [[help]] is meaningful. Hare's getopt @@ -296,7 +293,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { match (look) { case void => { if (opts.cap > 0) { - rtfree(opts.ptr: *void, + os.free(opts.ptr: *void, (opts.cap: u64) * 24u64); }; let e: error; @@ -318,7 +315,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { } else { if (i + 1 >= argv.len) { if (opts.cap > 0) { - rtfree(opts.ptr: *void, + os.free(opts.ptr: *void, (opts.cap: u64) * 24u64); }; let e: error; @@ -355,7 +352,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { export fn finish(cmd: *command) void = { if (cmd.optscap > 0) { // option layout: rune (4) + pad (4) + str (16) = 24B. - rtfree(cmd.optsptr: *void, (cmd.optscap: u64) * 24u64); + os.free(cmd.optsptr: *void, (cmd.optscap: u64) * 24u64); }; cmd.optsptr = nil: *option; cmd.optslen = 0; diff --git a/lib/memio/memio.ww b/lib/memio/memio.ww index 753f81aa..dc07fd09 100644 --- a/lib/memio/memio.ww +++ b/lib/memio/memio.ww @@ -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; }; diff --git a/lib/shlex/shlex.ww b/lib/shlex/shlex.ww index 08c284e2..6d8353aa 100644 --- a/lib/shlex/shlex.ww +++ b/lib/shlex/shlex.ww @@ -28,9 +28,10 @@ // // Divergences from Hare: // -// - Drop nomem: ww os.alloc aborts on OOM (same precedent as -// strings.dup, getopt.appendoption). Hare's -// (...|syntaxerr|nomem) collapses to (...|syntaxerr). +// - Drop nomem: ww os.alloc has no recoverable failure path (OOM +// yields a poisonous pointer that faults on deref; see lib/os.ww +// comment). Same precedent as strings.dup, getopt.appendoption. +// Hare's (...|syntaxerr|nomem) collapses to (...|syntaxerr). // // - Byte-wise iteration via i32 cursor instead of Hare's // strings::iterator (no UTF-8 rune iteration in the language @@ -66,13 +67,10 @@ // greppable across modules. When the append-builtin is fixed, // all three (getopt, shlex, anywhere else) collapse in one go. // -// - Internal [[dupstr]] inlined rather than `use strings;`: lib/io -// and lib/os both export read/write/close as C symbols with -// different signatures, and `use strings;` would transitively -// pull `use os;` (strings:6) which collides with `use io;` here. -// Same defensive shape lib/fmt and lib/memio use until task #17 -// (cgen module-mangles fn labels) lands; at that point dupstr -// graduates to strings.dup and the workaround retires. +// - Internal [[dupstr]] inlined rather than `use strings;`: keeps +// the dep surface small for a 6-line helper. shlex doesn't reach +// for any other strings:: routine, and callers free split()'s +// result via [[strings.freeall]] directly. // // Caller layout — split: // @@ -96,18 +94,13 @@ use io; use memio; +use os; -// Direct rt_alloc / rt_free / rt_ensure bindings rather than `use os;` -// — os exports read/write/close, which collide with io.read/write/close -// under the driver's flat-scope concat. Same workaround as lib/memio, -// lib/fmt, lib/log; retires when task #17 (cgen mod-mangling) lands. -// // rt_ensure is the runtime slice-growth helper invoked by the // `append(s, v)` builtin. We bind it directly because the builtin's // expansion stores only 8 bytes of the new element (cgen emits a -// single MOVQ), losing the `len` half of a `str` (16B). -@symbol("rt_alloc") fn rtalloc(n: u64) *void; -@symbol("rt_free") fn rtfree(p: *void, n: u64) void; +// single MOVQ), losing the `len` half of a `str` (16B). No public +// stdlib facade exposes it, hence the direct @symbol. @symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; // syntaxerr — the input wasn't a valid shell-tokenizable string @@ -122,16 +115,15 @@ export fn strerror(err: syntaxerr) str = { return "Invalid shell syntax"; }; -// dupstr — local strings.dup. Inlined to keep `use strings;` out of -// this module (would re-trip the lib/io ↔ lib/os C-symbol collision; -// see file header). Same algorithm and same {nil, 0} handling for -// empty input. Retires when task #17 lands. +// dupstr — local strings.dup. Inlined to avoid a `use strings;` dep +// on this small site; same algorithm and same {nil, 0} handling for +// empty input. fn dupstr(s: str) str = { let r: str; r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = rtalloc(s.len: u64): *u8; + let buf: *u8 = os.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; @@ -160,12 +152,12 @@ fn freepartial(slice: []str) void = { let i: i32 = 0; for (i < slice.len) { if (slice[i].len > 0) { - rtfree(slice[i].ptr: *void, slice[i].len: u64); + os.free(slice[i].ptr: *void, slice[i].len: u64); }; i += 1; }; if (slice.cap > 0) { - rtfree(slice.ptr: *void, (slice.cap: u64) * 16u64); + os.free(slice.ptr: *void, (slice.cap: u64) * 16u64); }; };