c1/c2 widened flatvariantidxt (selfhost) with the chain + structural tag-synthesis arms and a >=2 ambiguity os.exit, scoped to the cgen WIDEN consumer. But flatvariantidxt is a choke-point: the wwstage is/as ACCEPTANCE gate (check.ww:4677, the #198 spread fallback) reuses it, so the widening leaked into checker acceptance — vs base329481c: * `let v:(void|ali)=…; v is base` (ali=base): cstage rejects, wwstage ACCEPTED+built — new cs!=ww acceptance divergence (rule-10 break); * `(void|tb)`, `v is ta` (unrelated same-layout): same leak via the c2 structural arm; * `(ali|ali2)`, `v is base`: wwstage DIED with the cgen-internal fatal "flatvariantidxt: source alias chain reaches >=2 variants" DURING CHECK — a cgen diag surfacing in the checker (layering). cstage is unaffected: its is/as gate (check.c:2036) is independent of cg_tag_for_variant (cgen-phase only), so the fuse was already broken at this site — the cgen-helper change moved wwstage's CHECKER but not cstage's. This contradicts the #95 fold scope ("cgen-tag fold, no acceptance change except the ambiguity hard-error [at the widen site]"). Fix (rob-ruled): the is/as gate needs only nominal variant membership = pass 1. Add an explicit `exactonly` mode to flatvariantidxt — the checker caller passes true (returns after the exact loop: no chain/ structural arms, no os.exit), every cgen caller passes false (full tag-synthesis, unchanged). Two consumers, two modes — the honest representation, not a wrapper. cstage's cg_tag_for_variant has no twin checker caller, so it stays full-only and is UNTOUCHED by c3 (rule-10 satisfied: the param changes no asm — cgen always passes false; the checker now MATCHES cstage's reject). casevariantin still backs the #198 spread fallback. Pins (test/wcc/944_variant_chain_b95_run.c, +4 rows -> 56 checks): isas_chain_reject / isas_unrel_reject — BOTH stages reject the leaked is/as shapes (shared experr substring "not a variant"); the c1 chain + c2 structural arms no longer widen acceptance. isas_amb_reject_notcrash — the (ali|ali2)/`is base` shape rejects CLEANLY (the cgen fatal text would be absent -> red), NOT a crash. twin_prim_alias_amb — rob's obligated mixed prim/alias TWIN: (int | ai) ai=int, source aj=int — both share the int bottom under all-variants counting, so the cgen WIDEN (full mode) hard-errors ("source alias chain reaches >=2 variants"), pinned LOUD both stages. The deferred question (should is/as EVER accept cgen's richer chain/ structural shapes? = a checker-strictness feature, both stages together) is filed as task #107, explicitly NOT folded here. Invariants: c1/c2 cgen behavior unchanged (all cgen callers pass false = full mode); suite byte-id rows + the dissolution corpus hold. make all 0; sizelint 0; peellint 0 (the mode param adds no peel sites); combined.ww regen idempotent; test-unit "all 295 tests passed". c3 touches ZERO cstage bytes — cmd/w6c/cgen.c carries only the c1/c2 additions, and cmd/wcc/check.c is unchanged from base329481c.
571 lines
19 KiB
C
571 lines
19 KiB
C
/*
|
|
* 944_variant_chain_b95_run — #95: variant-match chain-membership arm
|
|
* (pass 1b) + structural fallback (c2), BOTH stages fused (cstage
|
|
* cg_tag_for_variant + wwstage flatvariantidxt are the same route).
|
|
* Pre-#95 a NAMED struct source that was not pointer-identical to a
|
|
* NAMED variant fell through every pass and the widen stored tag 0 —
|
|
* both stages, byte-identical, gate-blind (the worst class on the
|
|
* board: silent wrong tag on VALID code).
|
|
*
|
|
* Row sources are ken's #95 first-position oracle
|
|
* (.ai/ken-95-oracle.md, probes /tmp/b95/src + kb4/kb5) and rob's
|
|
* fold spec (.ai/rob-95-spec.md). Reference: harec
|
|
* tagged_select_subtype P1/P2/P3 (ref/harec/src/types.c:702-739,
|
|
* :988-1140).
|
|
*
|
|
* row | shape (oracle row) | cs/ww
|
|
* --------------------+-------------------------------------+------
|
|
* chain_1lvl_i | kb5_v2s1i HEADLINE: depth-1 ident |
|
|
* | src base → (void|al0) — was both- |
|
|
* | wrong-IDENTICAL byte-id tag 0 | 0/0
|
|
* chain_2lvl_i | kb95_2lvl_i: ident src depth-2 — |
|
|
* | the class extends to any depth | 0/0
|
|
* chain_deep_src | kb95_deep_src: src ali2 → (void| |
|
|
* | ali) — chain-direction DOWN | 0/0
|
|
* chain_deep_var | kb95_deep_var: src base → (void| |
|
|
* | ali2) — chain-direction UP | 0/0
|
|
* chain_call_bound81 | kb5_v2s1: CALL src 1-lvl — runs |
|
|
* | 0/0; byte-id waived: pre-existing |
|
|
* | #81 uninit-local zero-fill asm |
|
|
* | noise (NO at the #95 base too) | 0/0
|
|
* chain_call2_bound81 | kb4_v2_struct2 (#95's original): |
|
|
* | CALL src 2-lvl — same #81 waiver | 0/0
|
|
* chain_amb_loud | kb95_amb: (ali|ali2) over one base, |
|
|
* | src base — TODAY silently tagged |
|
|
* | member 0; ≥2 chain hits cannot be |
|
|
* | disambiguated in the nominal-lossy |
|
|
* | model (harec nassign≥2 → NULL) — |
|
|
* | hard-error, twin texts share the |
|
|
* | experr tail | err/err
|
|
* chain_amb_srcA/B | src ali / src ali2 into (ali|ali2): |
|
|
* | pass-1 EXACT fires first (harec P1 |
|
|
* | short-circuit) — precedence pins, |
|
|
* | MUST NOT MOVE | 0/0
|
|
* nom_str / nom_err | (str|linerr) #218 nominal pins: str |
|
|
* | src tags str, linerr src tags |
|
|
* | linerr — MUST NOT MOVE through the |
|
|
* | new arm | 0/0
|
|
* exact_ctl | kb5_v2sE2: exact-match ident |
|
|
* | control — MUST NOT MOVE | 0/0
|
|
* bare_ctl/bare_2lvl | kb4_v2_ctrl / kb4_v2_alias2: pass-2 |
|
|
* | bare-source controls — MUST NOT |
|
|
* | MOVE | 0/0
|
|
* bare_ambig/2 | kb4_v2_ambig(2): pass-2 ambiguity |
|
|
* | guards hold their loud texts | err/err
|
|
* callret_bound277 | kb5_v2sE: CALL src typed-as-alias — |
|
|
* | ww #277 aggregate-return capability |
|
|
* | leg, OUT of #95; cells pinned as |
|
|
* | observed (cs runs, ww loud) | 0/err
|
|
* unrel_struct | kb95_unrel (c2): ta/tb nominally- |
|
|
* | unrelated same-layout structs, src |
|
|
* | ta → (void|tb) — was both-wrong- |
|
|
* | identical byte-id; harec interning |
|
|
* | makes dealias(ta)==dealias(tb) → |
|
|
* | accept, tag tb (types.c:72-81 + |
|
|
* | :1000-1002) | 0/0
|
|
*
|
|
* K_RUN rows build+run BOTH drivers (exit==want) and assert cstage/
|
|
* wwstage asm byte-id. K_RUN_NOID waives byte-id — cite the blocking
|
|
* task at the row. K_RUN_CS_WWERR: cstage build+run exit==cswant AND
|
|
* wwstage build FAILS with experr (944_alias_accept_run kind). NNN<950,
|
|
* self-contained /tmp sources, no imports (944 precedent).
|
|
*/
|
|
#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;
|
|
}
|
|
|
|
static int
|
|
slurp_eq(const char *a, const char *b)
|
|
{
|
|
FILE *fa = fopen(a, "rb");
|
|
FILE *fb = fopen(b, "rb");
|
|
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
|
int rc = 0;
|
|
for (;;) {
|
|
int ca = fgetc(fa), cb = fgetc(fb);
|
|
if (ca != cb) { rc = -1; break; }
|
|
if (ca == EOF) break;
|
|
}
|
|
fclose(fa); fclose(fb);
|
|
return rc;
|
|
}
|
|
|
|
#define K_RUN 0 /* build+run BOTH, exits==wants, + byte-id */
|
|
#define K_BUILDERR 1 /* build FAILS with experr on BOTH drivers */
|
|
#define K_RUN_NOID 2 /* build+run BOTH, exits==wants, byte-id
|
|
* SKIPPED — cite the blocking task */
|
|
#define K_RUN_CS_WWERR 3 /* bound row: cstage build+run exit==cswant;
|
|
* wwstage build FAILS with experr — cite
|
|
* the blocking task */
|
|
|
|
struct row { const char *label; const char *src;
|
|
int cswant; int wwwant; int kind; const char *experr; };
|
|
|
|
/* errlog_has — a builderr cell must fail WITH its diagnostic; any other
|
|
* failure (parse error, crash, hang-kill) is a vacuous reject (940
|
|
* precedent). */
|
|
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 const struct row rows[] = {
|
|
/* ---- c1: chain-membership graduations (were tag-0 silent) */
|
|
{ "chain_1lvl_i",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | al0) = s;\n"
|
|
" if (!(v is al0)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "chain_2lvl_i",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"type ali = al0;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | ali) = s;\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "chain_deep_src",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: ali2;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | ali) = s;\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "chain_deep_var",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | ali2) = s;\n"
|
|
" if (!(v is ali2)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
/* CALL-src legs run 0/0; byte-id waived: the mk() frame carries
|
|
* the pre-existing #81 uninit-local zero-fill asm divergence
|
|
* (cells were byteid=NO at the #95 base too). */
|
|
{ "chain_call_bound81",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"fn mk() base = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" return s;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
" let v: (void | al0) = mk();\n"
|
|
" if (!(v is al0)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN_NOID, NULL }, /* byte-id: task #81 */
|
|
{ "chain_call2_bound81",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"type ali = al0;\n"
|
|
"fn mk() base = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" return s;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
" let v: (void | ali) = mk();\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN_NOID, NULL }, /* byte-id: task #81 */
|
|
/* ≥2 chain hits — was a SILENT member-0 tag; twin texts share
|
|
* the experr tail (prefix convention). */
|
|
{ "chain_amb_loud",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: base;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (ali | ali2) = s;\n"
|
|
" if (v is ali) { return 1; };\n"
|
|
" if (v is ali2) { return 2; };\n"
|
|
" return 3;\n"
|
|
"};\n", 0, 0, K_BUILDERR,
|
|
"source alias chain reaches >=2 variants" },
|
|
/* pass-1 EXACT precedence pins: an exact source distinguishes
|
|
* (ali|ali2) — the chain arm must stay BEHIND pass 1. */
|
|
{ "chain_amb_srcA",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: ali;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (ali | ali2) = s;\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" if (v is ali2) { return 2; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "chain_amb_srcB",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: ali2;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (ali | ali2) = s;\n"
|
|
" if (!(v is ali2)) { return 1; };\n"
|
|
" if (v is ali) { return 2; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
/* #218 nominal regression pins (rob §3e): (str | linerr) stays
|
|
* distinguishable through the new arm. */
|
|
{ "nom_str",
|
|
"package main;\n"
|
|
"type linerr = str;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: str = \"ok\";\n"
|
|
" let v: (str | linerr) = s;\n"
|
|
" if (!(v is str)) { return 1; };\n"
|
|
" if (v is linerr) { return 2; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "nom_err",
|
|
"package main;\n"
|
|
"type linerr = str;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let e: linerr = \"bad\";\n"
|
|
" let v: (str | linerr) = e;\n"
|
|
" if (!(v is linerr)) { return 1; };\n"
|
|
" if (v is str) { return 2; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "exact_ctl",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"type ali = al0;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: ali;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | ali) = s;\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
/* ---- pass-2 bare-source controls (the #15 leg stays put) */
|
|
{ "bare_ctl",
|
|
"package main;\n"
|
|
"type vt = struct { a: int };\n"
|
|
"type stream = *vt;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let x: vt;\n"
|
|
" x.a = 7;\n"
|
|
" let v: (void | stream) = &x;\n"
|
|
" if (!(v is stream)) { return 1; };\n"
|
|
" match (v) {\n"
|
|
" case let s: stream => { if (s.a != 7) { return 2; }; };\n"
|
|
" case void => { return 3; };\n"
|
|
" };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "bare_2lvl",
|
|
"package main;\n"
|
|
"type vt = struct { a: int };\n"
|
|
"type st0 = *vt;\n"
|
|
"type stream = st0;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let x: vt;\n"
|
|
" x.a = 7;\n"
|
|
" let v: (void | stream) = &x;\n"
|
|
" if (!(v is stream)) { return 1; };\n"
|
|
" match (v) {\n"
|
|
" case let s: stream => { if (s.a != 7) { return 2; }; };\n"
|
|
" case void => { return 3; };\n"
|
|
" };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
{ "bare_ambig",
|
|
"package main;\n"
|
|
"type p1 = *i32;\n"
|
|
"type p2 = *i32;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let x: i32 = 7;\n"
|
|
" let v: (p1 | p2) = &x;\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_BUILDERR,
|
|
"bare source structurally matches >=2 NAMED variants" },
|
|
{ "bare_ambig2",
|
|
"package main;\n"
|
|
"type b1 = *i32;\n"
|
|
"type b2 = *i32;\n"
|
|
"type q1 = b1;\n"
|
|
"type q2 = b2;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let x: i32 = 7;\n"
|
|
" let v: (q1 | q2) = &x;\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_BUILDERR,
|
|
"bare source structurally matches >=2 NAMED variants" },
|
|
/* ww #277 aggregate-return capability leg — OUT of #95; dual
|
|
* cells pinned as observed so neither side drifts silently. */
|
|
{ "callret_bound277",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type al0 = base;\n"
|
|
"type ali = al0;\n"
|
|
"fn mk() ali = {\n"
|
|
" let s: ali;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" return s;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
" let v: (void | ali) = mk();\n"
|
|
" if (!(v is ali)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN_CS_WWERR,
|
|
"deferred #277" }, /* ww: task #277 */
|
|
/* ---- c2: structural fallback — was both-wrong-identical
|
|
* byte-id silent (live metric-1: checkers accept, cgen tagged
|
|
* 0). harec accepts by interning (one node, tag tb). */
|
|
{ "unrel_struct",
|
|
"package main;\n"
|
|
"type ta = struct { a: int, b: int };\n"
|
|
"type tb = struct { a: int, b: int };\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: ta;\n"
|
|
" s.a = 4; s.b = 9;\n"
|
|
" let v: (void | tb) = s;\n"
|
|
" if (!(v is tb)) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_RUN, NULL },
|
|
/* ---- c3 (#107): is/as ACCEPTANCE must stay nominal-exact. cgen's
|
|
* chain/structural tag-synthesis arms widen tag lookup at the WIDEN
|
|
* site only; the wwstage is/as gate (check.ww:4677) reuses
|
|
* flatvariantidxt and, pre-c3, the widening leaked into ACCEPTANCE
|
|
* (cs!=ww) and surfaced the >=2 cgen fatal mid-check. c3 added an
|
|
* exact-only mode for that caller. These pin BOTH stages REJECTING
|
|
* (cstage's independent gate check.c:2036 already does; wwstage now
|
|
* matches it) — NOT a crash for the ambiguity shape. Shared experr
|
|
* substring "not a variant" (cstage "X is not a variant of (...)",
|
|
* wwstage "is/as: not a variant of operand (X)"). */
|
|
{ "isas_chain_reject",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"fn f(v: (void | ali)) i32 = {\n"
|
|
" if (v is base) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = { return 0; };\n",
|
|
0, 0, K_BUILDERR, "not a variant" },
|
|
{ "isas_unrel_reject",
|
|
"package main;\n"
|
|
"type ta = struct { a: int, b: int };\n"
|
|
"type tb = struct { a: int, b: int };\n"
|
|
"fn f(v: (void | tb)) i32 = {\n"
|
|
" if (v is ta) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = { return 0; };\n",
|
|
0, 0, K_BUILDERR, "not a variant" },
|
|
{ "isas_amb_reject_notcrash",
|
|
"package main;\n"
|
|
"type base = struct { a: int, b: int };\n"
|
|
"type ali = base;\n"
|
|
"type ali2 = ali;\n"
|
|
"fn f(v: (ali | ali2)) i32 = {\n"
|
|
" if (v is base) { return 1; };\n"
|
|
" return 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = { return 0; };\n",
|
|
0, 0, K_BUILDERR, "not a variant" },
|
|
/* ---- rob's obligated mixed prim/alias TWIN: union (int | ai)
|
|
* ai=int, source typed aj=int — under all-variants counting both the
|
|
* bare `int` variant and `ai` share the int bottom -> the cgen widen
|
|
* (full mode) hard-errors (harec nassign>=2 -> NULL). Pinned LOUD
|
|
* BOTH stages (the chain-ambiguity path; #95 deviation-2 ratified). */
|
|
{ "twin_prim_alias_amb",
|
|
"package main;\n"
|
|
"type ai = int;\n"
|
|
"type aj = int;\n"
|
|
"export fn main() i32 = {\n"
|
|
" let s: aj = 7;\n"
|
|
" let v: (int | ai) = s;\n"
|
|
" return 0;\n"
|
|
"};\n", 0, 0, K_BUILDERR,
|
|
"source alias chain reaches >=2 variants" },
|
|
};
|
|
|
|
static int
|
|
run_driver(const char *driver, const struct row *r, int i, int expect_err,
|
|
int want)
|
|
{
|
|
char src[96], tmpdir[96], errf[96], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/vc95_%d_%d.ww", getpid(), i);
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/vc95_%d_d_%d", getpid(), i);
|
|
snprintf(errf, sizeof errf, "/tmp/vc95_%d_e_%d", getpid(), i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
mkdir(tmpdir, 0755);
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && timeout 20 %s build %s >/dev/null 2>%s",
|
|
tmpdir, driver, src, errf);
|
|
int brc = runwait(cmd);
|
|
if (expect_err) {
|
|
int ok = (brc != 0)
|
|
&& (r->experr == NULL || errlog_has(errf, r->experr));
|
|
if (!ok)
|
|
fprintf(stderr, "row[%s]: %s expected loud builderr "
|
|
"\"%s\" (brc=%d)\n", r->label, driver,
|
|
r->experr ? r->experr : "", brc);
|
|
unlink(src); unlink(errf); rmdir(tmpdir);
|
|
return ok ? 0 : 1;
|
|
}
|
|
if (brc != 0) {
|
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
|
r->label, driver);
|
|
unlink(src); unlink(errf); rmdir(tmpdir);
|
|
return -1;
|
|
}
|
|
|
|
const char *base = strrchr(src, '/');
|
|
base = base ? base + 1 : src;
|
|
char outbin[256];
|
|
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(outbin); unlink(errf); rmdir(tmpdir);
|
|
if (got != want) {
|
|
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
|
|
r->label, driver, got, want);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
asm_byte_identical(const char *bin, const struct row *r, int i)
|
|
{
|
|
char src[96], cs[96], ws[96], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/vc95_asm_%d_%d.ww", getpid(), i);
|
|
snprintf(cs, sizeof cs, "/tmp/vc95_asm_%d_%d_c.s", getpid(), i);
|
|
snprintf(ws, sizeof ws, "/tmp/vc95_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;
|
|
}
|
|
int rc = slurp_eq(cs, ws);
|
|
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], wdrv[2120];
|
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
|
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
|
int total = 0, fail = 0;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
total++;
|
|
int cs_err = rows[i].kind == K_BUILDERR;
|
|
if (run_driver(cdrv, &rows[i], i, cs_err,
|
|
rows[i].cswant) != 0) fail++;
|
|
}
|
|
if (access(wdrv, X_OK) == 0) {
|
|
for (int i = 0; i < n; i++) {
|
|
total++;
|
|
int ww_err = rows[i].kind == K_BUILDERR
|
|
|| rows[i].kind == K_RUN_CS_WWERR;
|
|
if (run_driver(wdrv, &rows[i], i, ww_err,
|
|
rows[i].wwwant) != 0) fail++;
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
if (rows[i].kind != K_RUN)
|
|
continue;
|
|
total++;
|
|
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "variant_chain_b95: %d/%d checks failed\n",
|
|
fail, total);
|
|
return 1;
|
|
}
|
|
printf("variant_chain_b95: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|