selfhost/cmd/wcc/cgenutil: collapse fieldsize onto tinfo (#49, A.6.3e)

fieldsize selected the slot-padded byte width of a struct field's
type-AST via the same TBANG/TNAME/TPTR/TSLICE/TARRAY/TTAGGED walker
shape slotsize used pre-#48 — with TNAME branching into
structlookup, enumlookup, and aliaslookup to follow the AST chain
back to a primitive size or struct totsize. After A.6.2 stamping
+ #48's slotsize collapse, the same data is reachable through
the populated tinfo:

  TY_STRUCT / TY_ARRAY → ti.slotsize
  TY_TAGGED / TY_SLICE / TY_STR → ti.size
  TY_PTR / TY_FN / TY_CHAN → 8
  primitives + TY_ENUM + TY_TUPLE → ti.size (catch-all > 0)
  fallback → 8

cstage SSoT is `f->type->size` at cmd/w6c/cgen.c:1386, :1656, :2515,
:2535 — wwstage routes through ti.slotsize for composites (the
#48 verdict centralizes the slot-pad rule on tinfo) and through
ti.size where natural width and in-struct width coincide.

Two latent behavior fixes ride alongside the collapse, both
cstage-parity and dead-in-bootstrap (995 byte-id is the regression
gate, currently green at 133/133):

  - TY_TUPLE-typed struct field: old walker had no TTUPLE arm and
    fell through to 8; cstage `f->type->size` reads the natural
    sum (e.g., 24 for `(i64, str)`). New code reads ti.size,
    matching cstage.
  - !T-typed struct field: old walker had no TBANG arm and fell
    through to 8; cstage iserror-passthrough returns the inner
    type's size. tinfofornode strips N_TBANG (check.ww:1154-1161)
    so the new dispatch sees the inner ti directly.

No fixtures in selfhost exercise either case today; both fixes
are pre-correct for future code that does.

Body went from ~47 LOC to ~16 LOC. `c: *cgen` retained unused for
callsite stability (slotsize / localloadop precedent, a828c03 /
68219a1). Two callsites in cgenutil.ww:1870 + cgenexpr.ww:3594
untouched.

Dot-chain helpers (dotinnerstructptr, dotfieldtnode,
dotchainresolve) are split off as #49b — they walk a cgen-side
`structinfo` table keyed by field name + offset, and tinfo.fields
is not yet populated by check.ww's TSTRUCT/TTUPLE arms. Parallel
structure to #50's TY_TAGGED.params gap; treat as a separate
populate-then-port pair.

Net -48 LOC across cgenutil.ww + two .combined.ww bundler regens.
Full make test green at 133/133.
This commit is contained in:
2026-05-23 01:28:38 +09:00
parent a828c036d0
commit 883665e962
3 changed files with 93 additions and 141 deletions

View File

@@ -12192,55 +12192,39 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
return 8;
};
// registerstruct — compute field offsets + total size for a struct
// type-decl, store in c.structs. Field type sizes use the same
// slotsize logic (with primitives kept at their natural width — we
// only round to 8 for stack slots, not struct interiors).
// fieldsize — slot-padded byte width of a struct field's type-AST,
// consumed by registerstruct's alignment + offset math (≥8→8 /
// ≥4→4 / ≥2→2 ladder at L1875-1877) and by the `*p OP=` deref-
// compound at cgenexpr.ww:3594. Mirror of check.ww:1053
// `fieldslotsize` (the same dispatch on the populated tinfo);
// slotsize-template precedent at a828c03. cstage SSoT is
// `f->type->size` (cmd/w6c/cgen.c:1386, :1656, :2515, :2535);
// wwstage routes through ti.slotsize for composites since the
// stack-slot pad rules live on tinfo (#48 verdict), and through
// ti.size for the kinds whose natural size already equals their
// in-struct width.
//
// `c: *cgen` retained unused for callsite stability (slotsize /
// localloadop precedent, a828c03 / 68219a1).
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si.totsize; };
// Enum: size of its storage type. Mirrors the C cgen, which
// reads Type.size off the TY_ENUM (which inherits from .sub).
let en: *enumtype = enumlookup(c, nm);
if (en != nil) {
if (en.storage != nil) {
if (en.storage.kind == nkind.N_TNAME) {
let sps: i32 = primsize(en.storage.str);
if (sps > 0) { return sps; };
};
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
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;
let elemn: *node = tnode.lhs;
let elen: i64 = 1i64;
if (lenn != nil) {
if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; };
};
let esz: i32 = fieldsize(c, elemn);
return (esz: i64 * elen): i32;
};
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
if (k == tykind.TY_STRUCT) { return ti.slotsize: i32; };
if (k == tykind.TY_ARRAY) { return ti.slotsize: i32; };
if (k == tykind.TY_TAGGED) { return ti.size: i32; };
if (k == tykind.TY_SLICE) { return ti.size: i32; };
if (k == tykind.TY_PTR || k == tykind.TY_FN ||
k == tykind.TY_CHAN) { return 8; };
if (k == tykind.TY_STR) { return ti.size: i32; };
// Primitives + TY_ENUM keep natural width inside structs
// (cstage parity: cgen.c reads f->type->size directly). TY_TUPLE
// flows here too — pre-collapse fallback was 8, the populated
// ti.size carries the natural sum; ken-thompson 2026-05-23 review:
// keep the corrected behavior, no fixtures in selfhost exercise
// a tuple-typed struct field today (995 byte-id is the gate).
if (ti.size > 0u64) { return ti.size: i32; };
return 8;
};

View File

