cmd/w6c+selfhost/wcc+lib: route sizeof(str)/sizeof(slice) through SSoT

Audit §1.1/§1.2 cataloged 17 wwstage sites hardcoding 16 for sizeof(str)
and ~10 hardcoding 24 for sizeof(slice), plus 4 cstage str-size sites
and the cstage let_emit_size str/slice arms.  Each new size constant
required ~30 edits in both stages to bump cleanly — task #1 (str → 24B
{ptr,len,cap}) can't land until the literal sweep is done.

Track A — wwstage codegen (selfhost/cmd/wcc/*):

  - check.ww introduces two stateless helpers next to astsize:
    primtypesize(nm)  — primitive-name → byte size (i64; -1 unknown)
    tyslicesize()     — slice-header bytes (i64; 24 today)
    astsize now reads both for its N_TNAME-primitive and N_TSLICE arms,
    so the size(T) fold gets the SSoT for free.
  - cgen.ww, cgenutil.ww, cgenstmt.ww, cgendecl.ww: every `return 16`
    / `esz = 16` / `sz0 = 16` for str, every `return 24` /
    `localadd(c, _, 24, _)` for slice, plus the matching `sz == 16` /
    `sz == 24` / `for (i < 16/24)` gates in the global-let DATAW emit,
    route through primtypesize / tyslicesize.
  - Direct delegation slotsize→astsize would require restructuring
    astsize to drop its *checker dep (resolvealias) — the leaf
    primitive/slice cases factor out cleanly, the alias-chain leaves
    diverge because cgen's aliaslookup/structlookup tables and check's
    scope chain aren't unified yet (§1.8, task #50 follow-up).  Sharing
    the leaf table satisfies the SSoT promise without that refactor.

Track B — cstage (cmd/w6c/cgen.c):

  - let_emit_size's TY_STR/TY_SLICE arms drop the hardcoded 16/24 and
    fall to `(int)u->size` like the existing TY_STRUCT/TUPLE/TAGGED arms.
  - N_LET cgstmt's per-kind `sz` cascade collapses to a single
    `if (lu->kind ∈ {ARRAY,SLICE,STR,STRUCT,TUPLE,TAGGED}) sz = lu->size`.
  - N_LET cgexpr's match-bind primitive sizing: `bsz = (int)bu->size`
    drops the TY_STR/TY_SLICE special-cases (same outcome — ty_str/
    ty_slice already have ->size set by type.c).
  - Three `sz == 16` / `let_emit_size(d->type) != 16` gates against the
    str slot width route through ty_str->size.

  Cap-offset sites (cgen.c:2440/1994/3206/5517 `delta = 16` for
  slice's .cap field-write) intentionally NOT touched: 16 there is the
  *offset of .cap inside a slice header*, structurally always 16
  regardless of str.size.  #1 doesn't move the slice layout.

Track C — lib/ user code:

  - lib/strings.freeall + appendstr, lib/shlex.freepartial + appendstr:
    the four `16u64` literals (per-str-element stride for rt_ensure and
    os.free) become `size(str): u64`.  Check-time fold via #42's
    intercept resolves to 16 today; #1 reroutes via the bumped tinfo.

After this commit, bumping ty_str to 24B for task #1 requires editing
exactly two places (cmd/wcc/type.c:64 ty_str.size, plus check.ww
primtypesize's "str" arm) for the SSoT to propagate.

Verification:
  - 131/131 tests pass.  994_w6c_ww + 995_self_rebuild byte-identity
    holds — each replacement evaluates to the same constant the
    literal had today, so cgen output is unchanged.
  - selfhost source's `size(str): u64` folds at check time (cstage
    cmd/wcc/check.c:907-960 for the C-bootstrap of selfhost; wwstage
    check.ww:898-942 for the rebuild path), no runtime call introduced.
This commit is contained in:
2026-05-20 08:50:40 +09:00
parent 3ec944a67f
commit 8e93b31088
11 changed files with 181 additions and 118 deletions

View File

@@ -1196,7 +1196,7 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
let elem: *node = ft.lhs;
if (elem != nil) {
if (elem.kind == nkind.N_TNAME) {
if (streq(elem.str, "str")) { return 16; };
if (streq(elem.str, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(elem.str);
if (ps > 0) { return ps; };
// Pointer to named struct: indexing
@@ -1304,11 +1304,11 @@ fn elemsizeof(t: *node) i32 = {
// `*[]T`: stride is the slice header (24B). Hare-faithful — a
// pointer-to-slice is a 1D array of slices, not of T. Mirrors the
// cstage check.c default `*U → U` path for U=[]T (slice element).
if (elem.kind == nkind.N_TSLICE) { return 24; };
if (elem.kind == nkind.N_TSLICE) { return tyslicesize(): i32; };
if (elem.kind == nkind.N_TNAME) {
let nm: str = elem.str;
// str element is 16B (ptr+len). primsize returns 0 for it.
if (streq(nm, "str")) { return 16; };
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
@@ -1903,7 +1903,7 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
// (ptr+len) — primsize returns 0 for
// it, so it'd slot 8B without this.
if (streq(elemn.str, "str")) {
esz = 16;
esz = primtypesize("str"): i32;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
@@ -1952,7 +1952,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TFN) { return 8; };
if (k == nkind.N_TCHAN) { return 8; };
if (k == nkind.N_TSLICE) { return 24; };
if (k == nkind.N_TSLICE) { return tyslicesize(): i32; };
if (k == nkind.N_TTUPLE) {
// Sum element sizes. Mirrors C cgen which uses raw type
// sizes; padding to 8 happens inside slotsize for primitives,
@@ -1992,7 +1992,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
// slot+8 picking up the callee's stale-DX. Bypassing primsize's
// `> 0` guard keeps the rest of the prim-pad-to-8 contract intact.
if (streq(nm, "void")) { return 0; };
if (streq(nm, "str")) { return 16; };
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) {
// Pad to 8 for stack slots — matches C cgen which spills
@@ -2033,7 +2033,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
// explicit case a `[N]str` would slot 8B/elem,
// collapsing the per-element stride and losing
// every .len half.
if (streq(en, "str")) { esz = 16; };
if (streq(en, "str")) { esz = primtypesize("str"): i32; };
let ps: i32 = primsize(en);
if (esz == 8) { if (ps > 0) { esz = ps; }
else {
@@ -2091,7 +2091,7 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return 16; };
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let si: *structinfo = structlookup(c, nm);
@@ -2118,7 +2118,7 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TSLICE) { return 24; };
if (k == nkind.N_TSLICE) { return tyslicesize(): i32; };
if (k == nkind.N_TARRAY) {
// Same shape as slotsize's TARRAY branch.
let lenn: *node = tnode.rhs;