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.
605 lines
18 KiB
C
605 lines
18 KiB
C
/*
|
|
* 809_delete_range — cstage and wwstage agree, byte-for-byte and at
|
|
* runtime, that the range form `delete(xs[lo:hi])` removes elements
|
|
* [lo, hi) from a slice: the tail [hi..len) shifts down count = hi-lo
|
|
* strides, len -= count, cap unchanged; lo defaults 0, hi defaults
|
|
* len (so delete(xs[:]) clears the slice, storage retained). The
|
|
* range half of the delete() builtin — fold-5a prereq P2 for the
|
|
* regex.ha:333 consumer `delete(jump_idxs[group_level][..])` (harec
|
|
* ref/harec/src/check.c:1981-2027 accepts EXPR_SLICE alongside the
|
|
* 804-covered ACCESS_INDEX form).
|
|
*
|
|
* Lowering (BOTH stages, converged byte-identical by construction):
|
|
* push &hdr, lo and count = hi-lo, then an ascending word-copy loop
|
|
* moves element j+count onto element j until j+count >= len, then
|
|
* hdr.len -= count. The per-element move is 804's same-slice
|
|
* whole-stride word copy with a DYNAMIC src offset (count*esz via a
|
|
* src register) instead of the constant one-stride; the esz spread
|
|
* below (1/2/4/8/16/24) re-proves every copy-tail arm against the
|
|
* dynamic src. Base shapes: local slice ident, deref-of-local
|
|
* ptr-to-slice, and — new for the range arm — an INDEXED local slice
|
|
* base xs[g][lo:hi] (the regex fold-5a consumer shape). Bounds are
|
|
* implicit (no range check), matching the 804 single-element arm and
|
|
* the rest of cgen — so no out-of-bounds abort rows here; a
|
|
* violating range is as undefined as a violating index.
|
|
*
|
|
* row | shape | want
|
|
* -----------------+----------------------------------------+------
|
|
* full_range | [7,11,13], delete(xs[:]) -> len 0 | 7
|
|
* full_explicit | [7,11,13], delete(xs[0:3]) -> len 0 | 8
|
|
* full_twice | delete(xs[:]) twice — second is a | 9
|
|
* | no-op on the emptied slice (count 0) |
|
|
* head | [1,2,3], delete(xs[:2]) — lo default 0 | 13
|
|
* mid | [1..5], delete(xs[1:3]) | 148
|
|
* tail_open | [1,2,3], delete(xs[1:]) — hi default | 11
|
|
* | len, copy loop never entered |
|
|
* empty_range | [1,2], delete(xs[1:1]) — count 0, | 23
|
|
* | self-copy loop, len unchanged |
|
|
* end_boundary | [1,2], delete(xs[len:len]) — lo==hi== | 212
|
|
* | len, loop exits at entry, no deref |
|
|
* | past the end |
|
|
* empty_explicit | delete(xs[0:0]) on a never-appended | 31
|
|
* | empty slice (ptr nil) — count 0 and |
|
|
* | len 0, nothing dereferenced |
|
|
* single_via_range | delete(xs[1:2]) == delete(ys[1]) | 42
|
|
* | element-for-element |
|
|
* cap_preserved | manual {ptr,len=3,cap=8} header, | 80
|
|
* | delete(xs[:]); cap must still read 8 |
|
|
* u8_narrow | []u8 esz=1 — MOVB tail, dynamic src | 215
|
|
* u16_narrow | []u16 esz=2 — MOVW tail, dynamic src | 25
|
|
* i32_narrow | []i32 esz=4 — MOVL tail, dynamic src | 50
|
|
* struct16 | []pair esz=16 — 2 whole qwords, no | 216
|
|
* | sub-word tail (804's struct esz twin) |
|
|
* str_elem | []str esz=24 — 3-qword headers move | 235
|
|
* | whole across a 2-stride shift |
|
|
* regex_exact | [][]size, delete(jj[lvl][:]) — the | 10
|
|
* | EXACT regex.ha:333 consumer shape |
|
|
* | (indexed base, size-typed index) |
|
|
* deref_shape | delete((*p)[:]) behind *[]u8 — the | 17
|
|
* | 804 regex_shape twin |
|
|
* order_of_eval | delete(xs[lof():hif()]) — lo then hi, | 16
|
|
* | each CALLED exactly once (trace must |
|
|
* | read 12; harec evaluates the slicing |
|
|
* | operands left-to-right, once) |
|
|
* alias_visible | delete(xs[poke(&xs):]) — lo's write | 91
|
|
* | through &xs lands BEFORE the shift |
|
|
* | (loop reads ptr/len/elems fresh from |
|
|
* | the header, after operand eval) |
|
|
* reject_array | delete(t[0:2]) on [3]i64 — "delete | BUILD_FAIL
|
|
* | must operate on a slice" |
|
|
* reject_str | delete(s[:]) on str — slicing a str | BUILD_FAIL
|
|
* | yields str, not a slice |
|
|
*
|
|
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
|
* both stages) — a build that fails for any other reason (parse error,
|
|
* crash) is a vacuous reject and fails the row.
|
|
*
|
|
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
|
|
* which subsumes the frame canary (TEXT main,$N) and the rdl_l/rdl_e
|
|
* label-counter symmetry.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.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;
|
|
}
|
|
|
|
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
|
|
* emit expect_err on stderr (the checker reject set — message text
|
|
* included — is part of the contract: rule 7, never a silent
|
|
* acceptance; without the message check a row would pass vacuously on
|
|
* any unrelated build failure). */
|
|
#define BUILD_FAIL (-2147483647 - 1)
|
|
|
|
struct row {
|
|
const char *label;
|
|
const char *src;
|
|
int want;
|
|
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
|
|
};
|
|
|
|
static const struct row rows[] = {
|
|
{ "full_range",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 7);\n"
|
|
"\tappend(xs, 11);\n"
|
|
"\tappend(xs, 13);\n"
|
|
"\tdelete(xs[:]);\n"
|
|
"\treturn len(xs): i32 + 7;\n"
|
|
"};\n",
|
|
7, NULL },
|
|
|
|
{ "full_explicit",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 7);\n"
|
|
"\tappend(xs, 11);\n"
|
|
"\tappend(xs, 13);\n"
|
|
"\tdelete(xs[0:3]);\n"
|
|
"\treturn len(xs): i32 + 8;\n"
|
|
"};\n",
|
|
8, NULL },
|
|
|
|
/* Second full-range delete sees len == 0: count = len - 0 = 0,
|
|
* the copy loop exits at entry, len -= 0. Pins that an emptied
|
|
* slice survives a re-clear (the regex `)` arm clears
|
|
* jump_idxs[group_level] whether or not `|` populated it). */
|
|
{ "full_twice",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 7);\n"
|
|
"\tappend(xs, 11);\n"
|
|
"\tdelete(xs[:]);\n"
|
|
"\tdelete(xs[:]);\n"
|
|
"\treturn len(xs): i32 + 9;\n"
|
|
"};\n",
|
|
9, NULL },
|
|
|
|
{ "head",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tappend(xs, 3);\n"
|
|
"\tdelete(xs[:2]);\n"
|
|
"\treturn xs[0]: i32 + len(xs)*10;\n"
|
|
"};\n",
|
|
13, NULL },
|
|
|
|
{ "mid",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tappend(xs, 3);\n"
|
|
"\tappend(xs, 4);\n"
|
|
"\tappend(xs, 5);\n"
|
|
"\tdelete(xs[1:3]);\n"
|
|
"\treturn (xs[0]*100 + xs[1]*10 + xs[2]): i32 + len(xs);\n"
|
|
"};\n",
|
|
148, NULL },
|
|
|
|
{ "tail_open",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tappend(xs, 3);\n"
|
|
"\tdelete(xs[1:]);\n"
|
|
"\treturn xs[0]: i32 + len(xs)*10;\n"
|
|
"};\n",
|
|
11, NULL },
|
|
|
|
{ "empty_range",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tdelete(xs[1:1]);\n"
|
|
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
|
"};\n",
|
|
23, NULL },
|
|
|
|
/* lo == hi == len: the loop guard j+count >= len holds at entry
|
|
* with j = len, so nothing past the end is ever addressed. */
|
|
{ "end_boundary",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tdelete(xs[len(xs):len(xs)]);\n"
|
|
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs)*100;\n"
|
|
"};\n",
|
|
212, NULL },
|
|
|
|
/* Never-appended empty slice: ptr is nil. count 0 and len 0 mean
|
|
* the loop body (the only dereferencing code) never runs. */
|
|
{ "empty_explicit",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tdelete(xs[0:0]);\n"
|
|
"\treturn len(xs): i32 + 31;\n"
|
|
"};\n",
|
|
31, NULL },
|
|
|
|
{ "single_via_range",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 7);\n"
|
|
"\tappend(xs, 11);\n"
|
|
"\tappend(xs, 13);\n"
|
|
"\tdelete(xs[1:2]);\n"
|
|
"\tlet ys: []i64 = [];\n"
|
|
"\tappend(ys, 7);\n"
|
|
"\tappend(ys, 11);\n"
|
|
"\tappend(ys, 13);\n"
|
|
"\tdelete(ys[1]);\n"
|
|
"\tif (len(xs) != len(ys)) { return 99; };\n"
|
|
"\tif (xs[0] != ys[0]) { return 98; };\n"
|
|
"\tif (xs[1] != ys[1]) { return 97; };\n"
|
|
"\treturn 42;\n"
|
|
"};\n",
|
|
42, NULL },
|
|
|
|
{ "cap_preserved",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n"
|
|
"\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n"
|
|
"\tdelete(xs[:]);\n"
|
|
"\treturn (xs.cap*10 + xs.len): i32;\n"
|
|
"};\n",
|
|
80, NULL },
|
|
|
|
{ "u8_narrow",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []u8 = [];\n"
|
|
"\tappend(xs, 1u8);\n"
|
|
"\tappend(xs, 2u8);\n"
|
|
"\tappend(xs, 3u8);\n"
|
|
"\tappend(xs, 4u8);\n"
|
|
"\tappend(xs, 5u8);\n"
|
|
"\tdelete(xs[1:4]);\n"
|
|
"\treturn (xs[0]*10u8 + xs[1]): i32 + len(xs)*100;\n"
|
|
"};\n",
|
|
215, NULL },
|
|
|
|
{ "u16_narrow",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []u16 = [];\n"
|
|
"\tappend(xs, 1u16);\n"
|
|
"\tappend(xs, 2u16);\n"
|
|
"\tappend(xs, 3u16);\n"
|
|
"\tappend(xs, 4u16);\n"
|
|
"\tdelete(xs[1:3]);\n"
|
|
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
|
"};\n",
|
|
25, NULL },
|
|
|
|
{ "i32_narrow",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i32 = [];\n"
|
|
"\tappend(xs, 10i32);\n"
|
|
"\tappend(xs, 20i32);\n"
|
|
"\tappend(xs, 30i32);\n"
|
|
"\tappend(xs, 40i32);\n"
|
|
"\tdelete(xs[2:4]);\n"
|
|
"\treturn xs[0] + xs[1] + len(xs)*10;\n"
|
|
"};\n",
|
|
50, NULL },
|
|
|
|
/* esz 16: exactly two whole qwords per element — the only spread
|
|
* point with multiple MOVQs and NO sub-word tail. */
|
|
{ "struct16",
|
|
"package main;\n"
|
|
"type pair = struct { a: i64, b: i64 };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []pair = [];\n"
|
|
"\tappend(xs, pair{ a = 1, b = 2 });\n"
|
|
"\tappend(xs, pair{ a = 3, b = 4 });\n"
|
|
"\tappend(xs, pair{ a = 5, b = 6 });\n"
|
|
"\tdelete(xs[1:2]);\n"
|
|
"\treturn (xs[0].a*10 + xs[1].b): i32 + len(xs)*100;\n"
|
|
"};\n",
|
|
216, NULL },
|
|
|
|
{ "str_elem",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []str = [];\n"
|
|
"\tlet a: str = \"abc\";\n"
|
|
"\tlet b: str = \"de\";\n"
|
|
"\tlet g: str = \"fghi\";\n"
|
|
"\tlet h: str = \"jklmn\";\n"
|
|
"\tappend(xs, a);\n"
|
|
"\tappend(xs, b);\n"
|
|
"\tappend(xs, g);\n"
|
|
"\tappend(xs, h);\n"
|
|
"\tdelete(xs[1:3]);\n"
|
|
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
|
|
"};\n",
|
|
235, NULL },
|
|
|
|
/* The fold-5a consumer, shape-EXACT: indexed local-slice base,
|
|
* size-typed outer index, defaulted full range (regex.ha:333
|
|
* `delete(jump_idxs[group_level][..])`; ww spells `[..]` as
|
|
* `[:]`, D7). The header lives at jj.ptr + g*size([]size) —
|
|
* outer stride off the type table, never a literal. */
|
|
{ "regex_exact",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet jump_idxs: [][]size = [];\n"
|
|
"\tlet lvl0: []size = [];\n"
|
|
"\tlet a: size = 3;\n"
|
|
"\tlet b: size = 9;\n"
|
|
"\tappend(lvl0, a);\n"
|
|
"\tappend(lvl0, b);\n"
|
|
"\tappend(jump_idxs, lvl0);\n"
|
|
"\tlet group_level: size = 0;\n"
|
|
"\tdelete(jump_idxs[group_level][:]);\n"
|
|
"\treturn len(jump_idxs[0]): i32 + len(jump_idxs): i32 * 10;\n"
|
|
"};\n",
|
|
10, NULL },
|
|
|
|
{ "deref_shape",
|
|
"package main;\n"
|
|
"fn clear(p: *[]u8) void = {\n"
|
|
"\tdelete((*p)[:]);\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []u8 = [];\n"
|
|
"\tappend(xs, 5u8);\n"
|
|
"\tappend(xs, 6u8);\n"
|
|
"\tclear(&xs);\n"
|
|
"\treturn len(xs): i32 + 17;\n"
|
|
"};\n",
|
|
17, NULL },
|
|
|
|
/* lo and hi as CALLs: each side effect fires exactly once, lo
|
|
* before hi (trace 12, never 21/121/122) — pins that neither
|
|
* bound expression is re-evaluated by the count computation or
|
|
* the copy loop. */
|
|
{ "order_of_eval",
|
|
"package main;\n"
|
|
"let trace: i64 = 0;\n"
|
|
"fn lof() size = {\n"
|
|
"\ttrace = trace*10 + 1;\n"
|
|
"\treturn 1;\n"
|
|
"};\n"
|
|
"fn hif() size = {\n"
|
|
"\ttrace = trace*10 + 2;\n"
|
|
"\treturn 3;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tappend(xs, 3);\n"
|
|
"\tappend(xs, 4);\n"
|
|
"\tdelete(xs[lof():hif()]);\n"
|
|
"\tif (trace != 12) { return 90; };\n"
|
|
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs);\n"
|
|
"};\n",
|
|
16, NULL },
|
|
|
|
/* lo mutates the slice it is deleting from: only the header
|
|
* ADDRESS is taken before operand eval; ptr/len/elements are read
|
|
* after, so poke's write to xs[0] survives into the result. */
|
|
{ "alias_visible",
|
|
"package main;\n"
|
|
"fn poke(p: *[]i64) size = {\n"
|
|
"\t(*p)[0] = 9;\n"
|
|
"\treturn 1;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []i64 = [];\n"
|
|
"\tappend(xs, 1);\n"
|
|
"\tappend(xs, 2);\n"
|
|
"\tappend(xs, 3);\n"
|
|
"\tdelete(xs[poke(&xs):]);\n"
|
|
"\treturn xs[0]: i32 * 10 + len(xs);\n"
|
|
"};\n",
|
|
91, NULL },
|
|
|
|
{ "reject_array",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet t: [3]i64 = [1, 2, 3];\n"
|
|
"\tdelete(t[0:2]);\n"
|
|
"\treturn 0;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "delete must operate on a slice" },
|
|
|
|
{ "reject_str",
|
|
"package main;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet s: str = \"abc\";\n"
|
|
"\tdelete(s[:]);\n"
|
|
"\treturn 0;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "delete must operate on a slice" },
|
|
};
|
|
|
|
/* errlog_has — the build-failure stderr must carry the row's expected
|
|
* diagnostic; any other failure (parse error, crash) is a vacuous
|
|
* reject and must not pass. */
|
|
static int
|
|
errlog_has(const char *path, const char *needle)
|
|
{
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return 0;
|
|
char buf[8192];
|
|
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
|
fclose(f);
|
|
buf[got] = '\0';
|
|
return strstr(buf, needle) != NULL;
|
|
}
|
|
|
|
static int
|
|
run_driver(const char *driver, const struct row *r, int i)
|
|
{
|
|
char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160];
|
|
char cmd[1200];
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/delr_%d_d_%d", getpid(), i);
|
|
mkdir(tmpdir, 0755);
|
|
snprintf(src, sizeof src, "%s/delr_%d_%d.ww", tmpdir, getpid(), i);
|
|
snprintf(outbin, sizeof outbin, "%s/delr_%d_%d", tmpdir, getpid(), i);
|
|
snprintf(errlog, sizeof errlog, "%s/err", tmpdir);
|
|
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 2>%s",
|
|
driver, outbin, src, errlog);
|
|
if (runwait(cmd) != 0) {
|
|
int rc = -1;
|
|
if (r->want != BUILD_FAIL) {
|
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
|
r->label, driver);
|
|
} else if (r->expect_err &&
|
|
!errlog_has(errlog, r->expect_err)) {
|
|
fprintf(stderr, "row[%s]: %s build failed without "
|
|
"expected diagnostic \"%s\"\n",
|
|
r->label, driver, r->expect_err);
|
|
rc = -3; /* failed, but for the wrong reason */
|
|
}
|
|
runwait(rmcmd);
|
|
return rc;
|
|
}
|
|
|
|
int got = runwait(outbin);
|
|
|
|
runwait(rmcmd);
|
|
return got;
|
|
}
|
|
|
|
/* asm_byte_identical — compile the row's source standalone with w6c and
|
|
* w6c_ww and diff. Both range-delete lowerings are written fresh, so
|
|
* this is the converged-by-construction gate: any drift in the shift
|
|
* loop, the rdl_l/rdl_e label sequence, the dynamic-src copy, or the
|
|
* indexed-base header address shows here. */
|
|
static int
|
|
asm_byte_identical(const char *bin, const struct row *r, int i)
|
|
{
|
|
char src[64], cs[64], ws[64], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/delr_asm_%d_%d.ww", getpid(), i);
|
|
snprintf(cs, sizeof cs, "/tmp/delr_asm_%d_%d_c.s", getpid(), i);
|
|
snprintf(ws, sizeof ws, "/tmp/delr_asm_%d_%d_w.s", getpid(), i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
|
unlink(src);
|
|
return -1;
|
|
}
|
|
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
|
bin, ws, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
|
unlink(src); unlink(cs);
|
|
return -1;
|
|
}
|
|
|
|
FILE *fc = fopen(cs, "rb");
|
|
FILE *fw = fopen(ws, "rb");
|
|
int rc = 0;
|
|
if (!fc || !fw) {
|
|
rc = -1;
|
|
} else {
|
|
for (;;) {
|
|
int a = fgetc(fc);
|
|
int b = fgetc(fw);
|
|
if (a != b) { rc = -1; break; }
|
|
if (a == EOF) break;
|
|
}
|
|
}
|
|
if (fc) fclose(fc);
|
|
if (fw) fclose(fw);
|
|
if (rc != 0)
|
|
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
|
r->label);
|
|
unlink(src); unlink(cs); unlink(ws);
|
|
return rc;
|
|
}
|
|
|
|
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, "delete_range: 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++;
|
|
int bad = rows[i].want == BUILD_FAIL
|
|
? (got != -1) : (got != rows[i].want);
|
|
if (bad) {
|
|
fprintf(stderr,
|
|
"delete_range[%s][%s]: exit=%d want=%d\n",
|
|
drivers[d].name, rows[i].label,
|
|
got, rows[i].want);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (access(wdrv, X_OK) == 0) {
|
|
for (int i = 0; i < n; i++) {
|
|
if (rows[i].want == BUILD_FAIL)
|
|
continue;
|
|
total++;
|
|
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr,
|
|
"delete_range: %d/%d fixtures failed\n", fail, total);
|
|
return 1;
|
|
}
|
|
printf("delete_range: %d fixtures passed\n", total);
|
|
return 0;
|
|
}
|