selfhost/cmd/wcc + lib/strings: restore SSoT routing for str/slice tinfo helpers

Phase A.5's tupleelemslot / fieldslotsize hardcoded 16u64 for TY_STR
and 24u64 for TY_SLICE — bypassing the tinfo.size SSoT seeded by
lib/ww/typ.ww:189 (the very pivot they were introduced to consult).
Route those four arms through pt.size / ft.size so #1 (str→24) and
#34 (slice graduation) land as a one-line bump at the seed.

lib/strings/stringstest.ww carried 12 `(cap: u64) * 16u64` strides
missed by #43's sweep over strings.ww + shlex.ww; convert to
`* size(str): u64` so the #42 fold owns the constant. Doc comments
in strings.ww (freeall + splitn) updated to the same SSoT form.

No-op at today's str.size=16 / slice=24: tinfo.size already matches
the literals these arms had baked in. Reviewer's pre/post asm-identity
probe (struct{i64,str,i64} + (i32,str,i32) tuple + bare str) shows
zero-byte diff. 131/131 + 994 + 995 + bootstrap (ww2==ww3==ww4) green.

Forward-link to #1 (str→24B bump) and #64 (sizelint pre-commit gate);
#65 filed for lib/bytes + lib/getopt sibling sites the reviewer
surfaced. Forward of #64 will catch any future regressions of this
class.
This commit is contained in:
2026-05-20 14:55:36 +09:00
parent 9fd79cdc33
commit 03b7336cae
6 changed files with 67 additions and 48 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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