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:
@@ -7778,6 +7778,175 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
||||
return;
|
||||
};
|
||||
|
||||
// dotchain — packed result struct for dotchainresolve. Out-params are
|
||||
// bundled to keep the helper at <= 6 register-passed args; wwstage's
|
||||
// per-fn arg-frame computation over-allocates by 16 bytes for any
|
||||
// function with > 6 args (task #7, a pre-existing quirk independent
|
||||
// of this fix), which would silently break the bootstrap fixed-point
|
||||
// gate (993 / 994 / 995).
|
||||
//
|
||||
// Numeric fields are all i64, not i32. wwstage zero-inits an i32
|
||||
// local with MOVQ (8-byte store) but subsequent `out.totaloff = …`
|
||||
// updates would emit MOVL (4-byte store), leaving the upper 4 bytes
|
||||
// stale from the wider init. Keeping the out-params at i64 makes the
|
||||
// init width and the update width agree, so the field reads back
|
||||
// what was written across both stages.
|
||||
type dotchain = struct {
|
||||
rootname: str,
|
||||
rootoff: i64,
|
||||
totaloff: i64,
|
||||
leaffi: *fieldinfo,
|
||||
slicedelta: i64,
|
||||
isglobal: bool,
|
||||
};
|
||||
|
||||
// Spine-walk a chained N_DOT (n) inward to a root ident, summing field
|
||||
// offsets through value-struct intermediates. Optional slice/str leaf
|
||||
// pseudo-field (.ptr / .len / .cap) on the last segment is folded into
|
||||
// `out.slicedelta` (0/8/16); otherwise out.leaffi is the leaf fieldinfo
|
||||
// and slicedelta stays -1. Returns true on success; on false the caller
|
||||
// falls through to other branches.
|
||||
//
|
||||
// Mirrors cmd/w6c/cgen.c's N_DOT chained walker; both stages must agree
|
||||
// on the same shapes so the bootstrap fixed-point holds. The chain
|
||||
// depth is capped at 16 — deeper chains are vanishingly rare and fall
|
||||
// through.
|
||||
//
|
||||
// On success the caller emits one load/store at root_base + out.totaloff
|
||||
// (+ slicedelta for pseudo leaf). Root resolves as: local frame slot
|
||||
// (out.rootoff != 0, isglobal false) or top-level let (isglobal true,
|
||||
// root accessed via LEAQ name(SB), CX).
|
||||
export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
out.rootname = "";
|
||||
out.rootoff = 0i64;
|
||||
out.isglobal = false;
|
||||
out.totaloff = 0i64;
|
||||
out.leaffi = nil;
|
||||
out.slicedelta = -1i64;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
// Walk inward, recording the N_DOT node at each step (leaf first).
|
||||
// We hold *node pointers (8B each, slotsize-stable across stages)
|
||||
// and read .str on demand — a [16]str array would mis-slot at
|
||||
// wwstage where slotsize("str") returns 8, breaking the bootstrap
|
||||
// fixed-point.
|
||||
let stk: [16]*node;
|
||||
let nsteps: i32 = 0;
|
||||
let cur: *node = n;
|
||||
for (cur != nil) {
|
||||
if (cur.kind != nkind.N_DOT) { break; };
|
||||
if (nsteps >= 16) { return false; };
|
||||
stk[nsteps] = cur;
|
||||
nsteps += 1;
|
||||
cur = cur.lhs;
|
||||
};
|
||||
if (nsteps < 2) { return false; };
|
||||
if (cur == nil) { return false; };
|
||||
if (cur.kind != nkind.N_IDENT) { return false; };
|
||||
out.rootname = cur.str;
|
||||
// Resolve the root's struct type and base.
|
||||
let rootstruct: str = "";
|
||||
let lc: *local = localfindnode(c, cur.str);
|
||||
let gsi: *structinfo = nil;
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
rootstruct = lc.tnode.str;
|
||||
out.rootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) { return false; };
|
||||
// Walk outward, resolving each field. stk is leaf-first; iterate
|
||||
// from i = nsteps - 1 (the root-most field) down to i = 0 (leaf).
|
||||
let curstruct: str = rootstruct;
|
||||
// Pre-declare per-iteration spills here so cstage / wwstage agree
|
||||
// on the frame layout. Both must emit byte-identical asm for the
|
||||
// bootstrap fixed-point (tests 993/995) — letting these locals get
|
||||
// declared inside the branch bodies trips a per-stage divergence in
|
||||
// slot counting.
|
||||
let stepnd: *node = nil;
|
||||
let stepnm: str = "";
|
||||
let fi: *fieldinfo = nil;
|
||||
let found: *fieldinfo = nil;
|
||||
let ft: *node = nil;
|
||||
let s0nd: *node = nil;
|
||||
let pseudo: str = "";
|
||||
let delta: i64 = 0i64;
|
||||
let i: i32 = nsteps - 1;
|
||||
for (i >= 0) {
|
||||
let csi: *structinfo = structlookup(c, curstruct);
|
||||
if (csi == nil) { return false; };
|
||||
// Materialise the *node first; wwstage's cgen mis-emits the
|
||||
// chained shape `stk[i].str` directly (task #8 — loses BX
|
||||
// between the index load and the field deref), so always
|
||||
// spill to an intermediate local before reading the str
|
||||
// field. The cstage emits the same pattern for byte-identity.
|
||||
stepnd = stk[i];
|
||||
if (stepnd == nil) { return false; };
|
||||
stepnm = stepnd.str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, stepnm)) { found = fi; break; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
if (found == nil) { return false; };
|
||||
if (i == 0) {
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.leaffi = found;
|
||||
return true;
|
||||
};
|
||||
// Intermediate step. Must be a nested value-struct, OR a slice/
|
||||
// str field with the leaf (i == 1, stk[0]) as a pseudo-field.
|
||||
ft = found.tnode;
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind == nkind.N_TNAME) {
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}; };
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
||||
//
|
||||
@@ -9324,6 +9493,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`
|
||||
@@ -9393,6 +9655,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;
|
||||
@@ -11158,6 +11423,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
|
||||
@@ -11165,6 +11525,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2207,3 +2207,172 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// dotchain — packed result struct for dotchainresolve. Out-params are
|
||||
// bundled to keep the helper at <= 6 register-passed args; wwstage's
|
||||
// per-fn arg-frame computation over-allocates by 16 bytes for any
|
||||
// function with > 6 args (task #7, a pre-existing quirk independent
|
||||
// of this fix), which would silently break the bootstrap fixed-point
|
||||
// gate (993 / 994 / 995).
|
||||
//
|
||||
// Numeric fields are all i64, not i32. wwstage zero-inits an i32
|
||||
// local with MOVQ (8-byte store) but subsequent `out.totaloff = …`
|
||||
// updates would emit MOVL (4-byte store), leaving the upper 4 bytes
|
||||
// stale from the wider init. Keeping the out-params at i64 makes the
|
||||
// init width and the update width agree, so the field reads back
|
||||
// what was written across both stages.
|
||||
type dotchain = struct {
|
||||
rootname: str,
|
||||
rootoff: i64,
|
||||
totaloff: i64,
|
||||
leaffi: *fieldinfo,
|
||||
slicedelta: i64,
|
||||
isglobal: bool,
|
||||
};
|
||||
|
||||
// Spine-walk a chained N_DOT (n) inward to a root ident, summing field
|
||||
// offsets through value-struct intermediates. Optional slice/str leaf
|
||||
// pseudo-field (.ptr / .len / .cap) on the last segment is folded into
|
||||
// `out.slicedelta` (0/8/16); otherwise out.leaffi is the leaf fieldinfo
|
||||
// and slicedelta stays -1. Returns true on success; on false the caller
|
||||
// falls through to other branches.
|
||||
//
|
||||
// Mirrors cmd/w6c/cgen.c's N_DOT chained walker; both stages must agree
|
||||
// on the same shapes so the bootstrap fixed-point holds. The chain
|
||||
// depth is capped at 16 — deeper chains are vanishingly rare and fall
|
||||
// through.
|
||||
//
|
||||
// On success the caller emits one load/store at root_base + out.totaloff
|
||||
// (+ slicedelta for pseudo leaf). Root resolves as: local frame slot
|
||||
// (out.rootoff != 0, isglobal false) or top-level let (isglobal true,
|
||||
// root accessed via LEAQ name(SB), CX).
|
||||
export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
out.rootname = "";
|
||||
out.rootoff = 0i64;
|
||||
out.isglobal = false;
|
||||
out.totaloff = 0i64;
|
||||
out.leaffi = nil;
|
||||
out.slicedelta = -1i64;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
// Walk inward, recording the N_DOT node at each step (leaf first).
|
||||
// We hold *node pointers (8B each, slotsize-stable across stages)
|
||||
// and read .str on demand — a [16]str array would mis-slot at
|
||||
// wwstage where slotsize("str") returns 8, breaking the bootstrap
|
||||
// fixed-point.
|
||||
let stk: [16]*node;
|
||||
let nsteps: i32 = 0;
|
||||
let cur: *node = n;
|
||||
for (cur != nil) {
|
||||
if (cur.kind != nkind.N_DOT) { break; };
|
||||
if (nsteps >= 16) { return false; };
|
||||
stk[nsteps] = cur;
|
||||
nsteps += 1;
|
||||
cur = cur.lhs;
|
||||
};
|
||||
if (nsteps < 2) { return false; };
|
||||
if (cur == nil) { return false; };
|
||||
if (cur.kind != nkind.N_IDENT) { return false; };
|
||||
out.rootname = cur.str;
|
||||
// Resolve the root's struct type and base.
|
||||
let rootstruct: str = "";
|
||||
let lc: *local = localfindnode(c, cur.str);
|
||||
let gsi: *structinfo = nil;
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
rootstruct = lc.tnode.str;
|
||||
out.rootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) { return false; };
|
||||
// Walk outward, resolving each field. stk is leaf-first; iterate
|
||||
// from i = nsteps - 1 (the root-most field) down to i = 0 (leaf).
|
||||
let curstruct: str = rootstruct;
|
||||
// Pre-declare per-iteration spills here so cstage / wwstage agree
|
||||
// on the frame layout. Both must emit byte-identical asm for the
|
||||
// bootstrap fixed-point (tests 993/995) — letting these locals get
|
||||
// declared inside the branch bodies trips a per-stage divergence in
|
||||
// slot counting.
|
||||
let stepnd: *node = nil;
|
||||
let stepnm: str = "";
|
||||
let fi: *fieldinfo = nil;
|
||||
let found: *fieldinfo = nil;
|
||||
let ft: *node = nil;
|
||||
let s0nd: *node = nil;
|
||||
let pseudo: str = "";
|
||||
let delta: i64 = 0i64;
|
||||
let i: i32 = nsteps - 1;
|
||||
for (i >= 0) {
|
||||
let csi: *structinfo = structlookup(c, curstruct);
|
||||
if (csi == nil) { return false; };
|
||||
// Materialise the *node first; wwstage's cgen mis-emits the
|
||||
// chained shape `stk[i].str` directly (task #8 — loses BX
|
||||
// between the index load and the field deref), so always
|
||||
// spill to an intermediate local before reading the str
|
||||
// field. The cstage emits the same pattern for byte-identity.
|
||||
stepnd = stk[i];
|
||||
if (stepnd == nil) { return false; };
|
||||
stepnm = stepnd.str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, stepnm)) { found = fi; break; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
if (found == nil) { return false; };
|
||||
if (i == 0) {
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.leaffi = found;
|
||||
return true;
|
||||
};
|
||||
// Intermediate step. Must be a nested value-struct, OR a slice/
|
||||
// str field with the leaf (i == 1, stk[0]) as a pseudo-field.
|
||||
ft = found.tnode;
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind == nkind.N_TNAME) {
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}; };
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -7778,6 +7778,175 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
||||
return;
|
||||
};
|
||||
|
||||
// dotchain — packed result struct for dotchainresolve. Out-params are
|
||||
// bundled to keep the helper at <= 6 register-passed args; wwstage's
|
||||
// per-fn arg-frame computation over-allocates by 16 bytes for any
|
||||
// function with > 6 args (task #7, a pre-existing quirk independent
|
||||
// of this fix), which would silently break the bootstrap fixed-point
|
||||
// gate (993 / 994 / 995).
|
||||
//
|
||||
// Numeric fields are all i64, not i32. wwstage zero-inits an i32
|
||||
// local with MOVQ (8-byte store) but subsequent `out.totaloff = …`
|
||||
// updates would emit MOVL (4-byte store), leaving the upper 4 bytes
|
||||
// stale from the wider init. Keeping the out-params at i64 makes the
|
||||
// init width and the update width agree, so the field reads back
|
||||
// what was written across both stages.
|
||||
type dotchain = struct {
|
||||
rootname: str,
|
||||
rootoff: i64,
|
||||
totaloff: i64,
|
||||
leaffi: *fieldinfo,
|
||||
slicedelta: i64,
|
||||
isglobal: bool,
|
||||
};
|
||||
|
||||
// Spine-walk a chained N_DOT (n) inward to a root ident, summing field
|
||||
// offsets through value-struct intermediates. Optional slice/str leaf
|
||||
// pseudo-field (.ptr / .len / .cap) on the last segment is folded into
|
||||
// `out.slicedelta` (0/8/16); otherwise out.leaffi is the leaf fieldinfo
|
||||
// and slicedelta stays -1. Returns true on success; on false the caller
|
||||
// falls through to other branches.
|
||||
//
|
||||
// Mirrors cmd/w6c/cgen.c's N_DOT chained walker; both stages must agree
|
||||
// on the same shapes so the bootstrap fixed-point holds. The chain
|
||||
// depth is capped at 16 — deeper chains are vanishingly rare and fall
|
||||
// through.
|
||||
//
|
||||
// On success the caller emits one load/store at root_base + out.totaloff
|
||||
// (+ slicedelta for pseudo leaf). Root resolves as: local frame slot
|
||||
// (out.rootoff != 0, isglobal false) or top-level let (isglobal true,
|
||||
// root accessed via LEAQ name(SB), CX).
|
||||
export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
out.rootname = "";
|
||||
out.rootoff = 0i64;
|
||||
out.isglobal = false;
|
||||
out.totaloff = 0i64;
|
||||
out.leaffi = nil;
|
||||
out.slicedelta = -1i64;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
// Walk inward, recording the N_DOT node at each step (leaf first).
|
||||
// We hold *node pointers (8B each, slotsize-stable across stages)
|
||||
// and read .str on demand — a [16]str array would mis-slot at
|
||||
// wwstage where slotsize("str") returns 8, breaking the bootstrap
|
||||
// fixed-point.
|
||||
let stk: [16]*node;
|
||||
let nsteps: i32 = 0;
|
||||
let cur: *node = n;
|
||||
for (cur != nil) {
|
||||
if (cur.kind != nkind.N_DOT) { break; };
|
||||
if (nsteps >= 16) { return false; };
|
||||
stk[nsteps] = cur;
|
||||
nsteps += 1;
|
||||
cur = cur.lhs;
|
||||
};
|
||||
if (nsteps < 2) { return false; };
|
||||
if (cur == nil) { return false; };
|
||||
if (cur.kind != nkind.N_IDENT) { return false; };
|
||||
out.rootname = cur.str;
|
||||
// Resolve the root's struct type and base.
|
||||
let rootstruct: str = "";
|
||||
let lc: *local = localfindnode(c, cur.str);
|
||||
let gsi: *structinfo = nil;
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
rootstruct = lc.tnode.str;
|
||||
out.rootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) { return false; };
|
||||
// Walk outward, resolving each field. stk is leaf-first; iterate
|
||||
// from i = nsteps - 1 (the root-most field) down to i = 0 (leaf).
|
||||
let curstruct: str = rootstruct;
|
||||
// Pre-declare per-iteration spills here so cstage / wwstage agree
|
||||
// on the frame layout. Both must emit byte-identical asm for the
|
||||
// bootstrap fixed-point (tests 993/995) — letting these locals get
|
||||
// declared inside the branch bodies trips a per-stage divergence in
|
||||
// slot counting.
|
||||
let stepnd: *node = nil;
|
||||
let stepnm: str = "";
|
||||
let fi: *fieldinfo = nil;
|
||||
let found: *fieldinfo = nil;
|
||||
let ft: *node = nil;
|
||||
let s0nd: *node = nil;
|
||||
let pseudo: str = "";
|
||||
let delta: i64 = 0i64;
|
||||
let i: i32 = nsteps - 1;
|
||||
for (i >= 0) {
|
||||
let csi: *structinfo = structlookup(c, curstruct);
|
||||
if (csi == nil) { return false; };
|
||||
// Materialise the *node first; wwstage's cgen mis-emits the
|
||||
// chained shape `stk[i].str` directly (task #8 — loses BX
|
||||
// between the index load and the field deref), so always
|
||||
// spill to an intermediate local before reading the str
|
||||
// field. The cstage emits the same pattern for byte-identity.
|
||||
stepnd = stk[i];
|
||||
if (stepnd == nil) { return false; };
|
||||
stepnm = stepnd.str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, stepnm)) { found = fi; break; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
if (found == nil) { return false; };
|
||||
if (i == 0) {
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.leaffi = found;
|
||||
return true;
|
||||
};
|
||||
// Intermediate step. Must be a nested value-struct, OR a slice/
|
||||
// str field with the leaf (i == 1, stk[0]) as a pseudo-field.
|
||||
ft = found.tnode;
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind == nkind.N_TNAME) {
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
s0nd = stk[0];
|
||||
if (s0nd == nil) { return false; };
|
||||
pseudo = s0nd.str;
|
||||
delta = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
out.slicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}; };
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
||||
//
|
||||
@@ -9324,6 +9493,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`
|
||||
@@ -9393,6 +9655,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;
|
||||
@@ -11158,6 +11423,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
|
||||
@@ -11165,6 +11525,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;
|
||||
|
||||
Reference in New Issue
Block a user