wcc/ww: paramfieldsize sizes slice/tuple fields through the type table

for-range destructure of an element with a slice- or tuple-typed field
strode by the default 8 (paramfieldsize had no N_TSLICE/N_TTUPLE arms),
silently reading the wrong words (review finding #43; live repro
cs=42 vs ww=8). Add the arms routed through tinfo per rule 13.
989_tupfieldsize_run pins cs==ww (red 1/2 pre-fix); rows assert
convergence, not absolutes — cstage's own single-word destructure-load
bug is filed as task #40. The N_TARRAY arm is deferred (task #39,
rule-7 comment at the fall-through).
This commit is contained in:
2026-06-12 00:11:11 +09:00
parent f25f5021d7
commit 7afc4df652
5 changed files with 292 additions and 3 deletions

View File

@@ -38409,13 +38409,36 @@ fn cgmlet(c: *cgen, n: *node) void = {
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
// i32/u32, 8 for i64/u64/*T/fn, 24 for str/slice (str IS []u8, the slice
// header SSoT), tuple → sum of its 8B-floored element slots, default 8.
fn paramfieldsize(t: *node) i32 = {
if (t == nil) { return 8; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TFN) { return 8; };
if (k == nkind.N_TCHAN) { return 8; };
// #43 (F7-c4): a slice tuple-field carries the 24B header (ptr+len+
// cap), not the 8B scalar default. Without this arm the for-range
// destructure over `[N]([]T, U)` strode the tuple at 8 not 24 and
// read field-2 at the wrong offset (cs=42/ww=8, the cat-A repro).
// tyslicesize() is the slice-header SSoT (rule-13); cstage reads the
// same width via tp->type->size (cmd/w6c/cgen.c N_FORRANGE).
if (k == nkind.N_TSLICE) { return tyslicesize(): i32; };
// #43 (F7-c4): a nested tuple field sizes as the sum of its element
// SLOTS — each element floored UP to one 8B eightbyte (str/slice keep
// their 24B header), per the tuple-slot ruling and tupeslot's
// roundup8. Recurse structurally so a tuple-of-tuple lands the same
// stride cstage's tp->type->size computes.
if (k == nkind.N_TTUPLE) {
let total: i32 = 0;
let dp: *node = t.list;
for (dp != nil) {
let esz: i32 = paramfieldsize(dp.lhs);
total = total + (esz + 7) / 8 * 8;
dp = dp.next;
};
return total;
};
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
@@ -38427,6 +38450,11 @@ fn paramfieldsize(t: *node) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
// rule-7: N_TARRAY (an array-typed tuple/struct field) is intentionally
// not sized here — deferred to task #39. No F7 repro or spec-§5-c4
// member feeds it, so it stays on the 8B default (mis-sized, latent)
// until a consumer surfaces; the divergence is pointed at the task, not
// silent.
return 8;
};

View File

@@ -3646,13 +3646,36 @@ fn cgmlet(c: *cgen, n: *node) void = {
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
// i32/u32, 8 for i64/u64/*T/fn, 24 for str/slice (str IS []u8, the slice
// header SSoT), tuple → sum of its 8B-floored element slots, default 8.
fn paramfieldsize(t: *node) i32 = {
if (t == nil) { return 8; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TFN) { return 8; };
if (k == nkind.N_TCHAN) { return 8; };
// #43 (F7-c4): a slice tuple-field carries the 24B header (ptr+len+
// cap), not the 8B scalar default. Without this arm the for-range
// destructure over `[N]([]T, U)` strode the tuple at 8 not 24 and
// read field-2 at the wrong offset (cs=42/ww=8, the cat-A repro).
// tyslicesize() is the slice-header SSoT (rule-13); cstage reads the
// same width via tp->type->size (cmd/w6c/cgen.c N_FORRANGE).
if (k == nkind.N_TSLICE) { return tyslicesize(): i32; };
// #43 (F7-c4): a nested tuple field sizes as the sum of its element
// SLOTS — each element floored UP to one 8B eightbyte (str/slice keep
// their 24B header), per the tuple-slot ruling and tupeslot's
// roundup8. Recurse structurally so a tuple-of-tuple lands the same
// stride cstage's tp->type->size computes.
if (k == nkind.N_TTUPLE) {
let total: i32 = 0;
let dp: *node = t.list;
for (dp != nil) {
let esz: i32 = paramfieldsize(dp.lhs);
total = total + (esz + 7) / 8 * 8;
dp = dp.next;
};
return total;
};
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
@@ -3664,6 +3687,11 @@ fn paramfieldsize(t: *node) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
// rule-7: N_TARRAY (an array-typed tuple/struct field) is intentionally
// not sized here — deferred to task #39. No F7 repro or spec-§5-c4
// member feeds it, so it stays on the 8B default (mis-sized, latent)
// until a consumer surfaces; the divergence is pointed at the task, not
// silent.
return 8;
};

View File

@@ -38409,13 +38409,36 @@ fn cgmlet(c: *cgen, n: *node) void = {
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
// i32/u32, 8 for i64/u64/*T/fn, 24 for str/slice (str IS []u8, the slice
// header SSoT), tuple → sum of its 8B-floored element slots, default 8.
fn paramfieldsize(t: *node) i32 = {
if (t == nil) { return 8; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TFN) { return 8; };
if (k == nkind.N_TCHAN) { return 8; };
// #43 (F7-c4): a slice tuple-field carries the 24B header (ptr+len+
// cap), not the 8B scalar default. Without this arm the for-range
// destructure over `[N]([]T, U)` strode the tuple at 8 not 24 and
// read field-2 at the wrong offset (cs=42/ww=8, the cat-A repro).
// tyslicesize() is the slice-header SSoT (rule-13); cstage reads the
// same width via tp->type->size (cmd/w6c/cgen.c N_FORRANGE).
if (k == nkind.N_TSLICE) { return tyslicesize(): i32; };
// #43 (F7-c4): a nested tuple field sizes as the sum of its element
// SLOTS — each element floored UP to one 8B eightbyte (str/slice keep
// their 24B header), per the tuple-slot ruling and tupeslot's
// roundup8. Recurse structurally so a tuple-of-tuple lands the same
// stride cstage's tp->type->size computes.
if (k == nkind.N_TTUPLE) {
let total: i32 = 0;
let dp: *node = t.list;
for (dp != nil) {
let esz: i32 = paramfieldsize(dp.lhs);
total = total + (esz + 7) / 8 * 8;
dp = dp.next;
};
return total;
};
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
@@ -38427,6 +38450,11 @@ fn paramfieldsize(t: *node) i32 = {
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
};
// rule-7: N_TARRAY (an array-typed tuple/struct field) is intentionally
// not sized here — deferred to task #39. No F7 repro or spec-§5-c4
// member feeds it, so it stays on the 8B default (mis-sized, latent)
// until a consumer surfaces; the divergence is pointed at the task, not
// silent.
return 8;
};