diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 1228092a..8c71cea2 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2523,6 +2523,7 @@ enum { DST_BP = 0, DST_PTR_LOCAL = 1, DST_GLOBAL = 2, + DST_PTR_SP = 3, }; static void cg_structlit_fill(Cg*, Local**, Type*, Node*, int, int, const char*, int); static void cg_structlit_fill_bp(Cg*, Local**, Type*, Node*, int); @@ -3296,6 +3297,15 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz) * DST_GLOBAL — base = BX, reloaded via `LEAQ name(SB), BX` with * the same reload cadence as DST_PTR_LOCAL. * srcoff unused. + * DST_PTR_SP — base = BX, reloaded via `MOVQ (SP), BX` with the + * same reload cadence as DST_PTR_LOCAL. The dst is the + * alloc-heap base saved by the alloc(value) arm's + * `PUSHQ AX` (top-of-stack); cgexpr is stack-balanced + * so (SP) keeps pointing at it across the field walk. + * srcoff/name unused. C7c: recurse into a nested + * struct/array/tuple field VALUE so alloc(Outer{x = + * Inner{..}}) writes the inner leaves instead of + * storing AX=0 over the whole inner slot. * * Param semantics (locked in here so the recursion contract is clear): * - `disp` is the per-recursion accumulator — grows by `foff` as @@ -3341,6 +3351,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, 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)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); int zi = 0; while (zi + 8 <= sz) { ins2(c, A_MOVQ, areg(D_AX), @@ -3384,6 +3396,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, areg(D_BX)); else if (mode == DST_GLOBAL) ins2(c, A_LEAQ, masym(c, name), areg(D_BX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); cg_widen_tagged_store(c, locals_p, fu, f->lhs, base_reg, disp + (int)foff, (int)fu->size); continue; @@ -3431,6 +3445,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, areg(D_BX)); else if (mode == DST_GLOBAL) ins2(c, A_LEAQ, masym(c, name), areg(D_BX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); int regs[3] = { D_AX, D_DX, D_CX }; int full = fsz / 8; int tail = fsz % 8; @@ -3466,6 +3482,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, if (mode == DST_PTR_LOCAL) ins2(c, A_MOVQ, amem(D_BP, srcoff), areg(D_DX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), + areg(D_DX)); else ins2(c, A_LEAQ, masym(c, name), areg(D_DX)); @@ -3524,6 +3543,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, else if (mode == DST_GLOBAL) ins2(c, A_LEAQ, masym(c, name), areg(D_BX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), + areg(D_BX)); int eoff = disp + (int)foff + idx * esz; if (is_float_el) ins2(c, fmov, areg(D_X0), @@ -3543,6 +3565,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, else if (mode == DST_GLOBAL) ins2(c, A_LEAQ, masym(c, name), areg(D_BX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), + areg(D_BX)); int eoff = disp + (int)foff + idx * esz; if (is_float_el) ins2(c, fmov, areg(D_X0), @@ -3581,6 +3606,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, if (mode == DST_PTR_LOCAL) ins2(c, A_MOVQ, amem(D_BP, srcoff), areg(D_BX)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), + areg(D_BX)); else ins2(c, A_LEAQ, masym(c, name), areg(D_BX)); @@ -3599,6 +3627,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, 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)); + else if (mode == DST_PTR_SP) + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); int sl_isf32 = 0; if (fld_isfloat(ft, &sl_isf32)) { int mov = sl_isf32 ? A_MOVSS : A_MOVSD; @@ -8772,48 +8802,21 @@ cgexpr(Cg *c, Node *n, Local *locals) label(c, alloc_ok); ins1(c, A_PUSHQ, areg(D_AX)); /* save ptr */ if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) { - for (Node *f = v->list; f; f = f->next) { - u64 foff = 0; - int fsz = 8; - Type *ftype = NULL; - for (Tfield *fl = u->fields; fl; fl = fl->next) { - if (strcmp(fl->name, f->str) == 0) { - foff = fl->offset; - fsz = (int)(fl->type ? fl->type->size : 8); - ftype = fl->type; - break; - } - } - cgexpr(c, f->lhs, locals); /* AX or (AX,BX) or X0 */ - int f_isf32 = 0; - if (fld_isfloat(ftype, &f_isf32)) { - int mov = f_isf32 ? A_MOVSS : A_MOVSD; - ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); - ins2(c, mov, areg(D_X0), - amem(D_BX, (int)foff)); - continue; - } - /* str IS []u8: cgexpr leaves (AX=ptr, BX=len, - * CX=cap). Route the heap base through DX so all - * three survive — CX now holds cap, BX holds len - * (#1/Phase 3). */ - Type *fu = type_chase_named(ftype); - if (fu && fu->kind == TY_STR) { - ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX)); - ins2(c, A_MOVQ, areg(D_AX), - amem(D_DX, (int)foff + 0)); - ins2(c, A_MOVQ, areg(D_BX), - amem(D_DX, (int)foff + 8)); - ins2(c, A_MOVQ, areg(D_CX), - amem(D_DX, (int)foff + 16)); - continue; - } - ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); - int op = A_MOVQ; - if (fsz == 1) op = A_MOVB; - else if (fsz == 4) op = A_MOVL; - ins2(c, op, areg(D_AX), amem(D_BX, (int)foff)); - } + /* C7c: route the heap field-fill through the shared + * structlit helper in DST_PTR_SP mode — base reloaded + * from the just-pushed heap ptr at (SP). The prior + * inline loop had only float/str/scalar arms, so a + * nested struct/array/tuple field VALUE (an inner + * N_STRUCTLIT / N_ARRLIT) fell to the scalar tail and + * stored AX=0 over the whole inner slot, dropping its + * leaves. The helper recurses to arbitrary depth and + * reuses the tagged/call/str/slice/array/agg arms, + * closing the class symmetrically with the BP-rel and + * global structlit sites. Byte-identical to the old + * inline loop for the float/str/scalar fields the + * corpus actually allocs (same (SP) reload, disp=0). */ + cg_structlit_fill(c, &locals, u, v, DST_PTR_SP, + 0, NULL, 0); } else { cgexpr(c, v, locals); /* AX = value */ ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 3b4ad792..f6f5123f 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -5911,60 +5911,19 @@ fn cgalloc(c: *cgen, n: *syntax.node) void = { emitline("\tPUSHQ\tAX\n"); if (v.kind == syntax.nkind.N_STRUCTLIT) { if (si != nil) { - let f: *syntax.node = v.list; - for (f != nil) { - if (f.kind == syntax.nkind.N_FIELD) { - let fname: str = f.str; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (syntax.streq(fn_, fname)) { - cgexpr(c, f.lhs); - // alloc(T{ fval = v }) for f64/f32 field: cgexpr left - // the value in X0, not AX — route the store via MOVSD/MOVSS. - if (isfloattype(c, fi.tnode)) { - let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; - emitline("\tMOVQ\t(SP), BX\n"); - emitline("\t"); - emitline(mov); - emitline("\tX0, "); - emitdispreg(fi.foff: i64, "BX"); - emitline("\n"); - fi = nil; - } else { if (isstrtype(c, fi.tnode)) { - // str IS []u8: cgexpr leaves (AX=ptr, - // BX=len, CX=cap). Route the heap base - // through DX so all three survive — CX - // holds cap, BX holds len (#1/Phase 3). - emitline("\tMOVQ\t(SP), DX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "DX"); - emitline("\n"); - emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "DX"); - emitline("\n"); - emitline("\tMOVQ\tCX, "); - emitdispreg((fi.foff + 16): i64, "DX"); - emitline("\n"); - fi = nil; - } else { - emitline("\tMOVQ\t(SP), BX\n"); - let sop: str = fieldstoreop(c, fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); - emitline("\n"); - fi = nil; - };}; - } else { - fi = fi.finext; - }; - }; - }; - f = f.next; - }; + // C7c: route the heap field-fill through the shared + // structlit helper in mode 3 (DST_PTR_SP) — base reloaded + // from the just-pushed heap ptr at (SP). The prior inline + // loop had only float/str/scalar arms, so a nested + // struct/array/tuple field VALUE (an inner N_STRUCTLIT / + // N_ARRLIT) fell to the scalar tail and stored AX=0 over + // the whole inner slot, dropping its leaves. The helper + // recurses to arbitrary depth and reuses the + // tagged/call/str/slice/array/agg arms, closing the class + // symmetrically with cstage's DST_PTR_SP arm. Byte-id to + // the old inline loop for the float/str/scalar fields the + // corpus actually allocs (same (SP) reload, disp=0). + cgstructlitfill(c, si, v, 3, 0, "", 0); }; } else { // #57 (retained cs!=ww, deferred): scalar/ptr alloc keeps the diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 5450fea2..c237ed33 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -5076,6 +5076,15 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node, // 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB), // BX` with the same cadence as DST_PTR_LOCAL. // srcoff unused. +// 3 = DST_PTR_SP — base = BX, reloaded via `MOVQ (SP), BX` with +// the same cadence as DST_PTR_LOCAL. dst is the +// alloc-heap base saved by cgalloc's `PUSHQ AX` +// (top-of-stack); cgexpr is stack-balanced so +// (SP) keeps pointing at it across the walk. +// srcoff/srcname unused. C7c: nested struct/ +// array/tuple field VALUE in alloc(Outer{x = +// Inner{..}}) now writes the inner leaves +// instead of storing AX=0 over the inner slot. // // Param semantics (locked in here so the recursion contract is // clear): @@ -5139,6 +5148,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; let zi: i32 = 0; for (zi + 8 <= totsize) { emitline("\tMOVQ\tAX, "); @@ -5198,6 +5210,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo, fieldnode.lhs, basereg, disp + fi.foff, fi.fsz); @@ -5293,6 +5308,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; let full: i32 = cfsz / 8; let ci: i32 = 0; for (ci < full) { @@ -5370,11 +5388,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitline("\tMOVQ\t"); emitoff(srcoff: i64); emitline("(BP), DX\n"); + } else { if (mode == 3) { + emitline("\tMOVQ\t(SP), DX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, srcname); emitline("(SB), DX\n"); - }; + }; }; emitline("\tMOVQ\tAX, "); emitdispreg((disp + fi.foff): i64, "DX"); emitline("\n"); @@ -5464,6 +5484,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; let eoff: i32 = disp + fi.foff + idx * esz; if (isfloatel) { emitline("\t"); @@ -5503,6 +5526,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; let eoff: i32 = disp + fi.foff + idx * esz; if (isfloatel) { emitline("\t"); @@ -5556,11 +5582,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitline("\tMOVQ\t"); emitoff(srcoff: i64); emitline("(BP), BX\n"); + } else { if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, srcname); emitline("(SB), BX\n"); - }; + }; }; if (disp + fi.foff != 0) { emitline("\tADDQ\t$"); emitint((disp + fi.foff): i64); @@ -5589,6 +5617,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; + if (mode == 3) { + emitline("\tMOVQ\t(SP), BX\n"); + }; if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; diff --git a/test/lang/alloc_nested_field_test.ww b/test/lang/alloc_nested_field_test.ww new file mode 100644 index 00000000..8ada193b --- /dev/null +++ b/test/lang/alloc_nested_field_test.ww @@ -0,0 +1,77 @@ +// alloc_nested_field_test — a nested aggregate field VALUE inside an +// alloc(struct-literal) heap init must survive (C7c, task #6). The +// alloc(value) field-fill loop had only float/str/scalar arms; a field +// whose value is itself an N_STRUCTLIT / N_ARRLIT fell to the scalar +// tail, so cgexpr(inner-literal) left AX=0 and the loop stored $0 over +// the whole inner slot — silently dropping every inner leaf (byte-id- +// gate-blind: both stages emitted the same wrong fill). The fix routes +// the heap fill through the shared cg_structlit_fill / cgstructlitfill +// helper in DST_PTR_SP mode (base reloaded from the pushed heap ptr at +// (SP)), which recurses to arbitrary depth. +// +// Every row reads the heap struct back via `let o: T = *p` (a whole- +// struct deref-load to a BP-rel local) so the assertions never traverse +// the chained-dot-through-heap-ptr read path (a separate pre-existing +// cs!=ww the C7c fill fix does not touch). Each row asserts the nested +// leaf VALUE (reddens when the fix is reverted: the dropped leaf reads +// 0) AND the sibling scalar leaves before/after it (no clobber). + +package alloc_nested_field_test; + +type Inner = struct { q: i64 }; +type Outer = struct { x: Inner, y: i64 }; + +type Mid = struct { lo: i64, inner: Inner, hi: i64 }; +type Nest = struct { a: i64, mid: Mid, b: i64 }; + +type WithArr = struct { lead: i64, arr: [3]i64, trail: i64 }; + +type TwoNest = struct { head: i64, s: Inner, arr: [2]i64, tail: i64 }; + +@test fn depth1_nested_struct() void = { + // Inner{q=10} is the field VALUE of x; pre-fix x.q dropped to 0. + let p = alloc(Outer{ x = Inner{ q = 10 }, y = 5 })!; + let o: Outer = *p; + assert(o.x.q == 10); // the dropped leaf + assert(o.y == 5); // trailing sibling, must not clobber +}; + +@test fn depth2_nested_struct() void = { + // Outer{ mid = Mid{ inner = Inner{q} } } — two levels of nesting. + // Every inner leaf lands at base + accumulated offset. + let p = alloc(Nest{ a = 1, mid = Mid{ lo = 2, inner = Inner{ q = 3 }, hi = 4 }, b = 5 })!; + let o: Nest = *p; + assert(o.a == 1); // lead sibling + assert(o.mid.lo == 2); // inner lead sibling + assert(o.mid.inner.q == 3); // depth-2 dropped leaf + assert(o.mid.hi == 4); // inner trail sibling + assert(o.b == 5); // trail sibling +}; + +@test fn nested_array_field() void = { + // arr = [10,20,30] is an N_ARRLIT field VALUE; pre-fix every + // element dropped (scalar tail stored word0 only). lead/trail + // bracket the array to prove the element stores stay in bounds. + let p = alloc(WithArr{ lead = 7, arr = [10, 20, 30], trail = 9 })!; + let o: WithArr = *p; + assert(o.lead == 7); // lead sibling + assert(o.arr[0] == 10); // dropped elements + assert(o.arr[1] == 20); + assert(o.arr[2] == 30); + assert(o.trail == 9); // trail sibling +}; + +@test fn multi_nested_fields() void = { + // Two ADJACENT nested-aggregate fields (nested STRUCT s, then nested + // ARRAY arr) bracketed by scalar siblings — proves the heap-base + // reload after a full nested recursion serves the NEXT nested field's + // recursion, not only a trailing scalar (every other row follows a + // nested field with a scalar). pre-fix both s.q and the arr elems drop. + let p = alloc(TwoNest{ head = 1, s = Inner{ q = 2 }, arr = [3, 4], tail = 5 })!; + let o: TwoNest = *p; + assert(o.head == 1); // lead sibling + assert(o.s.q == 2); // nested-struct leaf + assert(o.arr[0] == 3); // nested-array elems, after a nested struct + assert(o.arr[1] == 4); + assert(o.tail == 5); // trail sibling +};