Files
ww/test/wcc/806_append_place.c
Hojun-Cho 76994a8279 wcc+w6c_ww: typed dot-read resolver — kill both silent N_DOT fallbacks (F4)
A typed depth-2+ field chain behind an index/deref spine
(threads[0].cap.end, (*p)[i].f.g) aborted the cgdot chain walker
(N_IDENT roots only) and fell into the module-qualified-leaf fallback
— a silent `MOVQ <leaf>(SB)` global read of a colliding symbol
(p6min10 exit 66) or a link error. Single-dot reads behind a deref-
index ((*ts)[i].pc, (*p)[i].slicefield) fell into the bottom catch-all
instead, which is offset- and header-blind: offset-0 scalars read
correctly by COINCIDENCE, nonzero offsets and slice headers were
silently wrong in BOTH stages (FA3, pA5). `&threads[0].cap` dropped
the address-of silently and SEGFAULTed at the deref (task #6,
reviewer-A route).

cgplaceaddr (C1) grows an N_IDENT root (local / let / DATA-backed
def) and the N_INDEX base gate relaxes to recursion, so indexed-ident
and deref-rooted spines resolve; enumerated arms still dispatch first,
keeping every pre-C1 shape's asm. case N_DOT routes any TYPED read no
arm matched through the resolver (scalar fldloadop, float X0, str/
slice 3-word header, [N]T address); the module-leaf fallback is gated
to UNTYPED chains, the catch-all to untyped-str pseudo-fields, and
the TK_AMP tail is resolver-or-loud. Leaf kinds without a register
convention (tagged, aggregate) and unaddressable shapes die LOUD
(rule 7). wwstage mirrors symmetrically; two of its arm gaps must not
take the resolver (its sequence differs from cstage's arms — cs!=ww):
ident-indexed alias reads loud-cite C3 (task #8) and non-local-rooted
ptr-chains loud-cite task #37. A third verdict divergence is comment-
documented at the wwstage aggregate gate: cstage's let-init consumes
`let c = (*ts)[i].cap` BEFORE its N_DOT tail (emitting NO copy — the
F5 bug), so that shape cs-builds/ww-louds until the F5 let-copy lands
(task #7); absent from the gate corpus.

806 identroot_dot graduates from BUILD_FAIL: the C2 ident root makes
append(h.xs, v) through *holder resolve via C1.5's place consumer
(run-verified, byte-id). p6min9/p6min10 read-halves are fixed but the
probes stay blocked on the #36 literal under-copy this commit
unmasked (struct-ident field rhs copies 8B; repro filed with the
task).

test/805: +7 rows (typed depth-2 behind ident-index incl the 777
global-collision pin, deref-index, width/float/[N]u8 matrix, FA3
slice-field + .cap-behind-spine, &-route with compound-through-
pointer, C1's reject_tail graduated to stores, neutrality pins) and
+4 reject rows pinning the new loud texts; the C1.25 raw-byte
readbacks graduate to typed depth-2 reads.
2026-06-04 11:07:13 +09:00

596 lines
20 KiB
C

/*
* 806_append_place — cstage and wwstage agree, byte-for-byte and at
* runtime, that `append(*p, v)` / `append(*p, items...)` through a
* pointer-to-slice place lands the element (FA1, task #15 of the regex
* fold-2b blockers; the add_thread hot shape `threads: *[]thread`).
*
* Pre-fix this was ONE mirrored choke-point with TWO failure modes:
* cstage 0-defaulted the header base for any non-ident target
* (`sn_off = (sn->kind==N_IDENT) ? localfind : 0`), so 0(BP)/8(BP)
* became the "slice header" and rt_ensure corrupted the CALLER frame
* (SIGSEGV); wwstage cgappend silently emitted NOTHING (gate-blind
* cs≠ww). The fix re-keys the append lowering from BP-displacement
* assumptions onto a resolver-provided header PLACE (cgplaceaddr's
* third consumer after C1 assign-stores and C1.25 aggregate-field
* stores): the derived header address is spilled to a per-fn
* @apphdrscr slot so it survives rt_ensure (realloc moves .ptr, never
* the header), and every header access reloads from the slot.
* Ident-local targets keep the legacy BP-disp emission byte-identical.
* Any target place the resolver can't address dies LOUD (rule 7) —
* the old silent corruption can't come back through an unwired shape.
*
* row | shape | want
* ---------------------+-----------------------------------------+------
* scalar_param | append(*p, v) i64 via *[]i64 param | 60
* scalar_u8_multi | append(*q, a, b) u8 via local ptr | 61
* spread_param | append(*p, src...) i64 spread | 62
* spread_narrow_signed | append(*p, src...) i32 (MOVSXD load | 63
* | keyed off stamped tinfo, no tnode) |
* spread_wide | append(*p, src...) str (24B word-copy) | 64
* str_elem | append(*p, "hi") 3-word header | 65
* slice_elem | append(*p, one) [][]u8 3-word header | 66
* tagged_elem | append(*p, 42) (i64|void) widen-box | 67
* struct_lit | pA6 verbatim: append(*p, box{...}) | 68
* struct_ident | append(*p, b) local struct word-copy | 69
* realloc_loop | 100 appends via param, branched callee, | 70
* | cap-crossing reallocs, full multi-field |
* | readback + caller-frame sentinels |
* deref_spine | append((*q)[i].xs, v) resolver spine | 71
* neutral_direct | ident-local appends (the legacy arm — | 72
* | runtime-pins the asm-neutrality claim) |
* reentrant_value | append(*p, match{.. append(*q,..) ..}) | 33
* | — nested indirect append inside the |
* | outer's value expr; pins the per-SITE |
* | @apphdrscr slot (a shared slot clobbers)|
* identroot_dot | h.xs via *holder param — C1.5's reject, | 73
* | graduated by C2's resolver ident root |
* reject_spread_src | non-ident spread SOURCE through a deref | BUILD_FAIL
* | target (the #35 designed boundary) |
*
* 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.
*
* Readbacks deliberately avoid the still-open F2/F5 read shapes
* (`len(xs[i].field)`, `let s = xs[i].field`) — those are tracked
* separately; this test pins the append WRITE path.
*/
#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 (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[] = {
/* The FA1 minimum: two appends through a *[]i64 param, then a
* third through a local pointer binding. */
{ "scalar_param",
"package main;\n"
"fn add(p: *[]i64, v: i64) void = { append(*p, v); };\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tadd(&xs, 5);\n"
"\tadd(&xs, 9);\n"
"\tif (len(xs) != 2) { return 1; };\n"
"\tif (xs[0] != 5 || xs[1] != 9) { return 2; };\n"
"\tlet q: *[]i64 = &xs;\n"
"\tappend(*q, 11);\n"
"\tif (len(xs) != 3 || xs[2] != 11) { return 3; };\n"
"\treturn 60;\n"
"};\n",
60, NULL },
/* esz=1 store (MOVB) + multi-value list through one resolve. */
{ "scalar_u8_multi",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet bs: []u8 = [];\n"
"\tlet q: *[]u8 = &bs;\n"
"\tappend(*q, 1u8, 2u8);\n"
"\tif (len(bs) != 2) { return 1; };\n"
"\tif (bs[0] != 1u8 || bs[1] != 2u8) { return 2; };\n"
"\treturn 61;\n"
"};\n",
61, NULL },
{ "spread_param",
"package main;\n"
"fn addall(p: *[]i64, src: []i64) void = { append(*p, src...); };\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1, 2);\n"
"\tlet ys: []i64 = [];\n"
"\taddall(&ys, xs);\n"
"\taddall(&ys, xs);\n"
"\tif (len(ys) != 4) { return 1; };\n"
"\tif (ys[0] != 1 || ys[1] != 2 || ys[3] != 2) { return 2; };\n"
"\treturn 62;\n"
"};\n",
62, NULL },
/* Indirect mode has no declared tnode — the spread element LOAD
* op (sign extension) must come off the stamped tinfo; negative
* i32 elements pin MOVSXD. */
{ "spread_narrow_signed",
"package main;\n"
"fn addall(p: *[]i32, src: []i32) void = { append(*p, src...); };\n"
"export fn main() i32 = {\n"
"\tlet xs: []i32 = [];\n"
"\tappend(xs, -7i32, 9i32);\n"
"\tlet ys: []i32 = [];\n"
"\taddall(&ys, xs);\n"
"\tif (len(ys) != 2) { return 1; };\n"
"\tif (ys[0] != -7i32) { return 2; };\n"
"\tif (ys[1] != 9i32) { return 3; };\n"
"\treturn 63;\n"
"};\n",
63, NULL },
/* Wide (24B) spread element word-copy through the deref target. */
{ "spread_wide",
"package main;\n"
"fn addall(p: *[]str, src: []str) void = { append(*p, src...); };\n"
"export fn main() i32 = {\n"
"\tlet ss: []str = [];\n"
"\tappend(ss, \"ab\");\n"
"\tappend(ss, \"cde\");\n"
"\tlet tt: []str = [];\n"
"\taddall(&tt, ss);\n"
"\tif (len(tt) != 2) { return 1; };\n"
"\tlet a: str = tt[0];\n"
"\tlet b: str = tt[1];\n"
"\tif (a.len != 2 || b.len != 3) { return 2; };\n"
"\tif (b[0] != 'c') { return 3; };\n"
"\treturn 64;\n"
"};\n",
64, NULL },
/* str element: cgexpr leaves {ptr,len,cap} in AX/BX/CX; all three
* must survive rt_ensure AND the indirect header reload. */
{ "str_elem",
"package main;\n"
"fn adds(p: *[]str, v: str) void = { append(*p, v); };\n"
"export fn main() i32 = {\n"
"\tlet ss: []str = [];\n"
"\tadds(&ss, \"hi\");\n"
"\tadds(&ss, \"world\");\n"
"\tif (len(ss) != 2) { return 1; };\n"
"\tlet a: str = ss[0];\n"
"\tlet b: str = ss[1];\n"
"\tif (a.len != 2 || b.len != 5) { return 2; };\n"
"\tif (a[0] != 'h' || b[0] != 'w') { return 3; };\n"
"\treturn 65;\n"
"};\n",
65, NULL },
{ "slice_elem",
"package main;\n"
"fn addv(p: *[][]u8, v: []u8) void = { append(*p, v); };\n"
"export fn main() i32 = {\n"
"\tlet vv: [][]u8 = [];\n"
"\tlet one: []u8 = [];\n"
"\tappend(one, 5u8, 6u8);\n"
"\taddv(&vv, one);\n"
"\tif (len(vv) != 1) { return 1; };\n"
"\tlet got: []u8 = vv[0];\n"
"\tif (len(got) != 2 || got[1] != 6u8) { return 2; };\n"
"\treturn 66;\n"
"};\n",
66, NULL },
/* Tagged element: the #12 widen choke-point boxes through the
* resolver-derived slot pointer (grow-first, no register form). */
{ "tagged_elem",
"package main;\n"
"type tu = (i64 | void);\n"
"fn addt(p: *[]tu, v: i64) void = { append(*p, v); };\n"
"export fn main() i32 = {\n"
"\tlet ts: []tu = [];\n"
"\taddt(&ts, 42);\n"
"\tif (len(ts) != 1) { return 1; };\n"
"\tlet t0: tu = ts[0];\n"
"\tif (!(t0 is i64)) { return 2; };\n"
"\treturn 67;\n"
"};\n",
67, NULL },
/* The pA6 repro, verbatim shapes: struct-literal element fill
* through @appendscr while the header address sits in @apphdrscr. */
{ "struct_lit",
"package main;\n"
"type box = struct { pc: size, matched: bool };\n"
"fn add(p: *[]box, v: size) void = {\n"
"\tappend(*p, box { pc = v, matched = false });\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet bs: []box = [];\n"
"\tadd(&bs, 5);\n"
"\tadd(&bs, 9);\n"
"\tif (len(bs) != 2) { return 1; };\n"
"\tif (bs[0].pc != 5) { return 2; };\n"
"\tif (bs[1].pc != 9) { return 3; };\n"
"\treturn 68;\n"
"};\n",
68, NULL },
{ "struct_ident",
"package main;\n"
"type box = struct { pc: size, matched: bool };\n"
"fn add(p: *[]box, v: size) void = {\n"
"\tlet b: box = box { pc = v, matched = true };\n"
"\tappend(*p, b);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet bs: []box = [];\n"
"\tadd(&bs, 7);\n"
"\tif (len(bs) != 1) { return 1; };\n"
"\tif (bs[0].pc != 7) { return 2; };\n"
"\tif (!bs[0].matched) { return 3; };\n"
"\treturn 69;\n"
"};\n",
69, NULL },
/* The corruption symptom row: 100 appends through the param in a
* loop with a branched callee — crosses several cap-doubling
* reallocs. Sentinels on BOTH sides of the slice local pin the
* caller frame (pre-fix cstage incremented 8(BP) and passed (BP)
* to rt_ensure → caller-frame corruption); the full readback pins
* every element across the realloc moves. */
{ "realloc_loop",
"package main;\n"
"type box = struct { pc: size, matched: bool };\n"
"fn add(p: *[]box, v: size) void = {\n"
"\tif (v % 2 == 0) {\n"
"\t\tappend(*p, box { pc = v, matched = true });\n"
"\t} else {\n"
"\t\tappend(*p, box { pc = v, matched = false });\n"
"\t};\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet lo: i64 = 0x5151;\n"
"\tlet bs: []box = [];\n"
"\tlet hi: i64 = 0x7272;\n"
"\tlet i: size = 0;\n"
"\tfor (i < 100) {\n"
"\t\tadd(&bs, i);\n"
"\t\ti += 1;\n"
"\t};\n"
"\tif (len(bs) != 100) { return 1; };\n"
"\tlet j: size = 0;\n"
"\tfor (j < 100) {\n"
"\t\tif (bs[j].pc != j) { return 2; };\n"
"\t\tif (bs[j].matched != (j % 2 == 0)) { return 3; };\n"
"\t\tj += 1;\n"
"\t};\n"
"\tif (lo != 0x5151 || hi != 0x7272) { return 4; };\n"
"\treturn 70;\n"
"};\n",
70, NULL },
/* Deeper resolver spine: the header is a slice FIELD of an element
* behind a deref ((*q)[i].xs — N_DOT over N_INDEX over N_UN).
* Readback via a *holder ptr-dot, a sound read path. */
{ "deref_spine",
"package main;\n"
"type holder = struct { tag: i64, xs: []i64 };\n"
"fn addspine(q: *[]holder, i: i64, v: i64) void = {\n"
"\tappend((*q)[i].xs, v);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet hs: []holder = [];\n"
"\tlet h0: holder = holder { tag = 1, xs = [] };\n"
"\tappend(hs, h0);\n"
"\taddspine(&hs, 0, 41);\n"
"\taddspine(&hs, 0, 43);\n"
"\tlet hp: *holder = &hs[0];\n"
"\tlet g: []i64 = hp.xs;\n"
"\tif (len(g) != 2) { return 1; };\n"
"\tif (g[0] != 41 || g[1] != 43) { return 2; };\n"
"\tif (hp.tag != 1) { return 3; };\n"
"\treturn 71;\n"
"};\n",
71, NULL },
/* Ident-local targets stay with the legacy BP-disp emission (the
* resolver must never fire for them) — this row runtime-pins the
* shapes the before/after asm sweep diffed statically. */
{ "neutral_direct",
"package main;\n"
"type box = struct { pc: size, matched: bool };\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 8, 9);\n"
"\tlet ys: []i64 = [];\n"
"\tappend(ys, xs...);\n"
"\tlet ss: []str = [];\n"
"\tappend(ss, \"hi\");\n"
"\tlet ps: []box = [];\n"
"\tappend(ps, box { pc = 1, matched = false });\n"
"\tif (len(xs) != 3 || xs[2] != 9) { return 1; };\n"
"\tif (len(ys) != 3 || ys[0] != 7) { return 2; };\n"
"\tif (len(ss) != 1 || len(ps) != 1) { return 3; };\n"
"\treturn 72;\n"
"};\n",
72, NULL },
/* Reentrancy: a nested append-through-pointer inside the outer
* append's VALUE expression (match-yield arm) spills its own
* header address. Pins the per-SITE @apphdrscr slot — a shared
* per-fn slot hands the outer's post-rt_ensure reloads the inner
* target's header and the outer element lands in the wrong
* slice (silent, both counts wrong). */
{ "reentrant_value",
"package main;\n"
"type tu = (i64 | void);\n"
"fn nest(p: *[]i64, q: *[]i64, t: tu) void = {\n"
"\tappend(*p, match (t) {\n"
"\tcase let x: i64 => {\n"
"\t\tappend(*q, 500);\n"
"\t\tyield x;\n"
"\t};\n"
"\tcase void => {\n"
"\t\tyield 0;\n"
"\t};\n"
"\t});\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet a: []i64 = [];\n"
"\tlet b: []i64 = [];\n"
"\tlet t: tu = 7;\n"
"\tnest(&a, &b, t);\n"
"\tif (len(a) != 1) { return 1; };\n"
"\tif (len(b) != 1) { return 2; };\n"
"\tif (a[0] != 7) { return 3; };\n"
"\tif (b[0] != 500) { return 4; };\n"
"\treturn 33;\n"
"};\n",
33, NULL },
/* Ident-rooted dot target (h.xs through *holder): C1.5's loud
* boundary (the resolver had no ident root then; pre-FA1 this
* shape silently corrupted the frame in cstage), graduated by
* C2's cgplaceaddr N_IDENT root — the *holder base derefs once
* inside the N_DOT hop and the header place lands on &h.xs.
* Readbacks use the .len pseudo, not len(): len() of a non-tuple
* N_DOT is the pre-existing F2 enumeration gap (task #10). */
{ "identroot_dot",
"package main;\n"
"type holder = struct { tag: i64, xs: []i64 };\n"
"fn addfield(h: *holder, v: i64) void = { append(h.xs, v); };\n"
"export fn main() i32 = {\n"
"\tlet hl: holder = holder { tag = 2, xs = [] };\n"
"\taddfield(&hl, 9);\n"
"\taddfield(&hl, 11);\n"
"\tif (hl.xs[0] != 9) { return 1; };\n"
"\tif (hl.xs[1] != 11) { return 2; };\n"
"\tif (hl.xs.len != 2) { return 3; };\n"
"\tif (hl.tag != 2) { return 4; };\n"
"\treturn 73;\n"
"};\n",
73, NULL },
/* The FA4 designed boundary (task #35, old #37): a spread SOURCE that is
* not an ident local must stay loud even now that the deref
* TARGET resolves. */
{ "reject_spread_src",
"package main;\n"
"type holder = struct { tag: i64, xs: []i64 };\n"
"fn dup(p: *[]i64, q: *[]holder, i: i64) void = {\n"
"\tappend(*p, (*q)[i].xs...);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet ys: []i64 = [];\n"
"\tlet hs: []holder = [];\n"
"\tdup(&ys, &hs, 0);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "#34: append() spread source shape unsupported (rule-7)" },
};
/* 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/applp_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/applp_%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. The header-place emission is written fresh on both
* sides, so this is the converged-by-construction gate: any drift in
* the resolve/spill/reload sequence 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/applp_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/applp_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/applp_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[2080];
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[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[2120];
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, "append_place: 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,
"append_place[%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,
"append_place: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("append_place: %d fixtures passed\n", total);
return 0;
}