From 3c37b9816440d6099a0ad9899e89277bddff50f3 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 2 Jun 2026 12:54:01 +0900 Subject: [PATCH] w6c+wwstage: [N]struct literal element store (#270-1c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `let x: [2]inner = [inner{..}, inner{..}]` left the array unpopulated: the N_ARRLIT per-element store handled scalar/str/float ONLY, so a struct/array/tuple element hit the multi-word-store gap and stored just the first 8 bytes (cs0/ww0). Both stages symmetric-broken; converge on the populated result (#263). Fix: an aggregate element of an array literal fills each element slot from its source — cg_structlit_fill_bp for an N_STRUCTLIT element, word-copy for an N_IDENT element (reusing COMMIT 2's per-element copy shape). esz is the element's natural size (cstage esub->size). cgen.c N_ARRLIT arm + cgenstmt.ww cglet. An aggregate `...` repeat and other element shapes hard-stop loud (rule-7). 949 rows: arrlit_structlit, arrlit_structident (8B struct, byteid=1, full readback). All 96 pass; test-unit 241 green; smoke OK. --- cmd/w6c/cgen.c | 66 ++++++++++++++- selfhost/cmd/w6c/main.combined.ww | 113 +++++++++++++++++++++----- selfhost/cmd/wcc/cgenstmt.ww | 113 +++++++++++++++++++++----- selfhost/cmd/wwdump/main.combined.ww | 113 +++++++++++++++++++++----- test/wcc/949_dotbase_addr_slice_run.c | 22 +++++ 5 files changed, 366 insertions(+), 61 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f459affa..ac37b4a3 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -8783,6 +8783,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) && lu->kind == TY_ARRAY) { Type *esub = lu->sub; int esz = esub ? (int)esub->size : 1; + /* #270-1c: an AGGREGATE (struct/array/tuple) element + * of an array literal — the scalar per-element MOVQ + * below stores only the first 8 bytes (unpopulated + * tail). Fill each element slot from its literal + * (cg_structlit_fill_bp) or source ident (word-copy). */ + Type *esubu = type_chase_named(esub); + int is_agg = esubu && (esubu->kind == TY_STRUCT + || esubu->kind == TY_ARRAY + || esubu->kind == TY_TUPLE); int is_str_el = type_isstr(esub); /* float element → store FROM X0; the AX path stores * raw double low-bits, garbage for f32 (#122, twin of @@ -8810,8 +8819,60 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) repeat = 1; break; } - cgexpr(c, e, *locals); int base = off + idx * esz; + if (is_agg) { + if (e->kind == N_STRUCTLIT) { + cg_structlit_fill_bp(c, locals, + esubu, e, base); + } else if (e->kind == N_IDENT) { + int soff = localfind(*locals, + e->str); + int k = 0; + for (; k + 8 <= esz; k += 8) { + ins2(c, A_MOVQ, + amem(D_BP, soff + k), + areg(D_AX)); + ins2(c, A_MOVQ, + areg(D_AX), + amem(D_BP, base + k)); + } + if (k + 4 <= esz) { + ins2(c, A_MOVL, + amem(D_BP, soff + k), + areg(D_AX)); + ins2(c, A_MOVL, + areg(D_AX), + amem(D_BP, base + k)); + k += 4; + } + if (k + 2 <= esz) { + ins2(c, A_MOVW, + amem(D_BP, soff + k), + areg(D_AX)); + ins2(c, A_MOVW, + areg(D_AX), + amem(D_BP, base + k)); + k += 2; + } + if (k + 1 <= esz) { + ins2(c, A_MOVB, + amem(D_BP, soff + k), + areg(D_AX)); + ins2(c, A_MOVB, + areg(D_AX), + amem(D_BP, base + k)); + k += 1; + } + } else { + fatal("#270-1c: array-literal " + "aggregate element shape " + "unsupported (rule-7)"); + } + last = e; + idx++; + continue; + } + cgexpr(c, e, *locals); if (is_str_el) { ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, base)); @@ -8827,6 +8888,9 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) last = e; idx++; } + if (repeat && is_agg) + fatal("#270-1c: `...` repeat of an aggregate " + "array-literal element not wired (rule-7)"); if (repeat && last) { /* fill remaining slots with the value still in * AX (and BX for str). */ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 382edc94..8c196277 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -28853,6 +28853,22 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #270-1c: an AGGREGATE (struct/array/tuple) element of + // an array literal — the scalar per-element store below + // writes only the first 8 bytes (unpopulated tail). Fill + // each element slot from its literal (cgstructlitfillbp) + // or source ident (word-copy). esz is the element's + // natural size (cstage esub->size). + let esubti: *tinfo = nil; + if (elemn != nil) { esubti = elemn.type_: *tinfo; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { + esubti = esubti.under; + }; + let isagg: bool = esubti != nil + && (esubti.kind == tykind.TY_STRUCT + || esubti.kind == tykind.TY_ARRAY + || esubti.kind == tykind.TY_TUPLE); + if (isagg) { esz = esubti.size: i32; }; let mop: str = tnodestoreop(c, elemn, esz); // float element → store FROM X0 (MOVSS/MOVSD): cgexpr // leaves a float in X0 and for f32 the #104 CVTSD2SS @@ -28876,31 +28892,88 @@ fn cglet(c: *cgen, n: *node) void = { if (isellip) { e = nil; } else { - cgexpr(c, e); - if (isstrel) { - emitline("\tMOVQ\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + idx * esz + 8): i64); - emitline("(BP)\n"); - } else { if (isfloatel) { - emitline("\t"); - emitline(fmov); - emitline("\tX0, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); + if (isagg) { + if (e.kind == nkind.N_STRUCTLIT) { + let esi: *structinfo = structlookupchain(c, elemn); + cgstructlitfillbp(c, esi, e, off + idx * esz); + } else { if (e.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, e.str); + let soff: i32 = 0; + if (sl != nil) { soff = sl.off; }; + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 1; + }; + } else { + let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n"; + os.write(2, m1c.ptr, m1c.len: u64); + os.exit(1); + }; }; } else { - emitline("\t"); - emitline(mop); - emitline("\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - }; }; + cgexpr(c, e); + if (isstrel) { + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + idx * esz + 8): i64); + emitline("(BP)\n"); + } else { if (isfloatel) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(mop); + emitline("\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + }; }; + }; idx += 1; e = e.next; }; }; + if (repeat && isagg) { + let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n"; + os.write(2, m1cr.ptr, m1cr.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index fa43a99b..30c32a76 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1488,6 +1488,22 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #270-1c: an AGGREGATE (struct/array/tuple) element of + // an array literal — the scalar per-element store below + // writes only the first 8 bytes (unpopulated tail). Fill + // each element slot from its literal (cgstructlitfillbp) + // or source ident (word-copy). esz is the element's + // natural size (cstage esub->size). + let esubti: *tinfo = nil; + if (elemn != nil) { esubti = elemn.type_: *tinfo; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { + esubti = esubti.under; + }; + let isagg: bool = esubti != nil + && (esubti.kind == tykind.TY_STRUCT + || esubti.kind == tykind.TY_ARRAY + || esubti.kind == tykind.TY_TUPLE); + if (isagg) { esz = esubti.size: i32; }; let mop: str = tnodestoreop(c, elemn, esz); // float element → store FROM X0 (MOVSS/MOVSD): cgexpr // leaves a float in X0 and for f32 the #104 CVTSD2SS @@ -1511,31 +1527,88 @@ fn cglet(c: *cgen, n: *node) void = { if (isellip) { e = nil; } else { - cgexpr(c, e); - if (isstrel) { - emitline("\tMOVQ\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + idx * esz + 8): i64); - emitline("(BP)\n"); - } else { if (isfloatel) { - emitline("\t"); - emitline(fmov); - emitline("\tX0, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); + if (isagg) { + if (e.kind == nkind.N_STRUCTLIT) { + let esi: *structinfo = structlookupchain(c, elemn); + cgstructlitfillbp(c, esi, e, off + idx * esz); + } else { if (e.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, e.str); + let soff: i32 = 0; + if (sl != nil) { soff = sl.off; }; + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 1; + }; + } else { + let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n"; + os.write(2, m1c.ptr, m1c.len: u64); + os.exit(1); + }; }; } else { - emitline("\t"); - emitline(mop); - emitline("\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - }; }; + cgexpr(c, e); + if (isstrel) { + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + idx * esz + 8): i64); + emitline("(BP)\n"); + } else { if (isfloatel) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(mop); + emitline("\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + }; }; + }; idx += 1; e = e.next; }; }; + if (repeat && isagg) { + let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n"; + os.write(2, m1cr.ptr, m1cr.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fb1baa9c..5808e10d 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -28853,6 +28853,22 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #270-1c: an AGGREGATE (struct/array/tuple) element of + // an array literal — the scalar per-element store below + // writes only the first 8 bytes (unpopulated tail). Fill + // each element slot from its literal (cgstructlitfillbp) + // or source ident (word-copy). esz is the element's + // natural size (cstage esub->size). + let esubti: *tinfo = nil; + if (elemn != nil) { esubti = elemn.type_: *tinfo; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { + esubti = esubti.under; + }; + let isagg: bool = esubti != nil + && (esubti.kind == tykind.TY_STRUCT + || esubti.kind == tykind.TY_ARRAY + || esubti.kind == tykind.TY_TUPLE); + if (isagg) { esz = esubti.size: i32; }; let mop: str = tnodestoreop(c, elemn, esz); // float element → store FROM X0 (MOVSS/MOVSD): cgexpr // leaves a float in X0 and for f32 the #104 CVTSD2SS @@ -28876,31 +28892,88 @@ fn cglet(c: *cgen, n: *node) void = { if (isellip) { e = nil; } else { - cgexpr(c, e); - if (isstrel) { - emitline("\tMOVQ\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + idx * esz + 8): i64); - emitline("(BP)\n"); - } else { if (isfloatel) { - emitline("\t"); - emitline(fmov); - emitline("\tX0, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); + if (isagg) { + if (e.kind == nkind.N_STRUCTLIT) { + let esi: *structinfo = structlookupchain(c, elemn); + cgstructlitfillbp(c, esi, e, off + idx * esz); + } else { if (e.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, e.str); + let soff: i32 = 0; + if (sl != nil) { soff = sl.off; }; + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff((soff + kc): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + idx * esz + kc): i64); + emitline("(BP)\n"); + kc += 1; + }; + } else { + let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n"; + os.write(2, m1c.ptr, m1c.len: u64); + os.exit(1); + }; }; } else { - emitline("\t"); - emitline(mop); - emitline("\tAX, "); - emitoff((off + idx * esz): i64); - emitline("(BP)\n"); - }; }; + cgexpr(c, e); + if (isstrel) { + emitline("\tMOVQ\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + idx * esz + 8): i64); + emitline("(BP)\n"); + } else { if (isfloatel) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(mop); + emitline("\tAX, "); + emitoff((off + idx * esz): i64); + emitline("(BP)\n"); + }; }; + }; idx += 1; e = e.next; }; }; + if (repeat && isagg) { + let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n"; + os.write(2, m1cr.ptr, m1cr.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index 5fcaa7d3..35ef564c 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -1158,6 +1158,28 @@ static const struct row rows[] = { " let c: inner = a[1][0];\n" " return (c.a + c.b + c.c): i32;\n" "};\n", 66, 0 }, + /* #270-1c: `[N]struct` array LITERAL element store. The N_ARRLIT + * per-element store handled scalar/str/float ONLY; a struct/array + * element hit the multi-word-store gap and stored just the first 8 + * bytes (unpopulated). Fix fills each element from its literal + * (cg_structlit_fill_bp) or source ident (word-copy). 8B struct + * (slot==natural) keeps byteid=1; full readback of all members. */ + { "arrlit_structlit", + "package main;\n" + "type inner = struct { a: u32, b: u32 };\n" + "export fn main() i32 = {\n" + " let x: [2]inner = [inner{a=1u32,b=2u32}, inner{a=3u32,b=4u32}];\n" + " return (x[0].a + x[0].b + x[1].a + x[1].b): i32;\n" + "};\n", 10, 1 }, + { "arrlit_structident", + "package main;\n" + "type inner = struct { a: u32, b: u32 };\n" + "export fn main() i32 = {\n" + " let p: inner; p.a = 7u32; p.b = 8u32;\n" + " let q: inner; q.a = 1u32; q.b = 2u32;\n" + " let x: [2]inner = [p, q];\n" + " return (x[0].a + x[0].b + x[1].a + x[1].b): i32;\n" + "};\n", 18, 1 }, { NULL, NULL, 0, 0 } };