From ebbc3f98c2fd1d622b2d37a878021b049aece324 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 2 Jun 2026 12:00:58 +0900 Subject: [PATCH] w6c+wwstage: array return-by-value via the struct-return ABI (#267 fold-2) Wire TY_ARRAY into the existing struct-return gates so arrays ride the same reg-class (<=24B in AX:DX:CX) / sret-class (>24B) path the struct return ABI already emits byte-identically. No new ABI machinery. Both stages, uniform gate-widen: - cg_sret_retsize / sretretsize: +TY_ARRAY (natural size sub.size*len, the type table) -> auto-enables sret send/recv + the >24B sret N_IDENT word-copy + return-forward, all keyed on the shared sret SSoT. - cgreturn <=24B reg-send: +TY_ARRAY (N_IDENT scratch word-copy -> AX/DX/CX). reg-class return-forward rides the default cgexpr passthrough. - let-init / assign <=24B recv: +TY_ARRAY (AX/DX/CX sized stores). struct_float_class stays struct-only: pure-int element arrays only; no pure-float-array-return consumer exists today. 949 +11 rows: reg-class 8/16/24B + sret-class 32B, [N]u32 and [N]u8, at let-init/assign/return-forward, full-member readback, + a struct- return regression control. All cstage-run + cs==ww byte-id. --- cmd/w6c/cgen.c | 22 ++- selfhost/cmd/w6c/main.combined.ww | 200 ++++++++++++++++++++++++++ selfhost/cmd/wcc/cgenexpr.ww | 63 ++++++++ selfhost/cmd/wcc/cgenstmt.ww | 125 ++++++++++++++++ selfhost/cmd/wcc/cgenutil.ww | 12 ++ selfhost/cmd/wwdump/main.combined.ww | 200 ++++++++++++++++++++++++++ test/wcc/949_dotbase_addr_slice_run.c | 66 +++++++++ 7 files changed, 682 insertions(+), 6 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 91c3a35d..e5ea1517 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -311,6 +311,11 @@ cg_sret_retsize(Type *rt) if (rt == NULL) return 0; if (rt->kind == TY_STRUCT) return (int)rt->size <= 24 ? 0 : (int)rt->size; + /* #267: arrays ride the struct-return ABI — same ≤24 reg / >24 sret + * split. Pure-int element arrays only; no float-array-return + * consumer exists, so struct_float_class stays struct-only. */ + if (rt->kind == TY_ARRAY) + return (int)rt->size <= 24 ? 0 : (int)rt->size; if (rt->kind == TY_TUPLE) { int gptotal = 0, ssecount = 0, f32; for (Tparam *p = rt->params; p; p = p->next) { @@ -5453,7 +5458,7 @@ cgexpr(Cg *c, Node *n, Local *locals) * the receive side of #4's cgreturn (calls + literals). * Sizes >24B and non-{0,1,2,4}-byte tails fall through * to the existing scalar path. */ - if (lu && lu->kind == TY_STRUCT + if (lu && (lu->kind == TY_STRUCT || lu->kind == TY_ARRAY) && (int)lu->size <= 24) { int off = localfind(locals, n->lhs->str); if (off != 0) { @@ -8620,7 +8625,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) } } if (n->rhs && n->rhs->kind == N_CALL && lu - && lu->kind == TY_STRUCT && sz <= 24 + && (lu->kind == TY_STRUCT || lu->kind == TY_ARRAY) + && sz <= 24 && (sz % 8 == 0 || sz % 8 == 1 || sz % 8 == 2 || sz % 8 == 4)) { cgexpr(c, n->rhs, *locals); @@ -9171,7 +9177,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins0(c, A_RET); break; } - if (rt && rt->kind == TY_STRUCT + if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY) && (int)rt->size > 24 && (n->lhs->kind == N_IDENT || n->lhs->kind == N_STRUCTLIT)) { @@ -9182,9 +9188,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * narrow field (e.g. bool@32 in a 33B struct * padded to 40B) widened to an 8B MOVQ at the * loop tail — diverged from wwstage's MOVB - * tail. Task #33, Class A. */ + * tail. Task #33, Class A. An array (#267) has no + * fields; its natural size IS rt->size. */ int sz = 0; - for (Tfield *fl = rt->fields; fl; fl = fl->next) { + if (rt->kind == TY_ARRAY) { + sz = (int)rt->size; + } else for (Tfield *fl = rt->fields; fl; fl = fl->next) { int end = (int)fl->offset + (int)(fl->type ? fl->type->size : 8); if (end > sz) sz = end; @@ -9260,7 +9269,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * for the same rationale. The ≤24B register-return * ABI uses the same TY_STRUCT gate. */ Type *rt = type_chase_named(cg_ret_type); - if (rt && rt->kind == TY_STRUCT && rt->size <= 24 + if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY) + && rt->size <= 24 && (n->lhs->kind == N_IDENT || n->lhs->kind == N_STRUCTLIT)) { int sz = (int)rt->size; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index dcf8a161..c9c84697 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -16814,6 +16814,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = { }; return 0; }; + // #267: arrays ride the struct-return ABI — natural size + // (sub.size*len, the type table) gates ≤24 reg / >24 sret, mirroring + // cstage cg_sret_retsize TY_ARRAY arm. Pure-int element arrays only; + // no float-array-return consumer (structfloatclass stays struct-only). + if (r.kind == nkind.N_TARRAY) { + let ati: *tinfo = r.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati == nil) { return 0; }; + let asz: i32 = ati.size: i32; + if (asz <= 24) { return 0; }; + return asz; + }; if (r.kind != nkind.N_TNAME) { return 0; }; // Primitives / aliased-to-primitives are never sret. if (primsize(r.str) > 0) { return 0; }; @@ -26968,6 +26980,69 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV — `c = mk()` where c is + // an array local. Arrays ride the struct reg/sret recv path. + // >24B sret keys on callsretsize (the shared SSoT, c's slot + // IS the prealloc dest); ≤24B arrives in AX/DX/CX, sized + // stores. Array natural size (tinfo.size = sub.size*len) + // mirrors cstage lu->size. No structfloatclass (pure-int + // element arrays). + if (lcn != nil && n.op == tkind.TK_ASSIGN + && n.rhs != nil && n.rhs.kind == nkind.N_CALL) { + let acati: *tinfo = nil; + if (lcn.tnode != nil) { acati = lcn.tnode.type_: *tinfo; }; + for (acati != nil && acati.kind == tykind.TY_NAMED) { + acati = acati.under; + }; + if (acati != nil && acati.kind == tykind.TY_ARRAY) { + let lcsz: i32 = acati.size: i32; + if (lcsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestoff = off; + cgexpr(c, n.rhs); + c.sretdestoff = 0; + return; + }; + }; + if (lcsz <= 24) { + let tlm: i32 = lcsz - (lcsz / 8) * 8; + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, n.rhs); + let full: i32 = lcsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + return; + }; + }; + }; + }; // Float-typed local: rhs lands in X0; store via MOVSD/ // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads // slot into X1, combines into X1, stores X1 back — @@ -28229,6 +28304,80 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value SEND. >24B sret rides the sret + // block above (scs = sretretsize keys it, N_IDENT word-copy / + // N_CALL forward generic). ≤24B reg-class `return a;` (N_IDENT) + // mirrors the struct ≤24B path: zero-pad a 24B scratch, word- + // copy the array slot in, ship AX/DX/CX. Array natural size + // (tinfo.size = sub.size*len) mirrors cstage rt->size. No + // structfloatclass (pure-int arrays); N_CALL forward at reg- + // class falls to the default cgexpr passthrough below. + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY + && rhs.kind == nkind.N_IDENT) { + let ati: *tinfo = c.fnret.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let rsz: i32 = ati.size: i32; + if (rsz <= 24) { + let rl: *local = localfindnode(c, rhs.str); + if (rl != nil) { + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + for (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + for (k < rsz) { + emitline("\tMOVB\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; + }; + }; + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is @@ -28796,6 +28945,57 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV ≤24B — `let c = mk()` + // where mk returns an array. Arrays ride the struct reg-recv + // path (AX/DX/CX, sized tail). >24B sret rides the sret recv + // above (callsretsize keyed). Array natural size (tinfo.size + // = sub.size*len) mirrors cstage lu->size. No structfloatclass + // (pure-int element arrays). + if (rhs.kind == nkind.N_CALL && tn != nil + && tn.kind == nkind.N_TARRAY) { + let ati: *tinfo = tn.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let lsz: i32 = ati.size: i32; + let tlm: i32 = lsz - (lsz / 8) * 8; + if (lsz <= 24) { + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, rhs); + let full: i32 = lsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + c.lastwasreturn = 0; + return; + }; + }; + }; + }; // Struct ident copy: `let p2: T = p1;` where T is a struct // >8B and rhs is a local ident. Per-qword MOVQ from src // slot to dst slot, with a sized tail (MOVL/MOVB) for diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index f4fe7979..70751522 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -7500,6 +7500,69 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV — `c = mk()` where c is + // an array local. Arrays ride the struct reg/sret recv path. + // >24B sret keys on callsretsize (the shared SSoT, c's slot + // IS the prealloc dest); ≤24B arrives in AX/DX/CX, sized + // stores. Array natural size (tinfo.size = sub.size*len) + // mirrors cstage lu->size. No structfloatclass (pure-int + // element arrays). + if (lcn != nil && n.op == tkind.TK_ASSIGN + && n.rhs != nil && n.rhs.kind == nkind.N_CALL) { + let acati: *tinfo = nil; + if (lcn.tnode != nil) { acati = lcn.tnode.type_: *tinfo; }; + for (acati != nil && acati.kind == tykind.TY_NAMED) { + acati = acati.under; + }; + if (acati != nil && acati.kind == tykind.TY_ARRAY) { + let lcsz: i32 = acati.size: i32; + if (lcsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestoff = off; + cgexpr(c, n.rhs); + c.sretdestoff = 0; + return; + }; + }; + if (lcsz <= 24) { + let tlm: i32 = lcsz - (lcsz / 8) * 8; + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, n.rhs); + let full: i32 = lcsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + return; + }; + }; + }; + }; // Float-typed local: rhs lands in X0; store via MOVSD/ // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads // slot into X1, combines into X1, stores X1 back — diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 28fbc076..a25dde34 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1089,6 +1089,80 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value SEND. >24B sret rides the sret + // block above (scs = sretretsize keys it, N_IDENT word-copy / + // N_CALL forward generic). ≤24B reg-class `return a;` (N_IDENT) + // mirrors the struct ≤24B path: zero-pad a 24B scratch, word- + // copy the array slot in, ship AX/DX/CX. Array natural size + // (tinfo.size = sub.size*len) mirrors cstage rt->size. No + // structfloatclass (pure-int arrays); N_CALL forward at reg- + // class falls to the default cgexpr passthrough below. + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY + && rhs.kind == nkind.N_IDENT) { + let ati: *tinfo = c.fnret.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let rsz: i32 = ati.size: i32; + if (rsz <= 24) { + let rl: *local = localfindnode(c, rhs.str); + if (rl != nil) { + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + for (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + for (k < rsz) { + emitline("\tMOVB\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; + }; + }; + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is @@ -1656,6 +1730,57 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV ≤24B — `let c = mk()` + // where mk returns an array. Arrays ride the struct reg-recv + // path (AX/DX/CX, sized tail). >24B sret rides the sret recv + // above (callsretsize keyed). Array natural size (tinfo.size + // = sub.size*len) mirrors cstage lu->size. No structfloatclass + // (pure-int element arrays). + if (rhs.kind == nkind.N_CALL && tn != nil + && tn.kind == nkind.N_TARRAY) { + let ati: *tinfo = tn.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let lsz: i32 = ati.size: i32; + let tlm: i32 = lsz - (lsz / 8) * 8; + if (lsz <= 24) { + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, rhs); + let full: i32 = lsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + c.lastwasreturn = 0; + return; + }; + }; + }; + }; // Struct ident copy: `let p2: T = p1;` where T is a struct // >8B and rhs is a local ident. Per-qword MOVQ from src // slot to dst slot, with a sized tail (MOVL/MOVB) for diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 9646eb14..2d324afe 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1304,6 +1304,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = { }; return 0; }; + // #267: arrays ride the struct-return ABI — natural size + // (sub.size*len, the type table) gates ≤24 reg / >24 sret, mirroring + // cstage cg_sret_retsize TY_ARRAY arm. Pure-int element arrays only; + // no float-array-return consumer (structfloatclass stays struct-only). + if (r.kind == nkind.N_TARRAY) { + let ati: *tinfo = r.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati == nil) { return 0; }; + let asz: i32 = ati.size: i32; + if (asz <= 24) { return 0; }; + return asz; + }; if (r.kind != nkind.N_TNAME) { return 0; }; // Primitives / aliased-to-primitives are never sret. if (primsize(r.str) > 0) { return 0; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index c44f4465..1649f964 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -16814,6 +16814,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = { }; return 0; }; + // #267: arrays ride the struct-return ABI — natural size + // (sub.size*len, the type table) gates ≤24 reg / >24 sret, mirroring + // cstage cg_sret_retsize TY_ARRAY arm. Pure-int element arrays only; + // no float-array-return consumer (structfloatclass stays struct-only). + if (r.kind == nkind.N_TARRAY) { + let ati: *tinfo = r.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati == nil) { return 0; }; + let asz: i32 = ati.size: i32; + if (asz <= 24) { return 0; }; + return asz; + }; if (r.kind != nkind.N_TNAME) { return 0; }; // Primitives / aliased-to-primitives are never sret. if (primsize(r.str) > 0) { return 0; }; @@ -26968,6 +26980,69 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV — `c = mk()` where c is + // an array local. Arrays ride the struct reg/sret recv path. + // >24B sret keys on callsretsize (the shared SSoT, c's slot + // IS the prealloc dest); ≤24B arrives in AX/DX/CX, sized + // stores. Array natural size (tinfo.size = sub.size*len) + // mirrors cstage lu->size. No structfloatclass (pure-int + // element arrays). + if (lcn != nil && n.op == tkind.TK_ASSIGN + && n.rhs != nil && n.rhs.kind == nkind.N_CALL) { + let acati: *tinfo = nil; + if (lcn.tnode != nil) { acati = lcn.tnode.type_: *tinfo; }; + for (acati != nil && acati.kind == tykind.TY_NAMED) { + acati = acati.under; + }; + if (acati != nil && acati.kind == tykind.TY_ARRAY) { + let lcsz: i32 = acati.size: i32; + if (lcsz > 24) { + let rscs: i32 = callsretsize(c, n.rhs); + if (rscs > 0) { + c.sretdestoff = off; + cgexpr(c, n.rhs); + c.sretdestoff = 0; + return; + }; + }; + if (lcsz <= 24) { + let tlm: i32 = lcsz - (lcsz / 8) * 8; + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, n.rhs); + let full: i32 = lcsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + return; + }; + }; + }; + }; // Float-typed local: rhs lands in X0; store via MOVSD/ // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads // slot into X1, combines into X1, stores X1 back — @@ -28229,6 +28304,80 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value SEND. >24B sret rides the sret + // block above (scs = sretretsize keys it, N_IDENT word-copy / + // N_CALL forward generic). ≤24B reg-class `return a;` (N_IDENT) + // mirrors the struct ≤24B path: zero-pad a 24B scratch, word- + // copy the array slot in, ship AX/DX/CX. Array natural size + // (tinfo.size = sub.size*len) mirrors cstage rt->size. No + // structfloatclass (pure-int arrays); N_CALL forward at reg- + // class falls to the default cgexpr passthrough below. + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY + && rhs.kind == nkind.N_IDENT) { + let ati: *tinfo = c.fnret.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let rsz: i32 = ati.size: i32; + if (rsz <= 24) { + let rl: *local = localfindnode(c, rhs.str); + if (rl != nil) { + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + for (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + for (k < rsz) { + emitline("\tMOVB\t"); + emitoff((rl.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; + }; + }; + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is @@ -28796,6 +28945,57 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; + // #267: array return-by-value RECV ≤24B — `let c = mk()` + // where mk returns an array. Arrays ride the struct reg-recv + // path (AX/DX/CX, sized tail). >24B sret rides the sret recv + // above (callsretsize keyed). Array natural size (tinfo.size + // = sub.size*len) mirrors cstage lu->size. No structfloatclass + // (pure-int element arrays). + if (rhs.kind == nkind.N_CALL && tn != nil + && tn.kind == nkind.N_TARRAY) { + let ati: *tinfo = tn.type_: *tinfo; + for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; + if (ati != nil) { + let lsz: i32 = ati.size: i32; + let tlm: i32 = lsz - (lsz / 8) * 8; + if (lsz <= 24) { + if (tlm == 0 || tlm == 1 + || tlm == 2 || tlm == 4) { + cgexpr(c, rhs); + let full: i32 = lsz / 8; + let i: i32 = 0; + for (i < full) { + let reg: str = "AX"; + if (i == 1) { reg = "DX"; }; + if (i == 2) { reg = "CX"; }; + emitline("\tMOVQ\t"); + emitline(reg); + emitline(", "); + emitoff((off + i * 8): i64); + emitline("(BP)\n"); + i += 1; + }; + if (tlm > 0) { + let top: str = "MOVB"; + if (tlm == 4) { top = "MOVL"; }; + if (tlm == 2) { top = "MOVW"; }; + let treg: str = "AX"; + if (full == 1) { treg = "DX"; }; + if (full == 2) { treg = "CX"; }; + emitline("\t"); + emitline(top); + emitline("\t"); + emitline(treg); + emitline(", "); + emitoff((off + full * 8): i64); + emitline("(BP)\n"); + }; + c.lastwasreturn = 0; + return; + }; + }; + }; + }; // Struct ident copy: `let p2: T = p1;` where T is a struct // >8B and rhs is a local ident. Per-qword MOVQ from src // slot to dst slot, with a sized tail (MOVL/MOVB) for diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index a3dd71a0..ca835cf9 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -954,6 +954,72 @@ static const struct row rows[] = { " let c: T = G;\n" " return (c.a+c.b+c.c+c.d): i32;\n" "};\n", 100, 1 }, + /* #267 fold-2: array return-by-value ABI. Arrays ride the existing + * struct-return path (≤24B in AX:DX:CX, >24B via sret) — these rows + * pin the runtime value AND cs==ww byte-id across reg-class (8/16/ + * 24B) and sret-class (32B), [N]u32 and [N]u8, at the let-init / + * assign / return-forward receive contexts. ctrl_struct_ret is the + * regression control: a struct return still byte-id (the struct path + * the arrays were wired into didn't change). FULL readback (sum every + * member) so a dropped eightbyte surfaces. */ + { "ret_arr_u32_8", + "package main;\n" + "fn mk() [2]u32 = { let a: [2]u32; a[0]=3u32; a[1]=4u32; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[1]): i32; };\n", + 7, 1 }, + { "ret_arr_u32_16", + "package main;\n" + "fn mk() [4]u32 = { let a: [4]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[1]+c[2]+c[3]): i32; };\n", + 10, 1 }, + { "ret_arr_u32_24", + "package main;\n" + "fn mk() [6]u32 = { let a: [6]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32;a[4]=5u32;a[5]=6u32; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[1]+c[2]+c[3]+c[4]+c[5]): i32; };\n", + 21, 1 }, + { "ret_arr_u32_32_sret", + "package main;\n" + "fn mk() [8]u32 = { let a: [8]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32;a[4]=5u32;a[5]=6u32;a[6]=7u32;a[7]=8u32; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[1]+c[2]+c[3]+c[4]+c[5]+c[6]+c[7]): i32; };\n", + 36, 1 }, + { "ret_arr_u8_4", + "package main;\n" + "fn mk() [4]u8 = { let a: [4]u8; a[0]=10u8;a[1]=20u8;a[2]=30u8;a[3]=40u8; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[1]+c[2]+c[3]): i32; };\n", + 100, 1 }, + { "ret_arr_u8_32_sret", + "package main;\n" + "fn mk() [32]u8 = { let a: [32]u8; a[0]=50u8;a[15]=30u8;a[31]=40u8; return a; };\n" + "export fn main() i32 = { let c = mk(); return (c[0]+c[15]+c[31]): i32; };\n", + 120, 1 }, + { "ret_arr_assign_16", + "package main;\n" + "fn mk() [4]u32 = { let a: [4]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32; return a; };\n" + "export fn main() i32 = { let c: [4]u32; c = mk(); return (c[0]+c[1]+c[2]+c[3]): i32; };\n", + 10, 1 }, + { "ret_arr_assign_32_sret", + "package main;\n" + "fn mk() [8]u32 = { let a: [8]u32; a[0]=1u32;a[7]=8u32; return a; };\n" + "export fn main() i32 = { let c: [8]u32; c = mk(); return (c[0]+c[7]): i32; };\n", + 9, 1 }, + { "ret_arr_fwd_16", + "package main;\n" + "fn mk() [4]u32 = { let a: [4]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32; return a; };\n" + "fn fwd() [4]u32 = { return mk(); };\n" + "export fn main() i32 = { let c = fwd(); return (c[0]+c[1]+c[2]+c[3]): i32; };\n", + 10, 1 }, + { "ret_arr_fwd_32_sret", + "package main;\n" + "fn mk() [8]u32 = { let a: [8]u32; a[0]=1u32;a[7]=8u32; return a; };\n" + "fn fwd() [8]u32 = { return mk(); };\n" + "export fn main() i32 = { let c = fwd(); return (c[0]+c[7]): i32; };\n", + 9, 1 }, + { "ctrl_struct_ret", + "package main;\n" + "type T = struct { a: u32, b: u32, c: u32 };\n" + "fn mk() T = { let s: T; s.a=1u32;s.b=2u32;s.c=3u32; return s; };\n" + "export fn main() i32 = { let v = mk(); return (v.a+v.b+v.c): i32; };\n", + 6, 1 }, { NULL, NULL, 0, 0 } };