From 4adf914f2fa98d47a3c4c2c5a9aa5c791388489e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 23:19:49 +0900 Subject: [PATCH] wcc_ww/cgen: #92 rhsstructpayload N_STRUCTLIT arm through structlookupchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The N_STRUCTLIT arm still did bare structlookup on the literal's type name — an alias-named struct LITERAL widened into a union (`type ali = base; let v: (void|ali) = ali{...}`) missed the registered structinfo and fell to the scalar widen arm: word0-only/dropped payload, cs 0 / ww EXIT 1, byte-id NO (reviewer-B2 find, pre-existing at 486f7f8's parent). Exactly the class batch-2 c2 closed for the N_IDENT-local arm of the SAME function; same funnel fix — route through structlookupchain (cgenutil.ww:1758) and return si.sname so every consumer's re-lookup hits the REGISTERED name. One pre-authorized rider: structlookupchain's entry gate accepted only N_TNAME, but a struct literal's type ref parses as N_IDENT (expression position, lib/ww/parse/expr.ww builds s.lhs as N_IDENT — OBSERVED). Widened to `N_TNAME || N_IDENT`; the entry-kind predicate is spelled twice (the early-return and the chain loop's first iteration — the loop reassigns cur to aliaslookup results, which are always N_TNAME, so later iterations are untouched). Consumer census, all 10 call sites: cgenutil:3187(+this arm; post-commit position), cgenexpr:2927/3006/8775/9011, cgenstmt:3033 guard on N_TNAME explicitly before calling; cgenexpr:5756 (etnode = type-spec .lhs), cgenstmt:1978 (arrtn.lhs or the synthesized #79 N_TNAME), cgenstmt:2695 (cglet n.lhs declared type spec) pass parsetype products only — parsetype builds zero N_IDENT nodes (lib/ww/parse/parse.ww). No existing caller can pass N_IDENT: the widening is strictly accepting-more; existing callers unmoved. NO second inline chase in the arm (close-by-construction). Base spellings byte-id: structlookup hits at the chain entry and si.sname == the literal's own name — same string out, same asm. Pin: 944_alias_accept_run union_slit_{base_ctl,alias,alias2,order} — base control holds 0/0; 1/2-level alias + permuted decl order graduate cs0/ww1-byte-id-NO -> 0/0 byte-id. #63 PROBED post-c3 per the enrollment rule: does NOT green at this site (8B alias struct-lit let-init still ww silent-zero-fill exit 1; 16B still loud "aggregate init from unhandled rhs shape") — its miss is the cgenstmt let-init dispatch, not rhsstructpayload; documented on task #63, left red, no row enrolled. Light gates: test-unit 290 green; sizelint 0; 989 ratchet zero flips; five-mains NEUTRAL vs master-74195ac scratch on identical inputs + cs==ww on all five. combined.ww regens ride along (#110). --- selfhost/cmd/w6c/main.combined.ww | 29 +++++++---- selfhost/cmd/wcc/cgenutil.ww | 29 +++++++---- selfhost/cmd/wwdump/main.combined.ww | 29 +++++++---- test/wcc/944_alias_accept_run.c | 77 ++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 27 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 27aca54c..ee32d42b 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -17829,11 +17829,19 @@ fn structabisize(si: *structinfo) i32 = { // 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; }; + // #92: a struct LITERAL's type ref parses as N_IDENT (expression + // position, lib/ww/parse/expr.ww) where type specs parse N_TNAME + // — both carry the name in .str. Accept both at the ENTRY only: + // iterations past the first walk aliaslookup results, which are + // N_TNAME by the loop's own reassignment gate. Consumer census + // (commit body): no existing caller can pass N_IDENT. + if (tn.kind != nkind.N_TNAME && tn.kind != nkind.N_IDENT) { 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) { + for (cur != nil + && (cur.kind == nkind.N_TNAME || cur.kind == nkind.N_IDENT) + && si == nil) { let aliased: *node = aliaslookup(c, cur.str); if (aliased == nil) { cur = nil; } else { @@ -19217,13 +19225,16 @@ fn rhsstructpayload(c: *cgen, src: *node) str = { if (src.kind == nkind.N_STRUCTLIT) { let trefn: *node = src.lhs; if (trefn != nil) { - let nm: str; - nm.ptr = nil; nm.len = 0; - if (trefn.kind == nkind.N_IDENT) { nm = trefn.str; }; - if (trefn.kind == nkind.N_TNAME) { nm = trefn.str; }; - if (nm.len > 0) { - if (structlookup(c, nm) != nil) { return nm; }; - }; + // #92: bare structlookup missed an alias-named literal + // — `ali{...}` into a union fell to the scalar widen + // arm, word0-only payload (the class #62 L2 closed for + // the N_IDENT-local arm below). Same funnel: the + // REGISTERED name keeps every consumer's re-lookup + // hitting. Base spellings: structlookup hits at the + // chain entry and si.sname == the literal's own name — + // same string out, same asm. + let si: *structinfo = structlookupchain(c, trefn); + if (si != nil) { return si.sname; }; }; return empty; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 108d10da..77efff7d 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1757,11 +1757,19 @@ fn structabisize(si: *structinfo) i32 = { // 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; }; + // #92: a struct LITERAL's type ref parses as N_IDENT (expression + // position, lib/ww/parse/expr.ww) where type specs parse N_TNAME + // — both carry the name in .str. Accept both at the ENTRY only: + // iterations past the first walk aliaslookup results, which are + // N_TNAME by the loop's own reassignment gate. Consumer census + // (commit body): no existing caller can pass N_IDENT. + if (tn.kind != nkind.N_TNAME && tn.kind != nkind.N_IDENT) { 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) { + for (cur != nil + && (cur.kind == nkind.N_TNAME || cur.kind == nkind.N_IDENT) + && si == nil) { let aliased: *node = aliaslookup(c, cur.str); if (aliased == nil) { cur = nil; } else { @@ -3145,13 +3153,16 @@ fn rhsstructpayload(c: *cgen, src: *node) str = { if (src.kind == nkind.N_STRUCTLIT) { let trefn: *node = src.lhs; if (trefn != nil) { - let nm: str; - nm.ptr = nil; nm.len = 0; - if (trefn.kind == nkind.N_IDENT) { nm = trefn.str; }; - if (trefn.kind == nkind.N_TNAME) { nm = trefn.str; }; - if (nm.len > 0) { - if (structlookup(c, nm) != nil) { return nm; }; - }; + // #92: bare structlookup missed an alias-named literal + // — `ali{...}` into a union fell to the scalar widen + // arm, word0-only payload (the class #62 L2 closed for + // the N_IDENT-local arm below). Same funnel: the + // REGISTERED name keeps every consumer's re-lookup + // hitting. Base spellings: structlookup hits at the + // chain entry and si.sname == the literal's own name — + // same string out, same asm. + let si: *structinfo = structlookupchain(c, trefn); + if (si != nil) { return si.sname; }; }; return empty; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 61514535..8ef7b5df 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -17829,11 +17829,19 @@ fn structabisize(si: *structinfo) i32 = { // 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; }; + // #92: a struct LITERAL's type ref parses as N_IDENT (expression + // position, lib/ww/parse/expr.ww) where type specs parse N_TNAME + // — both carry the name in .str. Accept both at the ENTRY only: + // iterations past the first walk aliaslookup results, which are + // N_TNAME by the loop's own reassignment gate. Consumer census + // (commit body): no existing caller can pass N_IDENT. + if (tn.kind != nkind.N_TNAME && tn.kind != nkind.N_IDENT) { 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) { + for (cur != nil + && (cur.kind == nkind.N_TNAME || cur.kind == nkind.N_IDENT) + && si == nil) { let aliased: *node = aliaslookup(c, cur.str); if (aliased == nil) { cur = nil; } else { @@ -19217,13 +19225,16 @@ fn rhsstructpayload(c: *cgen, src: *node) str = { if (src.kind == nkind.N_STRUCTLIT) { let trefn: *node = src.lhs; if (trefn != nil) { - let nm: str; - nm.ptr = nil; nm.len = 0; - if (trefn.kind == nkind.N_IDENT) { nm = trefn.str; }; - if (trefn.kind == nkind.N_TNAME) { nm = trefn.str; }; - if (nm.len > 0) { - if (structlookup(c, nm) != nil) { return nm; }; - }; + // #92: bare structlookup missed an alias-named literal + // — `ali{...}` into a union fell to the scalar widen + // arm, word0-only payload (the class #62 L2 closed for + // the N_IDENT-local arm below). Same funnel: the + // REGISTERED name keeps every consumer's re-lookup + // hitting. Base spellings: structlookup hits at the + // chain entry and si.sname == the literal's own name — + // same string out, same asm. + let si: *structinfo = structlookupchain(c, trefn); + if (si != nil) { return si.sname; }; }; return empty; }; diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c index 95e93859..c73ecd5e 100644 --- a/test/wcc/944_alias_accept_run.c +++ b/test/wcc/944_alias_accept_run.c @@ -48,6 +48,11 @@ * | word0-only; cs half c138605, ww half | * | F2a batch-2 c2 — graduated K_RUN; | * | base ctl + #54-bound `as` row loud | 0 + * union_slit_* | task #92 (F2a batch-3 c3): alias- | + * | named struct LITERAL into a union — | + * | rhsstructpayload N_STRUCTLIT arm | + * | routed through structlookupchain; | + * | base ctl + 1/2-level + decl order | 0 * nested_alias_field_* | task #71: alias-typed nested-field | * | store/read/addr-of walks fold to | * | direct offsets (byte-id graduation, | @@ -753,6 +758,78 @@ static const struct row rows[] = { " };\n" " return 0;\n" "};\n", 0, K_RUN, NULL }, + /* ---- F2a batch-3 c3 (task #92, reviewer-B2 find): the same + * rhsstructpayload widen, sourced from a struct LITERAL instead + * of a local ident — the N_STRUCTLIT arm still did bare + * structlookup, so `ali{...}` into a union fell to the scalar + * widen arm (cs 0 / ww EXIT 1, byte-id NO, pre-existing on + * master at 486f7f8). Graduated by routing the arm through + * structlookupchain (entry gate widened to the literal's N_IDENT + * type-ref leaf — consumer census in the commit body). Values + * distinct per word, LAST word checked. The alias struct-LIT + * LET-INIT zero-fill (task #63) does NOT green at this site — + * probed post-c3, documented there, deliberately no row here. */ + { "union_slit_base_ctl", + "package main;\n" + "type base = struct { a: size, b: size };\n" + "export fn main() i32 = {\n" + " let v: (void | base) = base{a=4000, b=9000};\n" + " match (v) {\n" + " case let s: base => {\n" + " if (s.a != 4000) { return 1; };\n" + " if (s.b != 9000) { return 2; };\n" + " };\n" + " case void => { return 3; };\n" + " };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "union_slit_alias", + "package main;\n" + "type base = struct { a: size, b: size };\n" + "type ali = base;\n" + "export fn main() i32 = {\n" + " let v: (void | ali) = ali{a=4000, b=9000};\n" + " match (v) {\n" + " case let s: ali => {\n" + " if (s.a != 4000) { return 1; };\n" + " if (s.b != 9000) { return 2; };\n" + " };\n" + " case void => { return 3; };\n" + " };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* 2-level: the chain walk (structlookupchain's alias loop). */ + { "union_slit_alias2", + "package main;\n" + "type base = struct { a: size, b: size };\n" + "type ali = base;\n" + "type ali2 = ali;\n" + "export fn main() i32 = {\n" + " let v: (void | ali2) = ali2{a=4000, b=9000};\n" + " match (v) {\n" + " case let s: ali2 => {\n" + " if (s.a != 4000) { return 1; };\n" + " if (s.b != 9000) { return 2; };\n" + " };\n" + " case void => { return 3; };\n" + " };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "union_slit_order", + "package main;\n" + "export fn main() i32 = {\n" + " let v: (void | ali) = ali{a=4000, b=9000};\n" + " match (v) {\n" + " case let s: ali => {\n" + " if (s.a != 4000) { return 1; };\n" + " if (s.b != 9000) { return 2; };\n" + " };\n" + " case void => { return 3; };\n" + " };\n" + " return 0;\n" + "};\n" + "type ali = base;\n" + "type base = struct { a: size, b: size };\n", 0, K_RUN, NULL }, /* ---- F2a batch-2 c3/B1: exprprimresolved's N_DOT base walk was * a hand-rolled 2-peel — a 3-level alias base (or ptr-to-2-level) * left the cast-source width unknowable on wwstage only, so the