From 5b098c4a9953ffbd87e37111d8703ff9a872219e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 22:53:50 +0900 Subject: [PATCH] selfhost: cgfnparams slice/str straddle stitch (closes #11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- selfhost/cmd/w6c/main.combined.ww | 71 ++++++++++++++++--- selfhost/cmd/wcc/cgendecl.ww | 71 ++++++++++++++++--- selfhost/cmd/wwdump/main.combined.ww | 71 ++++++++++++++++--- test/wcc/670_frame_argcount.c | 102 +++++++++++++++++++++++---- 4 files changed, 279 insertions(+), 36 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b009c840..3cf44df8 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 { diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index b82461a6..12770ddb 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -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 { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 7bb6fac8..7c3e2bfe 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 { diff --git a/test/wcc/670_frame_argcount.c b/test/wcc/670_frame_argcount.c index 11f5b802..5e4f806b 100644 --- a/test/wcc/670_frame_argcount.c +++ b/test/wcc/670_frame_argcount.c @@ -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"