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;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||
// (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;
|
||||
};
|
||||
|
||||
|
||||
@@ -893,9 +893,40 @@ fn typeeqast(a: *node, b: *node) bool = {
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||
// (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;
|
||||
};
|
||||
|
||||
|
||||
@@ -11174,9 +11174,40 @@ fn typeeqast(a: *node, b: *node) bool = {
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
// #47 gap-B: tagged structural equality — mirror of cstage
|
||||
// type.c:288-300 (TY_TAGGED). A tuple member with a TAGGED element
|
||||
// (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;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,17 +18,15 @@
|
||||
*
|
||||
* Both stages get the SAME arm → byte-identical asm (rule 10).
|
||||
*
|
||||
* K_RUN rows (ret-shape: construct + return + `is error` consume, NO match
|
||||
* case-arm): build+run exit==want on BOTH drivers AND cs==ww byte-id. These
|
||||
* are the gap-A pin — wwstage's store correctness rides on byte-id with the
|
||||
* cstage runtime reference until gap-B unlocks wwstage's match-extract.
|
||||
*
|
||||
* K_CSRUN row (full b1c: construct + return + MATCH case-arm + is/as elem
|
||||
* reads): build+run exit==want on CSTAGE ONLY. cstage's checker accepts the
|
||||
* tuple-with-tagged case pattern; wwstage's does NOT (#47 gap-B, a separate
|
||||
* later commit — casevariantin/typeeqast align-up). So the wwstage match-
|
||||
* extract RUNTIME row lands with gap-B; here it is cstage-only and proves
|
||||
* the recursive store reads back every tuple-elem value correctly.
|
||||
* All rows are K_RUN: build+run exit==want on BOTH drivers AND cs==ww
|
||||
* byte-id. The ret-shape rows (construct + return + `is error` consume, NO
|
||||
* match case-arm) were the gap-A pin — wwstage's store correctness rides on
|
||||
* 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
|
||||
* because wwstage's checker rejected the tuple-with-tagged case pattern;
|
||||
* #47 gap-B taught typeeqast the N_TTAGGED arm (cmd/wcc/type.c:288-300
|
||||
* TY_TAGGED twin), so wwstage now ACCEPTS it and the row is promoted to
|
||||
* both-stage K_RUN — that full-b1c byte-id IS the gap-B correctness proof.
|
||||
*
|
||||
* 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.
|
||||
@@ -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_CSRUN 1 /* build+run CSTAGE ONLY (wwstage match-extract = gap-B) */
|
||||
|
||||
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"
|
||||
" return 235;\n"
|
||||
"};\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
|
||||
* = 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
|
||||
* matches. Reads back EVERY tuple-elem value the recursive store wrote.
|
||||
* wwstage cannot match the tuple-with-tagged case pattern yet (gap-B). */
|
||||
* void (+50), t.2 = 9 (+9) = 235. pr(2): error arm reads back the boxed
|
||||
* error str (ptr+len) and checks its length (25). Reads back EVERY
|
||||
* 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",
|
||||
"package main;\n"
|
||||
"type error = !str;\n"
|
||||
@@ -144,7 +147,6 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return \"Negative repetition count\": error;\n"
|
||||
"};\n"
|
||||
"import strings;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let acc: size = 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"
|
||||
" case let t: ((void | size), (void | size), size) => { return 252; };\n"
|
||||
" case let e: error => {\n"
|
||||
" if (strings.compare((e: str),\n"
|
||||
" \"Negative repetition count\") != 0) { return 253; };\n"
|
||||
" let s: str = e: str;\n"
|
||||
" if (len(s) != 25) { return 253; };\n"
|
||||
" };\n"
|
||||
" };\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
|
||||
* cstage-only K_CSRUN row. */
|
||||
/* build+run via a driver; returns 0 pass, nonzero fail. */
|
||||
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];
|
||||
|
||||
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(tmpdir, sizeof tmpdir, "/tmp/ttu_%d_d_%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_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[] = {
|
||||
{ "cstage", cdrv, 0, 0 },
|
||||
{ "wwstage", wdrv, 1, 1 },
|
||||
{ NULL, NULL, 0, 0 },
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
@@ -292,13 +290,13 @@ main(void)
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(drivers[d].path, drivers[d].is_ww,
|
||||
&rows[i], i) != 0) fail++;
|
||||
if (run_driver(drivers[d].path, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cs==ww byte-id for K_RUN rows only (K_CSRUN's match arm has no
|
||||
* wwstage .s until gap-B). */
|
||||
/* cs==ww byte-id for every K_RUN row (gap-B promoted the full-b1c
|
||||
* match-extract row, so all rows now emit wwstage .s). */
|
||||
if (access(w6c_ww, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].kind != K_RUN) continue;
|
||||
|
||||
Reference in New Issue
Block a user