w6c+wwstage: array global-aggregate-receive g = f() (#272 commit-2)

The caller-half of the global case: `g = mk()` into a GLOBAL array
stored only the first word — a ≤24B reg-return landed `MOVQ AX, g(SB)`
(8 of 24 bytes); a >24B sret-return hit the #220 sret-to-symbol gate
which was TY_STRUCT-only and fell through to the same truncation.

≤24B: the local aggregate-receive arm was `off != 0`-only, so a global
array fell to the scalar IDENT store. Add a global ARRAY arm — LEAQ
name(SB), DI then store the full+tail words from AX/DX/CX (an array is
never float-class, so AX/DX/CX is always the transport; no `g+8(SB)`
operand form exists). Mirrors the str/slice global arm.
>24B: add TY_ARRAY to the #220 sret-to-symbol gate (cg_sret_dest_sym /
sretdestnode) — the callee writes the whole array through RDI.

A ≤24B STRUCT global receive can be float-class (X0/X1, not AX/DX/CX),
so it is left at its pre-existing symmetric behaviour — no consumer.

949_aggret_source_run gains global_recv (c → 15) and global_recv_sret
(>24B → 22), both with per-row byte-id.
This commit is contained in:
2026-06-02 15:00:55 +09:00
parent 0d39129741
commit 9d81ba77b7
5 changed files with 249 additions and 50 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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