Flip the soft-default to a hard "missing package clause" error symmetrically in both stages (cmd/wcc/parse.c + lib/ww/syntax/parse.ww): the first real decl of a primary section with empty pathmod/resetmod and no seen clause is now rejected. Closes the documented soft-default divergence (the 63-wrapper carve-out). The gate flip can't be split from the migration it breaks, so this is one atomic commit: ~80 test/wcc wrappers gain `package main;` via a shared wwtestpkg.h helper, 6 data fixtures plus 17 asm-grep assertions update for the bare->main.<leaf> root-helper mangle shift, and rt/ declares `package rt;` with @symbol pinning the bare rt_ensure/rt_malloc linker names. Root mangling narrows: the executable entry `main` stays bare (existing carve-out), but root helper symbols become main.X. The #84 cluster is rewritten to assert main.run distinct from aa.run/test.run; its cgen fix and bare machinery are retained — still load-bearing for package-less module-reset deps. New table-driven test 782_strict_package.c (6 rows, both stages). Retiring //ww:module-reset is deferred to #24b: it is load-bearing (clears the .wwi pathmod so the body's package clause asserts), not a vestige; fusing its removal here would be a silent mismatch. All byte-id gates green; full make test reports "all 335 tests passed".
303 lines
11 KiB
C
303 lines
11 KiB
C
/*
|
|
* 691_dot_slice_arg — pass `<expr>.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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include "wwtestpkg.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[] = {
|
|
/* 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; }
|
|
wwtest_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;
|
|
}
|