selfhost: cgfnparams slice/str straddle stitch (closes #11)

Three branches (tagged/slice/str) in cgendecl.ww now structurally
symmetric — partial-fit reg+stack stitching. cgfn pre-scan predicate
widened to (istg || issl || isst). Variadic T... at straddle is the
last open class, deferred to task #12.
This commit is contained in:
2026-05-13 22:53:50 +09:00
parent 5c8724845a
commit 5b098c4a99
4 changed files with 279 additions and 36 deletions

View File

@@ -13606,10 +13606,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Caller's pushargsrev greedy-fills the
// remaining argregs (ptr,len,cap order), the tail
// spills to +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 24, 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 < 3) {
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 += 3;
};
};};
} else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs);
@@ -13625,10 +13652,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, 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 < 2) {
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 += 2;
};
};};
} else {
if (idx < 6) {
let off: i32 = localadd(c, nm, 8, p.lhs);
@@ -13725,14 +13779,15 @@ fn cgfn(c: *cgen, fn_: *node) void = {
frame += sz;
if (isf) { fargi += 1; }
else { argi += eb; };
} else { if (eb > 1 && regs_left > 0 && istg) {
// Tagged param straddles the reg/stack boundary;
} else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) {
// Multi-word param straddles the reg/stack boundary;
// cgfnparams stitches the tail from positive BP
// offsets into a single local slot, so we still
// reserve the full size. Slice/str at the same
// straddle are *not* stitched by cgfnparams today
// (task #11) — once that's fixed the predicate
// here must widen symmetrically.
// reserve the full size. Symmetric across tagged,
// slice and str (cgendecl.ww:440/491/537). Variadic
// `T...` is deliberately omitted (task #12) — the
// cgfnparams variadic branch doesn't stitch yet, so
// it falls through to the pure-stack accounting.
frame += sz;
argi = 6;
} else {

View File

@@ -488,10 +488,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Caller's pushargsrev greedy-fills the
// remaining argregs (ptr,len,cap order), the tail
// spills to +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 24, 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 < 3) {
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 += 3;
};
};};
} else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs);
@@ -507,10 +534,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, 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 < 2) {
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 += 2;
};
};};
} else {
if (idx < 6) {
let off: i32 = localadd(c, nm, 8, p.lhs);
@@ -607,14 +661,15 @@ fn cgfn(c: *cgen, fn_: *node) void = {
frame += sz;
if (isf) { fargi += 1; }
else { argi += eb; };
} else { if (eb > 1 && regs_left > 0 && istg) {
// Tagged param straddles the reg/stack boundary;
} else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) {
// Multi-word param straddles the reg/stack boundary;
// cgfnparams stitches the tail from positive BP
// offsets into a single local slot, so we still
// reserve the full size. Slice/str at the same
// straddle are *not* stitched by cgfnparams today
// (task #11) — once that's fixed the predicate
// here must widen symmetrically.
// reserve the full size. Symmetric across tagged,
// slice and str (cgendecl.ww:440/491/537). Variadic
// `T...` is deliberately omitted (task #12) — the
// cgfnparams variadic branch doesn't stitch yet, so
// it falls through to the pure-stack accounting.
frame += sz;
argi = 6;
} else {

View File

@@ -13606,10 +13606,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Caller's pushargsrev greedy-fills the
// remaining argregs (ptr,len,cap order), the tail
// spills to +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 24, 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 < 3) {
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 += 3;
};
};};
} else { if (isstrtype(c, p.lhs)) {
if (idx + 2 <= 6) {
let off: i32 = localadd(c, nm, 16, p.lhs);
@@ -13625,10 +13652,37 @@ fn cgfnparams(c: *cgen, params: *node) void = {
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
} else { if (idx < 6) {
// Partial-fit stitch — mirrors tagged at lines
// 440-469. Only idx=5 hits this (nw=2,
// regs_left=1): ptr lands in R9, len at
// +16+stkcursor*8(BP).
let off: i32 = localadd(c, nm, 16, 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 < 2) {
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 += 2;
};
};};
} else {
if (idx < 6) {
let off: i32 = localadd(c, nm, 8, p.lhs);
@@ -13725,14 +13779,15 @@ fn cgfn(c: *cgen, fn_: *node) void = {
frame += sz;
if (isf) { fargi += 1; }
else { argi += eb; };
} else { if (eb > 1 && regs_left > 0 && istg) {
// Tagged param straddles the reg/stack boundary;
} else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) {
// Multi-word param straddles the reg/stack boundary;
// cgfnparams stitches the tail from positive BP
// offsets into a single local slot, so we still
// reserve the full size. Slice/str at the same
// straddle are *not* stitched by cgfnparams today
// (task #11) — once that's fixed the predicate
// here must widen symmetrically.
// reserve the full size. Symmetric across tagged,
// slice and str (cgendecl.ww:440/491/537). Variadic
// `T...` is deliberately omitted (task #12) — the
// cgfnparams variadic branch doesn't stitch yet, so
// it falls through to the pure-stack accounting.
frame += sz;
argi = 6;
} else {

View File

@@ -101,20 +101,98 @@ static const struct row rows[] = {
" return 0;\n"
"};\n",
42 },
/* Slice at the reg/stack straddle: 4 i64 + 1 slice. idx=4
* entering slice (regs_left=2, nw=3). ptr+len go to R8+R9,
* cap spills to +16(BP); cgfnparams stitches into one slot.
* Used to segfault before task #11. */
{ "slice_straddle_arg5",
"fn ss(a: i64, b: i64, c: i64, d: i64, xs: []i64) i64 = {\n"
" let i: i32 = 0;\n"
" let acc: i64 = a + b + c + d;\n"
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
" return acc + (xs.cap: i64);\n"
"};\n"
"fn main() i32 = {\n"
" let buf: [3]i64;\n"
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
" let s: []i64 = buf[0:3];\n"
" let r: i64 = ss(1i64, 2i64, 3i64, 4i64, s);\n"
" if (r == 613i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* str at the reg/stack straddle: 5 i64 + 1 str. idx=5 entering
* str (regs_left=1, nw=2). ptr lands in R9, len spills to
* +16(BP). Used to read garbage for len/ptr. */
{ "str_straddle_arg6",
"fn ts(a: i64, b: i64, c: i64, d: i64, e: i64, s: str) i64 = {\n"
" return a + b + c + d + e + s.len: i64;\n"
"};\n"
"fn main() i32 = {\n"
" let r: i64 = ts(1i64, 2i64, 3i64, 4i64, 5i64, \"hello\");\n"
" if (r == 20i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Straddle followed by a scalar: 4 i64 + 1 slice + 1 i64.
* Pins stkcursor advancing correctly past the stitched cap
* word — the trailing i64 must land in R9, not back on stack. */
{ "slice_straddle_then_scalar",
"fn ssx(a: i64, b: i64, c: i64, d: i64, xs: []i64, tail: i64) i64 = {\n"
" let i: i32 = 0;\n"
" let acc: i64 = a + b + c + d + tail;\n"
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
" return acc;\n"
"};\n"
"fn main() i32 = {\n"
" let buf: [3]i64;\n"
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
" let s: []i64 = buf[0:3];\n"
" let r: i64 = ssx(1i64, 2i64, 3i64, 4i64, s, 99i64);\n"
" if (r == 709i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Slice at the *narrow* end of the straddle: 5 i64 + 1 slice.
* idx=5 entering slice (regs_left=1, nw=3) — ptr lands in R9,
* len and cap both spill to +16/+24(BP). Complement of
* slice_straddle_arg5 which exercises regs_left=2/nw=3. */
{ "slice_straddle_arg6",
"fn ss(a: i64, b: i64, c: i64, d: i64, e: i64, xs: []i64) i64 = {\n"
" let i: i32 = 0;\n"
" let acc: i64 = a + b + c + d + e;\n"
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
" return acc + (xs.cap: i64);\n"
"};\n"
"fn main() i32 = {\n"
" let buf: [3]i64;\n"
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
" let s: []i64 = buf[0:3];\n"
" let r: i64 = ss(1i64, 2i64, 3i64, 4i64, 5i64, s);\n"
" if (r == 618i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Str at the *wide* end before the straddle: 4 i64 + 1 str.
* idx=4 entering str (regs_left=2, nw=2) — full fit in R8+R9,
* no stitch needed. Complement of str_straddle_arg6 which
* exercises regs_left=1/nw=2; pins that the in-reg branch is
* untouched by the new stitch code. */
{ "str_inreg_arg5",
"fn ts(a: i64, b: i64, c: i64, d: i64, s: str) i64 = {\n"
" return a + b + c + d + s.len: i64;\n"
"};\n"
"fn main() i32 = {\n"
" let r: i64 = ts(1i64, 2i64, 3i64, 4i64, \"hello\");\n"
" if (r == 15i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Slice past the register window: 6 ints saturate argi, the
* slice (3 eightbytes) goes wholly to the stack at positive BP
* offsets via localaddstack. Verifies every element of the
* slice round-trips through a stack-passed slice arg.
*
* NOTE: a slice at the *straddle* boundary (arg #6 after 5
* ints, so 1 reg + 2 stack words) currently mismatches between
* pushargsrev (greedy reg fill) and cgfnparams (no stitch for
* slice/str — only tagged). That's a separate pre-existing
* cgfnparams bug, tracked as task #11 (wwstage cgfnparams:
* slice/str at reg/stack straddle don't stitch). Once #11 is
* fixed, the cgfn pre-scan must be updated symmetrically and
* this fixture extended to cover the straddle case.
*/
* offsets via localaddstack. Pure-stack regression check —
* the existing pure-stack branch should be untouched by the
* straddle stitch. */
{ "slice_purestack_arg7",
"fn ss(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, xs: []i64) i64 = {\n"
" let i: i32 = 0;\n"