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.
This commit is contained in:
2026-05-24 01:41:05 +09:00
parent e5092709e4
commit 6971f57356
4 changed files with 321 additions and 279 deletions

View File

@@ -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;
};
};
};
}; };
}; };
};
};
};

View File

@@ -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;
};
};
};
}; };
}; };
};
};
};

View File

@@ -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).

View File

@@ -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;
};
};
};
}; };
}; };
};
};
};