Files
ww/test/wcc/807_insert_elem.c
Hojun-Cho 9861f73bbb wcc+w6c+w6c_ww: insert() builtin — single-element slice insertion (part of #35)
Hare's insert(xs[idx], v) (ref/harec/src/check.c:745
check_expr_append_insert — append/insert share the checker arm,
"insert" at :786): checker accepts an INDEX place over a slice plus
one value, stamps void; idx == len is a legal end-insert (the
ref/hare os/exec/platform_cmd.ha:86 idiom). Loud-rejects with exact
texts: spread form insert(xs[i], vs...) (filed, #35 — also covers
harec's with-length form via the arity check), range place (not
Hare; harec only parses ACCESS_INDEX, :784), non-index operands,
array bases, wrong arity. delete()-parity throughout.

Lowering (both stages, converged byte-identical by construction) is
a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure) and the
entire #34 value-store dispatch (scalar / str-slice header / tagged
widen / struct fill) verbatim, one boxing choke-point — lands v at
slot len-1; then a rotate-right of [idx, len) moves it home through
a fresh per-site esz frame scratch (@insscr). The rotate is delete's
shift loop in reverse (descending j, the safe memmove-up direction)
and is a same-slice whole-stride raw byte move — no boxing exists
for any element kind. idx evaluates BEFORE the grow (Hare's
left-to-right operand order — pinned by the pregrow_len_idx row,
insert(xs[len(xs)-1], v): pre-grow [7,13,11] vs post-grow [7,11,13];
an idx==len(xs) end-insert cannot discriminate, the rotate
degenerates either way). Base shapes: local slice ident (LEAQ) and
deref-of-local ptr-to-slice (MOVQ); others rule-7 loud-stop, like
delete.

test/807: 57 fixtures — front/middle/end + idx==len via len(xs) +
the pre-grow eval-order pin, esz 1/2/4/8/16/24/56 (MOVB/MOVW/MOVL
tails, struct body, str header, 7-qword tagged from a typed local
[the regex fold-3 ha:347 newinst shape] and from a cast rvalue
[ha:419/441]), empty-slice grow, (*p)[i] deref base, front-insert
loop, 6 checker reject rows with diagnostic-text checks; every
accept row cs==ww asm byte-id.
2026-06-04 17:24:57 +09:00

616 lines
20 KiB
C

