w6c+wwstage: store struct-literal array-field init (#249 BUG A)

A struct literal initialising an array-typed field as a local
(`e{ encmap = [..] }`) silently dropped the initializer: cg_structlit_fill
(cstage) / cgstructlitfill (wwstage) had no TY_ARRAY field arm, so the
array field fell to the generic scalar tail — cgexpr the N_ARRLIT (→ AX≈0)
then store one sized word — losing every element. cstage returned 0;
wwstage emitted byte-identical wrong code. (The GLOBAL literal-init path
is unaffected: it goes through emit_struct_lit_bytes, already correct via
#129 A.3.)

Both stages now element-wise store the N_ARRLIT at base+field_off+i*esz,
reusing the proven N_LET array-init shape (cgen.c:8467 / cgenstmt.ww:1393)
for int and float elements plus its `...` repeat fill; esz routes through
the type table (rule 13). str/slice/struct/tagged ELEMENT arrays are the
N_LET path's documented multi-word gap (cgen.c:8462) — converted from the
silent drop to a LOUD rule-7 error in both stages, not left silent.
Symmetric both stages (rule 10), byte-identical .s.

The `...` repeat in a struct-literal array field is checker-unreachable
today (the field type-check rejects `[v...]` length inference — a
separate checker gap); the arm mirrors N_LET's repeat for symmetry.

Test 949_structlit_arrfield_run: +local literal-init reads (idx 0 / last
element), cstage run + cs==ww byte-id.
This commit is contained in:
2026-06-02 01:19:46 +09:00
parent 16b519465a
commit 0fb4bae337
5 changed files with 507 additions and 0 deletions

View File

@@ -19036,6 +19036,140 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
emitline("\n");
};
fi = nil;
} else if (fi.tnode != nil
&& fi.tnode.kind == nkind.N_TARRAY
&& fieldnode.lhs != nil
&& fieldnode.lhs.kind == nkind.N_ARRLIT) {
// #249: array field from an N_ARRLIT. Without this
// the generic tail below cgexprs the N_ARRLIT (→ AX)
// and stores one sized word, silently DROPPING every
// element. Store element-wise at disp+foff+i*esz,
// mirroring the N_LET array-init path
// (cgenstmt.ww:1393). int/float elements only;
// str/slice/struct/tagged elements are the N_LET
// multi-word gap — loud rule-7 error (cstage
// cg_structlit_fill twin).
let elemn: *node = fi.tnode.lhs;
let isstrel: bool = isstrtype(c, elemn);
let istagel: bool = istaggedtype(c, elemn);
let issliceel: bool = false;
let isstructel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TSLICE) {
issliceel = true;
};
if (elemn.kind == nkind.N_TNAME) {
if (primsize(elemn.str) == 0) {
if (structlookup(c, elemn.str) != nil) {
isstructel = true;
};
};
};
};
if (isstrel || issliceel || isstructel || istagel) {
let e1: str = "ww: struct-literal array field '";
os.write(2, e1.ptr, e1.len: u64);
os.write(2, fname.ptr, fname.len: u64);
let e2: str = "' has a str/slice/struct/tagged element — multi-word element store out of #249 scope (N_LET array-init gap)\n";
os.write(2, e2.ptr, e2.len: u64);
os.exit(1);
};
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
let mop: str = tnodestoreop(c, elemn, esz);
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = fieldnode.lhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = idx;
if (fi.tnode.rhs != nil) {
if (fi.tnode.rhs.kind == nkind.N_INTLIT) {
total = fi.tnode.rhs.uval: i32;
};
};
for (idx < total) {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
};
};
fi = nil;
} else {
cgexpr(c, fieldnode.lhs);
// For non-BP modes, cgexpr just clobbered

View File

@@ -3743,6 +3743,140 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
emitline("\n");
};
fi = nil;
} else if (fi.tnode != nil
&& fi.tnode.kind == nkind.N_TARRAY
&& fieldnode.lhs != nil
&& fieldnode.lhs.kind == nkind.N_ARRLIT) {
// #249: array field from an N_ARRLIT. Without this
// the generic tail below cgexprs the N_ARRLIT (→ AX)
// and stores one sized word, silently DROPPING every
// element. Store element-wise at disp+foff+i*esz,
// mirroring the N_LET array-init path
// (cgenstmt.ww:1393). int/float elements only;
// str/slice/struct/tagged elements are the N_LET
// multi-word gap — loud rule-7 error (cstage
// cg_structlit_fill twin).
let elemn: *node = fi.tnode.lhs;
let isstrel: bool = isstrtype(c, elemn);
let istagel: bool = istaggedtype(c, elemn);
let issliceel: bool = false;
let isstructel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TSLICE) {
issliceel = true;
};
if (elemn.kind == nkind.N_TNAME) {
if (primsize(elemn.str) == 0) {
if (structlookup(c, elemn.str) != nil) {
isstructel = true;
};
};
};
};
if (isstrel || issliceel || isstructel || istagel) {
let e1: str = "ww: struct-literal array field '";
os.write(2, e1.ptr, e1.len: u64);
os.write(2, fname.ptr, fname.len: u64);
let e2: str = "' has a str/slice/struct/tagged element — multi-word element store out of #249 scope (N_LET array-init gap)\n";
os.write(2, e2.ptr, e2.len: u64);
os.exit(1);
};
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
let mop: str = tnodestoreop(c, elemn, esz);
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = fieldnode.lhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = idx;
if (fi.tnode.rhs != nil) {
if (fi.tnode.rhs.kind == nkind.N_INTLIT) {
total = fi.tnode.rhs.uval: i32;
};
};
for (idx < total) {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
};
};
fi = nil;
} else {
cgexpr(c, fieldnode.lhs);
// For non-BP modes, cgexpr just clobbered

View File

@@ -19036,6 +19036,140 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
emitline("\n");
};
fi = nil;
} else if (fi.tnode != nil
&& fi.tnode.kind == nkind.N_TARRAY
&& fieldnode.lhs != nil
&& fieldnode.lhs.kind == nkind.N_ARRLIT) {
// #249: array field from an N_ARRLIT. Without this
// the generic tail below cgexprs the N_ARRLIT (→ AX)
// and stores one sized word, silently DROPPING every
// element. Store element-wise at disp+foff+i*esz,
// mirroring the N_LET array-init path
// (cgenstmt.ww:1393). int/float elements only;
// str/slice/struct/tagged elements are the N_LET
// multi-word gap — loud rule-7 error (cstage
// cg_structlit_fill twin).
let elemn: *node = fi.tnode.lhs;
let isstrel: bool = isstrtype(c, elemn);
let istagel: bool = istaggedtype(c, elemn);
let issliceel: bool = false;
let isstructel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TSLICE) {
issliceel = true;
};
if (elemn.kind == nkind.N_TNAME) {
if (primsize(elemn.str) == 0) {
if (structlookup(c, elemn.str) != nil) {
isstructel = true;
};
};
};
};
if (isstrel || issliceel || isstructel || istagel) {
let e1: str = "ww: struct-literal array field '";
os.write(2, e1.ptr, e1.len: u64);
os.write(2, fname.ptr, fname.len: u64);
let e2: str = "' has a str/slice/struct/tagged element — multi-word element store out of #249 scope (N_LET array-init gap)\n";
os.write(2, e2.ptr, e2.len: u64);
os.exit(1);
};
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
let mop: str = tnodestoreop(c, elemn, esz);
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = fieldnode.lhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = idx;
if (fi.tnode.rhs != nil) {
if (fi.tnode.rhs.kind == nkind.N_INTLIT) {
total = fi.tnode.rhs.uval: i32;
};
};
for (idx < total) {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
};
};
fi = nil;
} else {
cgexpr(c, fieldnode.lhs);
// For non-BP modes, cgexpr just clobbered