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

@@ -705,9 +705,8 @@ let_emit_size(Type *t)
case TY_F64:
return 8; /* MOVSD loads/stores 8B via LEAQ+indir. */
case TY_STR:
return 16; /* {ptr, len}; literal-strlit init NYI. */
case TY_SLICE:
return 24; /* {ptr, len, cap}; no init only. */
return (int)u->size; /* #43: ty_str / ty_slice SSoT. */
case TY_STRUCT:
return (int)u->size; /* zero-init only; field reads/
* scalar-field writes only. */
@@ -4988,10 +4987,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
amem(D_BP, voff));
}
} else {
/* #43: route through Type.size SSoT rather
* than re-asserting 16/24 for str/slice. */
int bsz = 8;
if (bu && bu->kind == TY_STR) bsz = 16;
else if (bu && bu->kind == TY_SLICE) bsz = 24;
else if (bu) bsz = (int)bu->size;
if (bu) bsz = (int)bu->size;
if (bsz <= 0) bsz = 8;
/* local_alloc to dodge name-collision
* dedup — a 16B str bind shadowing an
@@ -6350,13 +6349,14 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* collapsed the struct/slice/tagged sizing arms to the 8B
* fallback, and the slot under-allocated the local. */
Type *lu = type_chase_named(lt);
/* #43: every composite kind already has its byte size cached in
* lu->size; route through it instead of re-asserting 16/24 for
* str/slice and re-reading for the others. */
int sz = 8;
if (lu && lu->kind == TY_ARRAY) sz = (int)lu->size;
else if (lu && lu->kind == TY_SLICE) sz = 24;
else if (lu && lu->kind == TY_STR) sz = 16;
else if (lu && lu->kind == TY_STRUCT) sz = (int)lu->size;
else if (lu && lu->kind == TY_TUPLE) sz = (int)lu->size;
else if (lu && lu->kind == TY_TAGGED) sz = (int)lu->size;
if (lu && (lu->kind == TY_ARRAY || lu->kind == TY_SLICE
|| lu->kind == TY_STR || lu->kind == TY_STRUCT
|| lu->kind == TY_TUPLE || lu->kind == TY_TAGGED))
sz = (int)lu->size;
int off = localoff(c, locals, n->str, sz, frame);
int isf = cg_isfloat(lt);
int isf32 = type_isf32(lt);
@@ -6433,8 +6433,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
break;
}
}
/* str initialiser: cgexpr produces (AX=ptr, BX=len). */
if (n->rhs && type_isstr(lt) && sz == 16) {
/* str initialiser: cgexpr produces (AX=ptr, BX=len).
* #43: gate width via ty_str->size so a future str-layout
* bump (#1) propagates without touching this site. */
if (n->rhs && type_isstr(lt) && sz == (int)ty_str->size) {
cgexpr(c, n->rhs, *locals);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
@@ -7763,7 +7765,8 @@ emit_lets(Cg *c, FILE *out, Node *file)
/* str literal init: bake the interned label's address
* into the ptr half via a DATAR reloc, set the len half
* inline. */
if (sz == 16 && r != NULL && r->kind == N_STRLIT
/* #43: gate via ty_str->size so #1 propagates. */
if (sz == (int)ty_str->size && r != NULL && r->kind == N_STRLIT
&& r->strlen > 0) {
const char *lab = intern_strlit(c, r->str, r->strlen);
const char *sym = mod_mangle(c, d->str);
@@ -7916,7 +7919,7 @@ let_pre_intern(Cg *c, Node *file)
if (file == NULL) return;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_LET) continue;
if (let_emit_size(d->type) != 16) continue;
if (let_emit_size(d->type) != (int)ty_str->size) continue;
Node *r = d->rhs;
while (r != NULL && r->kind == N_CAST) r = r->lhs;
if (r == NULL || r->kind != N_STRLIT) continue;