From 827a05e5489987b4179a1bd78b6b578dfda1d53b Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 23 May 2026 16:10:46 +0900 Subject: [PATCH] selfhost/cmd/wcc/cgenutil: exprprimresolved N_DOT off dotfieldtnode (#59) exprprimresolved's N_DOT arm derived a struct field's prim width+sign by walking the field's declared *node (dotfieldtnode + typenodeprimresolved alias recursion). Read the checker-stamped tinfo instead, mirroring cstage castsrcprim N_DOT (cmd/w6c/cgen.c:323-344): the base's n.lhs.type_ must resolve to a struct (peel NAMED->PTR->NAMED, require TY_STRUCT) before the field counts -- excluding pseudo-fields .len/.cap/.ptr (stamped i32/*T at check.ww:1987-2004; their base is TY_SLICE/TY_STR/TY_ARRAY, never TY_STRUCT, so the guard yields sz=0) and tuple positionals, which stay sz=0 to preserve 995 byte-id. The field's width/sign comes from the N_DOT's own n.type_ (check.ww:2012), one NAMED peel then typeisint?size:0. Bool exclusion is now free via typeisint(bool)=false, dropping the old streq("bool") arm. Polarity DOWN per rule 10: cstage castsrcprim already reads the stamped type. typenamed() has zero callers in wwstage (tinfofornode collapses all alias depth to the body), so no tinfo carries kind TY_NAMED -- the NAMED peels are dead/inert, output matches cstage. Removes one dotfieldtnode caller (3->2; remaining cgenutil 1073, 1871). typenodeprimresolved retained (3 callers: cgenutil 1459/1465, cgenexpr 431). Part of the dotfield* consumer-migration arc. make test 133/133 (quiescent tree, byte-id 990-997 green). Coverage: 710_cast_enum_movl rows struct_field_rt + pseudo_field_clamp + bool_to_i8. --- selfhost/cmd/w6c/main.combined.ww | 26 ++++++++++++++++++++++++-- selfhost/cmd/wcc/cgenutil.ww | 26 ++++++++++++++++++++++++-- selfhost/cmd/wwdump/main.combined.ww | 26 ++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b2e28622..cdd1e12d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -11911,8 +11911,30 @@ export fn exprprimresolved(c: *cgen, n: *node, return; }; if (k == nkind.N_DOT) { - typenodeprimresolved(c, dotfieldtnode(c, n), - sz_out, unsigned_out); + // #59: read the checker-stamped tinfo instead of re-deriving the + // field type via dotfieldtnode's structinfo walk. Mirrors cstage + // castsrcprim N_DOT (cmd/w6c/cgen.c:323-344): the base must + // resolve to a struct (or ptr-to-struct) before the field type + // counts. That guard excludes pseudo-fields .len/.cap/.ptr (the + // checker stamps them i32/*T at check.ww:1987-2004) and tuple + // positionals, keeping them at sz=0 — asymmetry there breaks 995 + // byte-id (cgen.c:286-290). The field's own width/sign is the + // N_DOT's stamped type_ (check.ww:2012). One TY_NAMED peel, then + // typeisint ? size : 0; bool falls out because typeisint(bool) is + // false — the same exclusion the old streq("bool") arm encoded. + let bu: *tinfo = nil; + if (n.lhs != nil) { bu = n.lhs.type_: *tinfo; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_STRUCT) { + let u: *tinfo = n.type_: *tinfo; + if (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (typeisint(u)) { + *sz_out = u.size: i32; + *unsigned_out = typeisunsigned(u); + }; + }; return; }; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index debc152d..56e5ef63 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1470,8 +1470,30 @@ export fn exprprimresolved(c: *cgen, n: *node, return; }; if (k == nkind.N_DOT) { - typenodeprimresolved(c, dotfieldtnode(c, n), - sz_out, unsigned_out); + // #59: read the checker-stamped tinfo instead of re-deriving the + // field type via dotfieldtnode's structinfo walk. Mirrors cstage + // castsrcprim N_DOT (cmd/w6c/cgen.c:323-344): the base must + // resolve to a struct (or ptr-to-struct) before the field type + // counts. That guard excludes pseudo-fields .len/.cap/.ptr (the + // checker stamps them i32/*T at check.ww:1987-2004) and tuple + // positionals, keeping them at sz=0 — asymmetry there breaks 995 + // byte-id (cgen.c:286-290). The field's own width/sign is the + // N_DOT's stamped type_ (check.ww:2012). One TY_NAMED peel, then + // typeisint ? size : 0; bool falls out because typeisint(bool) is + // false — the same exclusion the old streq("bool") arm encoded. + let bu: *tinfo = nil; + if (n.lhs != nil) { bu = n.lhs.type_: *tinfo; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_STRUCT) { + let u: *tinfo = n.type_: *tinfo; + if (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (typeisint(u)) { + *sz_out = u.size: i32; + *unsigned_out = typeisunsigned(u); + }; + }; return; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index f433b852..44829cf5 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -11911,8 +11911,30 @@ export fn exprprimresolved(c: *cgen, n: *node, return; }; if (k == nkind.N_DOT) { - typenodeprimresolved(c, dotfieldtnode(c, n), - sz_out, unsigned_out); + // #59: read the checker-stamped tinfo instead of re-deriving the + // field type via dotfieldtnode's structinfo walk. Mirrors cstage + // castsrcprim N_DOT (cmd/w6c/cgen.c:323-344): the base must + // resolve to a struct (or ptr-to-struct) before the field type + // counts. That guard excludes pseudo-fields .len/.cap/.ptr (the + // checker stamps them i32/*T at check.ww:1987-2004) and tuple + // positionals, keeping them at sz=0 — asymmetry there breaks 995 + // byte-id (cgen.c:286-290). The field's own width/sign is the + // N_DOT's stamped type_ (check.ww:2012). One TY_NAMED peel, then + // typeisint ? size : 0; bool falls out because typeisint(bool) is + // false — the same exclusion the old streq("bool") arm encoded. + let bu: *tinfo = nil; + if (n.lhs != nil) { bu = n.lhs.type_: *tinfo; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_STRUCT) { + let u: *tinfo = n.type_: *tinfo; + if (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (typeisint(u)) { + *sz_out = u.size: i32; + *unsigned_out = typeisunsigned(u); + }; + }; return; }; };