From 5f87c60e6c5c6cae6cd7daf0578e2e965fa0243b Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 14 May 2026 01:11:07 +0900 Subject: [PATCH] selfhost: retire dotchain bundle workaround (closes #17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- selfhost/CLAUDE.md | 2 + selfhost/cmd/w6c/main.combined.ww | 259 +++++++++++++-------------- selfhost/cmd/wcc/cgenexpr.ww | 135 ++++++++------ selfhost/cmd/wcc/cgenutil.ww | 124 +++++-------- selfhost/cmd/wwdump/main.combined.ww | 259 +++++++++++++-------------- test/wcc/660_field_signed.c | 21 +++ 6 files changed, 383 insertions(+), 417 deletions(-) diff --git a/selfhost/CLAUDE.md b/selfhost/CLAUDE.md index f4fc1b06..3847e68e 100644 --- a/selfhost/CLAUDE.md +++ b/selfhost/CLAUDE.md @@ -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 diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 486a2cd6..245e128c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 181a51d9..9b1cf6ac 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index e6ea4b89..e6d67248 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 55cec0f3..1ae12709 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; diff --git a/test/wcc/660_field_signed.c b/test/wcc/660_field_signed.c index e4e9d1e0..b7c4004c 100644 --- a/test/wcc/660_field_signed.c +++ b/test/wcc/660_field_signed.c @@ -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