diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 15b486d4..842218c5 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -8763,6 +8763,49 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) break; } } + /* #265 fold-1: aggregate deref-rhs let-init + * `let c: T = *p` (T a struct or array, >8B). Neither + * the scalar tail below (one 8B word) nor a missing arm + * (cstage dropped the copy entirely) materialised the + * whole aggregate. cgexpr(rhs->lhs) leaves the SOURCE + * ADDRESS in AX (a `*p` ident loads the pointer value; + * `*(&s)` LEAQs the slot); memcpy sz bytes slot→slot via + * SI — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is + * lu->size (sz), the #254 non-slot-padded ABI extent. Both + * stages emit this identical sequence (rule-10); the by- + * value RETURN ABI is fold-2 (#267). */ + if (n->rhs && n->rhs->kind == N_UN + && n->rhs->op == TK_STAR && lu + && (lu->kind == TY_STRUCT || lu->kind == TY_ARRAY) + && sz > 8) { + cgexpr(c, n->rhs->lhs, *locals); + ins2(c, A_MOVQ, areg(D_AX), areg(D_SI)); + int k = 0; + for (; k + 8 <= sz; k += 8) { + ins2(c, A_MOVQ, amem(D_SI, k), areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, off + k)); + } + if (k + 4 <= sz) { + ins2(c, A_MOVL, amem(D_SI, k), areg(D_AX)); + ins2(c, A_MOVL, areg(D_AX), + amem(D_BP, off + k)); + k += 4; + } + if (k + 2 <= sz) { + ins2(c, A_MOVW, amem(D_SI, k), areg(D_AX)); + ins2(c, A_MOVW, areg(D_AX), + amem(D_BP, off + k)); + k += 2; + } + if (k + 1 <= sz) { + ins2(c, A_MOVB, amem(D_SI, k), areg(D_AX)); + ins2(c, A_MOVB, areg(D_AX), + amem(D_BP, off + k)); + k += 1; + } + break; + } if (n->rhs && sz == 8) { cgexpr(c, n->rhs, *locals); if (isf) { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 65304fb6..89517d7b 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -28858,6 +28858,78 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #265 fold-1: aggregate deref-rhs let-init `let c: T = *p` + // (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the + // SOURCE ADDRESS in AX (a `*p` ident loads the pointer value; + // `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a + // MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254 + // non-slot-padded ABI extent: structabisize for a struct (= + // cstage lu->size), tinfo.size for an array. Pre-fix wwstage + // copied only the first 8B (scalar tail below) and cstage + // dropped the copy entirely — both wrong; converge on the full + // copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the + // by-value RETURN ABI is fold-2 (#267). + if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) { + let ncopy: i32 = 0; + let dsi: *structinfo = structlookupchain(c, tn); + if (dsi != nil) { + ncopy = structabisize(dsi); + } else { + let dti: *tinfo = nil; + if (tn != nil) { dti = tn.type_: *tinfo; }; + for (dti != nil && dti.kind == tykind.TY_NAMED) { + dti = dti.under; + }; + if (dti != nil) { + if (dti.kind == tykind.TY_ARRAY) { + ncopy = dti.size: i32; + }; + }; + }; + if (ncopy > 8) { + cgexpr(c, rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + let k: i32 = 0; + for (k + 8 <= ncopy) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= ncopy) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= ncopy) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= ncopy) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 1; + }; + c.lastwasreturn = 0; + return; + }; + }; }; cgexpr(c, rhs); // Float local: cgexpr leaves the value in X0. Spill via // MOVSS (f32, 4B) or MOVSD (f64, 8B). diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index f5489239..1d7c05c6 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1718,6 +1718,78 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #265 fold-1: aggregate deref-rhs let-init `let c: T = *p` + // (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the + // SOURCE ADDRESS in AX (a `*p` ident loads the pointer value; + // `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a + // MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254 + // non-slot-padded ABI extent: structabisize for a struct (= + // cstage lu->size), tinfo.size for an array. Pre-fix wwstage + // copied only the first 8B (scalar tail below) and cstage + // dropped the copy entirely — both wrong; converge on the full + // copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the + // by-value RETURN ABI is fold-2 (#267). + if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) { + let ncopy: i32 = 0; + let dsi: *structinfo = structlookupchain(c, tn); + if (dsi != nil) { + ncopy = structabisize(dsi); + } else { + let dti: *tinfo = nil; + if (tn != nil) { dti = tn.type_: *tinfo; }; + for (dti != nil && dti.kind == tykind.TY_NAMED) { + dti = dti.under; + }; + if (dti != nil) { + if (dti.kind == tykind.TY_ARRAY) { + ncopy = dti.size: i32; + }; + }; + }; + if (ncopy > 8) { + cgexpr(c, rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + let k: i32 = 0; + for (k + 8 <= ncopy) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= ncopy) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= ncopy) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= ncopy) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 1; + }; + c.lastwasreturn = 0; + return; + }; + }; }; cgexpr(c, rhs); // Float local: cgexpr leaves the value in X0. Spill via // MOVSS (f32, 4B) or MOVSD (f64, 8B). diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fd344835..5d8a12bb 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -28858,6 +28858,78 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #265 fold-1: aggregate deref-rhs let-init `let c: T = *p` + // (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the + // SOURCE ADDRESS in AX (a `*p` ident loads the pointer value; + // `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a + // MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254 + // non-slot-padded ABI extent: structabisize for a struct (= + // cstage lu->size), tinfo.size for an array. Pre-fix wwstage + // copied only the first 8B (scalar tail below) and cstage + // dropped the copy entirely — both wrong; converge on the full + // copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the + // by-value RETURN ABI is fold-2 (#267). + if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) { + let ncopy: i32 = 0; + let dsi: *structinfo = structlookupchain(c, tn); + if (dsi != nil) { + ncopy = structabisize(dsi); + } else { + let dti: *tinfo = nil; + if (tn != nil) { dti = tn.type_: *tinfo; }; + for (dti != nil && dti.kind == tykind.TY_NAMED) { + dti = dti.under; + }; + if (dti != nil) { + if (dti.kind == tykind.TY_ARRAY) { + ncopy = dti.size: i32; + }; + }; + }; + if (ncopy > 8) { + cgexpr(c, rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + let k: i32 = 0; + for (k + 8 <= ncopy) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= ncopy) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= ncopy) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= ncopy) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((off + k): i64); + emitline("(BP)\n"); + k += 1; + }; + c.lastwasreturn = 0; + return; + }; + }; }; cgexpr(c, rhs); // Float local: cgexpr leaves the value in X0. Spill via // MOVSS (f32, 4B) or MOVSD (f64, 8B). diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index 57442e14..9cb993c1 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -750,6 +750,78 @@ static const struct row rows[] = { " case void => yield 1: i32;\n" " };\n" "};\n", 63, 1 }, + /* #265 fold-1 aggregate deref-rhs let-init `let c: T = *p` (T a + * struct or array, >8B). Pre-fix NEITHER stage copied the whole + * aggregate: cstage DROPPED the copy entirely (c read garbage); + * wwstage copied only the FIRST 8 bytes (the scalar `MOVQ AX,off` + * tail). Fix: both stages memcpy the ABI-size aggregate slot→slot + * via SI — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. Converged + * (rule-10, byteid=1). Each row writes DISTINCT values to ALL + * members and reads back EVERY member (sum), so a partial/zero copy + * fails — a c[0]-only readback would pass a truncated copy. Covers: + * struct{[4]u32} 16B + struct{[8]u32} 32B (sha256 `*h` shape, both + * `*(&s)` and `*p` pointer-ident) + a bare [4]u32 array + non-8-mult + * tails ([3]u32 12B → MOVL tail; [11]u8 11B → MOVW+MOVB tail). The + * by-value aggregate RETURN ABI is fold-2 (#267, deferred). */ + { "deref_struct16", + "package main;\n" + "type t = struct { h: [4]u32 };\n" + "export fn main() i32 = {\n" + " let s: t;\n" + " s.h[0]=10u32; s.h[1]=20u32; s.h[2]=30u32; s.h[3]=40u32;\n" + " let c: t = *(&s);\n" + " return (c.h[0]+c.h[1]+c.h[2]+c.h[3]): i32;\n" + "};\n", 100, 1 }, + { "deref_struct32", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "export fn main() i32 = {\n" + " let s: t;\n" + " s.h[0]=1u32; s.h[1]=2u32; s.h[2]=3u32; s.h[3]=4u32;\n" + " s.h[4]=5u32; s.h[5]=6u32; s.h[6]=7u32; s.h[7]=8u32;\n" + " let c: t = *(&s);\n" + " return (c.h[0]+c.h[1]+c.h[2]+c.h[3]\n" + " +c.h[4]+c.h[5]+c.h[6]+c.h[7]): i32;\n" + "};\n", 36, 1 }, + { "deref_ptr32", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "export fn main() i32 = {\n" + " let s: t;\n" + " s.h[0]=1u32; s.h[1]=2u32; s.h[2]=3u32; s.h[3]=4u32;\n" + " s.h[4]=5u32; s.h[5]=6u32; s.h[6]=7u32; s.h[7]=8u32;\n" + " let p: *t = &s;\n" + " let c: t = *p;\n" + " return (c.h[0]+c.h[1]+c.h[2]+c.h[3]\n" + " +c.h[4]+c.h[5]+c.h[6]+c.h[7]): i32;\n" + "};\n", 36, 1 }, + { "deref_array16", + "package main;\n" + "export fn main() i32 = {\n" + " let s: [4]u32;\n" + " s[0]=5u32; s[1]=6u32; s[2]=7u32; s[3]=8u32;\n" + " let c: [4]u32 = *(&s);\n" + " return (c[0]+c[1]+c[2]+c[3]): i32;\n" + "};\n", 26, 1 }, + { "deref_tail12", + "package main;\n" + "export fn main() i32 = {\n" + " let s: [3]u32;\n" + " s[0]=7u32; s[1]=8u32; s[2]=9u32;\n" + " let c: [3]u32 = *(&s);\n" + " return (c[0]+c[1]+c[2]): i32;\n" + "};\n", 24, 1 }, + { "deref_tail11", + "package main;\n" + "export fn main() i32 = {\n" + " let s: [11]u8;\n" + " s[0]=1u8; s[1]=2u8; s[2]=3u8; s[3]=4u8; s[4]=5u8;\n" + " s[5]=6u8; s[6]=7u8; s[7]=8u8; s[8]=9u8; s[9]=10u8;\n" + " s[10]=11u8;\n" + " let c: [11]u8 = *(&s);\n" + " return (c[0]+c[1]+c[2]+c[3]+c[4]+c[5]\n" + " +c[6]+c[7]+c[8]+c[9]+c[10]): i32;\n" + "};\n", 66, 1 }, { NULL, NULL, 0, 0 } };