The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the compiler's <stem>.sepwork scratch landed beside the source and was never cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs and fabricates phantom test failures + silent harness aborts, and for in-repo fixture builds leaked .sepwork into the tracked tree. Each leaking build now writes its source + output inside a per-invocation tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and rm -rf's the tmpdir on every exit path -- including fopen-fail and the expected-fail reject builds (scratch is mkdir'd before the build can fail). `ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993 byte-id comparison logic is byte-for-byte unchanged. Two items filed separately (this commit holds the no-Makefile / no-main.c rail): - #13: a stale <src>.s byte-id readback (749) silently no-ops since separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline. - #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no -o and leak main.sepwork in-tree (bounded, gitignored; own commit). One concern -- sepwork leak hygiene -- across 228 drivers; uniform transform applied per-file and two-round reviewed. make test: all 402 passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
368 lines
13 KiB
C
368 lines
13 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
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 tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfa_%d_d_%d", getpid(), i);
|
|
mkdir(tmpdir, 0755);
|
|
snprintf(src, sizeof src, "%s/wwfa_%d_%d.ww", tmpdir, getpid(), i);
|
|
snprintf(outbin, sizeof outbin, "%s/wwfa_%d_%d", tmpdir, getpid(), i);
|
|
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) { runwait(rmcmd); return -1; }
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
|
r->label, driver);
|
|
runwait(rmcmd);
|
|
return -1;
|
|
}
|
|
|
|
int got = runwait(outbin);
|
|
|
|
runwait(rmcmd);
|
|
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;
|
|
}
|