selfhost+test: decompose user-struct by-value params (#11)
wwstage param-slot allocator dispatched isfloat/istagged/isslice/
isstr/catch-all and skipped TY_STRUCT. `fn(a: S, b: S)` where S is
16B emitted $16 frame (DI/SI only); cstage emits $32 (DI/SI/DX/CX)
per SysV ABI.
Two-site fix mirroring cmd/w6c/cgen.c:6820 (callee prologue) and
:4240 (caller push):
- New structparamsize(c, t) helper in cgenutil.ww resolves the
TY_STRUCT TNAME chain, returns totsize for sizes (0,16], else 0.
>16B drops to stack — bug-compat with cstage's <=16 gate.
- New struct arm in cgfnparams + matching cgfn pre-scan in
cgendecl.ww. nw = (size>8) ? 2 : 1; partial-fit stitch (idx=5
+ nw=2) emits one reg + one stack tail.
- New struct branch in pushargsrev N_IDENT arm: MOVQ + PUSHQ
high→low so cgcall's existing pop drains correctly.
Test 717: 4 rows × {cstage, wwstage, asm-id}. Headline 2×16B,
mixed 16B+8B (caller-side surface), str+struct regression guard,
partial-fit 5×i64+16B stitch.
This commit is contained in:
@@ -6667,6 +6667,32 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
};
|
||||
return rest + nwords;
|
||||
};
|
||||
// By-value struct ident: load qword(s) from the slot
|
||||
// and push high → low so left-to-right pop on the
|
||||
// callee side lands word 0 / word 1 into the SysV arg
|
||||
// register pair. Mirrors cstage cgen.c §4240 (call
|
||||
// site) so the wwstage prologue's new struct spill arm
|
||||
// (cgendecl.ww structparamsize branch) sees the same
|
||||
// reg layout. Pre-#11 the call-site fell through to
|
||||
// `cgexpr(c, arg)` + scalar PUSHQ AX — only the first
|
||||
// 8B word made it across, and the callee's second-arg
|
||||
// slots picked up the wrong neighbour's value.
|
||||
let stsz: i32 = structparamsize(c, lc.tnode);
|
||||
if (stsz > 0) {
|
||||
if (stsz > 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let nw: i32 = 1;
|
||||
if (stsz > 8) { nw = 2; };
|
||||
return rest + nw;
|
||||
};
|
||||
};
|
||||
};
|
||||
// Float arg: cgexpr leaves the value in X0. Push 8 bytes from
|
||||
@@ -8259,6 +8285,30 @@ fn matchspillsz(c: *cgen, scrutt: *node) i32 = {
|
||||
return sz;
|
||||
};
|
||||
|
||||
// structparamsize — bytes occupied by a user-defined by-value struct
|
||||
// param if it fits in 1-2 SysV integer eightbytes (cstage cgen.c
|
||||
// struct_arg_size mirror; gates on size <= 16). Returns 0 for non-
|
||||
// struct types or oversized structs so callers can fall through to
|
||||
// other dispatch arms. Pre-#11 the wwstage prologue had no struct
|
||||
// branch — user-defined struct params dropped through to the 8B
|
||||
// scalar catch-all, the second-half value registers (DX/CX) were
|
||||
// never spilled, and field reads from the under-allocated slot
|
||||
// trailed into the saved-BP word.
|
||||
fn structparamsize(c: *cgen, t: *node) i32 = {
|
||||
if (c == nil) { return 0; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return 0; };
|
||||
if (r.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = r.str;
|
||||
if (streq(nm, "str")) { return 0; };
|
||||
if (primsize(nm) > 0) { return 0; };
|
||||
let si: *structinfo = structlookup(c, nm);
|
||||
if (si == nil) { return 0; };
|
||||
if (si.totsize <= 0) { return 0; };
|
||||
if (si.totsize > 16) { return 0; };
|
||||
return si.totsize;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
@@ -17206,18 +17256,66 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
||||
stkcursor += 2;
|
||||
};};
|
||||
} else {
|
||||
if (idx < 6) {
|
||||
let off: i32 = localadd(c, nm, 8, p.lhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(argregname(idx));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
} else { let stsz: i32 = structparamsize(c, p.lhs);
|
||||
if (stsz > 0) {
|
||||
// User-defined by-value struct ≤ 16B: 1 or 2
|
||||
// integer eightbytes. Mirrors cstage's
|
||||
// `struct_eb = (pu->size > 8) ? 2 : 1` and the
|
||||
// matching reg/stack/stitch arms in cgen.c cgfn.
|
||||
let nw: i32 = 1;
|
||||
if (stsz > 8) { nw = 2; };
|
||||
if (idx + nw <= 6) {
|
||||
let off: i32 = localadd(c, nm, stsz, p.lhs);
|
||||
let w: i32 = 0;
|
||||
for (w < nw) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(argregname(idx));
|
||||
emitline(", ");
|
||||
emitoff((off + w*8): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
w += 1;
|
||||
};
|
||||
} else { if (idx < 6 && nw > 1) {
|
||||
let off: i32 = localadd(c, nm, stsz, p.lhs);
|
||||
let regs_left: i32 = 6 - idx;
|
||||
let w: i32 = 0;
|
||||
for (w < regs_left) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(argregname(idx));
|
||||
emitline(", ");
|
||||
emitoff((off + w*8): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
w += 1;
|
||||
};
|
||||
for (w < nw) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((16 + stkcursor*8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + w*8): i64);
|
||||
emitline("(BP)\n");
|
||||
stkcursor += 1;
|
||||
w += 1;
|
||||
};
|
||||
} else {
|
||||
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
||||
stkcursor += nw;
|
||||
};};
|
||||
} else {
|
||||
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
||||
stkcursor += 1;
|
||||
if (idx < 6) {
|
||||
let off: i32 = localadd(c, nm, 8, p.lhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(argregname(idx));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
} else {
|
||||
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
||||
stkcursor += 1;
|
||||
};
|
||||
};
|
||||
};};};
|
||||
};
|
||||
@@ -17260,12 +17358,18 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
let istg: bool = false;
|
||||
let issl: bool = false;
|
||||
let isst: bool = false;
|
||||
let isstruct: bool = false;
|
||||
let structsz: i32 = 0;
|
||||
if (!isvar) {
|
||||
isf = isfloattype(c, scanp.lhs);
|
||||
istg = istaggedtype(c, scanp.lhs);
|
||||
if (!isf && !istg) {
|
||||
issl = isslicetype(c, scanp.lhs);
|
||||
if (!issl) { isst = isstrtype(c, scanp.lhs); };
|
||||
if (!issl && !isst) {
|
||||
structsz = structparamsize(c, scanp.lhs);
|
||||
isstruct = structsz > 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
let eb: i32 = 1;
|
||||
@@ -17274,18 +17378,23 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
else { if (istg) { sz = slotsize(c, scanp.lhs); eb = sz / 8; }
|
||||
else { if (issl) { eb = 3; sz = 24; }
|
||||
else { if (isst) { eb = 2; sz = 16; }
|
||||
else { if (isstruct) {
|
||||
sz = structsz;
|
||||
eb = 1;
|
||||
if (structsz > 8) { eb = 2; };
|
||||
}
|
||||
else { if (isf) {
|
||||
eb = 1;
|
||||
sz = 8;
|
||||
if (isf32type(c, scanp.lhs)) { sz = 4; };
|
||||
}; }; }; }; };
|
||||
}; }; }; }; }; };
|
||||
let regs_left: i32 = 6 - argi;
|
||||
if (isf) { regs_left = 8 - fargi; };
|
||||
if (regs_left >= eb) {
|
||||
frame += sz;
|
||||
if (isf) { fargi += 1; }
|
||||
else { argi += eb; };
|
||||
} else { if (eb > 1 && regs_left > 0 && (istg || issl || isst || isvar)) {
|
||||
} else { if (eb > 1 && regs_left > 0 && (istg || issl || isst || isstruct || isvar)) {
|
||||
// Multi-word param straddles the reg/stack boundary;
|
||||
// cgfnparams stitches the tail from positive BP
|
||||
// offsets into a single local slot, so we still
|
||||
|
||||
Reference in New Issue
Block a user