w6c+wwstage: [N]struct literal element store (#270-1c)

`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.
This commit is contained in:
2026-06-02 12:54:01 +09:00
parent 6f18f42a4a
commit 3c37b98164
5 changed files with 366 additions and 61 deletions

View File

@@ -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). */

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 }
};