cgen: #95 c1 chain-membership variant arm — both-stage fused

A NAMED struct source that was not pointer-identical to a NAMED
variant fell through every pass of cg_tag_for_variant (cmd/w6c/
cgen.c) / flatvariantidxt (selfhost/cmd/wcc/cgenutil.ww) and the
widen stored tag 0 — both stages, byte-identical, gate-blind: wrong
tag on VALID code at any alias depth, in both chain directions
(.ai/ken-95-oracle.md §2: kb5_v2s1i, kb95_2lvl_i, kb95_deep_src,
kb95_deep_var all both-wrong-identical at base).

New pass 1b, identical both stages (the same route — forced fuse):
after pass-1 exact (unchanged, FIRST — the (str|linerr) protection,
harec's P1 short-circuit), a NAMED source matches the variant whose
NAMED chain shares a pointer-identical node with the source's chain
(an alias IS-A its base through the chain). Two linear NAMED chains
intersect iff they share their chased bottom node (ken §1), so the
walk is implemented as pointer identity of the chased ends through
type_chase_named/tichase — the blessed chase choke-point. NO raw
.under/->under hops were added, so the anticipated `peel-ok: nominal
chain walk (#95)` annotations are unnecessary and the peellint
whitelist is UNCHANGED (continues the B6/B7 fold-peels-into-chase
arc; peellint green).

Variants are counted UNGATED (bare prims are type-table singletons,
so a bare variant node can BE the source's chased bottom): the >=2
guard stays equivalent to harec's nassign>=2 -> NULL
(ref/harec/src/types.c:734-738, tagged_select_subtype P2/P3). >=2
chain hits hard-error with twin texts (prefix convention, shared
tail "source alias chain reaches >=2 variants — ambiguous without
nominal layout (#95)") — drew's ambiguity proviso extended to the
chained set; was a SILENT member-0 tag. Pass-2 bare-source fallback
unchanged. Chased type EQUALITY only — no type_is_assignable scalar
import, no int widening (ken's binding scalar warning).

Pin table (new suite test/wcc/944_variant_chain_b95_run.c, 45
checks, Makefile-wired):
  GRADUATIONS exit 1->0 both stages: chain_1lvl_i (kb5_v2s1i
  HEADLINE, byte-id held), chain_2lvl_i, chain_deep_src,
  chain_deep_var (byte-id held), chain_call_bound81 (kb5_v2s1),
  chain_call2_bound81 (kb4_v2_struct2, #95's original) — the two
  CALL-src rows waive byte-id, pre-existing #81 zero-fill asm noise
  (NO at base too).
  NEW LOUD: chain_amb_loud (kb95_amb) — silent tag 0 -> hard-error
  both stages.
  MUST-NOT-MOVE held: chain_amb_srcA/B (pass-1 precedence),
  nom_str/nom_err (#218 nominal regression pin), exact_ctl
  (kb5_v2sE2), bare_ctl/bare_2lvl/bare_ambig/bare_ambig2 (pass-2
  controls), callret_bound277 (kb5_v2sE #277 cells unchanged,
  dual-cell pin).

Invariants: ken's 163-row dissolution matrix rerun — exactly 3
movers, all family graduations (v2s1i/v2s1/v2_struct2 1->0), zero
non-family movers, detectors unmoved. Five mains cs-vs-ww byte-id
OK (ww/w6c/w6a/w6l/wwdump). make all 0; sizelint 0; peellint 0; all
944 suites + 808 green. w6c_ww/wwdump_ww main.combined.ww regen'd
(cgenutil.ww embeds).
This commit is contained in:
2026-06-06 07:40:32 +09:00
parent 329481c920
commit 34c86bd681
6 changed files with 646 additions and 0 deletions

View File

@@ -311,6 +311,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_alias_cgen_b5_run \
$(BIN)/test_alias_cgen_b6_run \
$(BIN)/test_alias_emit_b7_run \
$(BIN)/test_variant_chain_b95_run \
$(BIN)/test_peellint_gate \
$(BIN)/test_tuple_nary_destructure_run \
$(BIN)/test_rvalue_tuple_destructure_run \
@@ -1510,6 +1511,12 @@ $(BIN)/test_alias_emit_b7_run: test/wcc/944_alias_emit_b7_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_variant_chain_b95_run: test/wcc/944_variant_chain_b95_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_peellint_gate: test/wcc/944_peellint_gate.c tools/peellint | $(BIN)
$(CC) $(CFLAGS) -o $@ $<

View File

@@ -892,6 +892,37 @@ cg_tag_for_variant(Type *t, Type *vt)
for (Tparam *p = t->params; p; p = p->next, idx++) {
if (cg_variant_match(p->type, vt)) return idx;
}
/* Pass 1b (#95): NAMED source, no exact variant — chain membership.
* An alias IS-A every type on its NAMED chain (ali2 is-a ali is-a
* base), so declaring the variant as `ali` admits any source whose
* chain shares a node with ali's. Two linear NAMED chains intersect
* iff they share their chased bottom node (.ai/ken-95-oracle.md §1),
* so membership reduces to pointer identity of the chased ends —
* type_chase_named is the blessed chase, no raw hops. Variants are
* counted UNGATED (bare prims are type-table singletons, so a bare
* variant node can BE the source's bottom): that keeps the guard ≡
* harec's nassign>=2 → NULL (ref/harec/src/types.c:734-738; the
* P1-exact short-circuit is pass 1 above). >=2 chain hits cannot be
* disambiguated once the nominal-lossy model collapses the chain —
* hard-error (drew's ambiguity proviso extended to the chained
* set). */
if (vt->kind == TY_NAMED) {
Type *sb = type_chase_named(vt);
if (sb == NULL) return -1;
int found = -1, n = 0;
idx = 0;
for (Tparam *p = t->params; p; p = p->next, idx++) {
if (p->type && type_chase_named(p->type) == sb) {
if (found < 0) found = idx;
n++;
}
}
if (n >= 2)
fatal("cg_tag_for_variant: source alias chain "
"reaches >=2 variants — ambiguous without "
"nominal layout (#95)");
return found;
}
/* Pass 2 (#15): no exact variant matched — try a structural match of
* a BARE source against a NAMED-alias variant (e.g. a bare `*vtable`
* into the `stream` (= *vtable) variant of `(file | stream)`). The

View File

@@ -18987,6 +18987,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = {
p = p.tnext;
idx += 1;
};
// Pass 1b (#95): NAMED source, no exact variant — chain membership.
// An alias IS-A every type on its NAMED chain (ali2 is-a ali is-a
// base), so declaring the variant as `ali` admits any source whose
// chain shares a node with ali's. Two linear NAMED chains intersect
// iff they share their chased bottom node (.ai/ken-95-oracle.md §1),
// so membership reduces to pointer identity of the chased ends —
// tichase is the blessed chase, no raw hops. Variants are counted
// UNGATED (bare prims are type-table singletons, so a bare variant
// node can BE the source's bottom): that keeps the guard ≡ harec's
// nassign>=2 → NULL (ref/harec/src/types.c:734-738; the P1-exact
// short-circuit is pass 1 above). >=2 chain hits cannot be
// disambiguated once the nominal-lossy model collapses the chain —
// hard-error (drew's ambiguity proviso extended to the chained
// set). cs twin cg_tag_for_variant fused in this commit.
if (want.kind == tykind.TY_NAMED) {
let sb: *tinfo = tichase(want);
if (sb == nil) { return -1; };
let q: *tparam = ti.params;
let qi: i32 = 0;
let found: i32 = -1;
let n: i32 = 0;
for (q != nil) {
if (q.type_ != nil && tichase(q.type_) == sb) {
if (found < 0) { found = qi; };
n += 1;
};
q = q.tnext;
qi += 1;
};
if (n >= 2) {
let msg: str = "flatvariantidxt: source alias chain reaches >=2 variants — ambiguous without nominal layout (#95)\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return found;
};
// Pass 2 (#15): no exact variant matched — structurally match a BARE
// source against a NAMED-alias variant (bare *vtable into the
// `stream` (= *vtable) variant of `(file | stream)`). The bare side

View File

@@ -2883,6 +2883,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = {
p = p.tnext;
idx += 1;
};
// Pass 1b (#95): NAMED source, no exact variant — chain membership.
// An alias IS-A every type on its NAMED chain (ali2 is-a ali is-a
// base), so declaring the variant as `ali` admits any source whose
// chain shares a node with ali's. Two linear NAMED chains intersect
// iff they share their chased bottom node (.ai/ken-95-oracle.md §1),
// so membership reduces to pointer identity of the chased ends —
// tichase is the blessed chase, no raw hops. Variants are counted
// UNGATED (bare prims are type-table singletons, so a bare variant
// node can BE the source's bottom): that keeps the guard ≡ harec's
// nassign>=2 → NULL (ref/harec/src/types.c:734-738; the P1-exact
// short-circuit is pass 1 above). >=2 chain hits cannot be
// disambiguated once the nominal-lossy model collapses the chain —
// hard-error (drew's ambiguity proviso extended to the chained
// set). cs twin cg_tag_for_variant fused in this commit.
if (want.kind == tykind.TY_NAMED) {
let sb: *tinfo = tichase(want);
if (sb == nil) { return -1; };
let q: *tparam = ti.params;
let qi: i32 = 0;
let found: i32 = -1;
let n: i32 = 0;
for (q != nil) {
if (q.type_ != nil && tichase(q.type_) == sb) {
if (found < 0) { found = qi; };
n += 1;
};
q = q.tnext;
qi += 1;
};
if (n >= 2) {
let msg: str = "flatvariantidxt: source alias chain reaches >=2 variants — ambiguous without nominal layout (#95)\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return found;
};
// Pass 2 (#15): no exact variant matched — structurally match a BARE
// source against a NAMED-alias variant (bare *vtable into the
// `stream` (= *vtable) variant of `(file | stream)`). The bare side

View File

@@ -18987,6 +18987,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = {
p = p.tnext;
idx += 1;
};
// Pass 1b (#95): NAMED source, no exact variant — chain membership.
// An alias IS-A every type on its NAMED chain (ali2 is-a ali is-a
// base), so declaring the variant as `ali` admits any source whose
// chain shares a node with ali's. Two linear NAMED chains intersect
// iff they share their chased bottom node (.ai/ken-95-oracle.md §1),
// so membership reduces to pointer identity of the chased ends —
// tichase is the blessed chase, no raw hops. Variants are counted
// UNGATED (bare prims are type-table singletons, so a bare variant
// node can BE the source's bottom): that keeps the guard ≡ harec's
// nassign>=2 → NULL (ref/harec/src/types.c:734-738; the P1-exact
// short-circuit is pass 1 above). >=2 chain hits cannot be
// disambiguated once the nominal-lossy model collapses the chain —
// hard-error (drew's ambiguity proviso extended to the chained
// set). cs twin cg_tag_for_variant fused in this commit.
if (want.kind == tykind.TY_NAMED) {
let sb: *tinfo = tichase(want);
if (sb == nil) { return -1; };
let q: *tparam = ti.params;
let qi: i32 = 0;
let found: i32 = -1;
let n: i32 = 0;
for (q != nil) {
if (q.type_ != nil && tichase(q.type_) == sb) {
if (found < 0) { found = qi; };
n += 1;
};
q = q.tnext;
qi += 1;
};
if (n >= 2) {
let msg: str = "flatvariantidxt: source alias chain reaches >=2 variants — ambiguous without nominal layout (#95)\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return found;
};
// Pass 2 (#15): no exact variant matched — structurally match a BARE
// source against a NAMED-alias variant (bare *vtable into the
// `stream` (= *vtable) variant of `(file | stream)`). The bare side

View File

@@ -0,0 +1,500 @@
/*
* 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 */
};
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;
}