From f26c86f5b7a1756156ad3661917dbbe7094f38f9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 14 May 2026 00:36:34 +0900 Subject: [PATCH] selfhost: cgfnparams variadic T... straddle stitch (closes #12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last reg/stack-straddle gap closed — variadic T... now stitches at idx=5/regs_left=1/nw=3. Four classes (tagged/slice/str/variadic) at cgendecl.ww 467/518/564/394 are now structurally symmetric. cgfn pre-scan predicate widened to (istg || issl || isst || isvar). cstage unchanged — type-promoted T... → []T already hits the slice partial-fit branch. --- selfhost/cmd/w6c/main.combined.ww | 37 ++++++++++++++--- selfhost/cmd/wcc/cgendecl.ww | 37 ++++++++++++++--- selfhost/cmd/wwdump/main.combined.ww | 37 ++++++++++++++--- test/wcc/670_frame_argcount.c | 59 ++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 18 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 06cedb14..486a2cd6 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -13910,10 +13910,37 @@ fn cgfnparams(c: *cgen, params: *node) void = { emitoff((off + 16): i64); emitline("(BP)\n"); idx += 1; + } else { if (idx < 6) { + // Partial-fit stitch — variadic `T...` is a slice + // at the ABI boundary (the call site synthesises a + // 24B descriptor and pushes ptr/len/cap), so this + // mirrors the slice branch at cgendecl.ww:518. + let off: i32 = localadd(c, nm, 24, tn); + 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, tn, 16 + stkcursor*8); stkcursor += 3; - }; + };}; p = p.next; continue; }; @@ -14180,15 +14207,13 @@ fn cgfn(c: *cgen, fn_: *node) void = { frame += sz; if (isf) { fargi += 1; } else { argi += eb; }; - } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) { + } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst || 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 // 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. + // slice, str and variadic `T...` + // (cgendecl.ww:467/518/564/394). frame += sz; argi = 6; } else { diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 12770ddb..e10fc09f 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -391,10 +391,37 @@ fn cgfnparams(c: *cgen, params: *node) void = { emitoff((off + 16): i64); emitline("(BP)\n"); idx += 1; + } else { if (idx < 6) { + // Partial-fit stitch — variadic `T...` is a slice + // at the ABI boundary (the call site synthesises a + // 24B descriptor and pushes ptr/len/cap), so this + // mirrors the slice branch at cgendecl.ww:518. + let off: i32 = localadd(c, nm, 24, tn); + 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, tn, 16 + stkcursor*8); stkcursor += 3; - }; + };}; p = p.next; continue; }; @@ -661,15 +688,13 @@ fn cgfn(c: *cgen, fn_: *node) void = { frame += sz; if (isf) { fargi += 1; } else { argi += eb; }; - } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) { + } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst || 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 // 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. + // slice, str and variadic `T...` + // (cgendecl.ww:467/518/564/394). frame += sz; argi = 6; } else { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 20ef4805..55cec0f3 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -13910,10 +13910,37 @@ fn cgfnparams(c: *cgen, params: *node) void = { emitoff((off + 16): i64); emitline("(BP)\n"); idx += 1; + } else { if (idx < 6) { + // Partial-fit stitch — variadic `T...` is a slice + // at the ABI boundary (the call site synthesises a + // 24B descriptor and pushes ptr/len/cap), so this + // mirrors the slice branch at cgendecl.ww:518. + let off: i32 = localadd(c, nm, 24, tn); + 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, tn, 16 + stkcursor*8); stkcursor += 3; - }; + };}; p = p.next; continue; }; @@ -14180,15 +14207,13 @@ fn cgfn(c: *cgen, fn_: *node) void = { frame += sz; if (isf) { fargi += 1; } else { argi += eb; }; - } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst)) { + } else { if (eb > 1 && regs_left > 0 && (istg || issl || isst || 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 // 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. + // slice, str and variadic `T...` + // (cgendecl.ww:467/518/564/394). frame += sz; argi = 6; } else { diff --git a/test/wcc/670_frame_argcount.c b/test/wcc/670_frame_argcount.c index 5e4f806b..5ef42ffb 100644 --- a/test/wcc/670_frame_argcount.c +++ b/test/wcc/670_frame_argcount.c @@ -209,6 +209,65 @@ static const struct row rows[] = { " return 0;\n" "};\n", 42 }, + /* Variadic `T...` fully in regs: 3 i64 + 1 variadic. idx=3 + * entering the synthesised slice (regs_left=3, nw=3) — DI/SI/DX + * carry a/b/c, CX/R8/R9 carry ptr/len/cap. Full-reg-fit regression: + * pins that the stitch addition doesn't shadow the `idx+3 <= 6` + * branch. */ + { "variadic_full_in_reg", + "fn vs(a: i64, b: i64, c: i64, xs: i64...) i64 = {\n" + " let i: i32 = 0;\n" + " let acc: i64 = a + b + c;\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 r: i64 = vs(1i64, 2i64, 3i64,\n" + " 10i64, 20i64, 30i64);\n" + " if (r == 69i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Variadic `T...` at the reg/stack straddle: 5 i64 + 1 variadic. + * idx=5 entering the synthesised slice (regs_left=1, nw=3) — ptr + * lands in R9, len and cap spill to +16/+24(BP). The variadic + * branch in cgfnparams must stitch identically to the slice branch + * (task #12). The body sums the fixed args, the variadic + * elements, and adds cap to pin the third header word. */ + { "variadic_straddle", + "fn vs(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 r: i64 = vs(1i64, 2i64, 3i64, 4i64, 5i64,\n" + " 10i64, 20i64, 30i64);\n" + " if (r == 78i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Variadic `T...` past the register window: 6 i64 saturate argi, + * the synthesised slice (3 eightbytes) goes wholly to the stack + * via localaddstack. Pure-stack regression check — pins that the + * new stitch code doesn't bleed into the existing pure-stack + * branch (regs_left=0, nw=3). */ + { "variadic_purestack", + "fn vs(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64,\n" + " xs: i64...) i64 = {\n" + " let i: i32 = 0;\n" + " let acc: i64 = a + b + c + d + e + f;\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 r: i64 = vs(1i64, 2i64, 3i64, 4i64, 5i64, 6i64,\n" + " 10i64, 20i64, 30i64);\n" + " if (r == 84i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, /* str past the register window: 6 ints saturate argi, the str * (2 eightbytes) goes wholly to the stack via localaddstack. * Both .ptr and .len must round-trip. */