diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f95a4426..0dfac600 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -5625,11 +5625,12 @@ cgexpr(Cg *c, Node *n, Local *locals) * global arm above (let_islet + LEAQ masym). The * scalar fall-through below would emit a truncated * 8-byte `MOVQ AX, g(SB)` and drop the struct body. - * Kept TY_STRUCT-only: a tuple-typed global reassign - * has no sret-to-symbol path in wwstage either, so - * leaving it to fall through keeps the stages aligned - * (rule-10). */ - if (lu && lu->kind == TY_STRUCT + * Kept aggregate-only (struct + #272 array): a + * tuple-typed global reassign has no sret-to-symbol + * path in wwstage either, so leaving it to fall + * through keeps the stages aligned (rule-10). */ + if (lu && (lu->kind == TY_STRUCT + || lu->kind == TY_ARRAY) && let_islet(n->lhs->str)) { cg_sret_dest_sym = n->lhs->str; cgexpr(c, n->rhs, locals); @@ -5687,6 +5688,35 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + } else if (lu->kind == TY_ARRAY + && let_islet(n->lhs->str) + && n->rhs && n->rhs->kind == N_CALL + && n->op == TK_ASSIGN) { + /* #272: `g = f();` where g is a GLOBAL + * aggregate ≤24B. The callee leaves the result + * in AX/DX/CX (#272 reg-return); the scalar IDENT + * fall-through below would store only MOVQ AX, + * g(SB) = the first word. The asm has no `g+8(SB)` + * operand form, so LEAQ the symbol into DI and + * store the full+tail words. Mirrors the str/slice + * global arm above and the #220 sret-to-symbol path. */ + int sz = (int)lu->size; + cgexpr(c, n->rhs, locals); + ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_DI)); + int regs[3] = { D_AX, D_DX, D_CX }; + int full = sz / 8; + int tail = sz % 8; + for (int i = 0; i < full; i++) + ins2(c, A_MOVQ, areg(regs[i]), + amem(D_DI, i * 8)); + if (tail > 0) { + int op = (tail == 4) ? A_MOVL + : (tail == 2) ? A_MOVW + : A_MOVB; + ins2(c, op, areg(regs[full]), + amem(D_DI, full * 8)); + } + break; } } } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 38ca399c..33eb9231 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -27067,30 +27067,81 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tX1, (CX)\n"); return; }; - // #220: `g = f();` where g is a GLOBAL struct >24B. - // No BP slot for the sret dest, so route RDI to g's - // symbol; the callee writes the struct straight into - // g's storage. The scalar store below would emit a + // #220: `g = f();` where g is a GLOBAL aggregate >24B + // (struct or #272 array). No BP slot for the sret dest, + // so route RDI to g's symbol; the callee writes straight + // into g's storage. The scalar store below would emit a // truncated `MOVQ AX, g(SB)` and drop the body. Mirror // of the C cgen N_ASSIGN global arm (cmd/w6c/cgen.c). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil) { + let gsz: i32 = 0; if (lvftn.kind == nkind.N_TNAME) { let gsi: *structinfo = structlookup(c, lvftn.str); - if (gsi != nil) { - let gsz: i32 = structabisize(gsi); - if (gsz > 24) { - let rscs: i32 = callsretsize(c, n.rhs); - if (rscs > 0) { - c.sretdestnode = lhs; - cgexpr(c, n.rhs); - c.sretdestnode = nil; - return; - }; - }; + if (gsi != nil) { gsz = structabisize(gsi); }; + }; + if (lvftn.kind == nkind.N_TARRAY) { + let gat: *tinfo = lvftn.type_: *tinfo; + for (gat != nil && gat.kind == tykind.TY_NAMED) { gat = gat.under; }; + if (gat != nil) { gsz = gat.size: i32; }; + }; + if (gsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestnode = lhs; + cgexpr(c, n.rhs); + c.sretdestnode = nil; + return; }; }; }; + // #272: `g = f();` where g is a GLOBAL ARRAY ≤24B. + // The callee leaves AX/DX/CX (#272 reg-return; an array + // is never float-class, so AX/DX/CX is always the + // transport); the scalar store below would truncate to + // MOVQ AX, g(SB). LEAQ the symbol into DI, store the + // full+tail words. Mirror of cstage cgen.c ≤24B global + // arm. A ≤24B STRUCT global receive can be float-class + // (X0/X1) so it stays at its pre-existing behaviour — no + // consumer (rule-10 aligned with cstage). + if (n.op == tkind.TK_ASSIGN && n.rhs != nil + && n.rhs.kind == nkind.N_CALL && lvftn != nil + && lvftn.kind == nkind.N_TARRAY) { + let aggsz: i32 = 0; + let aat: *tinfo = lvftn.type_: *tinfo; + for (aat != nil && aat.kind == tykind.TY_NAMED) { aat = aat.under; }; + if (aat != nil) { aggsz = aat.size: i32; }; + if (aggsz > 0 && aggsz <= 24) { + cgexpr(c, n.rhs); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), DI\n"); + let full: i32 = aggsz / 8; + let tail: i32 = aggsz - full * 8; + let i: i32 = 0; + for (i < full) { + emitline("\tMOVQ\t"); + emitline(tupreg(i)); + emitline(", "); + emitoff((i * 8): i64); + emitline("(DI)\n"); + i += 1; + }; + if (tail > 0) { + let top: str = "MOVB"; + if (tail == 4) { top = "MOVL"; }; + if (tail == 2) { top = "MOVW"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(tupreg(full)); + emitline(", "); + emitoff((full * 8): i64); + emitline("(DI)\n"); + }; + return; + }; + }; cgexpr(c, n.rhs); if (n.op == tkind.TK_ASSIGN) { // str/slice top-level let: str IS []u8, so both store the diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 9a9c4b70..f89a07bb 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -7441,30 +7441,81 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tX1, (CX)\n"); return; }; - // #220: `g = f();` where g is a GLOBAL struct >24B. - // No BP slot for the sret dest, so route RDI to g's - // symbol; the callee writes the struct straight into - // g's storage. The scalar store below would emit a + // #220: `g = f();` where g is a GLOBAL aggregate >24B + // (struct or #272 array). No BP slot for the sret dest, + // so route RDI to g's symbol; the callee writes straight + // into g's storage. The scalar store below would emit a // truncated `MOVQ AX, g(SB)` and drop the body. Mirror // of the C cgen N_ASSIGN global arm (cmd/w6c/cgen.c). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil) { + let gsz: i32 = 0; if (lvftn.kind == nkind.N_TNAME) { let gsi: *structinfo = structlookup(c, lvftn.str); - if (gsi != nil) { - let gsz: i32 = structabisize(gsi); - if (gsz > 24) { - let rscs: i32 = callsretsize(c, n.rhs); - if (rscs > 0) { - c.sretdestnode = lhs; - cgexpr(c, n.rhs); - c.sretdestnode = nil; - return; - }; - }; + if (gsi != nil) { gsz = structabisize(gsi); }; + }; + if (lvftn.kind == nkind.N_TARRAY) { + let gat: *tinfo = lvftn.type_: *tinfo; + for (gat != nil && gat.kind == tykind.TY_NAMED) { gat = gat.under; }; + if (gat != nil) { gsz = gat.size: i32; }; + }; + if (gsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestnode = lhs; + cgexpr(c, n.rhs); + c.sretdestnode = nil; + return; }; }; }; + // #272: `g = f();` where g is a GLOBAL ARRAY ≤24B. + // The callee leaves AX/DX/CX (#272 reg-return; an array + // is never float-class, so AX/DX/CX is always the + // transport); the scalar store below would truncate to + // MOVQ AX, g(SB). LEAQ the symbol into DI, store the + // full+tail words. Mirror of cstage cgen.c ≤24B global + // arm. A ≤24B STRUCT global receive can be float-class + // (X0/X1) so it stays at its pre-existing behaviour — no + // consumer (rule-10 aligned with cstage). + if (n.op == tkind.TK_ASSIGN && n.rhs != nil + && n.rhs.kind == nkind.N_CALL && lvftn != nil + && lvftn.kind == nkind.N_TARRAY) { + let aggsz: i32 = 0; + let aat: *tinfo = lvftn.type_: *tinfo; + for (aat != nil && aat.kind == tykind.TY_NAMED) { aat = aat.under; }; + if (aat != nil) { aggsz = aat.size: i32; }; + if (aggsz > 0 && aggsz <= 24) { + cgexpr(c, n.rhs); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), DI\n"); + let full: i32 = aggsz / 8; + let tail: i32 = aggsz - full * 8; + let i: i32 = 0; + for (i < full) { + emitline("\tMOVQ\t"); + emitline(tupreg(i)); + emitline(", "); + emitoff((i * 8): i64); + emitline("(DI)\n"); + i += 1; + }; + if (tail > 0) { + let top: str = "MOVB"; + if (tail == 4) { top = "MOVL"; }; + if (tail == 2) { top = "MOVW"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(tupreg(full)); + emitline(", "); + emitoff((full * 8): i64); + emitline("(DI)\n"); + }; + return; + }; + }; cgexpr(c, n.rhs); if (n.op == tkind.TK_ASSIGN) { // str/slice top-level let: str IS []u8, so both store the diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 5a357665..1fcdbc39 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -27067,30 +27067,81 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tX1, (CX)\n"); return; }; - // #220: `g = f();` where g is a GLOBAL struct >24B. - // No BP slot for the sret dest, so route RDI to g's - // symbol; the callee writes the struct straight into - // g's storage. The scalar store below would emit a + // #220: `g = f();` where g is a GLOBAL aggregate >24B + // (struct or #272 array). No BP slot for the sret dest, + // so route RDI to g's symbol; the callee writes straight + // into g's storage. The scalar store below would emit a // truncated `MOVQ AX, g(SB)` and drop the body. Mirror // of the C cgen N_ASSIGN global arm (cmd/w6c/cgen.c). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil) { + let gsz: i32 = 0; if (lvftn.kind == nkind.N_TNAME) { let gsi: *structinfo = structlookup(c, lvftn.str); - if (gsi != nil) { - let gsz: i32 = structabisize(gsi); - if (gsz > 24) { - let rscs: i32 = callsretsize(c, n.rhs); - if (rscs > 0) { - c.sretdestnode = lhs; - cgexpr(c, n.rhs); - c.sretdestnode = nil; - return; - }; - }; + if (gsi != nil) { gsz = structabisize(gsi); }; + }; + if (lvftn.kind == nkind.N_TARRAY) { + let gat: *tinfo = lvftn.type_: *tinfo; + for (gat != nil && gat.kind == tykind.TY_NAMED) { gat = gat.under; }; + if (gat != nil) { gsz = gat.size: i32; }; + }; + if (gsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestnode = lhs; + cgexpr(c, n.rhs); + c.sretdestnode = nil; + return; }; }; }; + // #272: `g = f();` where g is a GLOBAL ARRAY ≤24B. + // The callee leaves AX/DX/CX (#272 reg-return; an array + // is never float-class, so AX/DX/CX is always the + // transport); the scalar store below would truncate to + // MOVQ AX, g(SB). LEAQ the symbol into DI, store the + // full+tail words. Mirror of cstage cgen.c ≤24B global + // arm. A ≤24B STRUCT global receive can be float-class + // (X0/X1) so it stays at its pre-existing behaviour — no + // consumer (rule-10 aligned with cstage). + if (n.op == tkind.TK_ASSIGN && n.rhs != nil + && n.rhs.kind == nkind.N_CALL && lvftn != nil + && lvftn.kind == nkind.N_TARRAY) { + let aggsz: i32 = 0; + let aat: *tinfo = lvftn.type_: *tinfo; + for (aat != nil && aat.kind == tykind.TY_NAMED) { aat = aat.under; }; + if (aat != nil) { aggsz = aat.size: i32; }; + if (aggsz > 0 && aggsz <= 24) { + cgexpr(c, n.rhs); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), DI\n"); + let full: i32 = aggsz / 8; + let tail: i32 = aggsz - full * 8; + let i: i32 = 0; + for (i < full) { + emitline("\tMOVQ\t"); + emitline(tupreg(i)); + emitline(", "); + emitoff((i * 8): i64); + emitline("(DI)\n"); + i += 1; + }; + if (tail > 0) { + let top: str = "MOVB"; + if (tail == 4) { top = "MOVL"; }; + if (tail == 2) { top = "MOVW"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(tupreg(full)); + emitline(", "); + emitoff((full * 8): i64); + emitline("(DI)\n"); + }; + return; + }; + }; cgexpr(c, n.rhs); if (n.op == tkind.TK_ASSIGN) { // str/slice top-level let: str IS []u8, so both store the diff --git a/test/wcc/949_aggret_source_run.c b/test/wcc/949_aggret_source_run.c index 95c1cb88..d3fa413d 100644 --- a/test/wcc/949_aggret_source_run.c +++ b/test/wcc/949_aggret_source_run.c @@ -103,6 +103,22 @@ static const struct row rows[] = { "fn mk(p: *pair) pair = { return *p; };\n" "export fn main() i32 = { let x: pair = pair { a = 5i64, b = 9i64 };\n" " let r: pair = mk(&x); return (r.a + r.b): i32; };\n", 14 }, + /* #272 commit-2 caller-half: `g = mk()` into a GLOBAL array ≤24B. + * The receive must store the full AX/DX/CX, not the 8-byte AX + * truncation (g[1]/g[2] would read 0). */ + { "global_recv", + "package main;\n" + "let g: [3]i64 = [0i64, 0i64, 0i64];\n" + "fn mk() [3]i64 = { return [4i64, 5i64, 6i64]; };\n" + "export fn main() i32 = { g = mk();\n" + " return (g[0]+g[1]+g[2]): i32; };\n", 15 }, + /* global receive into a >24B array — the sret-to-symbol path. */ + { "global_recv_sret", + "package main;\n" + "let g: [4]i64 = [0i64,0i64,0i64,0i64];\n" + "fn mk(p: *[4]i64) [4]i64 = { return *p; };\n" + "export fn main() i32 = { let a: [4]i64 = [4i64,5i64,6i64,7i64];\n" + " g = mk(&a); return (g[0]+g[1]+g[2]+g[3]): i32; };\n", 22 }, { NULL, NULL, 0 } };