diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index e2c1f724..f32d1dc5 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -148,8 +148,9 @@ export fn dupall(s: []str) ([]str | nomem) = { // // Empty elements (`{nil, 0}` from a zero-length dup) are skipped: // os.free on a nil pointer at len 0 tickles the rt_free guard. The -// slice header itself is freed at `cap * 16` (one str = 16B); a -// never-grown slice (cap == 0) skips the header free. +// slice header itself is freed at `cap * size(str)` — the literal +// would drift under #1's str-layout bump, so route through the +// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free. export fn freeall(s: []str) void = { let i: i32 = 0; for (i < s.len) { @@ -774,7 +775,7 @@ fn appendstr(slice: *[]str, item: str) void = { // within the result are borrowed from `in`. // // The caller frees the returned slice via -// `os.free(r.ptr: *void, (r.cap: u64) * 16u64)`. +// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`. // // Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc // has no recoverable failure path. Same precedent as diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index e3b725a1..8c3db7a9 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -1441,7 +1441,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t1, 1, "my"); expect_str(t1, 2, "name"); expect_str(t1, 3, "is Drew"); - os.free(t1.ptr: *void, (t1.cap: u64) * 16u64); + os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64); // ref/hare/strings/tokenize.ha:263 — n > tokens leaves a single // slot holding the unchanged input (delim not found). @@ -1449,21 +1449,21 @@ fn expect_str(toks: []str, i: i32, want: str) void = { let t2: []str = strings.splitn("one", "=", 2); if (t2.len != 1) { fail(); }; expect_str(t2, 0, "one"); - os.free(t2.ptr: *void, (t2.cap: u64) * 16u64); + os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64); // n == 1 — single slot holding the whole input as remainder. signalled = 1742; let t3: []str = strings.splitn("a b c", " ", 1); if (t3.len != 1) { fail(); }; expect_str(t3, 0, "a b c"); - os.free(t3.ptr: *void, (t3.cap: u64) * 16u64); + os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64); // Empty input — empty result. signalled = 1743; let t4: []str = strings.splitn("", " ", 5); if (t4.len != 0) { fail(); }; if (t4.cap > 0) { - os.free(t4.ptr: *void, (t4.cap: u64) * 16u64); + os.free(t4.ptr: *void, (t4.cap: u64) * size(str): u64); }; // Multi-byte delim set (byte-set semantics per @@ -1475,7 +1475,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t5, 1, "world"); expect_str(t5, 2, "foo"); expect_str(t5, 3, "bar"); - os.free(t5.ptr: *void, (t5.cap: u64) * 16u64); + os.free(t5.ptr: *void, (t5.cap: u64) * size(str): u64); }; @test fn rsplitn_cases() void = { @@ -1488,7 +1488,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t1, 1, "name"); expect_str(t1, 2, "is"); expect_str(t1, 3, "Drew"); - os.free(t1.ptr: *void, (t1.cap: u64) * 16u64); + os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64); // n > token count — done short-circuit returns toks UN-reversed // (last-token-first order). Mirrors bytes.rsplitn (Hare's @@ -1500,14 +1500,14 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t2, 0, "c"); expect_str(t2, 1, "b"); expect_str(t2, 2, "a"); - os.free(t2.ptr: *void, (t2.cap: u64) * 16u64); + os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64); // n == 1 — single slot holding the whole input as remainder. signalled = 1752; let t3: []str = strings.rsplitn("a b c", " ", 1); if (t3.len != 1) { fail(); }; expect_str(t3, 0, "a b c"); - os.free(t3.ptr: *void, (t3.cap: u64) * 16u64); + os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64); // delim absent — first next_token yields the entire input as the // sole token; second iter sees done and short-circuits with the @@ -1516,7 +1516,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { let t4: []str = strings.rsplitn("abc", "=", 5); if (t4.len != 1) { fail(); }; expect_str(t4, 0, "abc"); - os.free(t4.ptr: *void, (t4.cap: u64) * 16u64); + os.free(t4.ptr: *void, (t4.cap: u64) * size(str): u64); }; @test fn split_cases() void = { @@ -1530,7 +1530,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t1, 2, "name"); expect_str(t1, 3, "is"); expect_str(t1, 4, "Drew"); - os.free(t1.ptr: *void, (t1.cap: u64) * 16u64); + os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64); // Leading + trailing delim — empty tokens at ends. signalled = 1761; @@ -1540,7 +1540,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t2, 1, "a"); expect_str(t2, 2, "b"); expect_str(t2, 3, ""); - os.free(t2.ptr: *void, (t2.cap: u64) * 16u64); + os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64); // Multi-byte delim set, byte-set semantics matching Hare's // strings::split example at ref/hare/strings/tokenize.ha:235. @@ -1551,7 +1551,7 @@ fn expect_str(toks: []str, i: i32, want: str) void = { expect_str(t3, 1, "world"); expect_str(t3, 2, "foo"); expect_str(t3, 3, "bar"); - os.free(t3.ptr: *void, (t3.cap: u64) * 16u64); + os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64); }; // ---- lpad / rpad ----------------------------------------------------- diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 78e34d26..48ae7f7d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1985,8 +1985,9 @@ export fn dupall(s: []str) ([]str | nomem) = { // // Empty elements (`{nil, 0}` from a zero-length dup) are skipped: // os.free on a nil pointer at len 0 tickles the rt_free guard. The -// slice header itself is freed at `cap * 16` (one str = 16B); a -// never-grown slice (cap == 0) skips the header free. +// slice header itself is freed at `cap * size(str)` — the literal +// would drift under #1's str-layout bump, so route through the +// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free. export fn freeall(s: []str) void = { let i: i32 = 0; for (i < s.len) { @@ -2611,7 +2612,7 @@ fn appendstr(slice: *[]str, item: str) void = { // within the result are borrowed from `in`. // // The caller frees the returned slice via -// `os.free(r.ptr: *void, (r.cap: u64) * 16u64)`. +// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`. // // Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc // has no recoverable failure path. Same precedent as @@ -7959,16 +7960,18 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { // tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI // spills each element into its own register / 8B eightbyte, so narrow // scalars pad to 8 (cgen's let_emit_size + AX:DX:CX positional layout). -// str stays 16 (composite primitive), slice 24, pointer/fn/chan 8; -// composites contribute their own slot-padded width. void contributes -// 0 (never appears in tuples emitted by user code, but kept for SSoT -// symmetry with cgen's N_TNAME-"void" fallback arm). +// str/slice and composites consult `pt.size` so a future #1 bump on +// any primitive layout propagates through the typ.ww SSoT seed +// instead of getting baked into this detour. pointer/fn/chan stay +// 8; void contributes 0 (never appears in tuples emitted by user +// code, but kept for SSoT symmetry with cgen's N_TNAME-"void" +// fallback arm). fn tupleelemslot(pt: *tinfo) u64 = { if (pt == nil) { return 8u64; }; let pk: tykind = pt.kind; if (pk == tykind.TY_VOID) { return 0u64; }; - if (pk == tykind.TY_STR) { return 16u64; }; - if (pk == tykind.TY_SLICE) { return 24u64; }; + if (pk == tykind.TY_STR) { return pt.size; }; + if (pk == tykind.TY_SLICE) { return pt.size; }; if (pk == tykind.TY_PTR || pk == tykind.TY_FN || pk == tykind.TY_CHAN || pk == tykind.TY_I64 || pk == tykind.TY_U64 || pk == tykind.TY_INT || @@ -7994,10 +7997,13 @@ fn fieldslotsize(ft: *tinfo) u64 = { if (fk == tykind.TY_STRUCT) { return ft.slotsize; }; if (fk == tykind.TY_ARRAY) { return ft.slotsize; }; if (fk == tykind.TY_TAGGED) { return ft.size; }; - if (fk == tykind.TY_SLICE) { return 24u64; }; + // str / slice read ft.size so the typ.ww SSoT seed is the single + // source for #1 (str→24) / #34 (slice graduation) — no hardcoded + // literal here to drift. + if (fk == tykind.TY_SLICE) { return ft.size; }; if (fk == tykind.TY_PTR || fk == tykind.TY_FN || fk == tykind.TY_CHAN) { return 8u64; }; - if (fk == tykind.TY_STR) { return 16u64; }; + if (fk == tykind.TY_STR) { return ft.size; }; // Primitives keep natural width inside structs (matches // cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a // struct currently defaults to 8 in cgenutil — preserve that diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 30ab6028..f5c54f96 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -873,16 +873,18 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { // tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI // spills each element into its own register / 8B eightbyte, so narrow // scalars pad to 8 (cgen's let_emit_size + AX:DX:CX positional layout). -// str stays 16 (composite primitive), slice 24, pointer/fn/chan 8; -// composites contribute their own slot-padded width. void contributes -// 0 (never appears in tuples emitted by user code, but kept for SSoT -// symmetry with cgen's N_TNAME-"void" fallback arm). +// str/slice and composites consult `pt.size` so a future #1 bump on +// any primitive layout propagates through the typ.ww SSoT seed +// instead of getting baked into this detour. pointer/fn/chan stay +// 8; void contributes 0 (never appears in tuples emitted by user +// code, but kept for SSoT symmetry with cgen's N_TNAME-"void" +// fallback arm). fn tupleelemslot(pt: *tinfo) u64 = { if (pt == nil) { return 8u64; }; let pk: tykind = pt.kind; if (pk == tykind.TY_VOID) { return 0u64; }; - if (pk == tykind.TY_STR) { return 16u64; }; - if (pk == tykind.TY_SLICE) { return 24u64; }; + if (pk == tykind.TY_STR) { return pt.size; }; + if (pk == tykind.TY_SLICE) { return pt.size; }; if (pk == tykind.TY_PTR || pk == tykind.TY_FN || pk == tykind.TY_CHAN || pk == tykind.TY_I64 || pk == tykind.TY_U64 || pk == tykind.TY_INT || @@ -908,10 +910,13 @@ fn fieldslotsize(ft: *tinfo) u64 = { if (fk == tykind.TY_STRUCT) { return ft.slotsize; }; if (fk == tykind.TY_ARRAY) { return ft.slotsize; }; if (fk == tykind.TY_TAGGED) { return ft.size; }; - if (fk == tykind.TY_SLICE) { return 24u64; }; + // str / slice read ft.size so the typ.ww SSoT seed is the single + // source for #1 (str→24) / #34 (slice graduation) — no hardcoded + // literal here to drift. + if (fk == tykind.TY_SLICE) { return ft.size; }; if (fk == tykind.TY_PTR || fk == tykind.TY_FN || fk == tykind.TY_CHAN) { return 8u64; }; - if (fk == tykind.TY_STR) { return 16u64; }; + if (fk == tykind.TY_STR) { return ft.size; }; // Primitives keep natural width inside structs (matches // cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a // struct currently defaults to 8 in cgenutil — preserve that diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index de3e2d7c..9f1e8a3f 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1985,8 +1985,9 @@ export fn dupall(s: []str) ([]str | nomem) = { // // Empty elements (`{nil, 0}` from a zero-length dup) are skipped: // os.free on a nil pointer at len 0 tickles the rt_free guard. The -// slice header itself is freed at `cap * 16` (one str = 16B); a -// never-grown slice (cap == 0) skips the header free. +// slice header itself is freed at `cap * size(str)` — the literal +// would drift under #1's str-layout bump, so route through the +// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free. export fn freeall(s: []str) void = { let i: i32 = 0; for (i < s.len) { @@ -2611,7 +2612,7 @@ fn appendstr(slice: *[]str, item: str) void = { // within the result are borrowed from `in`. // // The caller frees the returned slice via -// `os.free(r.ptr: *void, (r.cap: u64) * 16u64)`. +// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`. // // Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc // has no recoverable failure path. Same precedent as @@ -7959,16 +7960,18 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = { // tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI // spills each element into its own register / 8B eightbyte, so narrow // scalars pad to 8 (cgen's let_emit_size + AX:DX:CX positional layout). -// str stays 16 (composite primitive), slice 24, pointer/fn/chan 8; -// composites contribute their own slot-padded width. void contributes -// 0 (never appears in tuples emitted by user code, but kept for SSoT -// symmetry with cgen's N_TNAME-"void" fallback arm). +// str/slice and composites consult `pt.size` so a future #1 bump on +// any primitive layout propagates through the typ.ww SSoT seed +// instead of getting baked into this detour. pointer/fn/chan stay +// 8; void contributes 0 (never appears in tuples emitted by user +// code, but kept for SSoT symmetry with cgen's N_TNAME-"void" +// fallback arm). fn tupleelemslot(pt: *tinfo) u64 = { if (pt == nil) { return 8u64; }; let pk: tykind = pt.kind; if (pk == tykind.TY_VOID) { return 0u64; }; - if (pk == tykind.TY_STR) { return 16u64; }; - if (pk == tykind.TY_SLICE) { return 24u64; }; + if (pk == tykind.TY_STR) { return pt.size; }; + if (pk == tykind.TY_SLICE) { return pt.size; }; if (pk == tykind.TY_PTR || pk == tykind.TY_FN || pk == tykind.TY_CHAN || pk == tykind.TY_I64 || pk == tykind.TY_U64 || pk == tykind.TY_INT || @@ -7994,10 +7997,13 @@ fn fieldslotsize(ft: *tinfo) u64 = { if (fk == tykind.TY_STRUCT) { return ft.slotsize; }; if (fk == tykind.TY_ARRAY) { return ft.slotsize; }; if (fk == tykind.TY_TAGGED) { return ft.size; }; - if (fk == tykind.TY_SLICE) { return 24u64; }; + // str / slice read ft.size so the typ.ww SSoT seed is the single + // source for #1 (str→24) / #34 (slice graduation) — no hardcoded + // literal here to drift. + if (fk == tykind.TY_SLICE) { return ft.size; }; if (fk == tykind.TY_PTR || fk == tykind.TY_FN || fk == tykind.TY_CHAN) { return 8u64; }; - if (fk == tykind.TY_STR) { return 16u64; }; + if (fk == tykind.TY_STR) { return ft.size; }; // Primitives keep natural width inside structs (matches // cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a // struct currently defaults to 8 in cgenutil — preserve that diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 404336e2..ff746d86 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1876,8 +1876,9 @@ export fn dupall(s: []str) ([]str | nomem) = { // // Empty elements (`{nil, 0}` from a zero-length dup) are skipped: // os.free on a nil pointer at len 0 tickles the rt_free guard. The -// slice header itself is freed at `cap * 16` (one str = 16B); a -// never-grown slice (cap == 0) skips the header free. +// slice header itself is freed at `cap * size(str)` — the literal +// would drift under #1's str-layout bump, so route through the +// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free. export fn freeall(s: []str) void = { let i: i32 = 0; for (i < s.len) { @@ -2502,7 +2503,7 @@ fn appendstr(slice: *[]str, item: str) void = { // within the result are borrowed from `in`. // // The caller frees the returned slice via -// `os.free(r.ptr: *void, (r.cap: u64) * 16u64)`. +// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`. // // Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc // has no recoverable failure path. Same precedent as