diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index fab13eba..be82abec 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2079,6 +2079,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst) static void cgexpr(Cg*, Node*, Local*); static void cgstmt(Cg*, Node*, Local**, int*); +static int cgplaceaddr(Cg*, Node*, int, Local*); static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int); static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int, int); static void cg_widen_tag_remap(Cg*, Type*, Type*, int); @@ -2278,8 +2279,16 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals) } return 0; } - if (src->kind == N_DOT) - return cg_dotchain_addr(c, src, dst, locals); + if (src->kind == N_DOT) { + if (cg_dotchain_addr(c, src, dst, locals)) + return 1; + /* task #6: an N_INDEX link inside the dot chain + * (handles[k].result) — the chain walker has no index hop + * (AX-clean spill contract for cg_dotbase_addr); the place + * resolver is the C4/#40 fallback, and the failed walk is + * emission-free so the fallback starts clean. */ + return cgplaceaddr(c, src, dst, locals); + } if (src->kind == N_INDEX) { Node *base = src->lhs; Node *idx = src->rhs; @@ -7373,11 +7382,21 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_LEAQ, masym(c, n->rhs->str), areg(D_SI)); - } else if (n->rhs->kind == N_DOT) { - cg_dotchain_addr(c, n->rhs, D_SI, locals); - } else if (!cgplaceaddr(c, n->rhs, D_SI, locals)) { - fatal("indexed aggregate assignment source " - "unresolved"); + } else { + /* task #6: the chain walker declines an + * N_INDEX link (emission-free) — its + * result was unchecked here, so the copy + * below read through a stale SI. Fall to + * the generic place resolver. */ + int dok = 0; + if (n->rhs->kind == N_DOT) + dok = cg_dotchain_addr(c, + n->rhs, D_SI, locals); + if (!dok && !cgplaceaddr(c, n->rhs, + D_SI, locals)) + fatal("indexed aggregate " + "assignment source " + "unresolved"); } ins1(c, A_POPQ, areg(D_BX)); /* dest */ int k = 0; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 7c5ee504..69af04b8 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1689,7 +1689,13 @@ fn aggargsrcaddr(c: *cgen, src: *syntax.node, dst: str) bool = { return false; }; if (src.kind == syntax.nkind.N_DOT) { - return dotchainaddr(c, src, dst); + if (dotchainaddr(c, src, dst)) { return true; }; + // task #6: an N_INDEX link inside the dot chain + // (handles[k].result) — the chain walker has no index hop + // (AX-clean spill contract for dotbaseaddr); the place + // resolver is the C4/#40 fallback, and the failed walk is + // emission-free so the fallback starts clean. + return cgplaceaddr(c, src, dst); }; if (src.kind == syntax.nkind.N_INDEX) { let base: *syntax.node = src.lhs; @@ -9778,15 +9784,26 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { emitsymname(c, n.rhs.str); emitline("(SB), SI\n"); }; - } else { if (n.rhs.kind == syntax.nkind.N_DOT) { - dotchainaddr(c, n.rhs, "SI"); } else { - if (!cgplaceaddr(c, n.rhs, "SI")) { - let msrc: str = "indexed aggregate assignment source unresolved\n"; - os.write(2, msrc.ptr, msrc.len: u64); - os.exit(1); + // task #6: the chain walker + // declines an N_INDEX link + // (emission-free) — its result + // was unchecked here, so the + // copy below read through a + // stale SI. Fall to the + // generic place resolver. + let dok: bool = false; + if (n.rhs.kind == syntax.nkind.N_DOT) { + dok = dotchainaddr(c, n.rhs, "SI"); }; - };};}; + if (!dok) { + if (!cgplaceaddr(c, n.rhs, "SI")) { + let msrc: str = "indexed aggregate assignment source unresolved\n"; + os.write(2, msrc.ptr, msrc.len: u64); + os.exit(1); + }; + }; + };}; emitline("\tPOPQ\tBX\n"); // dest let kc: i32 = 0; for (kc + 8 <= esz) { diff --git a/test/lang/idxdot_aggcopy_test.ww b/test/lang/idxdot_aggcopy_test.ww new file mode 100644 index 00000000..9111e7cb --- /dev/null +++ b/test/lang/idxdot_aggcopy_test.ww @@ -0,0 +1,133 @@ +// idxdot_aggcopy_test — aggregate copies whose SOURCE is an +// indexed-then-dotted chain (elem[k].field). The dot-chain address +// walker has no index hop (AX-clean spill contract), so these shapes +// ride the generic place-resolver fallback (task #6); the indexed +// element-store row pins the once-SILENT unchecked-walker site that +// copied through a stale SI in both stages. + +package idxdot_aggcopy_test; + +type inner = struct { + a: i64, + b: i64, +}; + +type handle = struct { + id: i64, + result: inner, +}; + +type mid = struct { + pad: i64, + in_: inner, +}; + +type outer = struct { + tag: i64, + m: mid, +}; + +fn mk() [4]handle = { + let hs: [4]handle; + hs[0] = handle{id = 1, result = inner{a = 1, b = 2}}; + hs[1] = handle{id = 2, result = inner{a = 3, b = 4}}; + hs[2] = handle{id = 9, result = inner{a = 11, b = 31}}; + hs[3] = handle{id = 4, result = inner{a = 5, b = 6}}; + return hs; +}; + +fn use(v: inner) i64 = { + return v.a + v.b; +}; + +@test fn letcopy() void = { + let hs: [4]handle = mk(); + let k: i64 = 2; + let r: inner = hs[k].result; + assert(r.a == 11); + assert(r.b == 31); +}; + +@test fn callarg() void = { + let hs: [4]handle = mk(); + let k: i64 = 2; + assert(use(hs[k].result) == 42); +}; + +type bag = struct { + n: i64, + res: inner, +}; + +@test fn fieldassign() void = { + let hs: [4]handle = mk(); + let k: i64 = 2; + let g: bag; + g.n = 0; + g.res = hs[k].result; + assert(g.res.a == 11); + assert(g.res.b == 31); +}; + +fn pickresult(hs: *[4]handle, k: i64) inner = { + return (*hs)[k].result; +}; + +@test fn aggreturn() void = { + let hs: [4]handle = mk(); + let r: inner = pickresult(&hs, 2); + assert(r.a == 11); + assert(r.b == 31); +}; + +@test fn slicebase() void = { + let hs: [4]handle = mk(); + let sl: []handle = hs[0:4]; + let k: i64 = 2; + assert(use(sl[k].result) == 42); +}; + +@test fn nesteddot() void = { + let os_: [3]outer; + os_[1] = outer{tag = 7, m = mid{pad = 0, in_ = inner{a = 20, b = 22}}}; + let k: i64 = 1; + let l: inner = os_[k].m.in_; + assert(l.a == 20); + assert(l.b == 22); +}; + +// the once-silent shape: indexed element STORE from an indexed-dotted +// source (unchecked walker left SI stale; both stages copied garbage). +@test fn indexedstore() void = { + let hs: [4]handle = mk(); + let out: [3]inner; + out[0] = inner{a = 1, b = 1}; + out[1] = inner{a = 2, b = 2}; + out[2] = inner{a = 3, b = 3}; + let k: i64 = 2; + let j: i64 = 1; + out[j] = hs[k].result; + assert(out[1].a == 11); + assert(out[1].b == 31); + assert(out[0].a == 1); + assert(out[2].a == 3); +}; + +@test fn identreassign() void = { + let hs: [4]handle = mk(); + let k: i64 = 2; + let r: inner = inner{a = 0, b = 0}; + r = hs[k].result; + assert(r.a == 11); + assert(r.b == 31); +}; + +// control: the LHS twin (store INTO elem[k].field) was already wired. +@test fn lhsstore() void = { + let hs: [4]handle = mk(); + let k: i64 = 0; + let r: inner = inner{a = 40, b = 41}; + hs[k].result = r; + assert(hs[0].result.a == 40); + assert(hs[0].result.b == 41); +};