From 1e2f55aed8c196330343cd86c1f00ba0d09f1310 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 01:49:00 +0900 Subject: [PATCH] lib: graduate bytes/strings find-funcs to (i32 | void) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the -1 sentinel return on indexbyte/byteindex/rbyteindex/ index with Hare's optional-shaped tagged union. Callers `match` on the result and bind the index from the i32 variant. Two cgen fixes were needed first: 1. resolve_type for N_TTAGGED rounded value payload up to an 8-byte multiple. (i32 | void) was sized 12 — tag (8) + payload (4) — which made the reg-passing ABI compute size/8 = 1 word and drop the value word. 2. The call-arg push path special-cased struct and slice args but not tagged-return calls. A nested `f(g())` where g returns a tagged union pushed only AX (tag); the matching pop loaded a stale DX/SI for the value. Now pushes AX/DX[/CX] in order so the pop side drains tag → arg-reg[0], value(s) → arg-reg[1..]. strings.contains rewritten to match on the new tagged result. No other callers existed in lib/ — bufio/io still use their own shapes. --- cmd/w6c/cgen.c | 9 +++++++++ cmd/wcc/check.c | 7 ++++++- lib/bytes/bytes.ww | 18 ++++++++++-------- lib/strings/strings.ww | 37 ++++++++++++++++++++----------------- test/wcc/700_e2e.c | 26 +++++++++++++++++++------- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index bfce4097..eeff3265 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1670,6 +1670,15 @@ cgexpr(Cg *c, Node *n, Local *locals) } else if (node_isstr(args[i])) { ins1(c, A_PUSHQ, areg(D_BX)); /* len */ ins1(c, A_PUSHQ, areg(D_AX)); /* ptr — top */ + } else if (node_istaggedarg(args[i])) { + /* Tagged-return ABI: AX=tag, DX=val0[, CX=val1]. + * Push high-to-low so pop drains tag first (into + * arg-reg[0]), then values into arg-reg[1..]. */ + int sz = tagged_arg_size(args[i]->type); + if (sz > 16) + ins1(c, A_PUSHQ, areg(D_CX)); + ins1(c, A_PUSHQ, areg(D_DX)); + ins1(c, A_PUSHQ, areg(D_AX)); } else { ins1(c, A_PUSHQ, areg(D_AX)); } diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index fa4cad4c..bb04e697 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -192,7 +192,12 @@ resolve_type(Checker *c, Node *n) if (nv == 0) return ty_never; if (nv == 1 && head) return head->type; t->params = head; - t->size = 8 + maxsz; + /* Round value payload up to an 8-byte multiple so the slot + * layout (tag + N value words) stays word-aligned. The reg- + * passing ABI counts size/8 words; 12-byte unions like + * (i32 | void) would otherwise lose a value register. */ + u64 vsz = (maxsz + 7) & ~(u64)7; + t->size = 8 + vsz; t->align = al; return t; } diff --git a/lib/bytes/bytes.ww b/lib/bytes/bytes.ww index a511b331..8d3979b4 100644 --- a/lib/bytes/bytes.ww +++ b/lib/bytes/bytes.ww @@ -10,13 +10,15 @@ export fn equal(a: []u8, b: []u8) bool = { return i == b.len; }; -export fn indexbyte(s: []u8, c: u8) i32 = { +// indexbyte — first index of byte `c` in `s`. Hare-shaped optional: +// (i32 | void). void variant indicates "not found". +export fn indexbyte(s: []u8, c: u8) (i32 | void) = { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; i += 1; }; - return -1; + return; }; export fn copy(dst: []u8, src: []u8) i32 = { @@ -30,12 +32,12 @@ export fn copy(dst: []u8, src: []u8) i32 = { return n; }; -// index — first index of `sub` in `s`, or -1. Mirrors Hare's -// bytes::index (the []u8 needle variant; the u8 needle stays as -// indexbyte until we have union-arg dispatch). Empty `sub` matches at 0. -export fn index(s: []u8, sub: []u8) i32 = { +// index — first index of `sub` in `s`. Mirrors Hare's bytes::index +// (the []u8 needle variant; the u8 needle stays as indexbyte until we +// have union-arg dispatch). Empty `sub` matches at 0. +export fn index(s: []u8, sub: []u8) (i32 | void) = { if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return -1; }; + if (sub.len > s.len) { return; }; let last: i32 = s.len - sub.len; let i: i32 = 0; for (i <= last) { @@ -48,5 +50,5 @@ export fn index(s: []u8, sub: []u8) i32 = { if (ok) { return i; }; i += 1; }; - return -1; + return; }; diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 1f047f7c..1e885e0f 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -39,36 +39,34 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// byteindex — first index of byte `c` in `s`, or -1 if absent. Hare -// name (strings::byteindex). Plan 9-style sentinel return; callers that -// prefer a fallible shape can wrap this in their own (i32 | str). No -// allocation. -export fn byteindex(s: str, c: u8) i32 = { +// byteindex — first index of byte `c` in `s`. Hare-shaped optional: +// (i32 | void). void variant indicates "not found". +export fn byteindex(s: str, c: u8) (i32 | void) = { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; i += 1; }; - return -1; + return; }; -// rbyteindex — last index of byte `c` in `s`, or -1 if absent. Mirrors -// Hare's strings::rbyteindex. -export fn rbyteindex(s: str, c: u8) i32 = { +// rbyteindex — last index of byte `c` in `s`. Mirrors Hare's +// strings::rbyteindex. +export fn rbyteindex(s: str, c: u8) (i32 | void) = { let i: i32 = s.len - 1; for (i >= 0) { if (s[i] == c) { return i; }; i -= 1; }; - return -1; + return; }; -// index — first index of `sub` in `s`, or -1. Naive scan; fine for -// short patterns and small strings, which dominate config and CLI -// parsing. Empty `sub` matches at 0. -export fn index(s: str, sub: str) i32 = { +// index — first index of `sub` in `s`. Naive scan; fine for short +// patterns and small strings, which dominate config and CLI parsing. +// Empty `sub` matches at 0. +export fn index(s: str, sub: str) (i32 | void) = { if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return -1; }; + if (sub.len > s.len) { return; }; let last: i32 = s.len - sub.len; let i: i32 = 0; for (i <= last) { @@ -81,11 +79,16 @@ export fn index(s: str, sub: str) i32 = { if (ok) { return i; }; i += 1; }; - return -1; + return; }; export fn contains(s: str, sub: str) bool = { - return index(s, sub) >= 0; + let r: (i32 | void) = index(s, sub); + match (r) { + case let i: i32 => return true; + case void => return false; + }; + return false; }; // concat — joins two strings into a fresh str. Caller owns the diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 80e0a66d..6c92a50b 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -784,17 +784,24 @@ static const struct row rows[] = { " };\n" " return acc;\n" "};", 143 }, /* 123 + 20 */ - /* strings.byteindex (Plan 9 -1) and strings.index (substring). */ + /* strings.byteindex and strings.index: now (i32 | void). */ { "use strings;\n" + "fn pick(r: (i32 | void), miss: i32) i32 = {\n" + " match (r) {\n" + " case let i: i32 => return i;\n" + " case void => return miss;\n" + " };\n" + " return 0;\n" + "};\n" "fn main() i32 = {\n" " let s: str = \"hello, world\";\n" - " let i1: i32 = strings.byteindex(s, 44u8);\n" - " let i2: i32 = strings.byteindex(s, 122u8);\n" - " let i3: i32 = strings.index(s, \"world\");\n" - " let i4: i32 = strings.index(s, \"nope\");\n" + " let i1: i32 = pick(strings.byteindex(s, 44u8), -1);\n" + " let i2: i32 = pick(strings.byteindex(s, 122u8), -1);\n" + " let i3: i32 = pick(strings.index(s, \"world\"), -1);\n" + " let i4: i32 = pick(strings.index(s, \"nope\"), -1);\n" " return i1 + i2 + i3 + i4;\n" "};", 10 }, /* 5 + (-1) + 7 + (-1) */ - /* bytes.index: substring search over []u8. */ + /* bytes.index: substring search over []u8, (i32 | void). */ { "use bytes;\n" "fn main() i32 = {\n" " let buf: [12]u8;\n" @@ -803,7 +810,12 @@ static const struct row rows[] = { " buf[8] = 111u8; buf[9] = 114u8; buf[10] = 108u8; buf[11] = 100u8;\n" " let needle: [3]u8;\n" " needle[0] = 119u8; needle[1] = 111u8; needle[2] = 114u8;\n" - " return bytes.index(buf[0:12], needle[0:3]);\n" + " let r: (i32 | void) = bytes.index(buf[0:12], needle[0:3]);\n" + " match (r) {\n" + " case let i: i32 => return i;\n" + " case void => return -1;\n" + " };\n" + " return 0;\n" "};", 7 }, /* errors.equal — sentinel comparison through a (T | error) union. * Sets up two errors, dispatches each, and confirms the matching