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.
968 lines
35 KiB
C
968 lines
35 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 |
|
||
* idxfield_struct | append(res, threads[i].root_capture) | 74
|
||
* | 56B indexed-FIELD source (#49, ha:819) |
|
||
* idx_computed_elem | append(ds, bs[i + 1]) computed-index | 75
|
||
* | whole-element source |
|
||
* derefspine_field | append(*r, (*q)[i].rc) param spines | 76
|
||
* | both sides |
|
||
* deref_src | append(*ds, *p) deref source | 77
|
||
* self_append_realloc | append(bs, bs[i]) ×33 — source inside | 78
|
||
* | the grown slice, resolve-after-grow |
|
||
* selfidx_oldlen_once | append(bs, bs[f(bs)]) — index expr runs | 80
|
||
* | ONCE, PRE-grow (sees old len): the #49 |
|
||
* | ruling's split-order semantics pin |
|
||
* elem_kinds_place | place-resolved SOURCE × every other | 79
|
||
* | element kind (scalar/narrow/str/slice/ |
|
||
* | tagged via the pre-existing arms) |
|
||
* spread_place_deref | append(*p, (*q)[i].xs...) — the old #35 | 81
|
||
* | reject source, graduated (cgplaceaddr) |
|
||
* spread_dup_copy | PG6: deref-spine + indexed 56B-struct | 139
|
||
* | spread sources (regex.ha:569/820 dup |
|
||
* | shapes) + copy-semantics pin |
|
||
* spread_place_kinds | place-sourced spread × str header / | 82
|
||
* | narrow i32 / empty deref source |
|
||
* spread_growth_place | 40-elem place-sourced spread crossing | 83
|
||
* | cap doublings (dst base moves mid-loop) |
|
||
* spread_selfalias_chain| source header inside the dst's grown | 119
|
||
* | buffer — #49 stale-base hole pin |
|
||
* reject_spread_call_src| CALL rvalue spread source — #35 | BUILD_FAIL
|
||
* | residual (task #27) |
|
||
* reject_spread_array_src| [N]T array spread source — was a | BUILD_FAIL
|
||
* | silent 16-byte garbage header read |
|
||
* reject_call_src | CALL rvalue element source — the #49 | BUILD_FAIL
|
||
* | loud tail (#42-style bound) |
|
||
*
|
||
* 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 },
|
||
|
||
/* #49 (#35's single-element sibling): the struct-element append
|
||
* arm dispatched on SOURCE node kind — N_STRUCTLIT and N_IDENT
|
||
* only; every place-resolvable chain (the regex.ha:819
|
||
* result-build line `append(res, threads[best_idx].root_capture)`)
|
||
* died on the rule-7 fatal. The fix routes the remaining shapes
|
||
* through cgplaceaddr (the C1 resolver): dst slot spills to
|
||
* @appendscr, source resolves AFTER the grow, whole-width
|
||
* word-copy through DX. Five rows: indexed-field 56B capture,
|
||
* computed-index whole element, deref-spine through params,
|
||
* deref source, self-append across reallocs. */
|
||
{ "idxfield_struct",
|
||
"package main;\n"
|
||
"type capture = struct { content: str, start: size,\n"
|
||
"\tstart_bytesize: size, end: size, end_bytesize: size };\n"
|
||
"type thread = struct { pc: size, root_capture: capture,\n"
|
||
"\tmatched: bool };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet threads: []thread = [];\n"
|
||
"\tappend(threads, thread { pc = 7, ... });\n"
|
||
"\tappend(threads, thread { pc = 1, root_capture = capture {\n"
|
||
"\t\tcontent = \"bcd\", start = 1, start_bytesize = 2,\n"
|
||
"\t\tend = 4, end_bytesize = 5 }, ... });\n"
|
||
"\tlet res: []capture = [];\n"
|
||
"\tlet i: size = 1;\n"
|
||
"\tappend(res, threads[i].root_capture);\n"
|
||
"\tif (len(res) != 1) { return 1; };\n"
|
||
"\tif (res[0].start != 1) { return 2; };\n"
|
||
"\tif (res[0].start_bytesize != 2) { return 3; };\n"
|
||
"\tif (res[0].end != 4) { return 4; };\n"
|
||
"\tif (res[0].end_bytesize != 5) { return 5; };\n"
|
||
"\tif (res[0].content.len != 3) { return 6; };\n"
|
||
"\tif (threads[1].root_capture.end != 4) { return 7; };\n"
|
||
"\treturn 74;\n"
|
||
"};\n",
|
||
74, NULL },
|
||
|
||
{ "idx_computed_elem",
|
||
"package main;\n"
|
||
"type box = struct { pc: size, a: i64, b: i64, x: i64 };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet bs: []box = [];\n"
|
||
"\tappend(bs, box { pc = 1, a = 10, ... });\n"
|
||
"\tappend(bs, box { pc = 2, a = 20, ... });\n"
|
||
"\tappend(bs, box { pc = 3, a = 30, b = 7, x = 8 });\n"
|
||
"\tlet ds: []box = [];\n"
|
||
"\tlet i: i64 = 1;\n"
|
||
"\tappend(ds, bs[i + 1]);\n"
|
||
"\tif (len(ds) != 1) { return 1; };\n"
|
||
"\tif (ds[0].pc != 3) { return 2; };\n"
|
||
"\tif (ds[0].a != 30) { return 3; };\n"
|
||
"\tif (ds[0].b != 7 || ds[0].x != 8) { return 4; };\n"
|
||
"\treturn 75;\n"
|
||
"};\n",
|
||
75, NULL },
|
||
|
||
{ "derefspine_field",
|
||
"package main;\n"
|
||
"type cap = struct { s: size, e: size };\n"
|
||
"type th = struct { pc: size, rc: cap, m: bool };\n"
|
||
"fn pick(r: *[]cap, q: *[]th, i: size) void = {\n"
|
||
"\tappend(*r, (*q)[i].rc);\n"
|
||
"};\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet ts: []th = [];\n"
|
||
"\tappend(ts, th { pc = 1, rc = cap { s = 5, e = 9 }, ... });\n"
|
||
"\tlet rs: []cap = [];\n"
|
||
"\tpick(&rs, &ts, 0);\n"
|
||
"\tif (len(rs) != 1) { return 1; };\n"
|
||
"\tif (rs[0].s != 5) { return 2; };\n"
|
||
"\tif (rs[0].e != 9) { return 3; };\n"
|
||
"\treturn 76;\n"
|
||
"};\n",
|
||
76, NULL },
|
||
|
||
{ "deref_src",
|
||
"package main;\n"
|
||
"type box = struct { pc: size, a: i64, b: i64, x: i64 };\n"
|
||
"fn addsrc(ds: *[]box, p: *box) void = { append(*ds, *p); };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet b: box = box { pc = 4, a = 40, b = 41, x = 42 };\n"
|
||
"\tlet ds: []box = [];\n"
|
||
"\taddsrc(&ds, &b);\n"
|
||
"\tif (len(ds) != 1) { return 1; };\n"
|
||
"\tif (ds[0].pc != 4) { return 2; };\n"
|
||
"\tif (ds[0].a != 40 || ds[0].b != 41 || ds[0].x != 42) { return 3; };\n"
|
||
"\treturn 77;\n"
|
||
"};\n",
|
||
77, NULL },
|
||
|
||
/* Self-append: the source element lives in the SLICE BEING GROWN,
|
||
* so its address is only valid post-rt_ensure — pins the
|
||
* resolve-AFTER-grow order across several cap-doubling reallocs.
|
||
* Sentinels around the header pin the frame. */
|
||
{ "self_append_realloc",
|
||
"package main;\n"
|
||
"type box = struct { pc: size, a: i64, b: i64, x: i64 };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet lo: i64 = 111;\n"
|
||
"\tlet bs: []box = [];\n"
|
||
"\tlet hi: i64 = 222;\n"
|
||
"\tappend(bs, box { pc = 9, a = 100, b = 200, x = 300 });\n"
|
||
"\tlet i: i32 = 0;\n"
|
||
"\tfor (i < 33) {\n"
|
||
"\t\tappend(bs, bs[i]);\n"
|
||
"\t\ti += 1;\n"
|
||
"\t};\n"
|
||
"\tif (len(bs) != 34) { return 1; };\n"
|
||
"\tif (bs[0].pc != 9 || bs[33].pc != 9) { return 2; };\n"
|
||
"\tif (bs[17].a != 100 || bs[33].b != 200) { return 3; };\n"
|
||
"\tif (bs[33].x != 300) { return 4; };\n"
|
||
"\tif (lo != 111 || hi != 222) { return 5; };\n"
|
||
"\treturn 78;\n"
|
||
"};\n",
|
||
78, NULL },
|
||
|
||
/* The #49 ruling's order pin, positive semantics row (converged
|
||
* with Hare, not a documented divergence): the source chain's
|
||
* rvalues — here a CALLED index helper reading the slice's len —
|
||
* evaluate exactly ONCE, PRE-grow. calls != 1 catches a
|
||
* double-evaluation; bs[2] != old-last catches a post-grow
|
||
* resolution (the helper would see the bumped len and return the
|
||
* uninitialized new slot — the pre-split emission failed exactly
|
||
* there, exit 3). */
|
||
{ "selfidx_oldlen_once",
|
||
"package main;\n"
|
||
"type box = struct { pc: i64, a: i64 };\n"
|
||
"let calls: i64 = 0;\n"
|
||
"fn lastidx(xs: []box) i64 = {\n"
|
||
"\tcalls += 1;\n"
|
||
"\tlet n: i64 = len(xs): i64;\n"
|
||
"\treturn n - 1;\n"
|
||
"};\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet bs: []box = [];\n"
|
||
"\tappend(bs, box { pc = 11, a = 1 });\n"
|
||
"\tappend(bs, box { pc = 4242, a = 2 });\n"
|
||
"\tappend(bs, bs[lastidx(bs)]);\n"
|
||
"\tif (calls != 1) { return 1; };\n"
|
||
"\tif (len(bs) != 3) { return 2; };\n"
|
||
"\tif (bs[2].pc != 4242 || bs[2].a != 2) { return 3; };\n"
|
||
"\tif (bs[0].pc != 11) { return 4; };\n"
|
||
"\treturn 80;\n"
|
||
"};\n",
|
||
80, NULL },
|
||
|
||
/* Element-kind × place-source matrix: struct was the ONLY kind
|
||
* the source-shape dispatch rejected — scalar (wide + narrow),
|
||
* str-header, slice-header and tagged sources route through the
|
||
* pre-existing cgexpr-based arms and already worked at 796d41b.
|
||
* This row is the regression net pinning that matrix (runtime +
|
||
* byte-id, dual-driver), not a fix pin. */
|
||
{ "elem_kinds_place",
|
||
"package main;\n"
|
||
"type box = struct { n: i64, m: i32, name: str, xs: []u8 };\n"
|
||
"type tv = (i64 | void);\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet inner: []u8 = [];\n"
|
||
"\tappend(inner, 7u8);\n"
|
||
"\tappend(inner, 8u8);\n"
|
||
"\tlet bs: []box = [];\n"
|
||
"\tappend(bs, box { n = 41, m = 9, name = \"hello\", xs = inner });\n"
|
||
"\tlet ns: []i64 = [];\n"
|
||
"\tappend(ns, bs[0].n);\n"
|
||
"\tlet ms: []i32 = [];\n"
|
||
"\tappend(ms, bs[0].m);\n"
|
||
"\tlet ss: []str = [];\n"
|
||
"\tappend(ss, bs[0].name);\n"
|
||
"\tlet vs: [][]u8 = [];\n"
|
||
"\tappend(vs, bs[0].xs);\n"
|
||
"\tlet ts: []tv = [];\n"
|
||
"\tappend(ts, bs[0].n);\n"
|
||
"\tif (len(ns) != 1 || ns[0] != 41) { return 1; };\n"
|
||
"\tif (len(ms) != 1 || ms[0] != 9) { return 2; };\n"
|
||
"\tif (len(ss) != 1 || len(ss[0]) != 5) { return 3; };\n"
|
||
"\tif (len(vs) != 1 || len(vs[0]) != 2) { return 4; };\n"
|
||
"\tif (vs[0][1] != 8u8) { return 5; };\n"
|
||
"\tmatch (ts[0]) {\n"
|
||
"\tcase let v: i64 => { if (v != 41) { return 6; }; };\n"
|
||
"\tcase void => { return 7; };\n"
|
||
"\t};\n"
|
||
"\treturn 79;\n"
|
||
"};\n",
|
||
79, NULL },
|
||
|
||
/* #35 graduation: the FA4-era designed boundary — a place-chain
|
||
* spread SOURCE — now resolves through cgplaceaddr (header
|
||
* address pre-grow, ptr/len re-read through it per iteration).
|
||
* This row is the OLD reject source verbatim, now with elements
|
||
* and a readback. */
|
||
{ "spread_place_deref",
|
||
"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 inner: []i64 = [];\n"
|
||
"\tappend(inner, 5);\n"
|
||
"\tappend(inner, 9);\n"
|
||
"\tlet ys: []i64 = [];\n"
|
||
"\tlet hs: []holder = [];\n"
|
||
"\tappend(hs, holder { tag = 1, xs = inner });\n"
|
||
"\tdup(&ys, &hs, 0);\n"
|
||
"\tif (len(ys) != 2) { return 1; };\n"
|
||
"\tif (ys[0] != 5 || ys[1] != 9) { return 2; };\n"
|
||
"\treturn 81;\n"
|
||
"};\n",
|
||
81, NULL },
|
||
|
||
/* #35, the fold-5 dup shapes (PG6): (a) deref-spine source
|
||
* `(*tsp)[0].caps...` (add_thread regex.ha:569/572), (b) indexed
|
||
* source `ts[0].caps...` (search ha:820), 56B struct elements;
|
||
* plus the COPY-semantics pin — mutating the dup must not touch
|
||
* the source. */
|
||
{ "spread_dup_copy",
|
||
"package main;\n"
|
||
"type capture = struct {\n"
|
||
"\tcontent: str,\n"
|
||
"\tstart: size,\n"
|
||
"\tstart_bytesize: size,\n"
|
||
"\tend: size,\n"
|
||
"\tend_bytesize: size,\n"
|
||
"};\n"
|
||
"type thr = struct { pc: size, caps: []capture };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet c0: []capture = [];\n"
|
||
"\tappend(c0, capture { content = \"x\", start = 3: size, start_bytesize = 3: size, end = 8: size, end_bytesize = 8: size });\n"
|
||
"\tappend(c0, capture { content = \"yz\", start = 5: size, start_bytesize = 5: size, end = 9: size, end_bytesize = 9: size });\n"
|
||
"\tlet ts: []thr = [];\n"
|
||
"\tappend(ts, thr { pc = 0: size, caps = c0 });\n"
|
||
"\tlet tsp = &ts;\n"
|
||
"\tlet dup: []capture = [];\n"
|
||
"\tappend(dup, (*tsp)[0].caps...);\n"
|
||
"\tif (len(dup) != 2) { return 1; };\n"
|
||
"\tif (len(dup[1].content) != 2 || dup[1].content[0] != 'y') { return 2; };\n"
|
||
"\tlet res: []capture = [];\n"
|
||
"\tappend(res, ts[0].caps...);\n"
|
||
"\tif (len(res) != 2) { return 3; };\n"
|
||
"\tif (res[0].end != 8 || res[1].start != 5) { return 4; };\n"
|
||
"\tdup[0].start = 100: size;\n"
|
||
"\tif (ts[0].caps[0].start != 3) { return 5; };\n"
|
||
"\tlet acc = dup[0].start + dup[1].end + res[0].start + res[1].end_bytesize\n"
|
||
"\t\t+ ts[0].caps[0].end + (len(dup): size) * 5;\n"
|
||
"\treturn acc: i32;\n"
|
||
"};\n",
|
||
139, NULL },
|
||
|
||
/* #35 element kinds through a PLACE source: str 24B headers,
|
||
* narrow i32 (scalar load/store width), and the empty source
|
||
* (zero-iteration loop) through a deref. */
|
||
{ "spread_place_kinds",
|
||
"package main;\n"
|
||
"type holder = struct { tag: i64, ss: []str, ns: []i32 };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet h = holder { tag = 1, ss = [], ns = [] };\n"
|
||
"\tappend(h.ss, \"ab\");\n"
|
||
"\tappend(h.ss, \"cde\");\n"
|
||
"\tappend(h.ns, 7: i32);\n"
|
||
"\tlet hs: []holder = [];\n"
|
||
"\tappend(hs, h);\n"
|
||
"\tlet ss: []str = [];\n"
|
||
"\tappend(ss, hs[0].ss...);\n"
|
||
"\tif (len(ss) != 2) { return 1; };\n"
|
||
"\tif (len(ss[0]) != 2 || ss[0][1] != 'b') { return 2; };\n"
|
||
"\tif (len(ss[1]) != 3 || ss[1][2] != 'e') { return 3; };\n"
|
||
"\tlet ns: []i32 = [];\n"
|
||
"\tappend(ns, hs[0].ns...);\n"
|
||
"\tif (len(ns) != 1 || ns[0] != 7) { return 4; };\n"
|
||
"\tlet empty: []i64 = [];\n"
|
||
"\tlet ep = ∅\n"
|
||
"\tlet acc: []i64 = [];\n"
|
||
"\tappend(acc, (*ep)...);\n"
|
||
"\tif (len(acc) != 0) { return 5; };\n"
|
||
"\treturn 82;\n"
|
||
"};\n",
|
||
82, NULL },
|
||
|
||
/* #35 growth across cap: a 40-element place-sourced spread into a
|
||
* non-empty dst crosses several cap doublings — rt_ensure moves
|
||
* the dst base mid-loop; every slot lands because the dst header
|
||
* re-reads post-grow each iteration. */
|
||
{ "spread_growth_place",
|
||
"package main;\n"
|
||
"type holder = struct { tag: i64, xs: []i64 };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet big: []i64 = [];\n"
|
||
"\tlet i: i64 = 0;\n"
|
||
"\tfor (i < 40) { append(big, i); i += 1; };\n"
|
||
"\tlet hs: []holder = [];\n"
|
||
"\tappend(hs, holder { tag = 2, xs = big });\n"
|
||
"\tlet dst: []i64 = [];\n"
|
||
"\tappend(dst, 100);\n"
|
||
"\tappend(dst, hs[0].xs...);\n"
|
||
"\tif (len(dst) != 41) { return 1; };\n"
|
||
"\tif (dst[0] != 100 || dst[1] != 0 || dst[40] != 39) { return 2; };\n"
|
||
"\treturn 83;\n"
|
||
"};\n",
|
||
83, NULL },
|
||
|
||
/* #35 aliasing: the source HEADER lives inside the dst's own
|
||
* buffer (xs[0].kids reached through xs.ptr); the first grow
|
||
* reallocs that buffer. The pre-grow-resolved header address then
|
||
* reads the STALE copy — bit-identical under the non-reclaiming
|
||
* rt/malloc, the documented #49 stale-base hole. Pins that the
|
||
* spread terminates AND copies the right elements. */
|
||
{ "spread_selfalias_chain",
|
||
"package main;\n"
|
||
"type tnode = struct { v: i64, kids: []tnode };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet inner: []tnode = [];\n"
|
||
"\tappend(inner, tnode { v = 7, kids = [] });\n"
|
||
"\tappend(inner, tnode { v = 9, kids = [] });\n"
|
||
"\tlet xs: []tnode = [];\n"
|
||
"\tappend(xs, tnode { v = 1, kids = inner });\n"
|
||
"\tappend(xs, xs[0].kids...);\n"
|
||
"\tif (len(xs) != 3) { return 1; };\n"
|
||
"\tif (xs[0].v != 1 || xs[1].v != 7 || xs[2].v != 9) { return 2; };\n"
|
||
"\treturn (81 + xs[1].v * 2 + xs[2].v * 2 + (len(xs): i64) * 2): i32;\n"
|
||
"};\n",
|
||
119, NULL },
|
||
|
||
/* The #35 residual boundary (task #27): a CALL rvalue spread
|
||
* source has no place to resolve — stays loud until a
|
||
* scratch-receive route lands. */
|
||
{ "reject_spread_call_src",
|
||
"package main;\n"
|
||
"fn mk() []i64 = { let r: []i64 = []; append(r, 5); return r; };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet xs: []i64 = [];\n"
|
||
"\tappend(xs, mk()...);\n"
|
||
"\treturn 0;\n"
|
||
"};\n",
|
||
BUILD_FAIL, "#35: append() spread source shape unsupported (rule-7)" },
|
||
|
||
/* The #35 residual boundary (task #27): a [N]T ARRAY source place
|
||
* IS its storage, not a {ptr,len,cap} header — pre-guard the
|
||
* ident path silently read its first 16 data bytes as ptr/len.
|
||
* Loud until wired. */
|
||
{ "reject_spread_array_src",
|
||
"package main;\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet a: [2]i64 = [3, 4];\n"
|
||
"\tlet xs: []i64 = [];\n"
|
||
"\tappend(xs, a...);\n"
|
||
"\treturn 0;\n"
|
||
"};\n",
|
||
BUILD_FAIL, "#35: append() spread source shape unsupported (rule-7)" },
|
||
|
||
/* The #49 boundary: a CALL rvalue source has no place to resolve
|
||
* — stays loud (the #42-style bound) until a scratch-receive
|
||
* route lands. */
|
||
{ "reject_call_src",
|
||
"package main;\n"
|
||
"type box = struct { pc: size, a: i64 };\n"
|
||
"fn mk() box = { return box { pc = 1, a = 2 }; };\n"
|
||
"export fn main() i32 = {\n"
|
||
"\tlet bs: []box = [];\n"
|
||
"\tappend(bs, mk());\n"
|
||
"\treturn 0;\n"
|
||
"};\n",
|
||
BUILD_FAIL, "#34: append() struct element 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 tmpdir[64], src[128], outbin[128], errlog[160];
|
||
char rmcmd[160], cmd[1200];
|
||
snprintf(tmpdir, sizeof tmpdir, "/tmp/applp_%d_d_%d", getpid(), i);
|
||
mkdir(tmpdir, 0755);
|
||
snprintf(src, sizeof src, "%s/applp_%d_%d.ww", tmpdir, getpid(), i);
|
||
snprintf(outbin, sizeof outbin, "%s/applp_%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 — 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;
|
||
}
|