w6c+selfhost: cgen chained N_DOT/N_ASSIGN spine walk

Loop-shaped spine walker for value-struct chains (o.i.a) and slice/str
pseudo-fields (s.buf.len), read+write, both stages. SB-fallback at the
catch-all preserved for unresolved module-qualified idents.

Follow-ups filed: tasks #7-#10 (wwstage >6-arg frame over-alloc, chained
array-elem field BX loss, & through chained DOT, signed sub-word field
loads zero-extend).
This commit is contained in:
2026-05-13 21:44:50 +09:00
parent 036b2c851f
commit 461a448d5f
7 changed files with 1593 additions and 1 deletions

View File

@@ -1543,6 +1543,99 @@ fn cgdot(c: *cgen, n: *node) void = {
return;
};
};
// Chained N_DOT spine through value-struct fields (any depth).
// Walks the spine to a root ident, summing field offsets, then
// emits ONE load at base + total_off. Also handles a slice/str
// pseudo-field leaf (`b.buf.len`): the walk lands on the slice/
// str header and slicedelta picks ptr/len/cap. Mirror of cstage
// cgen.c's chained-DOT read branch. Without this, depth ≥ 3
// shapes (`v.a.a.a`) and `b.buf.len` fall through to the non-
// ident-base pseudo branch below — which would cgexpr the inner
// (loading only .ptr into AX) and shuffle stale BX into AX.
// Placed BEFORE the .ptr/.len fast paths so the chain wins.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let r: dotchain;
let pok: bool = dotchainresolve(c, n, &r);
if (pok) {
if (r.slicedelta >= 0i64) {
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\tMOVQ\t");
emitdispreg(r.totaloff + r.slicedelta, "CX");
emitline(", AX\n");
} else {
emitline("\tMOVQ\t");
emitoff(r.rootoff + r.totaloff + r.slicedelta);
emitline("(BP), AX\n");
};
return;
};
if (isstrtype(c, r.leaffi.tnode)) {
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\tMOVQ\t");
emitdispreg(r.totaloff + 0i64, "CX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg(r.totaloff + 8i64, "CX");
emitline(", BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(r.rootoff + r.totaloff);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff(r.rootoff + r.totaloff + 8i64);
emitline("(BP), BX\n");
};
return;
};
if (isfloattype(c, r.leaffi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(r.totaloff, "CX");
emitline(", X0\n");
} else {
emitline("\t");
emitline(mov);
emitline("\t");
emitoff(r.rootoff + r.totaloff);
emitline("(BP), X0\n");
};
return;
};
let lop: str = fieldloadop(r.leaffi);
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(r.totaloff, "CX");
emitline(", AX\n");
} else {
emitline("\t");
emitline(lop);
emitline("\t");
emitoff(r.rootoff + r.totaloff);
emitline("(BP), AX\n");
};
return;
};
};
};
// Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`.
// Evaluate the str-producing expression — that leaves
// (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len`
@@ -1612,6 +1705,9 @@ fn cgdot(c: *cgen, n: *node) void = {
// field. Mirror of the cgassign branch added for the same shape.
// Without this, `L.cur.kind` (cur a by-value struct of *L)
// falls into the SB-fallback and emits `MOVQ kind(SB), AX`.
// Kept as a fallback below the generalized walker above (placed
// earlier in cgdot) to preserve byte-identical output on shapes
// it already handles.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let inner: *node = lhs.lhs;
@@ -3377,6 +3473,101 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// Chained N_DOT spine write through value-struct fields (any
// depth) — `o.i.a = 10`, `v.a.b.c = …`. Also handles a slice/str
// pseudo-field leaf (`b.buf.len = 5`). Mirror of cstage cgen.c's
// chained-DOT write branch. Without this, depth ≥ 3 writes and
// the slice/str pseudo-field write through a value-struct chain
// silently emit no store. Only plain `=` is wired.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_DOT
&& n.op == tkind.TK_ASSIGN) {
let r: dotchain;
let yok: bool = dotchainresolve(c, lhs, &r);
if (yok) {
if (r.slicedelta >= 0i64) {
cgexpr(c, n.rhs);
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(r.totaloff + r.slicedelta, "CX");
emitline("\n");
} else {
emitline("\tMOVQ\tAX, ");
emitoff(r.rootoff + r.totaloff + r.slicedelta);
emitline("(BP)\n");
};
return;
};
if (isstrtype(c, r.leaffi.tnode)) {
cgexpr(c, n.rhs);
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(r.totaloff + 0i64, "CX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg(r.totaloff + 8i64, "CX");
emitline("\n");
} else {
emitline("\tMOVQ\tAX, ");
emitoff(r.rootoff + r.totaloff);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff(r.rootoff + r.totaloff + 8i64);
emitline("(BP)\n");
};
return;
};
if (isfloattype(c, r.leaffi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
cgexpr(c, n.rhs);
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(r.totaloff, "CX");
emitline("\n");
} else {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff(r.rootoff + r.totaloff);
emitline("(BP)\n");
};
return;
};
let sop: str = fieldstoreop(r.leaffi);
cgexpr(c, n.rhs);
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(r.totaloff, "CX");
emitline("\n");
} else {
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff(r.rootoff + r.totaloff);
emitline("(BP)\n");
};
return;
};
};
};
// Chained `(ident).f1.f2 = v` where f1 is a struct-by-value
// field. The earlier chained-DOT branch handles f1: *T (deref
// then store). This handles f1: T (in-place sub-struct), which
@@ -3384,6 +3575,8 @@ fn cgassign(c: *cgen, n: *node) void = {
// to flatten `cur.kind`/`cur.ival`/... into top-level fields to
// work around it. Only plain `=` is wired; compound on a by-
// value sub-field hasn't surfaced.
// Kept as fallback below the generalized walker for any shape
// the walker doesn't recognize.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let base: *node = lhs.lhs;