/* * 691_dot_slice_arg — pass `.slicefield` as a call argument * (and return one too). * * Task #29: the cgen N_DOT-slice-field paths previously loaded only * .ptr into AX, leaving BX/CX stale. The slice-arg push at the call * site then pushed three words from AX/BX/CX, so .len/.cap silently * came from whatever the prior expression left in those registers. * Surfaced as `998_bufio_run`'s bstreamunread mid-read len mismatch. * * Each fixture passes (or returns) a slice header through a call and * reads .len / .cap / .ptr on the callee side to prove all three * words round-trip. Rows cover: * - single dot through `*T` root (`p.sl`) * - chained through `*T` root (`p.inner.sl`) — pins * task #22's spine walker still works with the slice-leaf fix * in place * - local value-struct root, single dot (`o.sl`) * - local value-struct root, chained (`o.inner.sl`) * - direct slice arg (no dot) — baseline / negative * control: confirms the existing fast-path still works * - slice + int neighbor arg — pins call-arg eval * order doesn't drop the integer slot beside the slice * - slice mutation through `.ptr` — pins `.ptr` half * still arrives correctly post-fix * - N_RETURN of slice N_DOT (probe 3) — exercises the same * cgen N_DOT slice-load path on the return-stmt site, not * just the call-arg site * * Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage). */ #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[] = { /* Single dot through *T root. `slen(p.sl)` should see len=5, * cap=8, ptr[0]=7 (we set arr[0]=7 in main). */ { "ptr_root_slice_arg", "type outer = struct { sl: []u8, x: i32 };\n" "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 7u8;\n" " let o: outer;\n" " o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n" " let p: *outer = &o;\n" " if (slen(p.sl) != 5) { return 1; };\n" " if (scap(p.sl) != 8) { return 2; };\n" " if (sfirst(p.sl) != 7) { return 3; };\n" " return 42;\n" "};\n", 42 }, /* Chained through *T root with a value-struct hop — the #22 * spine walker shape, leaf is a slice. */ { "ptr_root_chained_slice_arg", "type inner = struct { sl: []u8, pad: i32 };\n" "type outer = struct { i: inner, tag: i32 };\n" "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n" "fn main() i32 = {\n" " let arr: [16]u8; arr[0] = 11u8;\n" " let o: outer;\n" " o.i.sl.ptr = &arr[0]; o.i.sl.len = 9; o.i.sl.cap = 16;\n" " let p: *outer = &o;\n" " if (slen(p.i.sl) != 9) { return 1; };\n" " if (scap(p.i.sl) != 16) { return 2; };\n" " if (sfirst(p.i.sl) != 11) { return 3; };\n" " return 42;\n" "};\n", 42 }, /* Local value-struct rooted, single dot. The previous bug was * present here too — the "real struct field" cgen branch loaded * only AX. */ { "local_root_slice_arg", "type outer = struct { sl: []u8, x: i32 };\n" "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 3u8;\n" " let o: outer;\n" " o.sl.ptr = &arr[0]; o.sl.len = 4; o.sl.cap = 8;\n" " if (slen(o.sl) != 4) { return 1; };\n" " if (scap(o.sl) != 8) { return 2; };\n" " if (sfirst(o.sl) != 3) { return 3; };\n" " return 42;\n" "};\n", 42 }, /* Local value-struct rooted, chained value-struct hop. Spine * walker without ptr_root. */ { "local_root_chained_slice_arg", "type inner = struct { sl: []u8, pad: i32 };\n" "type outer = struct { i: inner, tag: i32 };\n" "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn main() i32 = {\n" " let arr: [16]u8;\n" " let o: outer;\n" " o.i.sl.ptr = &arr[0]; o.i.sl.len = 6; o.i.sl.cap = 16;\n" " if (slen(o.i.sl) != 6) { return 1; };\n" " if (scap(o.i.sl) != 16) { return 2; };\n" " return 42;\n" "};\n", 42 }, /* Chained through a `*struct` mid-hop (`o.i.sl` where `i: *inner`). * Distinct cgen site from rows 2/4: the spine walker (dotchain- * resolve) only walks value-struct intermediate hops, so a *struct * mid-hop falls through to the "chained N_DOT through *struct * field" branch (cgen.c ~4832, cgenexpr.ww ~1834) — the fourth * TY_SLICE site touched by this commit. AX is the *struct base * after `cgexpr(o.i)`, so the load order must end with .ptr → AX. */ { "ptr_field_chained_slice_arg", "type inner = struct { sl: []u8, pad: i32 };\n" "type outer = struct { i: *inner, tag: i32 };\n" "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 17u8;\n" " let inn: inner;\n" " inn.sl.ptr = &arr[0]; inn.sl.len = 3; inn.sl.cap = 8;\n" " let o: outer; o.i = &inn; o.tag = 0;\n" " if (slen(o.i.sl) != 3) { return 1; };\n" " if (scap(o.i.sl) != 8) { return 2; };\n" " if (sfirst(o.i.sl) != 17) { return 3; };\n" " return 42;\n" "};\n", 42 }, /* Negative control: direct slice arg, no dot. Pre-existing fast * path; confirms the fix didn't disturb it. */ { "direct_slice_arg_baseline", "fn slen(s: []u8) i32 = { return s.len: i32; };\n" "fn scap(s: []u8) i32 = { return s.cap: i32; };\n" "fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 9u8;\n" " let s: []u8;\n" " s.ptr = &arr[0]; s.len = 5; s.cap = 8;\n" " if (slen(s) != 5) { return 1; };\n" " if (scap(s) != 8) { return 2; };\n" " if (sfirst(s) != 9) { return 3; };\n" " return 42;\n" "};\n", 42 }, /* Mixed args: pass a slice-field next to another arg, so the * fix has to leave neighbouring arg-slot registers undisturbed. * AX/BX/CX are clobbered while building the slice; the integer * `tag` arg goes in DI on SysV, so this pins the call-arg eval * order doesn't drop the tag. */ { "ptr_root_slice_with_neighbor", "type outer = struct { sl: []u8, x: i32 };\n" "fn slen_tag(s: []u8, t: i32) i32 = { return s.len: i32 + t; };\n" "fn main() i32 = {\n" " let arr: [8]u8;\n" " let o: outer;\n" " o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n" " let p: *outer = &o;\n" " let v: i32 = slen_tag(p.sl, 37);\n" " if (v != 42) { return 1; };\n" " return 42;\n" "};\n", 42 }, /* Mutation through the passed slice: callee writes via s[0], * caller reads arr[0] after. Proves the .ptr half made it * across — even before the fix this *would have* worked (only * .len/.cap dropped), but co-testing it guards against a future * regression that swaps ptr for len. */ { "ptr_root_slice_mutate_ptr", "type outer = struct { sl: []u8, x: i32 };\n" "fn poke(s: []u8) void = { s[0] = 0x42u8; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 0u8;\n" " let o: outer;\n" " o.sl.ptr = &arr[0]; o.sl.len = 1; o.sl.cap = 8;\n" " let p: *outer = &o;\n" " poke(p.sl);\n" " if (arr[0]: i32 != 0x42) { return 1; };\n" " return 42;\n" "};\n", 42 }, /* Probe 3: N_RETURN of a slice N_DOT — a function returns * `p.sl` directly. cgen's return-stmt site also has to leave * (AX=ptr, BX=len, CX=cap), or the caller's `let s: []u8 = * fetch(p)` will lose .len/.cap. Distinct site from the call- * arg push but exercises the same cgen N_DOT slice-load path. */ { "ptr_root_slice_return", "type outer = struct { sl: []u8, x: i32 };\n" "fn fetch(p: *outer) []u8 = { return p.sl; };\n" "fn main() i32 = {\n" " let arr: [8]u8; arr[0] = 19u8;\n" " let o: outer;\n" " o.sl.ptr = &arr[0]; o.sl.len = 6; o.sl.cap = 8;\n" " let p: *outer = &o;\n" " let s: []u8 = fetch(p);\n" " if (s.len: i32 != 6) { return 1; };\n" " if (s.cap: i32 != 8) { return 2; };\n" " if (s[0]: i32 != 19) { return 3; };\n" " return 42;\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/wdsa_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/wdsa_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wdsa_%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, "dot_slice_arg: 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, "dot_slice_arg[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "dot_slice_arg: %d/%d fixtures failed\n", fail, total); return 1; } printf("dot_slice_arg: %d/%d ok\n", total, total); return 0; }