/* * 670_frame_argcount — function-frame size for functions with more * than six register-class args. wwstage's cgfn pre-scan used to add * a full slot per param regardless of class, then round up — so a * 7-arg i64 fn over-allocated by 16B (cstage $48, wwstage $64) and * broke the bootstrap byte-identity gate (tests 993/994/995). Fix: * pre-scan now mirrors cgfnparams' SysV class accounting (and cstage * cgen.c §5130-5223), so a stack-spilled param adds 0 to the frame * and never bumps the reg cursor. * * Each row asserts value correctness. Byte-identity is asserted at * the bootstrap level by 993/994/995, so this file pins the runtime * contract: a function that mixes reg and stack args must compute * the same answer in both stages. */ #include #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } struct row { const char *label; const char *src; int want; }; static const struct row rows[] = { /* 7-arg i64: 6 regs + 1 stack. Pins the frame size that used to * over-allocate by 16B in wwstage. Returns the sum (1..7 = 28). */ { "i64_7args", "fn s7(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, g: i64) i64 = {\n" " return a + b + c + d + e + f + g;\n" "};\n" "fn main() i32 = {\n" " let r: i64 = s7(1i64, 2i64, 3i64, 4i64, 5i64, 6i64, 7i64);\n" " if (r == 28i64) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* 8-arg i64: 6 regs + 2 stack. Stack-cursor advances twice. */ { "i64_8args", "fn s8(a: i64, b: i64, c: i64, d: i64, e: i64,\n" " f: i64, g: i64, h: i64) i64 = {\n" " return a + b + c + d + e + f + g + h;\n" "};\n" "fn main() i32 = {\n" " let r: i64 = s8(1i64, 2i64, 3i64, 4i64,\n" " 5i64, 6i64, 7i64, 8i64);\n" " if (r == 36i64) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Mixed-signed sum: an i64 thread through 7 args. */ { "i64_7args_signed", "fn s7(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, g: i64) i64 = {\n" " return a + b + c + d + e + f + g;\n" "};\n" "fn main() i32 = {\n" " let r: i64 = s7(-1i64, -2i64, -3i64, -4i64, -5i64, -6i64, 49i64);\n" " if (r == 28i64) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* 9-arg f64: 8 XMM regs + 1 stack. Exercises fargi past 8. */ { "f64_9args", "fn s9f(a: f64, b: f64, c: f64, d: f64, e: f64,\n" " f: f64, g: f64, h: f64, i: f64) f64 = {\n" " return a + b + c + d + e + f + g + h + i;\n" "};\n" "fn main() i32 = {\n" " let r: f64 = s9f(1.0, 2.0, 3.0, 4.0, 5.0,\n" " 6.0, 7.0, 8.0, 9.0);\n" " if ((r: i64) == 45i64) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Mixed: 5 ints fill ints 1..5, 3 floats fill XMM 1..3, then 4 * more ints — int #6 goes to reg, ints #7..9 spill to stack. The * int and float classes have independent cursors, so XMM stays * at 3 even after the trailing 4 ints. */ { "mixed_5i_3f_4i", "fn mx(a: i64, b: i64, c: i64, d: i64, e: i64,\n" " f1: f64, f2: f64, f3: f64,\n" " g: i64, h: i64, i: i64, j: i64) i64 = {\n" " return a + b + c + d + e\n" " + (f1: i64) + (f2: i64) + (f3: i64)\n" " + g + h + i + j;\n" "};\n" "fn main() i32 = {\n" " let r: i64 = mx(1i64, 2i64, 3i64, 4i64, 5i64,\n" " 6.0, 7.0, 8.0,\n" " 9i64, 10i64, 11i64, 12i64);\n" " if (r == 78i64) { return 42; };\n" " 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. 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" " 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;\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, 6i64, s);\n" " if (r == 621i64) { return 42; };\n" " 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. */ { "str_purestack_arg7", "fn ts(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, s: str) i64 = {\n" " return a + b + c + d + e + f + s.len: i64;\n" "};\n" "fn main() i32 = {\n" " let r: i64 = ts(1i64, 2i64, 3i64, 4i64, 5i64, 6i64, \"hello\");\n" " if (r == 26i64) { return 42; };\n" " return 0;\n" "};\n", 42 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/wwfa_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfa_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "frame_argcount: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "frame_argcount[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "frame_argcount: %d/%d fixtures failed\n", fail, total); return 1; } printf("frame_argcount: %d/%d ok\n", total, total); return 0; }