/*
* 807_insert_elem — cstage and wwstage agree, byte-for-byte and at
* runtime, that `insert(xs[idx], v)` inserts v BEFORE idx: len += 1,
* the tail [idx..oldlen) shifts up one stride, v lands at idx
* (the insert-half of task #35, delete()'s twin; regex fold-3's
* `|`/`?`/`*` compile-arm consumers, regex.ha:347/419/441).
*
* Lowering (BOTH stages, converged byte-identical by construction)
* is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure)
* and the whole #34 value-store dispatch (scalar / str-slice header /
* tagged widen / struct fill) verbatim, one boxing choke-point —
* lands v at slot len-1; then a rotate-right of [idx, len) moves it
* home through an esz frame scratch (@insscr). The rotate is
* delete's shift loop in reverse (descending j, the safe memmove-up
* direction) and is a same-slice whole-stride raw byte move — no
* boxing exists for any element kind. idx evaluates BEFORE the grow
* (Hare's left-to-right operand order — the pregrow_len_idx row pins
* insert(xs[len(xs)-1], v) reading the pre-grow len; an end-insert
* via len(xs) cannot discriminate, see the end_insert_len row).
*
* idx == len is a legal end-insert per harec's shared append/insert
* checker arm (ref/harec/src/check.c:745, "insert" at :786; the
* ref/hare os/exec/platform_cmd.ha:86 `insert(cmd.env[len(...)...]`
* idiom) — pinned by the i64_end / end_insert_len rows.
*
* row | shape | want
* ----------------+----------------------------------------+------
* i64_front | [7,11,13], insert(xs[0], 5) | 115
* i64_middle | [7,11,13], insert(xs[1], 5) | 97
* i64_end | [7,11,13], insert(xs[3], 5) (idx==len, | 157
* | rotate loop never runs) |
* end_insert_len | insert(xs[len(xs)], v) — idx==len | 13
* | spelled through a dynamic len read |
* pregrow_len_idx | insert(xs[len(xs)-1], v) — the eval- | 48
* | order pin: pre-grow idx=1 -> [7,13,11],|
* | post-grow idx=2 -> [7,11,13] |
* i32_narrow | []i32 esz=4 — the MOVL copy tail | 42
* u16_narrow | []u16 esz=2 — the MOVW copy tail | 43
* u8_narrow | []u8 esz=1 — the MOVB copy tail | 44
* empty_insert | insert(xs[0], v) on an empty slice | 15
* | (grow 0->1; rotate degenerates) |
* str_elem | []str esz=24 — 3-qword headers move | 45
* | whole |
* struct_elem | []p2t esz=16 — struct body insert + | 46
* | survivor shift |
* tagged_56b | [](s6|bool) esz=56, value from a TYPED |
* | LOCAL (the regex newinst shape, |
* | ha:347); tag+payload survive the | 217
* | rotate; match head + raw tail byte |
* tagged_cast | insert(insts[k], (7: inst_split)) — |
* | CAST-rvalue value boxed by append's | 27
* | widen choke-point (ha:419/441 shape) |
* regex_shape | insert((*p)[i], v) behind *[]i64 — |
* | deref-of-local base, delete's | 171
* | regex_shape twin |
* insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47
* | len bookkeeping under iteration + |
* | per-position checks |
*
* Wants stay under 256 (the exit-status byte); the if-ladder rows
* return a distinct small failure code per check, so a wrong element
* pinpoints itself.
* reject_array | insert(t[0], v) on [3]i64 | BUILD_FAIL
* reject_nonindex | insert(xs, v) | BUILD_FAIL
* reject_range | insert(xs[0:1], v) — not Hare (harec | BUILD_FAIL
* | only parses an index place) |
* reject_arity1 | insert(xs[0]) | BUILD_FAIL
* reject_arity3 | insert(xs[0], a, b) — also covers the | BUILD_FAIL
* | harec with-length form (#35) |
* reject_spread | insert(xs[0], vs...) — multi form | BUILD_FAIL
* | deferred, message cites task #35 |
*
* 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 — @insscr sizing) and
* the ins_l/ins_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, esp. the #35 cite on the deferred spread form — 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[] = {
{ "i64_front",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[0], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
115, NULL },
{ "i64_middle",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[1], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
97, NULL },
{ "i64_end",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[3], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
157, NULL },
/* idx == len spelled THROUGH a dynamic len(xs) read (the
* ref/hare os/exec idiom). NOTE: this row does NOT discriminate
* idx eval order — under the desugar, post-grow idx=newlen
* degenerates the rotate and lands v at the end too; the
* pregrow_len_idx row below is the eval-order pin. */
{ "end_insert_len",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tinsert(xs[len(xs)], 13);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\treturn xs[2]: i32;\n"
"};\n",
13, NULL },
/* THE pre-grow eval-order pin (Hare's left-to-right operand
* order): idx = len(xs)-1 reads the PRE-grow len -> idx=1 ->
* [7,13,11]; a post-grow evaluation would compute idx=2 and
* produce a plain end-insert [7,11,13] — every position is
* checked, so the orders are distinguishable. */
{ "pregrow_len_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tinsert(xs[len(xs) - 1], 13);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 7) { return 2; };\n"
"\tif (xs[1] != 13) { return 3; };\n"
"\tif (xs[2] != 11) { return 4; };\n"
"\treturn 48;\n"
"};\n",
48, NULL },
{ "i32_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i32 = [];\n"
"\tappend(xs, 4i32);\n"
"\tappend(xs, 2i32);\n"
"\tinsert(xs[1], 9i32);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 4i32) { return 2; };\n"
"\tif (xs[1] != 9i32) { return 3; };\n"
"\tif (xs[2] != 2i32) { return 4; };\n"
"\treturn 42;\n"
"};\n",
42, NULL },
{ "u16_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u16 = [];\n"
"\tappend(xs, 4u16);\n"
"\tappend(xs, 2u16);\n"
"\tinsert(xs[1], 9u16);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 4u16) { return 2; };\n"
"\tif (xs[1] != 9u16) { return 3; };\n"
"\tif (xs[2] != 2u16) { return 4; };\n"
"\treturn 43;\n"
"};\n",
43, NULL },
{ "u8_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 3u8);\n"
"\tinsert(xs[0], 2u8);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 2u8) { return 2; };\n"
"\tif (xs[1] != 5u8) { return 3; };\n"
"\tif (xs[2] != 3u8) { return 4; };\n"
"\treturn 44;\n"
"};\n",
44, NULL },
{ "empty_insert",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tinsert(xs[0], 5);\n"
"\treturn xs[0]: i32 + len(xs)*10;\n"
"};\n",
15, NULL },
{ "str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet g: str = \"fghi\";\n"
"\tappend(xs, a);\n"
"\tappend(xs, g);\n"
"\tlet b: str = \"de\";\n"
"\tinsert(xs[1], b);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0].len != 3) { return 2; };\n"
"\tif (xs[1].len != 2) { return 3; };\n"
"\tif (xs[2].len != 4) { return 4; };\n"
"\treturn 45;\n"
"};\n",
45, NULL },
{ "struct_elem",
"package main;\n"
"type p2t = struct { x: i64, y: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []p2t = [];\n"
"\tappend(xs, p2t { x = 1, y = 2 });\n"
"\tappend(xs, p2t { x = 5, y = 6 });\n"
"\tinsert(xs[1], p2t { x = 3, y = 4 });\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0].x != 1 || xs[0].y != 2) { return 2; };\n"
"\tif (xs[1].x != 3 || xs[1].y != 4) { return 3; };\n"
"\tif (xs[2].x != 5 || xs[2].y != 6) { return 4; };\n"
"\treturn 46;\n"
"};\n",
46, NULL },
/* 56B element from a TYPED LOCAL — the regex fold-3 newinst
* shape (regex.ha:347: `insert(insts[split_idx], newinst)`).
* Tag qword + 48B payload: seven whole-qword moves through
* @insscr; tag AND payload must survive both the append store
* and the rotate. Readback is split like 804's tagged_56b row:
* match proves tag + head (s.a), and the tail qword of the
* SHIFTED element (f = 18, element 2 byte 48 -> absolute byte
* 160) is read RAW via a *u8 over xs.ptr — the indexed-match
* payload cursor truncates past 32B (pre-existing, task #43). */
{ "tagged_56b",
"package main;\n"
"type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
"type cell = (s6 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n"
"\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n"
"\tappend(xs, p0);\n"
"\tappend(xs, p2);\n"
"\tlet p1: cell = (s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 });\n"
"\tinsert(xs[1], p1);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (xs[1]) {\n"
"\tcase let s: s6 => r = s.a: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tlet bp: *u8 = xs.ptr: *u8;\n"
"\tr += (bp[160]: i32) * 10;\n"
"\treturn r + len(xs)*10;\n"
"};\n",
217, NULL },
/* CAST-rvalue value into a tagged slice — the regex ha:419/441
* shape `insert(insts[term_start_idx], after_idx: inst_split)`;
* append's widen choke-point boxes it post-grow. */
{ "tagged_cast",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_split = i64;\n"
"type inst = (inst_lit | inst_split);\n"
"export fn main() i32 = {\n"
"\tlet insts: []inst = [];\n"
"\tappend(insts, ('a': inst_lit));\n"
"\tappend(insts, ('b': inst_lit));\n"
"\tinsert(insts[1], (7: inst_split));\n"
"\tif (len(insts) != 3) { return 1; };\n"
"\tlet r: i32 = 0;\n"
"\tmatch (insts[1]) {\n"
"\tcase let z: inst_split => r += (z: i32);\n"
"\tcase => return 2;\n"
"\t};\n"
"\tmatch (insts[2]) {\n"
"\tcase let l: inst_lit => { if ((l: rune) == 'b') { r += 20; }; };\n"
"\tcase => return 3;\n"
"\t};\n"
"\treturn r;\n"
"};\n",
27, NULL },
/* The deref-of-local base — delete's regex_shape twin: the
* header lives behind a *[]i64 param. */
{ "regex_shape",
"package main;\n"
"fn ins_at(i: i64, p: *[]i64, v: i64) void = {\n"
"\tinsert((*p)[i], v);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 3);\n"
"\tins_at(1, &xs, 2);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\treturn (xs[0] + xs[1]*10 + xs[2]*50): i32;\n"
"};\n",
171, NULL },
/* Pins len bookkeeping under iteration: each front-insert must
* see the grown len and shift the whole accumulated tail;
* per-position checks of [4,3,2,1] make any wrong order, missed
* shift, or stale len visible. */
{ "insert_in_loop",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tlet v: i64 = 1;\n"
"\tfor (v <= 4) {\n"
"\t\tinsert(xs[0], v);\n"
"\t\tv += 1;\n"
"\t};\n"
"\tif (len(xs) != 4) { return 1; };\n"
"\tlet k: i32 = 0;\n"
"\tfor (k < 4) {\n"
"\t\tif (xs[k] != (4 - k): i64) { return 2 + k; };\n"
"\t\tk += 1;\n"
"\t};\n"
"\treturn 47;\n"
"};\n",
47, NULL },
{ "reject_array",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: [3]i64 = [1, 2, 3];\n"
"\tinsert(t[0], 9);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert must operate on a slice" },
{ "reject_nonindex",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs, 6u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: operand must be an indexing expression" },
{ "reject_range",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 6u8);\n"
"\tinsert(xs[0:1], 7u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: range place is invalid" },
{ "reject_arity1",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs[0]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: takes exactly two arguments" },
{ "reject_arity3",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs[0], 6u8, 7u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: takes exactly two arguments" },
{ "reject_spread",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tlet vs: []u8 = [];\n"
"\tappend(vs, 1u8);\n"
"\tinsert(xs[0], vs...);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)" },
};
/* 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 src[64], tmpdir[64], errlog[80], cmd[1200];
snprintf(src, sizeof src, "/tmp/inse_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/inse_%d_d_%d", getpid(), i);
snprintf(errlog, sizeof errlog, "%s.err", src);
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 2>%s",
tmpdir, driver, 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 */
}
unlink(src); unlink(errlog); rmdir(tmpdir);
return rc;
}
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(errlog); unlink(outbin); rmdir(tmpdir);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Both insert lowerings are written fresh, so this is
* the converged-by-construction gate: any drift in the rotate loop,
* the ins_l/ins_e label sequence, the @insscr frame slot, or the
* reused append body 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/inse_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/inse_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/inse_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, "insert_elem: 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,
"insert_elem[%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,
"insert_elem: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("insert_elem: %d fixtures passed\n", total);
return 0;
}