@@ -1820,55 +1820,39 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
return 8;
};
// registerstruct — compute field offsets + total size for a struct
// type-decl, store in c.structs. Field type sizes use the same
// slotsize logic (with primitives kept at their natural width — we
// only round to 8 for stack slots, not struct interiors).
// fieldsize — slot-padded byte width of a struct field's type-AST,
// consumed by registerstruct's alignment + offset math (≥8→8 /
// ≥4→4 / ≥2→2 ladder at L1875-1877) and by the `*p OP=` deref-
// compound at cgenexpr.ww:3594. Mirror of check.ww:1053
// `fieldslotsize` (the same dispatch on the populated tinfo);
// slotsize-template precedent at a828c03. cstage SSoT is
// `f->type->size` (cmd/w6c/cgen.c:1386, :1656, :2515, :2535);
// wwstage routes through ti.slotsize for composites since the
// stack-slot pad rules live on tinfo (#48 verdict), and through
// ti.size for the kinds whose natural size already equals their
// in-struct width.
//
// `c: *cgen` retained unused for callsite stability (slotsize /
// localloadop precedent, a828c03 / 68219a1).
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si.totsize; };
// Enum: size of its storage type. Mirrors the C cgen, which
// reads Type.size off the TY_ENUM (which inherits from .sub).
let en: *enumtype = enumlookup(c, nm);
if (en != nil) {
if (en.storage != nil) {
if (en.storage.kind == nkind.N_TNAME) {
let sps: i32 = primsize(en.storage.str);
if (sps > 0) { return sps; };
};
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
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;
let elemn: *node = tnode.lhs;
let elen: i64 = 1i64;
if (lenn != nil) {
if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; };
};
let esz: i32 = fieldsize(c, elemn);
return (esz: i64 * elen): i32;
};
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
if (k == tykind.TY_STRUCT) { return ti.slotsize: i32; };
if (k == tykind.TY_ARRAY) { return ti.slotsize: i32; };
if (k == tykind.TY_TAGGED) { return ti.size: i32; };
if (k == tykind.TY_SLICE) { return ti.size: i32; };
if (k == tykind.TY_PTR || k == tykind.TY_FN ||
k == tykind.TY_CHAN) { return 8; };
if (k == tykind.TY_STR) { return ti.size: i32; };
// Primitives + TY_ENUM keep natural width inside structs
// (cstage parity: cgen.c reads f->type->size directly). TY_TUPLE
// flows here too — pre-collapse fallback was 8, the populated
// ti.size carries the natural sum; ken-thompson 2026-05-23 review:
// keep the corrected behavior, no fixtures in selfhost exercise
// a tuple-typed struct field today (995 byte-id is the gate).
if (ti.size > 0u64) { return ti.size: i32; };
return 8;
};

View File

@@ -12192,55 +12192,39 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
return 8;
};
// registerstruct — compute field offsets + total size for a struct
// type-decl, store in c.structs. Field type sizes use the same
// slotsize logic (with primitives kept at their natural width — we
// only round to 8 for stack slots, not struct interiors).
// fieldsize — slot-padded byte width of a struct field's type-AST,
// consumed by registerstruct's alignment + offset math (≥8→8 /
// ≥4→4 / ≥2→2 ladder at L1875-1877) and by the `*p OP=` deref-
// compound at cgenexpr.ww:3594. Mirror of check.ww:1053
// `fieldslotsize` (the same dispatch on the populated tinfo);
// slotsize-template precedent at a828c03. cstage SSoT is
// `f->type->size` (cmd/w6c/cgen.c:1386, :1656, :2515, :2535);
// wwstage routes through ti.slotsize for composites since the
// stack-slot pad rules live on tinfo (#48 verdict), and through
// ti.size for the kinds whose natural size already equals their
// in-struct width.
//
// `c: *cgen` retained unused for callsite stability (slotsize /
// localloadop precedent, a828c03 / 68219a1).
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return primtypesize("str"): i32; };
let ps: i32 = primsize(nm);
if (ps > 0) { return ps; };
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si.totsize; };
// Enum: size of its storage type. Mirrors the C cgen, which
// reads Type.size off the TY_ENUM (which inherits from .sub).
let en: *enumtype = enumlookup(c, nm);
if (en != nil) {
if (en.storage != nil) {
if (en.storage.kind == nkind.N_TNAME) {
let sps: i32 = primsize(en.storage.str);
if (sps > 0) { return sps; };
};
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
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;
let elemn: *node = tnode.lhs;
let elen: i64 = 1i64;
if (lenn != nil) {
if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; };
};
let esz: i32 = fieldsize(c, elemn);
return (esz: i64 * elen): i32;
};
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
if (k == tykind.TY_STRUCT) { return ti.slotsize: i32; };
if (k == tykind.TY_ARRAY) { return ti.slotsize: i32; };
if (k == tykind.TY_TAGGED) { return ti.size: i32; };
if (k == tykind.TY_SLICE) { return ti.size: i32; };
if (k == tykind.TY_PTR || k == tykind.TY_FN ||
k == tykind.TY_CHAN) { return 8; };
if (k == tykind.TY_STR) { return ti.size: i32; };
// Primitives + TY_ENUM keep natural width inside structs
// (cstage parity: cgen.c reads f->type->size directly). TY_TUPLE
// flows here too — pre-collapse fallback was 8, the populated
// ti.size carries the natural sum; ken-thompson 2026-05-23 review:
// keep the corrected behavior, no fixtures in selfhost exercise
// a tuple-typed struct field today (995 byte-id is the gate).
if (ti.size > 0u64) { return ti.size: i32; };
return 8;
};