diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 2860ab58..f6a7ed5b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2547,6 +2547,84 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, } continue; } + /* #249: array-typed field initialised from an N_ARRLIT. No prior + * arm matched, so without this the generic scalar tail below + * would cgexpr the N_ARRLIT (→ AX≈0) and store one sized word, + * silently DROPPING every element. Store element-wise at + * disp+foff+i*esz, reusing the N_LET array-init shape (cgen.c: + * 8467) for int/float elements and its `...` repeat. For non-BP + * modes cgexpr clobbers BX, so reload the base before each store + * (the X0/AX value reg survives the reload). str/slice/struct/ + * tagged ELEMENT arrays are the N_LET path's documented multi- + * word gap (cgen.c:8462) — loud rule-7 error, not a silent drop. */ + if (fu && fu->kind == TY_ARRAY + && f->lhs && f->lhs->kind == N_ARRLIT) { + Type *esub = fu->sub; + Type *esubu = (esub && esub->kind == TY_NAMED) + ? esub->under : esub; + int esz = esub ? (int)esub->size : 1; + int al_isf32 = 0; + int is_float_el = fld_isfloat(esub, &al_isf32); + if (type_isstr(esub) || type_isslice(esub) + || (esubu && (esubu->kind == TY_STRUCT + || esubu->kind == TY_TAGGED))) + fatal("cg_structlit_fill: array field '%s' has a " + "str/slice/struct/tagged element — multi-word " + "element store is out of #249 scope (N_LET " + "array-init gap, cgen.c:8462)", + f->str ? f->str : "?"); + int eop = A_MOVQ; + if (esz == 1) eop = A_MOVB; + else if (esz == 2) eop = A_MOVW; + else if (esz == 4) eop = A_MOVL; + int fmov = al_isf32 ? A_MOVSS : A_MOVSD; + int idx = 0; + Node *last = NULL; + int repeat = 0; + for (Node *e = f->lhs->list; e; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + cgexpr(c, e, *locals_p); + if (mode == DST_PTR_LOCAL) + ins2(c, A_MOVQ, amem(D_BP, srcoff), + areg(D_BX)); + else if (mode == DST_GLOBAL) + ins2(c, A_LEAQ, masym(c, name), + areg(D_BX)); + int eoff = disp + (int)foff + idx * esz; + if (is_float_el) + ins2(c, fmov, areg(D_X0), + amem(base_reg, eoff)); + else + ins2(c, eop, areg(D_AX), + amem(base_reg, eoff)); + last = e; + idx++; + } + if (repeat && last) { + while (idx < (int)fu->alen) { + if (mode == DST_PTR_LOCAL) + ins2(c, A_MOVQ, + amem(D_BP, srcoff), + areg(D_BX)); + else if (mode == DST_GLOBAL) + ins2(c, A_LEAQ, masym(c, name), + areg(D_BX)); + int eoff = disp + (int)foff + idx * esz; + if (is_float_el) + ins2(c, fmov, areg(D_X0), + amem(base_reg, eoff)); + else + ins2(c, eop, areg(D_AX), + amem(base_reg, eoff)); + idx++; + } + } + continue; + } cgexpr(c, f->lhs, *locals_p); /* For non-BP modes, cgexpr just clobbered BX; reload it * before the store. */ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 325e9264..cc979ee3 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index c683a622..14b9b037 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index df8c6aab..1d51563a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/test/wcc/949_structlit_arrfield_run.c b/test/wcc/949_structlit_arrfield_run.c index 484c9b84..f5270f0b 100644 --- a/test/wcc/949_structlit_arrfield_run.c +++ b/test/wcc/949_structlit_arrfield_run.c @@ -64,6 +64,33 @@ static const struct row rows[] = { "def G: e = e { pad = [9u8, 9u8, 9u8]," " encmap = [65u8, 66u8, 67u8, 68u8] };\n" "export fn main() i32 = { return G.encmap[2]: i32; };\n", 67 }, + /* BUG A: local struct-literal init of a [4]u8 field, read idx 0. + * Pre-fix the array field fell to the generic scalar tail (cgexpr + * the N_ARRLIT → AX, store one word) and silently dropped every + * element → 0. encmap[0]='A' (65). */ + { "local_lit_read0", + "package main;\n" + "type e = struct { encmap: [4]u8 };\n" + "export fn main() i32 = {\n" + " let g: e = e { encmap = [65u8, 66u8, 67u8, 68u8] };\n" + " return g.encmap[0]: i32;\n" + "};\n", 65 }, + /* BUG A: same init, read idx 3 — asserts the LAST element landed + * (the pre-fix single-word store would never reach it). 'D' (68). */ + { "local_lit_read3", + "package main;\n" + "type e = struct { encmap: [4]u8 };\n" + "export fn main() i32 = {\n" + " let g: e = e { encmap = [65u8, 66u8, 67u8, 68u8] };\n" + " return g.encmap[3]: i32;\n" + "};\n", 68 }, + /* NB: a trailing `...` repeat in a struct-LITERAL array field + * (`encmap = [7u8...]`) is rejected by the CHECKER ("[1]u8 not + * assignable to [4]u8") — the field type-check doesn't apply the + * repeat-length inference that bare `let a: [N]T = [v...]` gets. + * The cg_structlit_fill array arm mirrors the N_LET `...` handling + * for symmetry, but that path is checker-unreachable today (separate + * checker gap, not #249). No row exercises it. */ { NULL, NULL, 0 } };