selfhost+cstage+test: graduate alias-chain unwrap to transitive (#22)

Single-peel TY_NAMED.under bottoms out at the inner alias when
chain length is 2+, surfaces in two stages with different
mechanisms: cstage's gates inline `if (t->kind == TY_NAMED)
t = t->under` at every callsite (cgreturn, cglet sizing, cgexpr
N_DOT, cgassign N_DOT, cg_sret_retsize) — graduated to a
while-loop via new type_chase_named helper across 11 sites.
wwstage routes all field-walks through structlookup, which
registers only direct struct definitions (not aliases) — missing
the alias-recurse fallback. New structlookupchain helper mirrors
slotsize's N_TARRAY arm precedent; sretretsize + 4 cgenexpr.ww
sites route through it. Splitting would either land cstage
without unblocking wwstage's strings.tokenize wrapper shape
(rule 10 byte-id regression) or land wwstage without cstage
gate parity (breaking 995 self-rebuild). 756 sentinel exercises
4 rows × cstage RC + wwstage RC + byte-id = 12 fixtures; pre-fix
rows 2 + 4 (slice-fields single alias, i32 double alias) fail
on both RC and byte-id. The ~67 cstage / ~26 wwstage candidate
sibling sites are #17-style structural-close follow-up; this
commit fixes the immediate strings.tokenize-wrapper blockers.
This commit is contained in:
2026-05-19 15:09:57 +09:00
parent 7e1b681701
commit a1d9f36d11
7 changed files with 513 additions and 40 deletions

View File

@@ -9150,6 +9150,39 @@ fn structnaturalsize(si: *structinfo) i32 = {
// cg_sret_retsize predicate. Resolves N_TNAME → struct via structlookup
// and unwraps one leading N_TBANG so `type box = !big;` still
// triggers sret on the underlying big.
//
// Chain-of-aliases (#22): `type a = struct{...}; type b = a;` registers
// `b → a` in c.aliases (target node = N_TNAME "a"), not `b → struct`.
// When structlookup(c, "b") misses, fall through to aliaslookup and
// recurse on the alias target — mirrors slotsize's N_TNAME arm
// (cgenutil.ww:1955) and the cstage while-loop in cg_sret_retsize.
// structlookupchain — resolve TNAME `tn` to its registered struct,
// chasing alias-of-alias (#22). Returns nil if the chain doesn't
// bottom out at a struct. Mirrors cstage's transitive
// `while (t->kind == TY_NAMED) t = t->under` peel; consumed by
// cgdot / cgassign at every "field-walk on a struct-typed local"
// site so a transitively-aliased struct name resolves to its
// fieldinfo list regardless of chain depth.
export fn structlookupchain(c: *cgen, tn: *node) *structinfo = {
if (tn == nil) { return nil; };
if (tn.kind != nkind.N_TNAME) { return nil; };
let si: *structinfo = structlookup(c, tn.str);
if (si != nil) { return si; };
let cur: *node = tn;
for (cur != nil && cur.kind == nkind.N_TNAME && si == nil) {
let aliased: *node = aliaslookup(c, cur.str);
if (aliased == nil) { cur = nil; }
else {
if (aliased.kind == nkind.N_TNAME) {
si = structlookup(c, aliased.str);
cur = aliased;
} else { cur = nil; };
};
};
return si;
};
export fn sretretsize(c: *cgen, t: *node) i32 = {
if (t == nil) { return 0; };
let r: *node = t;
@@ -9162,7 +9195,15 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
if (primsize(r.str) > 0) { return 0; };
if (streq(r.str, "str")) { return 0; };
let si: *structinfo = structlookup(c, r.str);
if (si == nil) { return 0; };
if (si == nil) {
if (c != nil) {
let aliased: *node = aliaslookup(c, r.str);
if (aliased != nil) {
return sretretsize(c, aliased);
};
};
return 0;
};
let n: i32 = structnaturalsize(si);
if (n <= 24) { return 0; };
return n;
@@ -12807,7 +12848,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// a miss so `*tokenizer` where tokenizer is
// a transitively-aliased struct still
// resolves to the underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -12890,8 +12935,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
// Direct struct local: field load at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// miss so a transitively-aliased struct (`type
// b = a; a = struct`) still resolves to the
// underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -15556,7 +15604,10 @@ fn cgassign(c: *cgen, n: *node) void = {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) handles the
// alias-chain miss; same shape as the
// cgdot pointer-to-struct read site.
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -15794,8 +15845,9 @@ fn cgassign(c: *cgen, n: *node) void = {
};
// Direct struct local: store at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) — same shape
// as the cgdot direct-local read site.
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {

View File

@@ -1280,7 +1280,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// a miss so `*tokenizer` where tokenizer is
// a transitively-aliased struct still
// resolves to the underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -1363,8 +1367,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
// Direct struct local: field load at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// miss so a transitively-aliased struct (`type
// b = a; a = struct`) still resolves to the
// underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -4029,7 +4036,10 @@ fn cgassign(c: *cgen, n: *node) void = {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) handles the
// alias-chain miss; same shape as the
// cgdot pointer-to-struct read site.
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -4267,8 +4277,9 @@ fn cgassign(c: *cgen, n: *node) void = {
};
// Direct struct local: store at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) — same shape
// as the cgdot direct-local read site.
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {

View File

@@ -1520,6 +1520,39 @@ fn structnaturalsize(si: *structinfo) i32 = {
// cg_sret_retsize predicate. Resolves N_TNAME → struct via structlookup
// and unwraps one leading N_TBANG so `type box = !big;` still
// triggers sret on the underlying big.
//
// Chain-of-aliases (#22): `type a = struct{...}; type b = a;` registers
// `b → a` in c.aliases (target node = N_TNAME "a"), not `b → struct`.
// When structlookup(c, "b") misses, fall through to aliaslookup and
// recurse on the alias target — mirrors slotsize's N_TNAME arm
// (cgenutil.ww:1955) and the cstage while-loop in cg_sret_retsize.
// structlookupchain — resolve TNAME `tn` to its registered struct,
// chasing alias-of-alias (#22). Returns nil if the chain doesn't
// bottom out at a struct. Mirrors cstage's transitive
// `while (t->kind == TY_NAMED) t = t->under` peel; consumed by
// cgdot / cgassign at every "field-walk on a struct-typed local"
// site so a transitively-aliased struct name resolves to its
// fieldinfo list regardless of chain depth.
export fn structlookupchain(c: *cgen, tn: *node) *structinfo = {
if (tn == nil) { return nil; };
if (tn.kind != nkind.N_TNAME) { return nil; };
let si: *structinfo = structlookup(c, tn.str);
if (si != nil) { return si; };
let cur: *node = tn;
for (cur != nil && cur.kind == nkind.N_TNAME && si == nil) {
let aliased: *node = aliaslookup(c, cur.str);
if (aliased == nil) { cur = nil; }
else {
if (aliased.kind == nkind.N_TNAME) {
si = structlookup(c, aliased.str);
cur = aliased;
} else { cur = nil; };
};
};
return si;
};
export fn sretretsize(c: *cgen, t: *node) i32 = {
if (t == nil) { return 0; };
let r: *node = t;
@@ -1532,7 +1565,15 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
if (primsize(r.str) > 0) { return 0; };
if (streq(r.str, "str")) { return 0; };
let si: *structinfo = structlookup(c, r.str);
if (si == nil) { return 0; };
if (si == nil) {
if (c != nil) {
let aliased: *node = aliaslookup(c, r.str);
if (aliased != nil) {
return sretretsize(c, aliased);
};
};
return 0;
};
let n: i32 = structnaturalsize(si);
if (n <= 24) { return 0; };
return n;

View File

@@ -9150,6 +9150,39 @@ fn structnaturalsize(si: *structinfo) i32 = {
// cg_sret_retsize predicate. Resolves N_TNAME → struct via structlookup
// and unwraps one leading N_TBANG so `type box = !big;` still
// triggers sret on the underlying big.
//
// Chain-of-aliases (#22): `type a = struct{...}; type b = a;` registers
// `b → a` in c.aliases (target node = N_TNAME "a"), not `b → struct`.
// When structlookup(c, "b") misses, fall through to aliaslookup and
// recurse on the alias target — mirrors slotsize's N_TNAME arm
// (cgenutil.ww:1955) and the cstage while-loop in cg_sret_retsize.
// structlookupchain — resolve TNAME `tn` to its registered struct,
// chasing alias-of-alias (#22). Returns nil if the chain doesn't
// bottom out at a struct. Mirrors cstage's transitive
// `while (t->kind == TY_NAMED) t = t->under` peel; consumed by
// cgdot / cgassign at every "field-walk on a struct-typed local"
// site so a transitively-aliased struct name resolves to its
// fieldinfo list regardless of chain depth.
export fn structlookupchain(c: *cgen, tn: *node) *structinfo = {
if (tn == nil) { return nil; };
if (tn.kind != nkind.N_TNAME) { return nil; };
let si: *structinfo = structlookup(c, tn.str);
if (si != nil) { return si; };
let cur: *node = tn;
for (cur != nil && cur.kind == nkind.N_TNAME && si == nil) {
let aliased: *node = aliaslookup(c, cur.str);
if (aliased == nil) { cur = nil; }
else {
if (aliased.kind == nkind.N_TNAME) {
si = structlookup(c, aliased.str);
cur = aliased;
} else { cur = nil; };
};
};
return si;
};
export fn sretretsize(c: *cgen, t: *node) i32 = {
if (t == nil) { return 0; };
let r: *node = t;
@@ -9162,7 +9195,15 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
if (primsize(r.str) > 0) { return 0; };
if (streq(r.str, "str")) { return 0; };
let si: *structinfo = structlookup(c, r.str);
if (si == nil) { return 0; };
if (si == nil) {
if (c != nil) {
let aliased: *node = aliaslookup(c, r.str);
if (aliased != nil) {
return sretretsize(c, aliased);
};
};
return 0;
};
let n: i32 = structnaturalsize(si);
if (n <= 24) { return 0; };
return n;
@@ -12807,7 +12848,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// a miss so `*tokenizer` where tokenizer is
// a transitively-aliased struct still
// resolves to the underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -12890,8 +12935,11 @@ fn cgdot(c: *cgen, n: *node) void = {
};
// Direct struct local: field load at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain walks the alias chain on
// miss so a transitively-aliased struct (`type
// b = a; a = struct`) still resolves to the
// underlying fieldinfo (#22).
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -15556,7 +15604,10 @@ fn cgassign(c: *cgen, n: *node) void = {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) handles the
// alias-chain miss; same shape as the
// cgdot pointer-to-struct read site.
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -15794,8 +15845,9 @@ fn cgassign(c: *cgen, n: *node) void = {
};
// Direct struct local: store at off+foff.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
// structlookupchain (#22) — same shape
// as the cgdot direct-local read site.
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {