cmd/w6c+selfhost/wcc: route cgparam/MLET/spill sizes through SSoT

#43 (8e93b31 + 087c85c) routed many sizeof(str) / sizeof(slice)
sites through primtypesize / tyslicesize / ty_*->size, but missed
the cgparam regs-fit, cgparam stack-stitch, cgmlet mixed
scalar+str receive, and vararg slice gather paths in both stages.
A bare #1 bump (str→24B) on top of #43 reds ~60 tests because
those paths still hardcoded 16/24.

Cstage:
- cgen.c:7360-7361 cgmlet: sz0/sz1 → (int)u0->size / (int)u1->size.
- cgen.c:7557 cgparam regs-fit: slice|is_str → (int)pu->size.
- cgen.c:7586 cgparam stack-stitch: same.
- cgen.c:4368 cgcall vararg gather: localoff slice descriptor →
  (int)vsu->size (the cstage twin of cgenexpr.ww:3084).

Wwstage:
- cgendecl.ww:225, :243 cgfnparams: 16 → primtypesize("str"): i32.
- cgenexpr.ww:3084 cgcall vararg gather: 24 → tyslicesize(): i32.

Plus a latent-bug fix at cgenstmt.ww cglet :1031 / :1040: the
str-init and slice-init arms dispatched on size only. Under #1's
str→24, both arms would have fired on a str let (duplicate
MOVQ BX,off+8 + bogus MOVQ CX,off+8). Added isstrtype / isslicetype
kind gates mirroring cstage cgen.c:6439's
`type_isstr(lt) && sz == ty_str->size`. Zero asm change today
because the size constants implicitly disambiguate at 16 vs 24.

Probe with temporary #1 bump (str.size=24) confirms 990_selfhost +
994_w6c_ww go green — the cgen-routing slice for #1 is now
closed. Remaining red under bump is lib/ww/typ.ww's parallel SSoT
seed + stringstest cap*16u64 strides + w6l_ww runtime SIGSEGV;
all tracked separately.

