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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user