selfhost: retire dotchain bundle workaround (closes #17)
dotchain struct + bundle pre-declares retired. dotchainresolve now takes 6 direct out-params; per-iter locals declared inline. Bootstrap ww2==ww3==ww4 byte-identical — end-to-end validation of session's N_DOT / N_INDEX / TK_AMP / fldloadop fixes. i64 widths retained on out-params via task #19 workaround (i32 deref- stores leave caller slot upper 4B stale, MOVQ reads zero-extend to garbage). Documented in selfhost/CLAUDE.md active-workarounds.
This commit is contained in:
@@ -8,6 +8,8 @@ The C bootstrap's cgen has known silent-miscompilation traps. They produce wrong
|
||||
|
||||
1. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
||||
|
||||
2. **Out-param `*p: *i32` deref-stores leave caller slot's upper 4 bytes stale.** `*p = v` lowers to MOVL (4B), but the caller's 8B slot was zero-init via MOVQ; a later i64-widening read emits MOVQ (8B raw) and zero-extends, so a negative i32 round-trips as a 4G-rooted positive i64. Default to `*i64` out-params for offsets / signed indices until task #19 lands. Symptom: garbage frame offsets like `MOVL AX, 4294967280(BP)` in stage-2 asm.
|
||||
|
||||
Fixed (no workaround needed):
|
||||
|
||||
- `def NAME: str = "..."` field access. `.len`/`.ptr` on an Sdef ident
|
||||
|
||||
@@ -7878,58 +7878,41 @@ 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.
|
||||
// *outslicedelta (0/8/16); otherwise *outleaffi is the leaf fieldinfo
|
||||
// and *outslicedelta 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
|
||||
// On success the caller emits one load/store at root_base + *outtotaloff
|
||||
// (+ 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;
|
||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// read sees the zero-extended low half, so a negative i32 root offset
|
||||
// would come back as a huge positive i64. Tracked as task #19; until
|
||||
// it lands, callers cast to i32 at the assign sites.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -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;
|
||||
@@ -7943,95 +7926,72 @@ export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
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.
|
||||
*outrootname = cur.str;
|
||||
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;
|
||||
*outrootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
let gsi: *structinfo = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
*outisglobal = 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 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; };
|
||||
if (stk[i] == nil) { return false; };
|
||||
stepnm = stk[i].str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
let stepnm: str = stk[i].str;
|
||||
let fi: *fieldinfo = csi.fields;
|
||||
let found: *fieldinfo = 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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outleaffi = 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;
|
||||
let ft: *node = 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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + (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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -9618,81 +9578,88 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (r.isglobal) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (r.isglobal) {
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, r.leaffi);
|
||||
if (r.isglobal) {
|
||||
let lop: str = fieldloadop(c, leaffi);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9968,21 +9935,28 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (r.slicedelta >= 0i64) { extra = r.slicedelta; };
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(r.totaloff + extra, "CX");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + extra);
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -11913,85 +11887,92 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0i64) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let sop: str = fieldstoreop(c, r.leaffi);
|
||||
let sop: str = fieldstoreop(c, leaffi);
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
|
||||
@@ -1575,81 +1575,88 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (r.isglobal) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (r.isglobal) {
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, r.leaffi);
|
||||
if (r.isglobal) {
|
||||
let lop: str = fieldloadop(c, leaffi);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -1925,21 +1932,28 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (r.slicedelta >= 0i64) { extra = r.slicedelta; };
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(r.totaloff + extra, "CX");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + extra);
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -3870,85 +3884,92 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0i64) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let sop: str = fieldstoreop(c, r.leaffi);
|
||||
let sop: str = fieldstoreop(c, leaffi);
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
|
||||
@@ -2308,58 +2308,41 @@ 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.
|
||||
// *outslicedelta (0/8/16); otherwise *outleaffi is the leaf fieldinfo
|
||||
// and *outslicedelta 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
|
||||
// On success the caller emits one load/store at root_base + *outtotaloff
|
||||
// (+ 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;
|
||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// read sees the zero-extended low half, so a negative i32 root offset
|
||||
// would come back as a huge positive i64. Tracked as task #19; until
|
||||
// it lands, callers cast to i32 at the assign sites.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -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;
|
||||
@@ -2373,95 +2356,72 @@ export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
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.
|
||||
*outrootname = cur.str;
|
||||
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;
|
||||
*outrootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
let gsi: *structinfo = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
*outisglobal = 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 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; };
|
||||
if (stk[i] == nil) { return false; };
|
||||
stepnm = stk[i].str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
let stepnm: str = stk[i].str;
|
||||
let fi: *fieldinfo = csi.fields;
|
||||
let found: *fieldinfo = 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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outleaffi = 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;
|
||||
let ft: *node = 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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + (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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -7878,58 +7878,41 @@ 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.
|
||||
// *outslicedelta (0/8/16); otherwise *outleaffi is the leaf fieldinfo
|
||||
// and *outslicedelta 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
|
||||
// On success the caller emits one load/store at root_base + *outtotaloff
|
||||
// (+ 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;
|
||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// read sees the zero-extended low half, so a negative i32 root offset
|
||||
// would come back as a huge positive i64. Tracked as task #19; until
|
||||
// it lands, callers cast to i32 at the assign sites.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -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;
|
||||
@@ -7943,95 +7926,72 @@ export fn dotchainresolve(c: *cgen, n: *node, out: *dotchain) bool = {
|
||||
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.
|
||||
*outrootname = cur.str;
|
||||
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;
|
||||
*outrootoff = lc.off: i64;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (rootstruct.len == 0) {
|
||||
gsi = letvarstructinfo(c, cur.str);
|
||||
let gsi: *structinfo = letvarstructinfo(c, cur.str);
|
||||
if (gsi != nil) {
|
||||
rootstruct = gsi.sname;
|
||||
out.isglobal = true;
|
||||
*outisglobal = 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 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; };
|
||||
if (stk[i] == nil) { return false; };
|
||||
stepnm = stk[i].str;
|
||||
fi = csi.fields;
|
||||
found = nil;
|
||||
let stepnm: str = stk[i].str;
|
||||
let fi: *fieldinfo = csi.fields;
|
||||
let found: *fieldinfo = 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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outleaffi = 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;
|
||||
let ft: *node = 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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
// Nested value-struct (named).
|
||||
out.totaloff = out.totaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + (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;
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -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;
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -9618,81 +9578,88 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (r.isglobal) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (r.isglobal) {
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, r.leaffi);
|
||||
if (r.isglobal) {
|
||||
let lop: str = fieldloadop(c, leaffi);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9968,21 +9935,28 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// 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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (r.slicedelta >= 0i64) { extra = r.slicedelta; };
|
||||
if (r.isglobal) {
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(r.totaloff + extra, "CX");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(r.rootoff + r.totaloff + extra);
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -11913,85 +11887,92 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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);
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (r.slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0i64) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + r.slicedelta, "CX");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff + r.slicedelta);
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isstrtype(c, r.leaffi.tnode)) {
|
||||
if (isstrtype(c, leaffi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(r.totaloff + 0i64, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(r.totaloff + 8i64, "CX");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(r.rootoff + r.totaloff + 8i64);
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (isfloattype(c, r.leaffi.tnode)) {
|
||||
if (isfloattype(c, leaffi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, r.leaffi.tnode)) { mov = "MOVSS"; };
|
||||
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
let sop: str = fieldstoreop(c, r.leaffi);
|
||||
let sop: str = fieldstoreop(c, leaffi);
|
||||
cgexpr(c, n.rhs);
|
||||
if (r.isglobal) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, r.rootname);
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(r.totaloff, "CX");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(r.rootoff + r.totaloff);
|
||||
emitoff(rootoff + totaloff);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
|
||||
@@ -175,6 +175,27 @@ static const struct row rows[] = {
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 pin (widen-deref). Natural shape `fn f(p: *i32)`
|
||||
* with `*p = -16i32` lowers to MOVL (4B store) into the
|
||||
* caller's 8B slot. The caller's slot was zero-init via MOVQ,
|
||||
* so the upper 4B stay zero; a later i64-widening read emits
|
||||
* MOVQ (8B raw) and returns 0x00000000_FFFFFFF0 = 4294967280
|
||||
* instead of -16. Surfaced inside dotchainresolve when
|
||||
* worker-retire-dotchain tried i32 out-params; the helper now
|
||||
* widens all numeric out-params to *i64 as a workaround until
|
||||
* #19 lands. This row pins the WORKAROUND: out-param typed
|
||||
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
|
||||
* round-trips intact. When #19 lands, add a sibling row that
|
||||
* exercises the natural `*p: *i32` shape and expects -16. */
|
||||
{ "i64_out_param_neg_widen_deref_workaround",
|
||||
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0i64;\n"
|
||||
" setneg(&x);\n"
|
||||
" if (x == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user