EIGHTBYTES register-count sites (cgen.c:7553-7554, cgendecl.ww:224
/:260) intentionally NOT touched — those are str ABI in-flight
3-reg work (task #34), not slot-width SSoT.
This commit is contained in:
2026-05-20 10:08:15 +09:00
parent 087c85c3cf
commit caa72f2365
6 changed files with 77 additions and 46 deletions

View File

@@ -4364,8 +4364,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
dname, nvar * esz, cg_frame); dname, nvar * esz, cg_frame);
} }
const char *slname = mklabel(c, "vararg_sl"); const char *slname = mklabel(c, "vararg_sl");
/* #60: route slice-descriptor width through
* vsu->size so a future slice-header bump
* propagates (mirrors wwstage cgcall vararg
* gather using tyslicesize()). */
int sloff = localoff(c, &locals, int sloff = localoff(c, &locals,
slname, 24, cg_frame); slname, (int)vsu->size, cg_frame);
if (nvar > 0) { if (nvar > 0) {
int v_is_tagged = velem && int v_is_tagged = velem &&
tagged_arg_size(velem) > 0; tagged_arg_size(velem) > 0;
@@ -7357,8 +7361,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
int s0_is_str = u0 && u0->kind == TY_STR; int s0_is_str = u0 && u0->kind == TY_STR;
int s1_is_str = u1 && u1->kind == TY_STR; int s1_is_str = u1 && u1->kind == TY_STR;
if (l0 && l1 && (s0_is_str ^ s1_is_str)) { if (l0 && l1 && (s0_is_str ^ s1_is_str)) {
int sz0 = s0_is_str ? 16 : 8; /* #60: route str-slot width through ty_str->size so #1
int sz1 = s1_is_str ? 16 : 8; * propagates here. Scalar side keeps the 8B slot. */
int sz0 = s0_is_str ? (int)u0->size : 8;
int sz1 = s1_is_str ? (int)u1->size : 8;
int off0 = localoff(c, locals, l0->str, sz0, frame); int off0 = localoff(c, locals, l0->str, sz0, frame);
int off1 = localoff(c, locals, l1->str, sz1, frame); int off1 = localoff(c, locals, l1->str, sz1, frame);
if (s0_is_str) { if (s0_is_str) {
@@ -7554,9 +7560,12 @@ cgfn(Cg *c, FILE *out, Node *fn)
(is_tagged ? tagged_eb : 1))); (is_tagged ? tagged_eb : 1)));
int regs_left = isf ? (8 - fargi) : (6 - argi); int regs_left = isf ? (8 - fargi) : (6 - argi);
if (regs_left >= eightbytes) { if (regs_left >= eightbytes) {
int sz = slice ? 24 : (is_str ? 16 : /* #60: route slice/str slot widths through Type.size SSoT
* so #1's ty_str.size bump propagates without retouching
* this site (or its stack-stitch mirror below). */
int sz = (slice || is_str) ? (int)pu->size :
(is_struct ? (int)pu->size : (is_struct ? (int)pu->size :
(is_tagged ? tagged_sz : 8))); (is_tagged ? tagged_sz : 8));
int off = localoff(c, &locals, p->str, sz, &frame); int off = localoff(c, &locals, p->str, sz, &frame);
if (slice || is_str || is_struct || is_tagged) { if (slice || is_str || is_struct || is_tagged) {
for (int k = 0; k < eightbytes; k++, argi++) for (int k = 0; k < eightbytes; k++, argi++)
@@ -7583,9 +7592,10 @@ cgfn(Cg *c, FILE *out, Node *fn)
* slot from both sources so the body sees a contiguous * slot from both sources so the body sees a contiguous
* value. Mirrors the SysV greedy reg fill the caller * value. Mirrors the SysV greedy reg fill the caller
* does. */ * does. */
int sz = slice ? 24 : (is_str ? 16 : /* #60: same SSoT routing as the regs-fit arm above. */
int sz = (slice || is_str) ? (int)pu->size :
(is_struct ? (int)pu->size : (is_struct ? (int)pu->size :
(is_tagged ? tagged_sz : 8))); (is_tagged ? tagged_sz : 8));
int off = localoff(c, &locals, p->str, sz, &frame); int off = localoff(c, &locals, p->str, sz, &frame);
extern int cg_stack_arg_cursor; extern int cg_stack_arg_cursor;
int k = 0; int k = 0;

View File

@@ -15708,7 +15708,10 @@ fn cgcall(c: *cgen, n: *node) void = {
if (nvar > 0) { if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil); doff = localadd(c, dname, nvar * esz, nil);
}; };
let soff: i32 = localadd(c, sname, 24, // #60: vararg gather builds a {ptr,len,cap} slice
// descriptor — route through tyslicesize so #34's
// slice-header bump propagates here.
let soff: i32 = localadd(c, sname, tyslicesize(): i32,
slicewrap(c, varp.lhs)); slicewrap(c, varp.lhs));
let aa2: *node = n.list; let aa2: *node = n.list;
let kk3: i32 = 0; let kk3: i32 = 0;
@@ -19433,20 +19436,19 @@ fn cglet(c: *cgen, n: *node) void = {
emitoff(off: i64); emitoff(off: i64);
emitline("(BP)\n"); emitline("(BP)\n");
// str init: cgexpr also leaves len in BX; store both. // str init: cgexpr also leaves len in BX; store both.
// #43: route through primtypesize so #1's str-size bump // #60: gate by kind too — under #1's str=24 bump, sizeof(str)
// surfaces this site (today sz==16 picks str; once str=24, // and sizeof(slice) collide, so a bare `sz ==` check fires
// the in-flight str ABI (#34) must propagate cap and the // both branches for one let. Mirrors cstage cgen.c:6439's
// dispatch needs reshaping by kind, not raw size). // `type_isstr(lt) && sz == ty_str->size` shape.
if (sz == primtypesize("str"): i32) { if (isstrtype(c, tn) && sz == primtypesize("str"): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
// slice init: ptr/len/cap in AX/BX/CX. // slice init: ptr/len/cap in AX/BX/CX. Same kind+size gate as
// #43: same routing — bare `sz == 24` would collide with a // the str arm — without the kind check this fires on a str let
// 24B str slot under #1; the kind disambiguation lives at // once sz==24 (#60).
// the caller (cglet's tn.kind == N_TSLICE shape check). if (isslicetype(c, tn) && sz == tyslicesize(): i32) {
if (sz == tyslicesize(): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");
@@ -20271,7 +20273,11 @@ fn cgfnparams(c: *cgen, params: *node) void = {
};}; };};
} else { if (isstrtype(c, p.lhs)) { } else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) { if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: route str-param slot width through the
// primtypesize SSoT so #1's ty_str bump propagates
// here (parent #43 covered the reg-fill site only
// inside cgexpr).
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitline(argregname(idx)); emitline(argregname(idx));
emitline(", "); emitline(", ");
@@ -20289,7 +20295,8 @@ fn cgfnparams(c: *cgen, params: *node) void = {
// 440-469. Only idx=5 hits this (nw=2, // 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at // regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP). // +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: same SSoT routing as the regs-fit arm above.
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
let regs_left: i32 = 6 - idx; let regs_left: i32 = 6 - idx;
let w: i32 = 0; let w: i32 = 0;
for (w < regs_left) { for (w < regs_left) {

View File

@@ -222,7 +222,11 @@ fn cgfnparams(c: *cgen, params: *node) void = {
};}; };};
} else { if (isstrtype(c, p.lhs)) { } else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) { if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: route str-param slot width through the
// primtypesize SSoT so #1's ty_str bump propagates
// here (parent #43 covered the reg-fill site only
// inside cgexpr).
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitline(argregname(idx)); emitline(argregname(idx));
emitline(", "); emitline(", ");
@@ -240,7 +244,8 @@ fn cgfnparams(c: *cgen, params: *node) void = {
// 440-469. Only idx=5 hits this (nw=2, // 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at // regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP). // +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: same SSoT routing as the regs-fit arm above.
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
let regs_left: i32 = 6 - idx; let regs_left: i32 = 6 - idx;
let w: i32 = 0; let w: i32 = 0;
for (w < regs_left) { for (w < regs_left) {

View File

@@ -3081,7 +3081,10 @@ fn cgcall(c: *cgen, n: *node) void = {
if (nvar > 0) { if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil); doff = localadd(c, dname, nvar * esz, nil);
}; };
let soff: i32 = localadd(c, sname, 24, // #60: vararg gather builds a {ptr,len,cap} slice
// descriptor — route through tyslicesize so #34's
// slice-header bump propagates here.
let soff: i32 = localadd(c, sname, tyslicesize(): i32,
slicewrap(c, varp.lhs)); slicewrap(c, varp.lhs));
let aa2: *node = n.list; let aa2: *node = n.list;
let kk3: i32 = 0; let kk3: i32 = 0;

View File

@@ -1024,20 +1024,19 @@ fn cglet(c: *cgen, n: *node) void = {
emitoff(off: i64); emitoff(off: i64);
emitline("(BP)\n"); emitline("(BP)\n");
// str init: cgexpr also leaves len in BX; store both. // str init: cgexpr also leaves len in BX; store both.
// #43: route through primtypesize so #1's str-size bump // #60: gate by kind too — under #1's str=24 bump, sizeof(str)
// surfaces this site (today sz==16 picks str; once str=24, // and sizeof(slice) collide, so a bare `sz ==` check fires
// the in-flight str ABI (#34) must propagate cap and the // both branches for one let. Mirrors cstage cgen.c:6439's
// dispatch needs reshaping by kind, not raw size). // `type_isstr(lt) && sz == ty_str->size` shape.
if (sz == primtypesize("str"): i32) { if (isstrtype(c, tn) && sz == primtypesize("str"): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
// slice init: ptr/len/cap in AX/BX/CX. // slice init: ptr/len/cap in AX/BX/CX. Same kind+size gate as
// #43: same routing — bare `sz == 24` would collide with a // the str arm — without the kind check this fires on a str let
// 24B str slot under #1; the kind disambiguation lives at // once sz==24 (#60).
// the caller (cglet's tn.kind == N_TSLICE shape check). if (isslicetype(c, tn) && sz == tyslicesize(): i32) {
if (sz == tyslicesize(): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");

View File

@@ -15708,7 +15708,10 @@ fn cgcall(c: *cgen, n: *node) void = {
if (nvar > 0) { if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil); doff = localadd(c, dname, nvar * esz, nil);
}; };
let soff: i32 = localadd(c, sname, 24, // #60: vararg gather builds a {ptr,len,cap} slice
// descriptor — route through tyslicesize so #34's
// slice-header bump propagates here.
let soff: i32 = localadd(c, sname, tyslicesize(): i32,
slicewrap(c, varp.lhs)); slicewrap(c, varp.lhs));
let aa2: *node = n.list; let aa2: *node = n.list;
let kk3: i32 = 0; let kk3: i32 = 0;
@@ -19433,20 +19436,19 @@ fn cglet(c: *cgen, n: *node) void = {
emitoff(off: i64); emitoff(off: i64);
emitline("(BP)\n"); emitline("(BP)\n");
// str init: cgexpr also leaves len in BX; store both. // str init: cgexpr also leaves len in BX; store both.
// #43: route through primtypesize so #1's str-size bump // #60: gate by kind too — under #1's str=24 bump, sizeof(str)
// surfaces this site (today sz==16 picks str; once str=24, // and sizeof(slice) collide, so a bare `sz ==` check fires
// the in-flight str ABI (#34) must propagate cap and the // both branches for one let. Mirrors cstage cgen.c:6439's
// dispatch needs reshaping by kind, not raw size). // `type_isstr(lt) && sz == ty_str->size` shape.
if (sz == primtypesize("str"): i32) { if (isstrtype(c, tn) && sz == primtypesize("str"): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");
}; };
// slice init: ptr/len/cap in AX/BX/CX. // slice init: ptr/len/cap in AX/BX/CX. Same kind+size gate as
// #43: same routing — bare `sz == 24` would collide with a // the str arm — without the kind check this fires on a str let
// 24B str slot under #1; the kind disambiguation lives at // once sz==24 (#60).
// the caller (cglet's tn.kind == N_TSLICE shape check). if (isslicetype(c, tn) && sz == tyslicesize(): i32) {
if (sz == tyslicesize(): i32) {
emitline("\tMOVQ\tBX, "); emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64); emitoff((off + 8): i64);
emitline("(BP)\n"); emitline("(BP)\n");
@@ -20271,7 +20273,11 @@ fn cgfnparams(c: *cgen, params: *node) void = {
};}; };};
} else { if (isstrtype(c, p.lhs)) { } else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) { if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: route str-param slot width through the
// primtypesize SSoT so #1's ty_str bump propagates
// here (parent #43 covered the reg-fill site only
// inside cgexpr).
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
emitline("\tMOVQ\t"); emitline("\tMOVQ\t");
emitline(argregname(idx)); emitline(argregname(idx));
emitline(", "); emitline(", ");
@@ -20289,7 +20295,8 @@ fn cgfnparams(c: *cgen, params: *node) void = {
// 440-469. Only idx=5 hits this (nw=2, // 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at // regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP). // +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, p.lhs); // #60: same SSoT routing as the regs-fit arm above.
let off: i32 = localadd(c, nm, primtypesize("str"): i32, p.lhs);
let regs_left: i32 = 6 - idx; let regs_left: i32 = 6 - idx;
let w: i32 = 0; let w: i32 = 0;
for (w < regs_left) { for (w < regs_left) {