wcc_ww/check: #47 gap-B tuple-with-tagged case-arm variant-match (align-up)
wwstage's checker rejected a `case let t: ((void|size),(void|size),
size) =>` arm against a (tuple|error) scrutinee ("case: not a variant
of scrutinee"), while cstage accepts and runs it. typeeqast's N_TTUPLE
arm recurses per-element, but a tagged element (void|size) is
N_TTAGGED -> fell to the conservative catch-all `return false`, so the
whole tuple-compare failed. typeeqast is the sole acceptance route
(casevariantpairmatch is N_TNAME-only).
Add an N_TTAGGED arm to typeeqast, sibling of N_TTUPLE, mirroring
cstage type.c:288-300 (type_eq TY_TAGGED): position-by-position
variant compare over the tagged node's .list (direct nodes, not
.lhs-wrapped). cstage's nullable-flag check is deliberately not ported
(resolved-Type property, no ww AST analogue; moot for case-match).
A spread variant (TK_ELLIPSIS) in the .list is loud-rejected rather
than compared: a naive streq would silently accept a `...ab` case that
cstage rejects (a new cs!=ww over-accept the bare arm introduced).
Flattening the spread is deferred (#115); until then it louds, matching
cstage.
ww-only (cstage already accepts); the gap-A cgen store landed in
6a5bb3e. wwstage now accepts the b1c match and runs the full shape
byte-identical to cstage -> #47 (both gaps) closed. The deferred
full-b1c row in 944_tuple_tagged_union_run is promoted to a both-stage
runtime row. Checker change is acceptance-only/additive -> bootstrap
byte-id neutral (990-997 green).
This commit is contained in:
@@ -11174,9 +11174,40 @@ fn typeeqast(a: *node, b: *node) bool = {
|
|||||||
};
|
};
|
||||||
return pb == nil;
|
return pb == nil;
|
||||||
};
|
};
|
||||||
// Conservative: anything else (struct/tagged/array) fails the
|
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||||
// cheap check. Selfhost code doesn't currently rely on equality
|
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||||
// at these shapes for the targeted checks.
|
// (e.g. ((void|size),(void|size),size)) recurses here from the
|
||||||
|
// N_TTUPLE arm; without it the per-element compare falls to the
|
||||||
|
// catch-all and the whole case-against-tagged-scrutinee is rejected.
|
||||||
|
// Variants are DIRECT .list nodes (casevariantin walks tagged.list
|
||||||
|
// + typeeqast(v,..) directly), NOT N_TPARAM-wrapped like tuple elems.
|
||||||
|
// Divergence: cstage's nullable-flag check (type.c:293) is a resolved-
|
||||||
|
// Type property with no AST analogue; for case-match both sides share
|
||||||
|
// a spelling so it's moot — no phantom AST nullable check.
|
||||||
|
if (k == nkind.N_TTAGGED) {
|
||||||
|
let pa: *node = aa.list;
|
||||||
|
let pb: *node = bb.list;
|
||||||
|
for (pa != nil) {
|
||||||
|
if (pb == nil) { return false; };
|
||||||
|
// #115: a `...inner` spread variant stays unflattened at
|
||||||
|
// this AST layer (casevariantin flattens only at the OUTER
|
||||||
|
// level; cstage flattens in resolve_type before type_eq ever
|
||||||
|
// runs). A position-by-position compare cannot honour it —
|
||||||
|
// the N_TNAME leg is pure streq, so `...ab` would silently
|
||||||
|
// match a plain `ab`, accepting a case cstage rejects.
|
||||||
|
// Conservatively loud-reject any spread until the flatten
|
||||||
|
// lands; b1c's (void|size) has none, so byte-id is untouched.
|
||||||
|
if (pa.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (pb.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (!typeeqast(pa, pb)) { return false; };
|
||||||
|
pa = pa.next;
|
||||||
|
pb = pb.next;
|
||||||
|
};
|
||||||
|
return pb == nil;
|
||||||
|
};
|
||||||
|
// Conservative: anything else (struct/array) fails the cheap
|
||||||
|
// check. Selfhost code doesn't currently rely on equality at
|
||||||
|
// these shapes for the targeted checks.
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -893,9 +893,40 @@ fn typeeqast(a: *node, b: *node) bool = {
|
|||||||
};
|
};
|
||||||
return pb == nil;
|
return pb == nil;
|
||||||
};
|
};
|
||||||
// Conservative: anything else (struct/tagged/array) fails the
|
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||||
// cheap check. Selfhost code doesn't currently rely on equality
|
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||||
// at these shapes for the targeted checks.
|
// (e.g. ((void|size),(void|size),size)) recurses here from the
|
||||||
|
// N_TTUPLE arm; without it the per-element compare falls to the
|
||||||
|
// catch-all and the whole case-against-tagged-scrutinee is rejected.
|
||||||
|
// Variants are DIRECT .list nodes (casevariantin walks tagged.list
|
||||||
|
// + typeeqast(v,..) directly), NOT N_TPARAM-wrapped like tuple elems.
|
||||||
|
// Divergence: cstage's nullable-flag check (type.c:293) is a resolved-
|
||||||
|
// Type property with no AST analogue; for case-match both sides share
|
||||||
|
// a spelling so it's moot — no phantom AST nullable check.
|
||||||
|
if (k == nkind.N_TTAGGED) {
|
||||||
|
let pa: *node = aa.list;
|
||||||
|
let pb: *node = bb.list;
|
||||||
|
for (pa != nil) {
|
||||||
|
if (pb == nil) { return false; };
|
||||||
|
// #115: a `...inner` spread variant stays unflattened at
|
||||||
|
// this AST layer (casevariantin flattens only at the OUTER
|
||||||
|
// level; cstage flattens in resolve_type before type_eq ever
|
||||||
|
// runs). A position-by-position compare cannot honour it —
|
||||||
|
// the N_TNAME leg is pure streq, so `...ab` would silently
|
||||||
|
// match a plain `ab`, accepting a case cstage rejects.
|
||||||
|
// Conservatively loud-reject any spread until the flatten
|
||||||
|
// lands; b1c's (void|size) has none, so byte-id is untouched.
|
||||||
|
if (pa.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (pb.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (!typeeqast(pa, pb)) { return false; };
|
||||||
|
pa = pa.next;
|
||||||
|
pb = pb.next;
|
||||||
|
};
|
||||||
|
return pb == nil;
|
||||||
|
};
|
||||||
|
// Conservative: anything else (struct/array) fails the cheap
|
||||||
|
// check. Selfhost code doesn't currently rely on equality at
|
||||||
|
// these shapes for the targeted checks.
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11174,9 +11174,40 @@ fn typeeqast(a: *node, b: *node) bool = {
|
|||||||
};
|
};
|
||||||
return pb == nil;
|
return pb == nil;
|
||||||
};
|
};
|
||||||
// Conservative: anything else (struct/tagged/array) fails the
|
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||||
// cheap check. Selfhost code doesn't currently rely on equality
|
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||||
// at these shapes for the targeted checks.
|
// (e.g. ((void|size),(void|size),size)) recurses here from the
|
||||||
|
// N_TTUPLE arm; without it the per-element compare falls to the
|
||||||
|
// catch-all and the whole case-against-tagged-scrutinee is rejected.
|
||||||
|
// Variants are DIRECT .list nodes (casevariantin walks tagged.list
|
||||||
|
// + typeeqast(v,..) directly), NOT N_TPARAM-wrapped like tuple elems.
|
||||||
|
// Divergence: cstage's nullable-flag check (type.c:293) is a resolved-
|
||||||
|
// Type property with no AST analogue; for case-match both sides share
|
||||||
|
// a spelling so it's moot — no phantom AST nullable check.
|
||||||
|
if (k == nkind.N_TTAGGED) {
|
||||||
|
let pa: *node = aa.list;
|
||||||
|
let pb: *node = bb.list;
|
||||||
|
for (pa != nil) {
|
||||||
|
if (pb == nil) { return false; };
|
||||||
|
// #115: a `...inner` spread variant stays unflattened at
|
||||||
|
// this AST layer (casevariantin flattens only at the OUTER
|
||||||
|
// level; cstage flattens in resolve_type before type_eq ever
|
||||||
|
// runs). A position-by-position compare cannot honour it —
|
||||||
|
// the N_TNAME leg is pure streq, so `...ab` would silently
|
||||||
|
// match a plain `ab`, accepting a case cstage rejects.
|
||||||
|
// Conservatively loud-reject any spread until the flatten
|
||||||
|
// lands; b1c's (void|size) has none, so byte-id is untouched.
|
||||||
|
if (pa.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (pb.op == tkind.TK_ELLIPSIS) { return false; };
|
||||||
|
if (!typeeqast(pa, pb)) { return false; };
|
||||||
|
pa = pa.next;
|
||||||
|
pb = pb.next;
|
||||||
|
};
|
||||||
|
return pb == nil;
|
||||||
|
};
|
||||||
|
// Conservative: anything else (struct/array) fails the cheap
|
||||||
|
// check. Selfhost code doesn't currently rely on equality at
|
||||||
|
// these shapes for the targeted checks.
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,17 +18,15 @@
|
|||||||
*
|
*
|
||||||
* Both stages get the SAME arm → byte-identical asm (rule 10).
|
* Both stages get the SAME arm → byte-identical asm (rule 10).
|
||||||
*
|
*
|
||||||
* K_RUN rows (ret-shape: construct + return + `is error` consume, NO match
|
* All rows are K_RUN: build+run exit==want on BOTH drivers AND cs==ww
|
||||||
* case-arm): build+run exit==want on BOTH drivers AND cs==ww byte-id. These
|
* byte-id. The ret-shape rows (construct + return + `is error` consume, NO
|
||||||
* are the gap-A pin — wwstage's store correctness rides on byte-id with the
|
* match case-arm) were the gap-A pin — wwstage's store correctness rides on
|
||||||
* cstage runtime reference until gap-B unlocks wwstage's match-extract.
|
* byte-id with the cstage runtime reference. The full-b1c row (construct +
|
||||||
*
|
* return + MATCH case-arm + is/as elem reads) was cstage-only under gap-A
|
||||||
* K_CSRUN row (full b1c: construct + return + MATCH case-arm + is/as elem
|
* because wwstage's checker rejected the tuple-with-tagged case pattern;
|
||||||
* reads): build+run exit==want on CSTAGE ONLY. cstage's checker accepts the
|
* #47 gap-B taught typeeqast the N_TTAGGED arm (cmd/wcc/type.c:288-300
|
||||||
* tuple-with-tagged case pattern; wwstage's does NOT (#47 gap-B, a separate
|
* TY_TAGGED twin), so wwstage now ACCEPTS it and the row is promoted to
|
||||||
* later commit — casevariantin/typeeqast align-up). So the wwstage match-
|
* both-stage K_RUN — that full-b1c byte-id IS the gap-B correctness proof.
|
||||||
* extract RUNTIME row lands with gap-B; here it is cstage-only and proves
|
|
||||||
* the recursive store reads back every tuple-elem value correctly.
|
|
||||||
*
|
*
|
||||||
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
|
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
|
||||||
* race does not apply (903/940/945 precedent). Cites #47 gap-A.
|
* race does not apply (903/940/945 precedent). Cites #47 gap-A.
|
||||||
@@ -66,7 +64,6 @@ slurp_eq(const char *a, const char *b)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define K_RUN 0 /* build+run BOTH drivers, exit==want, + cs==ww byte-id */
|
#define K_RUN 0 /* build+run BOTH drivers, exit==want, + cs==ww byte-id */
|
||||||
#define K_CSRUN 1 /* build+run CSTAGE ONLY (wwstage match-extract = gap-B) */
|
|
||||||
|
|
||||||
struct row { const char *label; const char *src; int kind; int want; };
|
struct row { const char *label; const char *src; int kind; int want; };
|
||||||
|
|
||||||
@@ -122,12 +119,18 @@ static const struct row rows[] = {
|
|||||||
" if (a is error) { return 1; };\n"
|
" if (a is error) { return 1; };\n"
|
||||||
" return 235;\n"
|
" return 235;\n"
|
||||||
"};\n", K_RUN, 235 },
|
"};\n", K_RUN, 235 },
|
||||||
/* full b1c (cstage-only): construct + return + MATCH case-arm + is/as
|
/* full b1c (gap-B): construct + return + MATCH case-arm + is/as
|
||||||
* elem reads on the boxed tuple. pr(0): t.0 is void (+1), t.1 is size
|
* elem reads on the boxed tuple. pr(0): t.0 is void (+1), t.1 is size
|
||||||
* = 5 (+5), t.2*10 (+70) = 76. pr(1): t.0 is size==3 (+100), t.1 is
|
* = 5 (+5), t.2*10 (+70) = 76. pr(1): t.0 is size==3 (+100), t.1 is
|
||||||
* void (+50), t.2 = 9 (+9) = 235. pr(2): error arm, strings.compare
|
* void (+50), t.2 = 9 (+9) = 235. pr(2): error arm reads back the boxed
|
||||||
* matches. Reads back EVERY tuple-elem value the recursive store wrote.
|
* error str (ptr+len) and checks its length (25). Reads back EVERY
|
||||||
* wwstage cannot match the tuple-with-tagged case pattern yet (gap-B). */
|
* tuple-elem value the recursive store wrote. gap-B taught wwstage's
|
||||||
|
* typeeqast the N_TTAGGED arm, so its checker now ACCEPTS this tuple-
|
||||||
|
* with-tagged case pattern — both-stage K_RUN (build+run 235 + cs==ww
|
||||||
|
* byte-id) is the gap-B correctness proof. Self-contained (no import,
|
||||||
|
* per this file's invariant): the error payload is verified with the
|
||||||
|
* len() builtin over an ident-bound str (a cast-expr len() operand is
|
||||||
|
* not place-resolvable, #10/#41) rather than a strings.compare import. */
|
||||||
{ "b1c_full",
|
{ "b1c_full",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"type error = !str;\n"
|
"type error = !str;\n"
|
||||||
@@ -144,7 +147,6 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return \"Negative repetition count\": error;\n"
|
" return \"Negative repetition count\": error;\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
"import strings;\n"
|
|
||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
" let acc: size = 0;\n"
|
" let acc: size = 0;\n"
|
||||||
" let a: (((void | size), (void | size), size) | error) = pr(0);\n"
|
" let a: (((void | size), (void | size), size) | error) = pr(0);\n"
|
||||||
@@ -169,24 +171,20 @@ static const struct row rows[] = {
|
|||||||
" match (c) {\n"
|
" match (c) {\n"
|
||||||
" case let t: ((void | size), (void | size), size) => { return 252; };\n"
|
" case let t: ((void | size), (void | size), size) => { return 252; };\n"
|
||||||
" case let e: error => {\n"
|
" case let e: error => {\n"
|
||||||
" if (strings.compare((e: str),\n"
|
" let s: str = e: str;\n"
|
||||||
" \"Negative repetition count\") != 0) { return 253; };\n"
|
" if (len(s) != 25) { return 253; };\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" return (acc: i32);\n"
|
" return (acc: i32);\n"
|
||||||
"};\n", K_CSRUN, 235 },
|
"};\n", K_RUN, 235 },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* build+run via a driver; returns 0 pass, nonzero fail. is_ww gates the
|
/* build+run via a driver; returns 0 pass, nonzero fail. */
|
||||||
* cstage-only K_CSRUN row. */
|
|
||||||
static int
|
static int
|
||||||
run_driver(const char *driver, int is_ww, const struct row *r, int i)
|
run_driver(const char *driver, const struct row *r, int i)
|
||||||
{
|
{
|
||||||
char src[96], tmpdir[96], errf[96], cmd[1024];
|
char src[96], tmpdir[96], errf[96], cmd[1024];
|
||||||
|
|
||||||
if (r->kind == K_CSRUN && is_ww)
|
|
||||||
return 0; /* wwstage match-extract is gap-B */
|
|
||||||
|
|
||||||
snprintf(src, sizeof src, "/tmp/ttu_%d_%d.ww", getpid(), i);
|
snprintf(src, sizeof src, "/tmp/ttu_%d_%d.ww", getpid(), i);
|
||||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ttu_%d_d_%d", getpid(), i);
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/ttu_%d_d_%d", getpid(), i);
|
||||||
snprintf(errf, sizeof errf, "/tmp/ttu_%d_e_%d", getpid(), i);
|
snprintf(errf, sizeof errf, "/tmp/ttu_%d_e_%d", getpid(), i);
|
||||||
@@ -274,11 +272,11 @@ main(void)
|
|||||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||||
|
|
||||||
struct { const char *name; const char *path; int gated; int is_ww; }
|
struct { const char *name; const char *path; int gated; }
|
||||||
drivers[] = {
|
drivers[] = {
|
||||||
{ "cstage", cdrv, 0, 0 },
|
{ "cstage", cdrv, 0 },
|
||||||
{ "wwstage", wdrv, 1, 1 },
|
{ "wwstage", wdrv, 1 },
|
||||||
{ NULL, NULL, 0, 0 },
|
{ NULL, NULL, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
@@ -292,13 +290,13 @@ main(void)
|
|||||||
}
|
}
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
total++;
|
total++;
|
||||||
if (run_driver(drivers[d].path, drivers[d].is_ww,
|
if (run_driver(drivers[d].path, &rows[i], i) != 0)
|
||||||
&rows[i], i) != 0) fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cs==ww byte-id for K_RUN rows only (K_CSRUN's match arm has no
|
/* cs==ww byte-id for every K_RUN row (gap-B promoted the full-b1c
|
||||||
* wwstage .s until gap-B). */
|
* match-extract row, so all rows now emit wwstage .s). */
|
||||||
if (access(w6c_ww, X_OK) == 0) {
|
if (access(w6c_ww, X_OK) == 0) {
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (rows[i].kind != K_RUN) continue;
|
if (rows[i].kind != K_RUN) continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user