From 7dc3150b6586437568d918ece93d05d6ee882fb5 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 7 Aug 2026 23:00:02 +0900 Subject: [PATCH] cgen: accept an indexed source in aggregate element assignment The a[i] = src copy loop enumerated ident/field/deref sources; an N_INDEX rhs fell to the scalar tail and truncated the element. Route it through the generic place-address funnel. Both stages. --- cmd/w6c/cgen.c | 11 ++++++++--- selfhost/cmd/wcc/cgenexpr.ww | 18 +++++++++++++----- test/lang/aggregate_slice_swap_test.ww | 26 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 test/lang/aggregate_slice_swap_test.ww diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 10a6e1b4..c48d9236 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -7295,8 +7295,9 @@ cgexpr(Cg *c, Node *n, Local *locals) * address, then word-copy esz bytes: the WRITE-twin of the * #268 let-init copy loop. Source shapes mirror that loop * (ident local/global, N_DOT field via cg_dotchain_addr, - * `*p` deref); struct-lit sources divert at the place_slit - * gate above (#20), array-lit dies loud (task #32), and a + * N_INDEX via cgplaceaddr, `*p` deref); struct-lit sources + * divert at the place_slit gate above (#20), array-lit dies + * loud (task #32), and a * by-value call result still falls to the scalar tail — * RAX-only store, task #31-G. */ if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN @@ -7306,6 +7307,7 @@ cgexpr(Cg *c, Node *n, Local *locals) && esz > 8 && ((n->rhs->kind == N_IDENT) || (n->rhs->kind == N_DOT) + || (n->rhs->kind == N_INDEX) || (n->rhs->kind == N_UN && n->rhs->op == TK_STAR))) { /* dest &a[i] → BX */ @@ -7358,8 +7360,11 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_LEAQ, masym(c, n->rhs->str), areg(D_SI)); - } else { + } 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"); } 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 19943fd7..91766a38 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -9591,15 +9591,17 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { // truncation. Compute &a[i] (dest) and the rhs SOURCE // address, then word-copy esz bytes: the WRITE-twin of // the #268 let-init copy loop. Source shapes mirror that - // loop (ident, N_DOT field via dotchainaddr, `*p` - // deref); struct-lit sources divert at the placeslit - // gate above (#20), array-lit dies loud (task #32), and + // loop (ident, N_DOT field via dotchainaddr, N_INDEX via + // cgplaceaddr, `*p` deref); struct-lit sources divert at + // the placeslit gate above (#20), array-lit dies loud + // (task #32), and // a by-value call result still falls to the scalar tail // — RAX-only store, task #31-G. esz>8 // non-str/non-slice IS a struct/array/tuple here (the // tagged element already returned above; floats are ≤8). let aggsrc: bool = (n.rhs.kind == syntax.nkind.N_IDENT) || (n.rhs.kind == syntax.nkind.N_DOT) + || (n.rhs.kind == syntax.nkind.N_INDEX) || (n.rhs.kind == syntax.nkind.N_UN && n.rhs.op == syntax.tkind.TK_STAR); if (esz > 8 && !isstrtype(c, elemtn) @@ -9663,9 +9665,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { emitsymname(c, n.rhs.str); emitline("(SB), SI\n"); }; - } else { + } 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); + }; + };};}; emitline("\tPOPQ\tBX\n"); // dest let kc: i32 = 0; for (kc + 8 <= esz) { diff --git a/test/lang/aggregate_slice_swap_test.ww b/test/lang/aggregate_slice_swap_test.ww new file mode 100644 index 00000000..a1d447b7 --- /dev/null +++ b/test/lang/aggregate_slice_swap_test.ww @@ -0,0 +1,26 @@ +// Indexed aggregate assignment must copy from the source element's storage. +// Sorting uses this three-step swap on slices, so preserving only one word can +// silently duplicate records while both compiler stages still agree. + +package aggregate_slice_swap_test; + +type row = struct { + key: i64, + value: i64, + guard: i64, +}; + +@test fn distinct_after_swap() void = { + let rows: []row = []; + append(rows, row { key = 1, value = 11, guard = 111 }); + append(rows, row { key = 2, value = 22, guard = 222 }); + let t: row = rows[1]; + rows[1] = rows[0]; + rows[0] = t; + assert(rows[0].key == 2); + assert(rows[0].value == 22); + assert(rows[0].guard == 222); + assert(rows[1].key == 1); + assert(rows[1].value == 11); + assert(rows[1].guard == 111); +};