/* * 767_match_variant_dispatch — project #179: wwstage cgmatch's * non-nullable arm gated its flat-tag dispatch on `pat.kind == * N_TNAME` (with an `N_TSLICE` else-branch for task #19's untyped * elem fallback). N_TPTR / N_TFN / N_TPTR(N_TFN) case-patterns fell * through both arms, leaving the local `r` at -1 → `want = 0`, so * every variant past 0 silently collapsed to tag 0 — the kind of * miscompile that runtime-passes only when the value happens to be * variant 0 (zero-coincidence). * * Cstage cg_tag_for_variant (cmd/w6c/cgen.c:624) walks Type directly * and is kind-agnostic; it has never had this gap. Drew's settled * principle: match dispatch is resolved-type-only (harec * check.c:2527 stores `_case->type = ctype`). Project #66 Phase-N * already flipped the typeeq compare; #179 was the one site that * still keyed on AST kind. Memory: project_tinfo_lossy_nominal. * * Fix: selfhost/cmd/wcc/cgenexpr.ww cgmatch non-nullable arm — * route through `flatvariantidxt(scrutt.type_, pat.type_)` directly, * guarded only by `istaggedtype(scrutt)` + `typeisslice(pattype)` * for the slice axis. No new helper; existing flatvariantidxt / * flatslicevariantidx wire up unchanged. * * Coverage (5 rows): * 1. nullable_ptr_fn — `(*fn(i32) i32 | void)`, branched * store (#105 lesson: two distinct * fns picked at runtime), match via * indirect call. KEN'S VERIFY GATE: * row 1 byte-id proves wwstage's * nullable-fold ordering matches * cstage cg_tag_for_variant — fail * here flags a separate-signoff fold. * Goes through the nullable disc path * (line 1484-1502, untouched by #179). * 2. ptr_variants_past_zero — `(*i32 | *i64)` storing &i64. * Pre-fix wwstage emits CMPQ $0 for * the *i64 arm; post-fix CMPQ $1. * 3. fn_ptr_variants_via_local — `(*fn(i32) i32 | *fn(i64) i64)` * built from a local. Direct N_TPTR * over N_TFN case-pattern is the new- * shape exercise; store path goes via * ident (not `&fn` inline) to dodge * the fn-variant store-side sibling * bug (see below). * 4. stored_variant1_roundtrip — `(*i32 | *i64)` with branched * runtime variant choice (pick = 1 * forces variant 1; defeats const- * fold to variant 0). Mirrors #105. * 5. aliased_ptr_variants — `type pa = *i32; type pb = *i64; * (pa | pb)`. typeeq through TY_NAMED * wrappers — the alias-aware path that * the surface-name compare couldn't * key on. OR-pattern was the original * row 5, dropped because the wwstage * parser doesn't accept `case T1 | T2`. * * Per-row gates: cstage runtime exit, wwstage runtime exit, * cs.s == ww.s byte-identical (rule-10 stage symmetry). * * Sibling bug surfaced (not fixed here, filed inline): widening * `&fn` INLINE into a `(*fn(...) | *fn(...))` slot computes the * wrong tag in wwstage — taggedvariantindext / typeeq on the * synthetic fn-ptr tinfo built from N_UN TK_AMP IDENT-of-FN doesn't * match the variant tinfo, so tag collapses to 0. Storing via an * intermediate ident-typed local (row 3 shape) routes through a * different store path and tags correctly. cstage cg_widen_tagged_ * store hits the same node with the same Type and tags correctly, * so the divergence is wwstage-only on the inline-amp shape. Out * of scope for #179 (which is the match-DISPATCH side, not the * widen-STORE side). * * GATE POLARITY: must stay GREEN. A red here means the cgmatch * non-nullable dispatch regressed, the nullable arm got perturbed, * or stage symmetry drifted on the new typeeq route. */ #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } #define STAGE_CS 1 #define STAGE_WW 2 struct row { const char *label; const char *src; int expected_exit; int stage_mask; }; static const struct row rows[] = { /* KEN'S VERIFY GATE: nullable disc path must byte-id even * though #179 didn't touch it. A divergence here flags a * separate flatvariantidxt nullable-fold ordering issue. */ { "nullable_ptr_fn", "fn ai(x: i32) i32 = { return x + 1; };\n" "fn bi(x: i32) i32 = { return x + 100; };\n" "fn indi(f: *fn(x: i32) i32, x: i32) i32 = { return (*f)(x); };\n" "export fn main() i32 = {\n" " let pick: i32 = 1;\n" " let v: (*fn(x: i32) i32 | void) = &ai;\n" " if (pick != 0) { v = &bi; };\n" " let r: i32 = match (v) {\n" " case void => yield 99: i32;\n" " case let f: *fn(x: i32) i32 => yield indi(f, 7);\n" " };\n" " return r;\n" "};\n", 107, STAGE_CS | STAGE_WW }, { "ptr_variants_past_zero", "export fn main() i32 = {\n" " let a: i64 = 42;\n" " let v: (*i32 | *i64) = &a;\n" " let r: i32 = match (v) {\n" " case *i32 => yield 11: i32;\n" " case *i64 => yield 22: i32;\n" " };\n" " return r;\n" "};\n", 22, STAGE_CS | STAGE_WW }, { "fn_ptr_variants_via_local", "fn ai(x: i32) i32 = { return x + 1; };\n" "fn bi(x: i64) i64 = { return x + 1; };\n" "fn indl(f: *fn(x: i64) i64, x: i64) i64 = { return (*f)(x); };\n" "export fn main() i32 = {\n" " let g: *fn(x: i64) i64 = &bi;\n" " let v: (*fn(x: i32) i32 | *fn(x: i64) i64) = g;\n" " let r: i32 = match (v) {\n" " case let p: *fn(x: i32) i32 => yield 1: i32;\n" " case let q: *fn(x: i64) i64 => yield indl(q, 9): i32;\n" " };\n" " return r;\n" "};\n", 10, STAGE_CS | STAGE_WW }, { "stored_variant1_roundtrip", "export fn main() i32 = {\n" " let a: i32 = 5;\n" " let b: i64 = 9;\n" " let pick: i32 = 1;\n" " let v: (*i32 | *i64) = &a;\n" " if (pick != 0) { v = &b; };\n" " let r: i32 = match (v) {\n" " case *i32 => yield 11: i32;\n" " case *i64 => yield 22: i32;\n" " };\n" " return r;\n" "};\n", 22, STAGE_CS | STAGE_WW }, { "aliased_ptr_variants", "type pa = *i32;\n" "type pb = *i64;\n" "export fn main() i32 = {\n" " let a: i64 = 100;\n" " let g: pb = &a;\n" " let v: (pa | pb) = g;\n" " let r: i32 = match (v) {\n" " case pa => yield 11: i32;\n" " case pb => yield 22: i32;\n" " };\n" " return r;\n" "};\n", 22, STAGE_CS | STAGE_WW }, }; static int write_source(const char *src_path, const char *src) { FILE *f = fopen(src_path, "wb"); if (!f) return -1; fputs(src, f); fclose(f); return 0; } static void cleanup_tmp(const char *tmpdir, const char *base) { char p[512]; snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p); /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); if (system(p)) {} /* best-effort */ rmdir(tmpdir); } static int build_via_driver(const char *driver, const char *tmpdir, const char *src) { char cmd[1024]; /* #93 sep layout: emit asm to .sepwork/__root.s; pin * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ snprintf(cmd, sizeof cmd, "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", tmpdir, src, tmpdir, driver); return runwait(cmd); } static int run_row(const char *driver, const struct row *r, int seq) { char tmpdir[256], src[256], base[64], outbin[512]; snprintf(tmpdir, sizeof tmpdir, "/tmp/mvd_%d_d_%d", getpid(), seq); snprintf(src, sizeof src, "%s/main767.ww", tmpdir); snprintf(base, sizeof base, "main767"); mkdir(tmpdir, 0755); if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; } int rc = -1; if (build_via_driver(driver, tmpdir, src) == 0) { snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); rc = runwait(outbin); } cleanup_tmp(tmpdir, base); return rc; } /* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so * ww_ww writing intermediates next to the source doesn't clobber the * cstage .s (filed bug per CLAUDE.md rule 14 phase split). */ static int asm_byte_identical(const char *cdrv, const char *wdrv, const struct row *r, int seq) { char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512]; snprintf(tdc, sizeof tdc, "/tmp/mvd_%d_c_%d", getpid(), seq); snprintf(tdw, sizeof tdw, "/tmp/mvd_%d_w_%d", getpid(), seq); snprintf(base, sizeof base, "main767"); mkdir(tdc, 0755); mkdir(tdw, 0755); snprintf(src, sizeof src, "%s/main767.ww", tdc); if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } int rc = -1; if (build_via_driver(cdrv, tdc, src) != 0) goto out; snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); snprintf(src, sizeof src, "%s/main767.ww", tdw); if (write_source(src, r->src) != 0) goto out; if (build_via_driver(wdrv, tdw, src) != 0) goto out; snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); if (fc && fw) { rc = 0; 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); out: cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; 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[1024], wdrv[1024]; 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; int wwpresent = (access(wdrv, X_OK) == 0); int seq = 0; for (int i = 0; i < n; i++) { if (rows[i].stage_mask & STAGE_CS) { total++; int got = run_row(cdrv, &rows[i], seq++); if (got != rows[i].expected_exit) { fprintf(stderr, "match_variant_dispatch[cstage run][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].expected_exit); fail++; } } if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { total++; int got = run_row(wdrv, &rows[i], seq++); if (got != rows[i].expected_exit) { fprintf(stderr, "match_variant_dispatch[wwstage run][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].expected_exit); fail++; } total++; if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { fprintf(stderr, "match_variant_dispatch[byte-id][%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } } } if (!wwpresent) fprintf(stderr, "match_variant_dispatch: skip wwstage (no %s)\n", wdrv); if (fail) { fprintf(stderr, "match_variant_dispatch: %d/%d fixtures failed\n", fail, total); return 1; } printf("match_variant_dispatch: %d/%d ok\n", total, total); return 0; }