/* * 928_match_nonident_idx_run — `match` on an N_INDEX scrutinee whose * BASE is not an ident (#48). * * Pre-#48 wwstage matchscrutt's N_INDEX arm required ibase.kind == * N_IDENT; an index over a struct field (`match (h.xs[i])` = N_INDEX * over N_DOT) returned nil → cgmatch dispatched with scrutt = nil: * every case arm's variant index clamped to 0 (CMPQ $0) and the * @match_spill slot fell to the 16B default. SILENT cs≠ww miscompile * (cstage N_MATCH reads the checker-stamped s->type for every * scrutinee shape, cmd/w6c/cgen.c:7510); regex fold-2a's * `match (re.insts[i])` is the consumer that surfaced it. The fix * resolves the element type from the stamped .type_ (the #45/#67 * stamped-carrier pattern), plus the load-half twin: cgindex's * generic-fallback tagged-element load was the only arm missing the * `slot > 24` R8 word (cgen.c:9106-9117), so a >24B-slot element via * a non-ident base under-read the cursor. * * Rows pin: the discovering repro shape (field-base slice index, all * three variants matched both ways), the regex shape (56B-slot * inst-like union over r.insts[i]; payload reads stay within the 32B * cursor — words past R8 are #43's deferred residual), a chained-dot * base (o.in_.xs[i] — depth independence), and the ident * scrutinee / ident-base array + slice index controls (byte-id at * master per the #48 scoping probes; cs is untouched by the fix, so * the per-row cs==ww cmp pins them unchanged). A call-BASE index * (`mk()[0]`) is NOT covered: cgindex's element classification (esz + * tagged) skips N_CALL bases — pre-existing sibling, filed separately. * Per row: w6c vs w6c_ww byte-id (rule 10 — cstage is the * runtime-correct reference) + runtime via both drivers. */ #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; } struct row { const char *label; const char *src; int want; }; #define TU_TYPES \ "type va = void;\n" \ "type vb = bool;\n" \ "type vc = size;\n" \ "type tu = (va | vb | vc);\n" static const struct row rows[] = { /* The discovering repro: slice field of a struct, indexed. * Pre-#48 wwstage dispatched the vb element to the FIRST arm * regardless of pattern (tag compared against 0) — exit 1. The * third match pins arm-order independence (variant 1 matched * from a non-first arm). */ { "field_slice_idx", TU_TYPES "type holder = struct { xs: []tu, n: size };\n" "export fn main() i32 = {\n" " let sl: []tu = [(true: vb), ((7: size): vc)];\n" " let h: holder;\n" " h.xs = sl;\n" " h.n = 2;\n" " match (h.xs[0]) {\n" " case let b: vb => { if (!(b: bool)) { return 2; }; };\n" " case => return 1;\n" " };\n" " match (h.xs[1]) {\n" " case let s: vc => { if ((s: size) != 7) { return 4; }; };\n" " case => return 3;\n" " };\n" " match (h.xs[0]) {\n" " case va => return 5;\n" " case let b: vb => { if (!(b: bool)) { return 6; }; };\n" " case vc => return 7;\n" " };\n" " return 0;\n" "};\n", 0 }, /* The regex fold-2a shape: 56B-slot inst-like union (8B tag + * 48B widest payload) matched over a struct-field slice index. * The big variant drives slot/spill sizing past 24B (the R8 * word both in cgindex's fallback load and cgmatch's spill); * matched payloads read word 1 only — within the 32B cursor. */ { "regex_inst_56b", "type big = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n" "type ilit = struct { r: i64 };\n" "type imatch = void;\n" "type inst = (big | ilit | imatch);\n" "type re = struct { insts: []inst, n: i64 };\n" "export fn main() i32 = {\n" " let sl: []inst;\n" " let l: ilit = ilit { r = 65 };\n" " let v: inst = l;\n" " append(sl, v);\n" " let m: inst = void: imatch;\n" " append(sl, m);\n" " let r: re;\n" " r.insts = sl;\n" " r.n = 2;\n" " match (r.insts[0]) {\n" " case let x: ilit => { if (x.r != 65) { return 2; }; };\n" " case => return 1;\n" " };\n" " match (r.insts[1]) {\n" " case imatch => { };\n" " case => return 3;\n" " };\n" " match (r.insts[0]) {\n" " case big => return 4;\n" " case let x: ilit => { if (x.r != 65) { return 5; }; };\n" " case imatch => return 6;\n" " };\n" " return 0;\n" "};\n", 0 }, /* Dot-DEPTH: the index base is a CHAINED dot (o.in_.xs[i] = * N_INDEX over N_DOT over N_DOT) — pins that the stamped-carrier * resolve is depth-independent (any non-ident base shape, not * just one dot level). */ { "dot_depth_idx", TU_TYPES "type inner = struct { xs: []tu, k: size };\n" "type outer = struct { in_: inner, n: size };\n" "export fn main() i32 = {\n" " let sl: []tu = [(true: vb), ((9: size): vc)];\n" " let o: outer;\n" " o.in_.xs = sl;\n" " o.in_.k = 1;\n" " o.n = 2;\n" " match (o.in_.xs[0]) {\n" " case let b: vb => { if (!(b: bool)) { return 2; }; };\n" " case => return 1;\n" " };\n" " match (o.in_.xs[1]) {\n" " case va => return 3;\n" " case let s: vc => { if ((s: size) != 9) { return 4; }; };\n" " case vb => return 5;\n" " };\n" " return 0;\n" "};\n", 0 }, /* Controls: ident scrutinee + ident-base array and slice index * — the pre-existing matchscrutt/cgmatch paths the fix must not * disturb (asm byte-id at master per the scoping probes). */ { "ident_and_identbase_controls", TU_TYPES "export fn main() i32 = {\n" " let v: tu = (true: vb);\n" " match (v) {\n" " case let b: vb => { if (!(b: bool)) { return 2; }; };\n" " case => return 1;\n" " };\n" " let arr: [2]tu = [(true: vb), ((7: size): vc)];\n" " match (arr[1]) {\n" " case let s: vc => { if ((s: size) != 7) { return 4; }; };\n" " case => return 3;\n" " };\n" " let sl: []tu = [(true: vb), ((7: size): vc)];\n" " match (sl[0]) {\n" " case let b: vb => { if (!(b: bool)) { return 6; }; };\n" " case => return 5;\n" " };\n" " return 0;\n" "};\n", 0 }, }; static const char *g_bin; static int compile_s(const char *tool, const char *src, const char *outpath) { char cmd[1024]; snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2>&1", g_bin, tool, src, outpath); return runwait(cmd); } static int file_eq(const char *a, const char *b) { char cmd[1024]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", a, b); return runwait(cmd) == 0; } static int run_driver(const char *driver, const char *tmpdir, const char *src, const char *label) { char outbin[256], cmd[1024]; snprintf(outbin, sizeof outbin, "%s/%s_bin", tmpdir, driver); snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s >/dev/null 2>&1", g_bin, driver, outbin, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", label, driver); return -1; } return runwait(outbin); } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; static char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } g_bin = bin; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { const struct row *r = &rows[i]; char tmpdir[128], src[160], cs_s[160], ww_s[160], rmcmd[192]; snprintf(tmpdir, sizeof tmpdir, "/tmp/matchnidx_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/matchnidx_%d_%d.ww", tmpdir, getpid(), i); snprintf(cs_s, sizeof cs_s, "%s/matchnidx_%d_%d_cs.s", tmpdir, getpid(), i); snprintf(ww_s, sizeof ww_s, "%s/matchnidx_%d_%d_ww.s", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return 1; } fputs("package main;\n\n", f); fputs(r->src, f); fclose(f); total++; int cs_rc = compile_s("w6c", src, cs_s); int ww_rc = compile_s("w6c_ww", src, ww_s); if (cs_rc != 0 || ww_rc != 0) { fprintf(stderr, "FAIL row[%s]: compile rc cs=%d " "ww=%d\n", r->label, cs_rc, ww_rc); fail++; runwait(rmcmd); continue; } if (!file_eq(cs_s, ww_s)) { fprintf(stderr, "FAIL row[%s]: cs != ww .s\n", r->label); fail++; } int got_cs = run_driver("ww", tmpdir, src, r->label); if (got_cs != r->want) { fprintf(stderr, "FAIL row[%s] cstage: want %d " "got %d\n", r->label, r->want, got_cs); fail++; } char wwdrv[600]; snprintf(wwdrv, sizeof wwdrv, "%s/ww_ww", g_bin); if (access(wwdrv, X_OK) == 0) { int got_ww = run_driver("ww_ww", tmpdir, src, r->label); if (got_ww != r->want) { fprintf(stderr, "FAIL row[%s] wwstage: " "want %d got %d\n", r->label, r->want, got_ww); fail++; } } runwait(rmcmd); } if (fail) { fprintf(stderr, "match_nonident_idx_run: %d/%d rows failed\n", fail, total); return 1; } printf("match_nonident_idx_run: %d rows ok\n", total); return 0; }