cstage+selfhost+test: cgreturn TY_STRUCT <=24B via AX/DX/CX
Whole-struct return ABI for sizes <=24B. Both stages materialise rhs
into a zero-padded 24B @retscr scratch slot, then load AX=bytes[0..7],
DX=bytes[8..15], CX=bytes[16..23] unconditionally — three MOVQs
regardless of declared struct size, so the receive side (landing in
task #5) can read all three words and mask by the declared size. R8
stays reserved for the tagged-return 4th word; the uniform-MOVQ shape
is cheap over a size-conditional partial-load and keeps the producer
diff vs the existing tagged-return AX/DX/CX/R8 path minimal.
Two rhs shapes wired this pass: N_IDENT (word-copy from rhs local slot,
MOVQ pairs + MOVL/MOVB tail bounded by declared struct size) and
N_STRUCTLIT (field-walk; tagged fields delegate to the existing tagged
widening helper, float fields go through X0, int fields use MOVQ/MOVL/
MOVB by field size). Sizes >24B fall through to the existing scalar
path (only AX gets the first qword), pending sret in a future task.
N_CALL chain-return (`return otherfn()`) is deferred to task #5's
receive side — until that lands the call-result lives in caller regs.
The wwstage mirror in cgenstmt.ww matches cgen.c byte-for-byte on the
new branch; cgendecl.ww's scanlocals pre-reserves 24B for @retscr under
the same predicate (N_RETURN, fnret is N_TNAME, structlookup hit,
totsize<=24, rhs is N_IDENT|N_STRUCTLIT) since wwstage writes its
prologue SUBQ from the upfront frame total — cstage patches SUBQ at fn
end so it can allocate inline.
Latent fsz==2 MOVW divergence between stages (cstage structlit int-
branch only special-cases fsz 1/4, wwstage's fieldstoreop also returns
MOVW for fsz==2) tracked as task #13; not exercised by the new fixtures
or by any current selfhost <=24B struct return.
main.combined.ww files also pick up worker-checkfix's wwstage
architectural comment from 7f60ebb (auto-regen ran after that commit).
This commit is contained in:
@@ -4830,6 +4830,20 @@ fn declmod(file: *node, d: *node) str = {
|
||||
// installdecl — install the top-level decl's name into the top scope.
|
||||
// We don't compute its type yet (that's the resolve pass) — just bind
|
||||
// the name so forward references resolve.
|
||||
//
|
||||
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
||||
// promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any
|
||||
// same-leaf SK_TYPE/SK_FN/SK_DEF/SK_VAR live as separate entries in
|
||||
// the same scope-bucket, distinguished by `sym.mod`. The dot-prefix
|
||||
// lookup in resolvewalk + scopelookupinmodule's mod-filter already
|
||||
// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same
|
||||
// leaf — no `use_alias` flag needed. So the cstage L1722-class bug
|
||||
// (promotion missing use_alias) is structurally non-reachable here.
|
||||
// Don't port the use_alias flag from cstage without first re-reading
|
||||
// the architecture: adding a field to `sym` changes its size and risks
|
||||
// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage
|
||||
// checkfile pass) will reconsider this when wwstage grows a real check
|
||||
// pass on the cgen path.
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
@@ -13671,6 +13685,146 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B. ABI: AX=bytes[0..7],
|
||||
// DX=bytes[8..15], CX=bytes[16..23]. Mirrors cstage cgen.c
|
||||
// N_RETURN TY_STRUCT branch. Two rhs shapes are wired:
|
||||
// N_IDENT (word-copy from rhs local slot) and N_STRUCTLIT
|
||||
// (field-by-field store at scratch+foff, with tagged fields
|
||||
// delegated to cgwidentaggedstore). Call-result chain return
|
||||
// is deferred to #5's receive side. Sizes > 24B fall through
|
||||
// to the scalar path below (only AX gets the first qword),
|
||||
// pending sret.
|
||||
let rname: str;
|
||||
rname.ptr = nil; rname.len = 0;
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
rname = c.fnret.str;
|
||||
};
|
||||
};
|
||||
if (rname.len > 0) {
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
let rsz: i32 = rsi.totsize;
|
||||
if (rsz <= 24) {
|
||||
let okrhs: bool = false;
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (okrhs) {
|
||||
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_STRUCTLIT) {
|
||||
let fn_: *node = rhs.list;
|
||||
for (fn_ != nil) {
|
||||
if (fn_.kind == nkind.N_FIELD) {
|
||||
let fi: *fieldinfo = rsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fn_.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
cgwidentaggedstore(c,
|
||||
fi.tnode, fn_.lhs,
|
||||
"BP",
|
||||
scroff + fi.foff,
|
||||
fi.fsz);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fn_.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fn_ = fn_.next;
|
||||
};
|
||||
} else {
|
||||
// N_IDENT: word-copy from rhs slot
|
||||
// to scratch. Whole 8B words via
|
||||
// MOVQ; tail via MOVL/MOVB so we
|
||||
// read no further than the source
|
||||
// slot's declared size.
|
||||
let rl: *local = localfindnode(c, rhs.str);
|
||||
if (rl != nil) {
|
||||
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
|
||||
@@ -14844,6 +14998,40 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B uses @retscr — when
|
||||
// the function's return type is a registered TY_STRUCT of
|
||||
// size <= 24 and the return rhs is N_IDENT or N_STRUCTLIT,
|
||||
// cgreturn materialises in @retscr then loads AX/DX/CX.
|
||||
// Mirrors cstage cgen.c which allocates the scratch slot
|
||||
// inline; here we must pre-reserve so the prologue SUBQ
|
||||
// reserves enough frame.
|
||||
if (n.kind == nkind.N_RETURN) {
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
let rname: str = c.fnret.str;
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
if (rsi.totsize <= 24) {
|
||||
let rhs: *node = n.lhs;
|
||||
let okrhs: bool = false;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
};
|
||||
if (okrhs) {
|
||||
if (!scanseenmark(c, "@retscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Call-site struct-payload widening uses @tagscr — when the
|
||||
// arg is a struct literal/ident and the callee's param is
|
||||
// tagged, pushargsrev materialises in scratch and pushes.
|
||||
|
||||
@@ -302,6 +302,40 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B uses @retscr — when
|
||||
// the function's return type is a registered TY_STRUCT of
|
||||
// size <= 24 and the return rhs is N_IDENT or N_STRUCTLIT,
|
||||
// cgreturn materialises in @retscr then loads AX/DX/CX.
|
||||
// Mirrors cstage cgen.c which allocates the scratch slot
|
||||
// inline; here we must pre-reserve so the prologue SUBQ
|
||||
// reserves enough frame.
|
||||
if (n.kind == nkind.N_RETURN) {
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
let rname: str = c.fnret.str;
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
if (rsi.totsize <= 24) {
|
||||
let rhs: *node = n.lhs;
|
||||
let okrhs: bool = false;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
};
|
||||
if (okrhs) {
|
||||
if (!scanseenmark(c, "@retscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Call-site struct-payload widening uses @tagscr — when the
|
||||
// arg is a struct literal/ident and the callee's param is
|
||||
// tagged, pushargsrev materialises in scratch and pushes.
|
||||
|
||||
@@ -253,6 +253,146 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B. ABI: AX=bytes[0..7],
|
||||
// DX=bytes[8..15], CX=bytes[16..23]. Mirrors cstage cgen.c
|
||||
// N_RETURN TY_STRUCT branch. Two rhs shapes are wired:
|
||||
// N_IDENT (word-copy from rhs local slot) and N_STRUCTLIT
|
||||
// (field-by-field store at scratch+foff, with tagged fields
|
||||
// delegated to cgwidentaggedstore). Call-result chain return
|
||||
// is deferred to #5's receive side. Sizes > 24B fall through
|
||||
// to the scalar path below (only AX gets the first qword),
|
||||
// pending sret.
|
||||
let rname: str;
|
||||
rname.ptr = nil; rname.len = 0;
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
rname = c.fnret.str;
|
||||
};
|
||||
};
|
||||
if (rname.len > 0) {
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
let rsz: i32 = rsi.totsize;
|
||||
if (rsz <= 24) {
|
||||
let okrhs: bool = false;
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (okrhs) {
|
||||
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_STRUCTLIT) {
|
||||
let fn_: *node = rhs.list;
|
||||
for (fn_ != nil) {
|
||||
if (fn_.kind == nkind.N_FIELD) {
|
||||
let fi: *fieldinfo = rsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fn_.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
cgwidentaggedstore(c,
|
||||
fi.tnode, fn_.lhs,
|
||||
"BP",
|
||||
scroff + fi.foff,
|
||||
fi.fsz);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fn_.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fn_ = fn_.next;
|
||||
};
|
||||
} else {
|
||||
// N_IDENT: word-copy from rhs slot
|
||||
// to scratch. Whole 8B words via
|
||||
// MOVQ; tail via MOVL/MOVB so we
|
||||
// read no further than the source
|
||||
// slot's declared size.
|
||||
let rl: *local = localfindnode(c, rhs.str);
|
||||
if (rl != nil) {
|
||||
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
|
||||
|
||||
@@ -4830,6 +4830,20 @@ fn declmod(file: *node, d: *node) str = {
|
||||
// installdecl — install the top-level decl's name into the top scope.
|
||||
// We don't compute its type yet (that's the resolve pass) — just bind
|
||||
// the name so forward references resolve.
|
||||
//
|
||||
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
||||
// promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any
|
||||
// same-leaf SK_TYPE/SK_FN/SK_DEF/SK_VAR live as separate entries in
|
||||
// the same scope-bucket, distinguished by `sym.mod`. The dot-prefix
|
||||
// lookup in resolvewalk + scopelookupinmodule's mod-filter already
|
||||
// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same
|
||||
// leaf — no `use_alias` flag needed. So the cstage L1722-class bug
|
||||
// (promotion missing use_alias) is structurally non-reachable here.
|
||||
// Don't port the use_alias flag from cstage without first re-reading
|
||||
// the architecture: adding a field to `sym` changes its size and risks
|
||||
// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage
|
||||
// checkfile pass) will reconsider this when wwstage grows a real check
|
||||
// pass on the cgen path.
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
@@ -13671,6 +13685,146 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B. ABI: AX=bytes[0..7],
|
||||
// DX=bytes[8..15], CX=bytes[16..23]. Mirrors cstage cgen.c
|
||||
// N_RETURN TY_STRUCT branch. Two rhs shapes are wired:
|
||||
// N_IDENT (word-copy from rhs local slot) and N_STRUCTLIT
|
||||
// (field-by-field store at scratch+foff, with tagged fields
|
||||
// delegated to cgwidentaggedstore). Call-result chain return
|
||||
// is deferred to #5's receive side. Sizes > 24B fall through
|
||||
// to the scalar path below (only AX gets the first qword),
|
||||
// pending sret.
|
||||
let rname: str;
|
||||
rname.ptr = nil; rname.len = 0;
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
rname = c.fnret.str;
|
||||
};
|
||||
};
|
||||
if (rname.len > 0) {
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
let rsz: i32 = rsi.totsize;
|
||||
if (rsz <= 24) {
|
||||
let okrhs: bool = false;
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (okrhs) {
|
||||
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_STRUCTLIT) {
|
||||
let fn_: *node = rhs.list;
|
||||
for (fn_ != nil) {
|
||||
if (fn_.kind == nkind.N_FIELD) {
|
||||
let fi: *fieldinfo = rsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fn_.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
cgwidentaggedstore(c,
|
||||
fi.tnode, fn_.lhs,
|
||||
"BP",
|
||||
scroff + fi.foff,
|
||||
fi.fsz);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fn_.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((scroff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fn_ = fn_.next;
|
||||
};
|
||||
} else {
|
||||
// N_IDENT: word-copy from rhs slot
|
||||
// to scratch. Whole 8B words via
|
||||
// MOVQ; tail via MOVL/MOVB so we
|
||||
// read no further than the source
|
||||
// slot's declared size.
|
||||
let rl: *local = localfindnode(c, rhs.str);
|
||||
if (rl != nil) {
|
||||
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
|
||||
@@ -14844,6 +14998,40 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Whole-struct return for sizes <= 24B uses @retscr — when
|
||||
// the function's return type is a registered TY_STRUCT of
|
||||
// size <= 24 and the return rhs is N_IDENT or N_STRUCTLIT,
|
||||
// cgreturn materialises in @retscr then loads AX/DX/CX.
|
||||
// Mirrors cstage cgen.c which allocates the scratch slot
|
||||
// inline; here we must pre-reserve so the prologue SUBQ
|
||||
// reserves enough frame.
|
||||
if (n.kind == nkind.N_RETURN) {
|
||||
if (c.fnret != nil) {
|
||||
if (c.fnret.kind == nkind.N_TNAME) {
|
||||
let rname: str = c.fnret.str;
|
||||
let rsi: *structinfo = structlookup(c, rname);
|
||||
if (rsi != nil) {
|
||||
if (rsi.totsize <= 24) {
|
||||
let rhs: *node = n.lhs;
|
||||
let okrhs: bool = false;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
okrhs = true;
|
||||
};
|
||||
if (rhs.kind == nkind.N_STRUCTLIT) {
|
||||
okrhs = true;
|
||||
};
|
||||
};
|
||||
if (okrhs) {
|
||||
if (!scanseenmark(c, "@retscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Call-site struct-payload widening uses @tagscr — when the
|
||||
// arg is a struct literal/ident and the callee's param is
|
||||
// tagged, pushargsrev materialises in scratch and pushes.
|
||||
|
||||
Reference in New Issue
Block a user