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);
}
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,
slname, 24, cg_frame);
slname, (int)vsu->size, cg_frame);
if (nvar > 0) {
int v_is_tagged = velem &&
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 s1_is_str = u1 && u1->kind == TY_STR;
if (l0 && l1 && (s0_is_str ^ s1_is_str)) {
int sz0 = s0_is_str ? 16 : 8;
int sz1 = s1_is_str ? 16 : 8;
/* #60: route str-slot width through ty_str->size so #1
* 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 off1 = localoff(c, locals, l1->str, sz1, frame);
if (s0_is_str) {
@@ -7554,9 +7560,12 @@ cgfn(Cg *c, FILE *out, Node *fn)
(is_tagged ? tagged_eb : 1)));
int regs_left = isf ? (8 - fargi) : (6 - argi);
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_tagged ? tagged_sz : 8)));
(is_tagged ? tagged_sz : 8));
int off = localoff(c, &locals, p->str, sz, &frame);
if (slice || is_str || is_struct || is_tagged) {
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
* value. Mirrors the SysV greedy reg fill the caller
* 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_tagged ? tagged_sz : 8)));
(is_tagged ? tagged_sz : 8));
int off = localoff(c, &locals, p->str, sz, &frame);
extern int cg_stack_arg_cursor;
int k = 0;

View File

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

View File

@@ -222,7 +222,11 @@ fn cgfnparams(c: *cgen, params: *node) void = {
};};
} else { if (isstrtype(c, p.lhs)) {
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(argregname(idx));
emitline(", ");
@@ -240,7 +244,8 @@ fn cgfnparams(c: *cgen, params: *node) void = {
// 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at
// +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 w: i32 = 0;
for (w < regs_left) {

View File

@@ -3081,7 +3081,10 @@ fn cgcall(c: *cgen, n: *node) void = {
if (nvar > 0) {
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));
let aa2: *node = n.list;
let kk3: i32 = 0;

View File

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

View File

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