From 6971f57356d1cf42188e05d4294f0e96985fa61d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 24 May 2026 01:41:05 +0900 Subject: [PATCH] selfhost/cmd/wcc: collapse chained-dot offset-emit onto tinfo.fields + delete dotinnerstructptr (#70, #12) Both chained-*struct-field sites (cgdot read, cgassign store) read the inner- struct layout off the stamped inner-dot tinfo (peel NAMED->under, TY_PTR->.sub ->NAMED->under->TY_STRUCT) and walk tinfo.fields for offset + per-field type, replacing dotinnerstructptr's structinfo re-walk + structlookup. Type dispatch (str/slice/float, load/store op) keys on tfield.type_ via the existing *tinfo predicates; ft.slotsize reproduces the old fieldsize exactly (== size for all kinds reached here except struct/array). cstage parity: cgen.c:1653-1655 (read fl->offset), 2514/2541 (store f->offset). Behavior-preserving: a strict per-level gate (every chain dot must peel through a *struct, root must be N_IDENT && localfindnode != nil) reproduces dotinnerstructptr's EXACT locals-only-with-all-*struct-intermediates firing set -- a by-value intermediate dot bails to the identical pre-existing generic path. asm is byte-identical (990-997 hold; both the strict gate and the looser superset tested 134/134, confirming the corpus has no by-value-intermediate chain). Deliberately NOT widened past the old set: global-root and by-value- intermediate chained dots stay on their old paths -- the global case is a pre-existing SHARED base-eval defect (cstage mis-evals the global base as (BP)->segfault; wwstage no-stores), filed #27 to fix in both stages together. dotinnerstructptr deleted (0 callers). Offset now sources from tinfo.fields (natural layout) vs old structinfo (slot-padded); they coincide for flat structs (all reached here), with nested-by-value-struct divergence pre-existing and left to #57/#6/#7 territory. --- selfhost/cmd/w6c/main.combined.ww | 200 ++++++++++++++------------- selfhost/cmd/wcc/cgenexpr.ww | 148 ++++++++++++++------ selfhost/cmd/wcc/cgenutil.ww | 52 +------ selfhost/cmd/wwdump/main.combined.ww | 200 ++++++++++++++------------- 4 files changed, 321 insertions(+), 279 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index a7c3e03b..c150283d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = { // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof +// - index helpers: indexbaseesz, elemsizeof // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -11493,56 +11493,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = { return 8; }; -// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots -// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the -// struct that the chain dereferences to (i.e., for `r.sym` where -// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain -// doesn't resolve to a *struct. -// -// Used by the chained-DOT cgen path so `r.sym.val` knows the outer -// is a field of `lsym`. -fn dotinnerstructptr(c: *cgen, n: *node) *node = { - if (n == nil) { return nil; }; - if (n.kind != nkind.N_DOT) { return nil; }; - let base: *node = n.lhs; - let fld: str = n.str; - if (base == nil) { return nil; }; - - // Resolve base's struct tnode. - let baset: *node = nil; - if (base.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, base.str); - if (lc == nil) { return nil; }; - let tn: *node = lc.tnode; - if (tn == nil) { return nil; }; - // base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR). - if (tn.kind == nkind.N_TNAME) { baset = tn; }; - if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; }; - } else { if (base.kind == nkind.N_DOT) { - baset = dotinnerstructptr(c, base); - };}; - if (baset == nil) { return nil; }; - if (baset.kind != nkind.N_TNAME) { return nil; }; - - // Look up the struct, find the field, return the field's *struct. - let si: *structinfo = structlookup(c, baset.str); - if (si == nil) { return nil; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return nil; }; - if (ft.kind != nkind.N_TPTR) { return nil; }; - let inner: *node = ft.lhs; - if (inner == nil) { return nil; }; - if (inner.kind != nkind.N_TNAME) { return nil; }; - return inner; - }; - fi = fi.finext; - }; - return nil; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -15908,22 +15858,54 @@ fn cgdot(c: *cgen, n: *node) void = { // the field. (Showed up porting w6l/pass.ww.) if (lhs != nil) { if (lhs.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped lhs.type_ + // (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's + // structinfo walk. Gate is strict-equal to the deleted helper: + // fire only when the chain root is a LOCAL ident AND every dot + // in the chain resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field and bailed on a by- + // value-struct intermediate). Reproducing that exactly avoids + // an untested widening past cstage; a deliberate widen, if ever + // wanted, is a future task with its own probe. Global-root + // chains stay in their pre-existing shared base-eval breakage + // (filed #27), untouched here. + let croot: *node = lhs; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = lhs.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; cgexpr(c, lhs); // AX = ptr to inner struct // str field: load both halves. - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; @@ -15931,41 +15913,41 @@ fn cgdot(c: *cgen, n: *node) void = { // into (AX, BX, CX). AX is the *struct // base, so load .ptr (which targets // AX) LAST. - if (isslicetype(c, fi.tnode)) { + if (typeisslice(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); + emitdispreg((tf.offset + 16u64): i64, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; // f64/f32 chained field: route through X0. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", X0\n"); return; }; - let lop: str = fieldloadop(c, fi); + let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32); emitline("\t"); emitline(lop); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; // Chained `(ident).f1.f2` read where f1 is a struct-by-value @@ -18737,16 +18719,48 @@ fn cgassign(c: *cgen, n: *node) void = { let fld: str = lhs.str; if (base != nil) { if (base.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, base); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped + // base.type_ (peel *→struct) + tinfo.fields, + // replacing dotinnerstructptr's structinfo walk. + // Gate is strict-equal to the deleted helper: fire + // only when the chain root is a LOCAL ident AND every + // dot resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field, bailing on a + // by-value-struct intermediate). Reproducing that + // exactly avoids an untested widening past cstage. + // Global-root chains stay in their pre-existing shared + // base-eval breakage (filed #27). + let croot: *node = base; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = base.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; if (n.op == tkind.TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { // str rhs: AX=ptr, BX=len. // Stash both, then load // the struct ptr into CX @@ -18759,19 +18773,19 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); + emitdispreg(tf.offset: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); + emitdispreg((tf.offset + 8u64): i64, "CX"); emitline("\n"); return; }; // f64/f32 chained plain `=`: cgexpr rhs left value in // X0. Spill to stack so cgexpr(base) can use AX, then // reload and MOVSD/MOVSS into the slot. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); @@ -18786,7 +18800,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; @@ -18795,19 +18809,19 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(c, fi); + let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 1c0acfdd..d91ed9b0 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2015,22 +2015,54 @@ fn cgdot(c: *cgen, n: *node) void = { // the field. (Showed up porting w6l/pass.ww.) if (lhs != nil) { if (lhs.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped lhs.type_ + // (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's + // structinfo walk. Gate is strict-equal to the deleted helper: + // fire only when the chain root is a LOCAL ident AND every dot + // in the chain resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field and bailed on a by- + // value-struct intermediate). Reproducing that exactly avoids + // an untested widening past cstage; a deliberate widen, if ever + // wanted, is a future task with its own probe. Global-root + // chains stay in their pre-existing shared base-eval breakage + // (filed #27), untouched here. + let croot: *node = lhs; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = lhs.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; cgexpr(c, lhs); // AX = ptr to inner struct // str field: load both halves. - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; @@ -2038,41 +2070,41 @@ fn cgdot(c: *cgen, n: *node) void = { // into (AX, BX, CX). AX is the *struct // base, so load .ptr (which targets // AX) LAST. - if (isslicetype(c, fi.tnode)) { + if (typeisslice(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); + emitdispreg((tf.offset + 16u64): i64, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; // f64/f32 chained field: route through X0. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", X0\n"); return; }; - let lop: str = fieldloadop(c, fi); + let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32); emitline("\t"); emitline(lop); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; // Chained `(ident).f1.f2` read where f1 is a struct-by-value @@ -4844,16 +4876,48 @@ fn cgassign(c: *cgen, n: *node) void = { let fld: str = lhs.str; if (base != nil) { if (base.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, base); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped + // base.type_ (peel *→struct) + tinfo.fields, + // replacing dotinnerstructptr's structinfo walk. + // Gate is strict-equal to the deleted helper: fire + // only when the chain root is a LOCAL ident AND every + // dot resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field, bailing on a + // by-value-struct intermediate). Reproducing that + // exactly avoids an untested widening past cstage. + // Global-root chains stay in their pre-existing shared + // base-eval breakage (filed #27). + let croot: *node = base; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = base.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; if (n.op == tkind.TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { // str rhs: AX=ptr, BX=len. // Stash both, then load // the struct ptr into CX @@ -4866,19 +4930,19 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); + emitdispreg(tf.offset: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); + emitdispreg((tf.offset + 8u64): i64, "CX"); emitline("\n"); return; }; // f64/f32 chained plain `=`: cgexpr rhs left value in // X0. Spill to stack so cgexpr(base) can use AX, then // reload and MOVSD/MOVSS into the slot. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); @@ -4893,7 +4957,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; @@ -4902,19 +4966,19 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(c, fi); + let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 73720e1e..ede21099 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -4,7 +4,7 @@ // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof +// - index helpers: indexbaseesz, elemsizeof // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -948,56 +948,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = { return 8; }; -// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots -// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the -// struct that the chain dereferences to (i.e., for `r.sym` where -// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain -// doesn't resolve to a *struct. -// -// Used by the chained-DOT cgen path so `r.sym.val` knows the outer -// is a field of `lsym`. -fn dotinnerstructptr(c: *cgen, n: *node) *node = { - if (n == nil) { return nil; }; - if (n.kind != nkind.N_DOT) { return nil; }; - let base: *node = n.lhs; - let fld: str = n.str; - if (base == nil) { return nil; }; - - // Resolve base's struct tnode. - let baset: *node = nil; - if (base.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, base.str); - if (lc == nil) { return nil; }; - let tn: *node = lc.tnode; - if (tn == nil) { return nil; }; - // base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR). - if (tn.kind == nkind.N_TNAME) { baset = tn; }; - if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; }; - } else { if (base.kind == nkind.N_DOT) { - baset = dotinnerstructptr(c, base); - };}; - if (baset == nil) { return nil; }; - if (baset.kind != nkind.N_TNAME) { return nil; }; - - // Look up the struct, find the field, return the field's *struct. - let si: *structinfo = structlookup(c, baset.str); - if (si == nil) { return nil; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return nil; }; - if (ft.kind != nkind.N_TPTR) { return nil; }; - let inner: *node = ft.lhs; - if (inner == nil) { return nil; }; - if (inner.kind != nkind.N_TNAME) { return nil; }; - return inner; - }; - fi = fi.finext; - }; - return nil; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e48e4593..ef1d1d73 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = { // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof +// - index helpers: indexbaseesz, elemsizeof // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -11493,56 +11493,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = { return 8; }; -// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots -// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the -// struct that the chain dereferences to (i.e., for `r.sym` where -// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain -// doesn't resolve to a *struct. -// -// Used by the chained-DOT cgen path so `r.sym.val` knows the outer -// is a field of `lsym`. -fn dotinnerstructptr(c: *cgen, n: *node) *node = { - if (n == nil) { return nil; }; - if (n.kind != nkind.N_DOT) { return nil; }; - let base: *node = n.lhs; - let fld: str = n.str; - if (base == nil) { return nil; }; - - // Resolve base's struct tnode. - let baset: *node = nil; - if (base.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, base.str); - if (lc == nil) { return nil; }; - let tn: *node = lc.tnode; - if (tn == nil) { return nil; }; - // base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR). - if (tn.kind == nkind.N_TNAME) { baset = tn; }; - if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; }; - } else { if (base.kind == nkind.N_DOT) { - baset = dotinnerstructptr(c, base); - };}; - if (baset == nil) { return nil; }; - if (baset.kind != nkind.N_TNAME) { return nil; }; - - // Look up the struct, find the field, return the field's *struct. - let si: *structinfo = structlookup(c, baset.str); - if (si == nil) { return nil; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return nil; }; - if (ft.kind != nkind.N_TPTR) { return nil; }; - let inner: *node = ft.lhs; - if (inner == nil) { return nil; }; - if (inner.kind != nkind.N_TNAME) { return nil; }; - return inner; - }; - fi = fi.finext; - }; - return nil; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -15908,22 +15858,54 @@ fn cgdot(c: *cgen, n: *node) void = { // the field. (Showed up porting w6l/pass.ww.) if (lhs != nil) { if (lhs.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped lhs.type_ + // (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's + // structinfo walk. Gate is strict-equal to the deleted helper: + // fire only when the chain root is a LOCAL ident AND every dot + // in the chain resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field and bailed on a by- + // value-struct intermediate). Reproducing that exactly avoids + // an untested widening past cstage; a deliberate widen, if ever + // wanted, is a future task with its own probe. Global-root + // chains stay in their pre-existing shared base-eval breakage + // (filed #27), untouched here. + let croot: *node = lhs; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = lhs.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; cgexpr(c, lhs); // AX = ptr to inner struct // str field: load both halves. - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; @@ -15931,41 +15913,41 @@ fn cgdot(c: *cgen, n: *node) void = { // into (AX, BX, CX). AX is the *struct // base, so load .ptr (which targets // AX) LAST. - if (isslicetype(c, fi.tnode)) { + if (typeisslice(ft)) { emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); + emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); + emitdispreg((tf.offset + 16u64): i64, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; // f64/f32 chained field: route through X0. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", X0\n"); return; }; - let lop: str = fieldloadop(c, fi); + let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32); emitline("\t"); emitline(lop); emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); + emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; // Chained `(ident).f1.f2` read where f1 is a struct-by-value @@ -18737,16 +18719,48 @@ fn cgassign(c: *cgen, n: *node) void = { let fld: str = lhs.str; if (base != nil) { if (base.kind == nkind.N_DOT) { - let innert: *node = dotinnerstructptr(c, base); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { + // #70 (#12): inner-struct layout via the stamped + // base.type_ (peel *→struct) + tinfo.fields, + // replacing dotinnerstructptr's structinfo walk. + // Gate is strict-equal to the deleted helper: fire + // only when the chain root is a LOCAL ident AND every + // dot resolves through a *struct (dotinnerstructptr + // recursed per level on a *struct field, bailing on a + // by-value-struct intermediate). Reproducing that + // exactly avoids an untested widening past cstage. + // Global-root chains stay in their pre-existing shared + // base-eval breakage (filed #27). + let croot: *node = base; + let allptr: bool = true; + for (croot != nil && croot.kind == nkind.N_DOT) { + let ct: *tinfo = croot.type_: *tinfo; + for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; }; + let okp: bool = false; + if (ct != nil) { if (ct.kind == tykind.TY_PTR) { + let cs: *tinfo = ct.sub; + for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; }; + if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; }; + }; }; + if (!okp) { allptr = false; }; + croot = croot.lhs; + }; + let it: *tinfo = nil; + if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) { + if (localfindnode(c, croot.str) != nil) { + it = base.type_: *tinfo; + }; + }; }; }; + for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; }; + if (it != nil) { if (it.kind == tykind.TY_PTR) { + let st: *tinfo = it.sub; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + let tf: *tfield = st.fields; + for (tf != nil) { + if (streq(tf.name, fld)) { + let ft: *tinfo = tf.type_; if (n.op == tkind.TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { + if (typeisstr(ft)) { // str rhs: AX=ptr, BX=len. // Stash both, then load // the struct ptr into CX @@ -18759,19 +18773,19 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); + emitdispreg(tf.offset: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); + emitdispreg((tf.offset + 8u64): i64, "CX"); emitline("\n"); return; }; // f64/f32 chained plain `=`: cgexpr rhs left value in // X0. Spill to stack so cgexpr(base) can use AX, then // reload and MOVSD/MOVSS into the slot. - if (isfloattype(c, fi.tnode)) { + if (typeisfloat(ft)) { let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + if (typeisf32(ft)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); @@ -18786,7 +18800,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; @@ -18795,19 +18809,19 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(c, fi); + let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); + emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; }; - fi = fi.finext; + tf = tf.tnext; }; - }; - }; + }; }; + }; }; }; }; };