w6c+selfhost: TK_AMP through chained N_DOT (closes #9)

Three shapes handled: value-struct chains (&o.i.a), pointer-field
(&p.f), slice/str pseudo-fields (&s.len). cstage inlines #6's spine
walker; wwstage reuses dotchainresolve unchanged. Silent-drop fallback
preserved.

Slice-header width mismatch in *&s.len writes filed as task #13.
This commit is contained in:
2026-05-13 23:17:15 +09:00
parent 5b098c4a99
commit d053560b80
6 changed files with 865 additions and 0 deletions

View File

@@ -1868,6 +1868,169 @@ fn cgun(c: *cgen, n: *node) void = {
};
return;
};
// Address-of through a DOT chain. Mirror of cstage
// cgen.c TK_AMP N_DOT branch. Three shapes converge
// here, all returning an 8B address (no fldloadop —
// just LEAQ / MOVQ+LEAQ).
//
// 1. Value-struct fields, any depth (`&o.f`,
// `&o.i.a`, `&o.a.b.c`) and slice/str pseudo-field
// tail (`&s.len`, `&b.buf.len`): the chained
// (depth ≥ 2) case reuses dotchainresolve; the
// single-DOT case is handled below by inspecting
// the IDENT base's tnode. Byte-identical to the
// cstage spine walker for both depths.
// 2. Pointer-field (`&p.f` where p:*T): single-DOT
// only; spine walker aborts on the *T base. Load
// p into AX, then LEAQ field_off(AX), AX. Mirror
// of the read at cgdot 1144.
if (opnd.kind == nkind.N_DOT) {
// Shape 1 chained: depth-≥2 via dotchainresolve.
// `opnd.lhs.kind == N_DOT` gates the helper at
// nsteps ≥ 2 (matches the read path's gate).
if (opnd.lhs != nil) {
if (opnd.lhs.kind == nkind.N_DOT) {
let r: dotchain;
let pok: bool = dotchainresolve(c, opnd, &r);
if (pok) {
let extra: i64 = 0i64;
if (r.slicedelta >= 0i64) { extra = r.slicedelta; };
if (r.isglobal) {
emitline("\tLEAQ\t");
emitsymname(c, r.rootname);
emitline("(SB), CX\n");
emitline("\tLEAQ\t");
emitdispreg(r.totaloff + extra, "CX");
emitline(", AX\n");
} else {
emitline("\tLEAQ\t");
emitoff(r.rootoff + r.totaloff + extra);
emitline("(BP), AX\n");
};
return;
};
};
};
// Shape 1/2 single-DOT on an IDENT base. Inspect
// the base's tnode to pick value-struct vs slice/
// str pseudo vs pointer-field.
if (opnd.lhs != nil) {
if (opnd.lhs.kind == nkind.N_IDENT) {
let basenm: str = opnd.lhs.str;
let fld: str = opnd.str;
let lc: *local = localfindnode(c, basenm);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
// Pointer-field: &p.f where p:*T.
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (inner != nil) {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), AX\n");
emitline("\tLEAQ\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
fi = fi.finext;
};
};
};
};
// Value-struct local: &o.f.
if (lkind == nkind.N_TNAME) {
let sname: str = tn.str;
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
emitline("\tLEAQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
return;
};
fi = fi.finext;
};
};
};
// Slice/str pseudo-field on a local:
// &s.ptr / &s.len / &s.cap. Delta is
// 0/8/16 — matches the spine walker.
let delta: i32 = -1;
if (streq(fld, "ptr")) { delta = 0; };
if (streq(fld, "len")) { delta = 8; };
if (streq(fld, "cap")) { delta = 16; };
if (delta >= 0) {
let isslor: bool = false;
if (lkind == nkind.N_TSLICE) { isslor = true; };
if (lkind == nkind.N_TNAME) {
if (streq(tn.str, "str")) { isslor = true; };
};
if (isslor) {
emitline("\tLEAQ\t");
emitoff((lc.off + delta): i64);
emitline("(BP), AX\n");
return;
};
};
};
// Global root: top-level let, either a
// struct or a slice/str.
if (isletvar(c, basenm)) {
let gsi: *structinfo = letvarstructinfo(c, basenm);
if (gsi != nil) {
let fi: *fieldinfo = gsi.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
emitline("\tLEAQ\t");
emitsymname(c, basenm);
emitline("(SB), CX\n");
emitline("\tLEAQ\t");
emitdispreg(fi.foff: i64, "CX");
emitline(", AX\n");
return;
};
fi = fi.finext;
};
};
let isstr: bool = letvarisstr(c, basenm);
let issl: bool = letvarisslice(c, basenm);
if (isstr || issl) {
let gdelta: i32 = -1;
if (streq(fld, "ptr")) { gdelta = 0; };
if (streq(fld, "len")) { gdelta = 8; };
if (issl) { if (streq(fld, "cap")) { gdelta = 16; }; };
if (gdelta >= 0) {
emitline("\tLEAQ\t");
emitsymname(c, basenm);
emitline("(SB), CX\n");
emitline("\tLEAQ\t");
emitdispreg(gdelta: i64, "CX");
emitline(", AX\n");
return;
};
};
};
};
};
// Fall through silently (mirrors cstage silent-
// drop fallback at the end of the TK_AMP block).
return;
};
if (opnd.kind == nkind.N_INDEX) {
// &base[i] = base + i*esz, no dereference.
let base: *node = opnd.lhs;