selfhost/cmd/wcc: peel TY_NAMED in 5 structural walkers (#63, Phase-N step 1)

Phase-N prerequisite (additive, byte-id unchanged). slotsize / fieldsize /
nullableptrtag (cgenutil) and tupleelemslot / fieldslotsize (check) read
size/slot/kind off a tinfo without peeling TY_NAMED. Once Phase-N step 2
(#64) makes tinfofornode build per-decl TY_NAMED wrappers, an unpeeled
reader would misbehave (fall through to 8 / take natural size not slot /
miss NAMED-of-tagged). Prepend a transitive `for (t != nil && t.kind ==
tykind.TY_NAMED) { t = t.under; }` peel + nil re-guard at each, mirroring
cstage's `while (t->kind == TY_NAMED) t = t->under` and the recursive
typeis* predicates.

Additive no-op today: tinfofornode still collapses aliases, so no NAMED is
ever built and the loop never executes. byte-id 990-997 unchanged (133/133).

Audit (worker + reviewer, independently, across all selfhost/cmd/wcc/*.ww
+ lib/ww/*.ww): these 5 are the ONLY non-peeling structural walkers. typeis*
recurse on .under; typeeq is nominal by design (ptr-identity, the step-3
goal); typeisuntyped cannot receive a NAMED; check.ww type constructors and
localloadop read only .size/.slotsize, which typenamed copies from .under so
they stay numerically correct on a NAMED without peeling.
This commit is contained in:
2026-05-23 18:44:12 +09:00
parent e0c0f75b2a
commit ae6a59a355
4 changed files with 117 additions and 36 deletions

View File

@@ -8124,10 +8124,16 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
let pk: tykind = pt.kind;
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let pk: tykind = t.kind;
if (pk == tykind.TY_VOID) { return 0u64; };
if (pk == tykind.TY_STR) { return pt.size; };
if (pk == tykind.TY_SLICE) { return pt.size; };
if (pk == tykind.TY_STR) { return t.size; };
if (pk == tykind.TY_SLICE) { return t.size; };
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
@@ -8139,7 +8145,7 @@ fn tupleelemslot(pt: *tinfo) u64 = {
pk == tykind.TY_U16 || pk == tykind.TY_U32 ||
pk == tykind.TY_F32 || pk == tykind.TY_ENUM) { return 8u64; };
// Composite — struct/tuple/array/tagged carry their own slot total.
return pt.slotsize;
return t.slotsize;
};
// #61 A.5 helper: per-field slot size mirroring cgenutil.ww
@@ -8149,17 +8155,23 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
let fk: tykind = ft.kind;
if (fk == tykind.TY_STRUCT) { return ft.slotsize; };
if (fk == tykind.TY_ARRAY) { return ft.slotsize; };
if (fk == tykind.TY_TAGGED) { return ft.size; };
// str / slice read ft.size so the typ.ww SSoT seed is the single
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let fk: tykind = t.kind;
if (fk == tykind.TY_STRUCT) { return t.slotsize; };
if (fk == tykind.TY_ARRAY) { return t.slotsize; };
if (fk == tykind.TY_TAGGED) { return t.size; };
// str / slice read t.size so the typ.ww SSoT seed is the single
// source for #1 (str→24) / #34 (slice graduation) — no hardcoded
// literal here to drift.
if (fk == tykind.TY_SLICE) { return ft.size; };
if (fk == tykind.TY_SLICE) { return t.size; };
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return ft.size; };
if (fk == tykind.TY_STR) { return t.size; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
@@ -8171,7 +8183,7 @@ fn fieldslotsize(ft: *tinfo) u64 = {
fk == tykind.TY_U32 || fk == tykind.TY_U64 ||
fk == tykind.TY_INT || fk == tykind.TY_UINT ||
fk == tykind.TY_UINTPTR || fk == tykind.TY_F32 ||
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return ft.size; };
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return t.size; };
return 8u64;
};
@@ -12148,6 +12160,11 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
if (kk == tykind.TY_VOID) { return 0; };
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
@@ -12180,6 +12197,11 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
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; };
@@ -12524,6 +12546,11 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };
let p: *tparam = ti.params;
let i: i32 = 0;

View File

@@ -1671,6 +1671,11 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
if (kk == tykind.TY_VOID) { return 0; };
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
@@ -1703,6 +1708,11 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
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; };
@@ -2047,6 +2057,11 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };
let p: *tparam = ti.params;
let i: i32 = 0;

View File

@@ -1027,10 +1027,16 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
let pk: tykind = pt.kind;
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let pk: tykind = t.kind;
if (pk == tykind.TY_VOID) { return 0u64; };
if (pk == tykind.TY_STR) { return pt.size; };
if (pk == tykind.TY_SLICE) { return pt.size; };
if (pk == tykind.TY_STR) { return t.size; };
if (pk == tykind.TY_SLICE) { return t.size; };
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
@@ -1042,7 +1048,7 @@ fn tupleelemslot(pt: *tinfo) u64 = {
pk == tykind.TY_U16 || pk == tykind.TY_U32 ||
pk == tykind.TY_F32 || pk == tykind.TY_ENUM) { return 8u64; };
// Composite — struct/tuple/array/tagged carry their own slot total.
return pt.slotsize;
return t.slotsize;
};
// #61 A.5 helper: per-field slot size mirroring cgenutil.ww
@@ -1052,17 +1058,23 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
let fk: tykind = ft.kind;
if (fk == tykind.TY_STRUCT) { return ft.slotsize; };
if (fk == tykind.TY_ARRAY) { return ft.slotsize; };
if (fk == tykind.TY_TAGGED) { return ft.size; };
// str / slice read ft.size so the typ.ww SSoT seed is the single
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let fk: tykind = t.kind;
if (fk == tykind.TY_STRUCT) { return t.slotsize; };
if (fk == tykind.TY_ARRAY) { return t.slotsize; };
if (fk == tykind.TY_TAGGED) { return t.size; };
// str / slice read t.size so the typ.ww SSoT seed is the single
// source for #1 (str→24) / #34 (slice graduation) — no hardcoded
// literal here to drift.
if (fk == tykind.TY_SLICE) { return ft.size; };
if (fk == tykind.TY_SLICE) { return t.size; };
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return ft.size; };
if (fk == tykind.TY_STR) { return t.size; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
@@ -1074,7 +1086,7 @@ fn fieldslotsize(ft: *tinfo) u64 = {
fk == tykind.TY_U32 || fk == tykind.TY_U64 ||
fk == tykind.TY_INT || fk == tykind.TY_UINT ||
fk == tykind.TY_UINTPTR || fk == tykind.TY_F32 ||
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return ft.size; };
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return t.size; };
return 8u64;
};

View File

@@ -8124,10 +8124,16 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
let pk: tykind = pt.kind;
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let pk: tykind = t.kind;
if (pk == tykind.TY_VOID) { return 0u64; };
if (pk == tykind.TY_STR) { return pt.size; };
if (pk == tykind.TY_SLICE) { return pt.size; };
if (pk == tykind.TY_STR) { return t.size; };
if (pk == tykind.TY_SLICE) { return t.size; };
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
@@ -8139,7 +8145,7 @@ fn tupleelemslot(pt: *tinfo) u64 = {
pk == tykind.TY_U16 || pk == tykind.TY_U32 ||
pk == tykind.TY_F32 || pk == tykind.TY_ENUM) { return 8u64; };
// Composite — struct/tuple/array/tagged carry their own slot total.
return pt.slotsize;
return t.slotsize;
};
// #61 A.5 helper: per-field slot size mirroring cgenutil.ww
@@ -8149,17 +8155,23 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
let fk: tykind = ft.kind;
if (fk == tykind.TY_STRUCT) { return ft.slotsize; };
if (fk == tykind.TY_ARRAY) { return ft.slotsize; };
if (fk == tykind.TY_TAGGED) { return ft.size; };
// str / slice read ft.size so the typ.ww SSoT seed is the single
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
let fk: tykind = t.kind;
if (fk == tykind.TY_STRUCT) { return t.slotsize; };
if (fk == tykind.TY_ARRAY) { return t.slotsize; };
if (fk == tykind.TY_TAGGED) { return t.size; };
// str / slice read t.size so the typ.ww SSoT seed is the single
// source for #1 (str→24) / #34 (slice graduation) — no hardcoded
// literal here to drift.
if (fk == tykind.TY_SLICE) { return ft.size; };
if (fk == tykind.TY_SLICE) { return t.size; };
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return ft.size; };
if (fk == tykind.TY_STR) { return t.size; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
@@ -8171,7 +8183,7 @@ fn fieldslotsize(ft: *tinfo) u64 = {
fk == tykind.TY_U32 || fk == tykind.TY_U64 ||
fk == tykind.TY_INT || fk == tykind.TY_UINT ||
fk == tykind.TY_UINTPTR || fk == tykind.TY_F32 ||
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return ft.size; };
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return t.size; };
return 8u64;
};
@@ -12148,6 +12160,11 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
if (kk == tykind.TY_VOID) { return 0; };
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
@@ -12180,6 +12197,11 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
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; };
@@ -12524,6 +12546,11 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };
let p: *tparam = ti.params;
let i: i32 = 0;