w6c+wwstage: aggregate return from any addressable source (#272 commit-1)

The N_RETURN aggregate arms gated the return source on N_IDENT ||
N_STRUCTLIT; every other aggregate rvalue (array literal, o.field N_DOT,
a[i] N_INDEX, *p deref) fell through to the scalar-AX default = a silent
8-byte truncation. Both stages emitted byte-IDENTICAL wrong asm, so the
byte-id gate could not catch it (#263 class) — the fix converges on the
runtime oracle.

Mirror the arg-side closure #271 landed: both arms (≤24B @retscr and
>24B sret) now funnel N_ARRLIT through the literal element fill and
N_DOT/N_INDEX/deref through aggarg_srcaddr + the #265/#268 whole-
aggregate copy. Type-agnostic, so struct AND array returns are closed.
A close-by-construction loud-stop (rule 7) guards any future unhandled
aggregate source from reaching the scalar default.

Closes the callee-half of (b)/(c) and the addressable siblings. The
g = mk() global-receive caller-half is commit-2.

949_aggret_source_run pins the class: array-literal / N_DOT / N_INDEX /
deref / named-ident control / >24B-sret-deref / struct-field / struct-
deref, each summing all members (full readback) with per-row byte-id.
This commit is contained in:
2026-06-02 14:52:44 +09:00
parent 42dd70dc0c
commit 0d39129741
6 changed files with 1299 additions and 112 deletions

View File

@@ -28454,8 +28454,19 @@ fn cgreturn(c: *cgen, n: *node) void = {
return;
};
let okrhs: bool = false;
// #272: >24B sret addressable-source closure —
// N_DOT/N_INDEX/deref land their address in SI then
// memcpy through *(@sretarg), mirroring cstage cgen.c
// N_RETURN sret arm. N_ARRLIT >24B has no consumer
// (loud-stops in cstage); not wired here.
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) { okrhs = true; };
if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; };
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
let trefn: *node = rhs.lhs;
@@ -28477,6 +28488,43 @@ fn cgreturn(c: *cgen, n: *node) void = {
1, sretargoff, emptys,
0);
};
} else { if (addrsrc) {
if (!aggargsrcaddr(c, rhs, "SI")) {
let m4: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m4.ptr, m4.len: u64);
os.exit(1);
};
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
emitline("(BP), BX\n");
let k: i32 = 0;
for (k + 8 <= scs) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 8;
};
for (k + 4 <= scs) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 4;
};
for (k < scs) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 1;
};
} else {
let rl: *local = localfindnode(c, rhs.str);
if (rl != nil) {
@@ -28512,7 +28560,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// sret return: RAX = dest pointer.
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
@@ -28549,12 +28597,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
let rsz: i32 = structabisize(rsi);
if (rsz <= 24) {
let okrhs: bool = false;
// #272: struct ≤24B addressable-source closure —
// N_DOT/N_INDEX/deref memcpy into @retscr before the
// shared structfloatclass tail (mirror cstage cgen.c).
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) {
okrhs = true;
};
if (rhs.kind == nkind.N_STRUCTLIT) {
okrhs = true;
};
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
let scroff: i32 = localadd(c,
"@retscr", 24, nil);
@@ -28577,6 +28634,51 @@ fn cgreturn(c: *cgen, n: *node) void = {
// values recurse instead of dropping
// trailing bytes.
cgstructlitfillbp(c, rsi, rhs, scroff);
} else { if (addrsrc) {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268 shape).
if (!aggargsrcaddr(c, rhs, "SI")) {
let m5: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m5.ptr, m5.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 1;
};
} else {
// N_IDENT: word-copy from rhs slot
// to scratch. Whole 8B words via
@@ -28614,7 +28716,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
@@ -28683,30 +28785,45 @@ fn cgreturn(c: *cgen, n: *node) void = {
// (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) {
if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) {
// #272: array return-by-value source-shape closure.
// Beyond the #267 N_IDENT word-copy, route N_ARRLIT
// (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr +
// memcpy) into @retscr — the mirror of cstage cgen.c
// N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the
// callee already left AX/DX/CX).
let arrok: bool = false;
if (rhs.kind == nkind.N_IDENT) { arrok = true; };
if (rhs.kind == nkind.N_ARRLIT) { arrok = true; };
if (rhs.kind == nkind.N_DOT) { arrok = true; };
if (rhs.kind == nkind.N_INDEX) { arrok = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { arrok = true; };
};
let ati: *tinfo = c.fnret.type_: *tinfo;
for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; };
if (ati != nil) {
if (arrok && 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 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");
if (rhs.kind == nkind.N_IDENT) {
let rl: *local = localfindnode(c, rhs.str);
let roff: i32 = 0;
if (rl != nil) { roff = rl.off; };
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
@@ -28715,7 +28832,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
@@ -28724,31 +28841,172 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k < rsz) {
emitline("\tMOVB\t");
emitoff((rl.off + k): i64);
emitoff((roff + 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;
};
} else { if (rhs.kind == nkind.N_ARRLIT) {
// scalar/float element fill; non-scalar
// elements loud-stop (rule 7, no consumer).
let esubti: *tinfo = nil;
if (ati.sub != nil) { esubti = ati.sub; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; };
let esz: i32 = 8;
if (esubti != nil) { esz = esubti.size: i32; };
let badel: bool = false;
if (esubti != nil) {
if (esubti.kind == tykind.TY_STRUCT) { badel = true; };
if (esubti.kind == tykind.TY_ARRAY) { badel = true; };
if (esubti.kind == tykind.TY_TUPLE) { badel = true; };
if (esubti.kind == tykind.TY_SLICE) { badel = true; };
if (esubti.kind == tykind.TY_STR) { badel = true; };
};
if (badel) {
let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n";
os.write(2, m2.ptr, m2.len: u64);
os.exit(1);
};
let esub: *node = c.fnret.lhs;
let isfl: bool = isfloattype(c, esub);
let fmov: str = "MOVSD";
if (isf32type(c, esub)) { fmov = "MOVSS"; };
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; isellip = true; };
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = rsz / esz;
for (idx < total) {
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
};
};
} else {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268
// copy shape). Loud-stop unaddressable sources.
if (!aggargsrcaddr(c, rhs, "SI")) {
let m3: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m3.ptr, m3.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), 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;
};
};
};
// #272 close-by-construction: addressable aggregate-return
// sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in
// the arms above; an aggregate N_CALL passes through cgexpr
// (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching
// here would truncate to AX silently — loud-stop (rule 7),
// mirroring cstage cgen.c N_RETURN.
{
let aggret: bool = false;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; };
if (c.fnret.kind == nkind.N_TNAME) {
if (structlookup(c, c.fnret.str) != nil) { aggret = true; };
};
};
if (aggret && rhs.kind != nkind.N_CALL) {
let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n";
os.write(2, m6.ptr, m6.len: u64);
os.exit(1);
};
};
cgexpr(c, rhs);
} else {
// Bare `return;` from a tagged-union-returning fn is

View File

@@ -868,8 +868,19 @@ fn cgreturn(c: *cgen, n: *node) void = {
return;
};
let okrhs: bool = false;
// #272: >24B sret addressable-source closure —
// N_DOT/N_INDEX/deref land their address in SI then
// memcpy through *(@sretarg), mirroring cstage cgen.c
// N_RETURN sret arm. N_ARRLIT >24B has no consumer
// (loud-stops in cstage); not wired here.
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) { okrhs = true; };
if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; };
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
let trefn: *node = rhs.lhs;
@@ -891,6 +902,43 @@ fn cgreturn(c: *cgen, n: *node) void = {
1, sretargoff, emptys,
0);
};
} else { if (addrsrc) {
if (!aggargsrcaddr(c, rhs, "SI")) {
let m4: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m4.ptr, m4.len: u64);
os.exit(1);
};
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
emitline("(BP), BX\n");
let k: i32 = 0;
for (k + 8 <= scs) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 8;
};
for (k + 4 <= scs) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 4;
};
for (k < scs) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 1;
};
} else {
let rl: *local = localfindnode(c, rhs.str);
if (rl != nil) {
@@ -926,7 +974,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// sret return: RAX = dest pointer.
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
@@ -963,12 +1011,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
let rsz: i32 = structabisize(rsi);
if (rsz <= 24) {
let okrhs: bool = false;
// #272: struct ≤24B addressable-source closure —
// N_DOT/N_INDEX/deref memcpy into @retscr before the
// shared structfloatclass tail (mirror cstage cgen.c).
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) {
okrhs = true;
};
if (rhs.kind == nkind.N_STRUCTLIT) {
okrhs = true;
};
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
let scroff: i32 = localadd(c,
"@retscr", 24, nil);
@@ -991,6 +1048,51 @@ fn cgreturn(c: *cgen, n: *node) void = {
// values recurse instead of dropping
// trailing bytes.
cgstructlitfillbp(c, rsi, rhs, scroff);
} else { if (addrsrc) {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268 shape).
if (!aggargsrcaddr(c, rhs, "SI")) {
let m5: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m5.ptr, m5.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 1;
};
} else {
// N_IDENT: word-copy from rhs slot
// to scratch. Whole 8B words via
@@ -1028,7 +1130,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
@@ -1097,30 +1199,45 @@ fn cgreturn(c: *cgen, n: *node) void = {
// (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) {
if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) {
// #272: array return-by-value source-shape closure.
// Beyond the #267 N_IDENT word-copy, route N_ARRLIT
// (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr +
// memcpy) into @retscr — the mirror of cstage cgen.c
// N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the
// callee already left AX/DX/CX).
let arrok: bool = false;
if (rhs.kind == nkind.N_IDENT) { arrok = true; };
if (rhs.kind == nkind.N_ARRLIT) { arrok = true; };
if (rhs.kind == nkind.N_DOT) { arrok = true; };
if (rhs.kind == nkind.N_INDEX) { arrok = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { arrok = true; };
};
let ati: *tinfo = c.fnret.type_: *tinfo;
for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; };
if (ati != nil) {
if (arrok && 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 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");
if (rhs.kind == nkind.N_IDENT) {
let rl: *local = localfindnode(c, rhs.str);
let roff: i32 = 0;
if (rl != nil) { roff = rl.off; };
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
@@ -1129,7 +1246,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
@@ -1138,31 +1255,172 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k < rsz) {
emitline("\tMOVB\t");
emitoff((rl.off + k): i64);
emitoff((roff + 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;
};
} else { if (rhs.kind == nkind.N_ARRLIT) {
// scalar/float element fill; non-scalar
// elements loud-stop (rule 7, no consumer).
let esubti: *tinfo = nil;
if (ati.sub != nil) { esubti = ati.sub; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; };
let esz: i32 = 8;
if (esubti != nil) { esz = esubti.size: i32; };
let badel: bool = false;
if (esubti != nil) {
if (esubti.kind == tykind.TY_STRUCT) { badel = true; };
if (esubti.kind == tykind.TY_ARRAY) { badel = true; };
if (esubti.kind == tykind.TY_TUPLE) { badel = true; };
if (esubti.kind == tykind.TY_SLICE) { badel = true; };
if (esubti.kind == tykind.TY_STR) { badel = true; };
};
if (badel) {
let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n";
os.write(2, m2.ptr, m2.len: u64);
os.exit(1);
};
let esub: *node = c.fnret.lhs;
let isfl: bool = isfloattype(c, esub);
let fmov: str = "MOVSD";
if (isf32type(c, esub)) { fmov = "MOVSS"; };
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; isellip = true; };
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = rsz / esz;
for (idx < total) {
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
};
};
} else {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268
// copy shape). Loud-stop unaddressable sources.
if (!aggargsrcaddr(c, rhs, "SI")) {
let m3: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m3.ptr, m3.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), 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;
};
};
};
// #272 close-by-construction: addressable aggregate-return
// sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in
// the arms above; an aggregate N_CALL passes through cgexpr
// (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching
// here would truncate to AX silently — loud-stop (rule 7),
// mirroring cstage cgen.c N_RETURN.
{
let aggret: bool = false;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; };
if (c.fnret.kind == nkind.N_TNAME) {
if (structlookup(c, c.fnret.str) != nil) { aggret = true; };
};
};
if (aggret && rhs.kind != nkind.N_CALL) {
let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n";
os.write(2, m6.ptr, m6.len: u64);
os.exit(1);
};
};
cgexpr(c, rhs);
} else {
// Bare `return;` from a tagged-union-returning fn is

View File

@@ -28454,8 +28454,19 @@ fn cgreturn(c: *cgen, n: *node) void = {
return;
};
let okrhs: bool = false;
// #272: >24B sret addressable-source closure —
// N_DOT/N_INDEX/deref land their address in SI then
// memcpy through *(@sretarg), mirroring cstage cgen.c
// N_RETURN sret arm. N_ARRLIT >24B has no consumer
// (loud-stops in cstage); not wired here.
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) { okrhs = true; };
if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; };
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
let trefn: *node = rhs.lhs;
@@ -28477,6 +28488,43 @@ fn cgreturn(c: *cgen, n: *node) void = {
1, sretargoff, emptys,
0);
};
} else { if (addrsrc) {
if (!aggargsrcaddr(c, rhs, "SI")) {
let m4: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m4.ptr, m4.len: u64);
os.exit(1);
};
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
emitline("(BP), BX\n");
let k: i32 = 0;
for (k + 8 <= scs) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 8;
};
for (k + 4 <= scs) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 4;
};
for (k < scs) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff(k: i64);
emitline("(BX)\n");
k += 1;
};
} else {
let rl: *local = localfindnode(c, rhs.str);
if (rl != nil) {
@@ -28512,7 +28560,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// sret return: RAX = dest pointer.
emitline("\tMOVQ\t");
emitoff(sretargoff: i64);
@@ -28549,12 +28597,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
let rsz: i32 = structabisize(rsi);
if (rsz <= 24) {
let okrhs: bool = false;
// #272: struct ≤24B addressable-source closure —
// N_DOT/N_INDEX/deref memcpy into @retscr before the
// shared structfloatclass tail (mirror cstage cgen.c).
let addrsrc: bool = false;
if (rhs.kind == nkind.N_IDENT) {
okrhs = true;
};
if (rhs.kind == nkind.N_STRUCTLIT) {
okrhs = true;
};
if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; };
};
if (okrhs) {
let scroff: i32 = localadd(c,
"@retscr", 24, nil);
@@ -28577,6 +28634,51 @@ fn cgreturn(c: *cgen, n: *node) void = {
// values recurse instead of dropping
// trailing bytes.
cgstructlitfillbp(c, rsi, rhs, scroff);
} else { if (addrsrc) {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268 shape).
if (!aggargsrcaddr(c, rhs, "SI")) {
let m5: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m5.ptr, m5.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 1;
};
} else {
// N_IDENT: word-copy from rhs slot
// to scratch. Whole 8B words via
@@ -28614,7 +28716,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
k += 1;
};
};
};
}; };
// #171a: float-bearing struct RETURN (return
// twin of #165's param recv). A qualifying
// struct's float eightbytes ride the SSE return
@@ -28683,30 +28785,45 @@ fn cgreturn(c: *cgen, n: *node) void = {
// (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) {
if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) {
// #272: array return-by-value source-shape closure.
// Beyond the #267 N_IDENT word-copy, route N_ARRLIT
// (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr +
// memcpy) into @retscr — the mirror of cstage cgen.c
// N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the
// callee already left AX/DX/CX).
let arrok: bool = false;
if (rhs.kind == nkind.N_IDENT) { arrok = true; };
if (rhs.kind == nkind.N_ARRLIT) { arrok = true; };
if (rhs.kind == nkind.N_DOT) { arrok = true; };
if (rhs.kind == nkind.N_INDEX) { arrok = true; };
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) { arrok = true; };
};
let ati: *tinfo = c.fnret.type_: *tinfo;
for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; };
if (ati != nil) {
if (arrok && 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 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");
if (rhs.kind == nkind.N_IDENT) {
let rl: *local = localfindnode(c, rhs.str);
let roff: i32 = 0;
if (rl != nil) { roff = rl.off; };
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
@@ -28715,7 +28832,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff((rl.off + k): i64);
emitoff((roff + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
@@ -28724,31 +28841,172 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
for (k < rsz) {
emitline("\tMOVB\t");
emitoff((rl.off + k): i64);
emitoff((roff + 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;
};
} else { if (rhs.kind == nkind.N_ARRLIT) {
// scalar/float element fill; non-scalar
// elements loud-stop (rule 7, no consumer).
let esubti: *tinfo = nil;
if (ati.sub != nil) { esubti = ati.sub; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; };
let esz: i32 = 8;
if (esubti != nil) { esz = esubti.size: i32; };
let badel: bool = false;
if (esubti != nil) {
if (esubti.kind == tykind.TY_STRUCT) { badel = true; };
if (esubti.kind == tykind.TY_ARRAY) { badel = true; };
if (esubti.kind == tykind.TY_TUPLE) { badel = true; };
if (esubti.kind == tykind.TY_SLICE) { badel = true; };
if (esubti.kind == tykind.TY_STR) { badel = true; };
};
if (badel) {
let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n";
os.write(2, m2.ptr, m2.len: u64);
os.exit(1);
};
let esub: *node = c.fnret.lhs;
let isfl: bool = isfloattype(c, esub);
let fmov: str = "MOVSD";
if (isf32type(c, esub)) { fmov = "MOVSS"; };
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; isellip = true; };
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = rsz / esz;
for (idx < total) {
if (isfl) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((scroff + idx * esz): i64);
emitline("(BP)\n");
};
idx += 1;
};
};
} else {
// N_DOT / N_INDEX / deref: land src addr in SI,
// then memcpy rsz bytes into @retscr (#265/#268
// copy shape). Loud-stop unaddressable sources.
if (!aggargsrcaddr(c, rhs, "SI")) {
let m3: str = "#272: aggregate return from unsupported source kind\n";
os.write(2, m3.ptr, m3.len: u64);
os.exit(1);
};
let k: i32 = 0;
for (k + 8 <= rsz) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= rsz) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= rsz) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((scroff + k): i64);
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= rsz) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), 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;
};
};
};
// #272 close-by-construction: addressable aggregate-return
// sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in
// the arms above; an aggregate N_CALL passes through cgexpr
// (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching
// here would truncate to AX silently — loud-stop (rule 7),
// mirroring cstage cgen.c N_RETURN.
{
let aggret: bool = false;
if (c.fnret != nil) {
if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; };
if (c.fnret.kind == nkind.N_TNAME) {
if (structlookup(c, c.fnret.str) != nil) { aggret = true; };
};
};
if (aggret && rhs.kind != nkind.N_CALL) {
let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n";
os.write(2, m6.ptr, m6.len: u64);
os.exit(1);
};
};
cgexpr(c, rhs);
} else {
// Bare `return;` from a tagged-union-returning fn is