From 70fa9e2264dcd09bf6a54213df3da4cd254f4eb3 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 4 Jun 2026 02:22:25 +0900 Subject: [PATCH] lib: collapse the manual rt_ensure append workarounds onto the fixed builtin (#34 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shlex.appendstr, getopt.appendoption, bytes.appendslice and strings.appendstr existed only because the append builtin stored the first 8 bytes of the element; each carried its own @symbol("rt_ensure") bind and a grow-then-store-through-*T body, with comments promising to "collapse in one go when the append builtin is fixed". The previous commit fixed the builtin; this removes all four helpers and their rt_ensure binds and spells every call site as plain append(). Bonus correctness: getopt's appendoption passed a hardcoded membsz of 24, stale since the str 24B redesign made option {rune, str} 32B — the manual growth under-allocated past 6 options while &opts.ptr[i] strode 32 (latent OOB). The builtin derives membsz from the type table (probe: MOVQ $32, SI), closing that drift by construction. --- lib/bytes/bytes.ww | 30 ++------------ lib/getopt/getopt.ww | 26 ++---------- lib/shlex/shlex.ww | 34 ++-------------- lib/strings/strings.ww | 30 +++----------- selfhost/cmd/w6a/main.combined.ww | 60 +++++----------------------- selfhost/cmd/w6c/main.combined.ww | 60 +++++----------------------- selfhost/cmd/w6l/main.combined.ww | 60 +++++----------------------- selfhost/cmd/ww/main.combined.ww | 60 +++++----------------------- selfhost/cmd/wwdump/main.combined.ww | 60 +++++----------------------- 9 files changed, 66 insertions(+), 354 deletions(-) diff --git a/lib/bytes/bytes.ww b/lib/bytes/bytes.ww index 5c5ca654..fc37838d 100644 --- a/lib/bytes/bytes.ww +++ b/lib/bytes/bytes.ww @@ -386,28 +386,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -432,7 +410,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -441,7 +419,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -470,7 +448,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -479,7 +457,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; diff --git a/lib/getopt/getopt.ww b/lib/getopt/getopt.ww index 22721d16..4d4be5ff 100644 --- a/lib/getopt/getopt.ww +++ b/lib/getopt/getopt.ww @@ -97,14 +97,6 @@ import encoding.utf8; import os; import strings; -// 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. -// 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 // also has a subcmd_help variant; not shipped here (see file header). export type helpkind = enum i32 { @@ -205,18 +197,6 @@ export type command = struct { helpcap: i32, }; -// appendoption — grow `*opts` by one slot and store `(flag, value)`. -// Avoids the `append(opts, pair)` builtin: cgen lowers that to a -// MOVQ-of-the-first-8-bytes, which drops the `value: str` half. -fn appendoption(opts: *[]option, flag: rune, value: str) void = { - let newlen: i32 = opts.len + 1; - opts.len = newlen; - rtensure(opts: *void, 24u64); - let dst: *option = &opts.ptr[newlen - 1]; - dst.flag = flag; - dst.value = value; -}; - // findflag — lookup the kind of `r` in `hs`. Returns void if absent. // // Reads go through `&hs[i]` rather than `hs[i].field` directly: the @@ -307,7 +287,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { }; case let k: helpkind => { if (k == helpkind.FLAG) { - appendoption(&opts, r, ""); + append(opts, option { flag = r, value = "" }); bi += 1; } else { // PARAM: glued value, or next argv slot. @@ -325,7 +305,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { case utf8.invalid => abort("getopt: malformed argv UTF-8"); }; - appendoption(&opts, r, v); + append(opts, option { flag = r, value = v }); advanced = true; } else { if (i + 1 >= argv.len) { @@ -340,7 +320,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { return e; }; i += 1; - appendoption(&opts, r, argv[i]); + append(opts, option { flag = r, value = argv[i] }); advanced = true; }; }; diff --git a/lib/shlex/shlex.ww b/lib/shlex/shlex.ww index f46ea213..12f63df2 100644 --- a/lib/shlex/shlex.ww +++ b/lib/shlex/shlex.ww @@ -30,7 +30,7 @@ // // - 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. +// comment). Same precedent as strings.dup. // Hare's (...|syntaxerr|nomem) collapses to (...|syntaxerr). // // - Byte-wise iteration via i32 cursor instead of Hare's @@ -55,15 +55,6 @@ // "write one byte" path goes through `io.write(s, buf[0:1])` // against a stack `[N]u8`. Same shape as fmt's rune-arm. // -// - `[]str` grown via direct rt_ensure rather than the `append` -// builtin: cgen lowers `append(slice, element)` to a single MOVQ -// of the first 8 bytes, which drops the `len` half of a 16B str -// element. [[appendstr]] writes both halves through `*str`. -// Same gap, same workaround, same comment shape as -// [[getopt.appendoption]] (lib/getopt/getopt.ww:96-106) — kept -// 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;`: keeps // the dep surface small for a 6-line helper. shlex doesn't reach // for any other strings:: routine, and callers free split()'s @@ -94,13 +85,6 @@ import memio; import os; import strings; -// 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). 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 // (unterminated quote / bare trailing backslash). Mirrors Hare's // shlex::syntaxerr; a `!void` so it can ride a tagged-union return @@ -127,18 +111,6 @@ fn dupstr(s: str) str = { return strings.frombytes(buf); }; -// appendstr — grow `*slice` by one and store `item` (16B). Bypasses -// the `append` builtin's first-8B-only store gap; mirror of -// [[getopt.appendoption]] for the `[]option` case. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // freepartial — drop a partially built [[split]] result on the // syntaxerr path. Hare uses a `defer if (!ok)` guard; ww has no defer, // so we hand-roll the cleanup at every error return. Mirror of @@ -271,7 +243,7 @@ export fn split(in: str) ([]str | syntaxerr) = { if (!first) { let view: str = memio.string(&st); let owned: str = dupstr(view); - appendstr(&slice, owned); + append(slice, owned); memio.reset(&st); }; dirty = false; @@ -315,7 +287,7 @@ export fn split(in: str) ([]str | syntaxerr) = { if (dirty) { let view: str = memio.string(&st); let owned: str = dupstr(view); - appendstr(&slice, owned); + append(slice, owned); }; let _c = io.close(snk); diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 0c209cd5..e0fb9c70 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -88,7 +88,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -107,7 +107,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -733,24 +733,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -773,7 +755,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -782,7 +764,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -807,7 +789,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -816,7 +798,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 5ae9891e..86ae658a 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; @@ -1955,7 +1933,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0db6a6e8..7c37c445 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; @@ -1955,7 +1933,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 18cf5c22..596a5bf3 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; @@ -1955,7 +1933,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index cc9c93cd..b82d66a2 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; @@ -1955,7 +1933,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9e1ac5b8..d0a22421 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -2738,28 +2738,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = { return s.in; }; -// 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/.cap fields of a []u8 element (24B). -// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and -// lib/getopt.getopt (appendoption, 24B); collapses in one go when the -// append builtin learns to store the full element width. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendslice — grow `*slice` by one and store `item` (24B). Mirror -// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the -// `append` builtin's first-8B-only-store gap for a slice-element. -fn appendslice(slice: *[][]u8, item: []u8) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, 24u64); - let dst: *[]u8 = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; - dst.cap = item.cap; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. @@ -2784,7 +2762,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -2793,7 +2771,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; return toks; @@ -2822,7 +2800,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: []u8 => { appendslice(&toks, s); }; + case let s: []u8 => { append(toks, s); }; case done => { return toks; }; }; i += 1; @@ -2831,7 +2809,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { case done => void; case let pk: []u8 => { let r: []u8 = remaining_tokens(&tok); - appendslice(&toks, r); + append(toks, r); }; }; @@ -3472,7 +3450,7 @@ export fn dup(s: str) str = { // so the only nomem propagation point is the initial slice alloc. // With no inner failure path, the rollback is structurally a no-op // and is omitted; it returns once dup graduates to `(str | nomem)` -// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's +// (#46). The pre-allocated slice has `cap == s.len`, so append's // rt_ensure call never reaches the grow branch. // // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes @@ -3491,7 +3469,7 @@ export fn dupall(s: []str) ([]str | nomem) = { let newsl: []str = alloc([], s.len)?; let i: i32 = 0; for (i < s.len) { - appendstr(&newsl, dup(s[i])); + append(newsl, dup(s[i])); i += 1; }; return newsl; @@ -4117,24 +4095,6 @@ export fn rcut(in: str, delim: str) (str, str) = { return (frombytes(a), frombytes(b)); }; -// rt_ensure is the runtime slice-growth helper invoked by the -// `append(s, v)` builtin. Direct bind for the same reason as -// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores -// only 8B of the new element, losing the `.len` half of a `str`. -@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void; - -// appendstr — grow `*slice` by one and store `item` (16B). Mirror of -// lib/shlex.shlex appendstr. Collapses when the append builtin learns -// to store the full element width. -fn appendstr(slice: *[]str, item: str) void = { - let newlen: i32 = slice.len + 1; - slice.len = newlen; - rtensure(slice: *void, size(str): u64); - let dst: *str = &slice.ptr[newlen - 1]; - dst.ptr = item.ptr; - dst.len = item.len; -}; - // splitn — split `in` on any byte in `delim`, returning up to `n` // tokens via forward iteration. The trailing slot (when more than // `n - 1` tokens exist) holds the unconsumed remainder. Strings @@ -4157,7 +4117,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -4166,7 +4126,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; }; return toks; @@ -4191,7 +4151,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { let i: i32 = 0; for (i < n - 1) { match (next_token(&tok)) { - case let s: str => { appendstr(&toks, s); }; + case let s: str => { append(toks, s); }; case bytes.done => { return toks; }; }; i += 1; @@ -4200,7 +4160,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = { case bytes.done => void; case let pk: str => { let r: str = remaining_tokens(&tok); - appendstr(&toks, r); + append(toks, r); }; };