package matchdispatch_test; // Direct-w6c asm-window gate over match tag dispatch under // cross-module fn-name shadowing. Port of the retired native carrier // test/wcc/728_match_4arm_cross_module.c (#31); every assertion // preserved. The mod-disambiguated match-scrutinee claim has no other // owner: pre-fix wwstage's name-only fnretlookup collapsed arms 2/3 // of a `match (a.next())` inside a shadowing `b.next` to CMPQ $0, // leaving their case bodies silently unreachable. // // Per row: every `CMPQ $K, AX` inside [TEXT b.next .. next TEXT) is // collected; at least `arms` must exist and the first `arms` values // must be pairwise distinct and inside [0, arms). Checked on both // stages. // // Strengthened: a per-row byte-id leg (testenv.same over both .s). // The C header claimed a cmp -s that its code never ran; the leg // verifies green on all three rows. // // Dropped C machinery, not assertions: the w6c_ww-absent skip gate, // getpid()-keyed /tmp names, slurp caps. import os; import os.exec; import strings; import testenv; import time; fn fail(label: str, why: str) void = { let m: str = strings.concat("matchdispatch FAIL: ", label, " -- ", why, "\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; fn tmo() time.duration = { return (180i64 * (time.second: i64)): time.duration; }; fn emitstage(td: str, label: str, stage: str, drv: str, outname: str) void = { let av: []str = []; append(av, drv); append(av, "-o"); append(av, outname); append(av, "src.ww"); let co: testenv.commandout; testenv.runcommand(td, td, stage, av, tmo(), &co); let ok: bool = co.termination == exec.termination.EXIT && co.code == 0; if (!ok) { fail(label, strings.concat(stage, " compile failed")); }; }; fn posafter(s: str, start: i32, needle: str) i32 = { let p: i32 = testenv.pos(strings.sub(s, start, s.len), needle); if (p < 0) { return -1; }; return start + p; }; // Collect every `CMPQ $K, AX` tag inside [TEXT b.next .. next TEXT) // (the C capped collection at 16 entries and K < 16), then assert // count, range and pairwise distinctness over the first `arms`. fn cmpqcheck(label: str, stage: str, s: str, arms: i32) void = { let fnhdr: str = "TEXT b.next"; let fp: i32 = testenv.pos(s, fnhdr); if (fp < 0) { fail(label, strings.concat(stage, ": no ", fnhdr, " in .s")); }; let ep: i32 = posafter(s, fp + fnhdr.len, "\nTEXT "); if (ep < 0) { ep = s.len; }; let w: str = strings.sub(s, fp, ep); let seen: []i32 = alloc([], 16u64)!; let p: i32 = 0; for (p < w.len) { let m: i32 = posafter(w, p, "CMPQ\t$"); if (m < 0) { break; }; let d: i32 = m + 6; let k: i32 = 0; let nd: i32 = 0; for (d < w.len) { let c: u8 = w[d]; if (c < '0' || c > '9') { break; }; k = k * 10 + ((c - '0'): i32); nd += 1; d += 1; }; if (nd > 0 && d + 4 <= w.len && testenv.same(strings.sub(w, d, d + 4), ", AX")) { if (seen.len < 16 && k < 16) { append(seen, k); }; }; p = m + 1; }; if (seen.len < arms) { fail(label, strings.concat(stage, ": too few `CMPQ $K, AX` in b.next body")); }; let i: i32 = 0; for (i < arms) { if (seen[i] < 0 || seen[i] >= arms) { fail(label, strings.concat(stage, ": arm tag out of [0, arms)")); }; let j: i32 = 0; for (j < i) { if (seen[j] == seen[i]) { fail(label, strings.concat(stage, ": repeated arm tag (collapse)")); }; j += 1; }; i += 1; }; }; fn cmpqrow(label: str, src: str, arms: i32) void = { let td: str = testenv.fresh(); testenv.writefile(strings.concat(td, "/src.ww"), src); emitstage(td, label, "cstage", testenv.driver("w6c"), "cs.s"); emitstage(td, label, "wwstage", testenv.driver("w6c_ww"), "ws.s"); let cs: str = testenv.readfile(strings.concat(td, "/cs.s")); let ws: str = testenv.readfile(strings.concat(td, "/ws.s")); cmpqcheck(label, "cstage", cs, arms); cmpqcheck(label, "wwstage", ws, arms); if (!testenv.same(cs, ws)) { fail(label, "cstage vs wwstage asm differs"); }; testenv.clean(td); }; // Callee `a.next` returns the wide tagged; caller `b.next` shadows // the leaf with a 2-arm subset return so a name-only lookup sees the // wrong scrutinee type. The reverse row pins that dispatch follows // variantindex, not source order; the 3-arm row pins the boundary // (collapse begins at arms >= the caller's variant count). @test fn match4armcrossmodule() void = { cmpqrow("4arm_shadowed_callee", strings.concat( "package a;\n", "export type more = void;\n", "export type invalid = !void;\n", "export type done = void;\n", "export fn next() (rune | done | more | invalid) = {\n", " let r: rune;\n", " return r;\n", "};\n", "package b;\n", "import a;\n", "type done = void;\n", "fn next() (rune | done) = {\n", " match (a.next()) {\n", " case let r: rune => return r;\n", " case let dn: a.done => { let v: done; return v; };\n", " case let m: a.more => { abort(\"i\"); };\n", " case let e: a.invalid => { abort(\"i\"); };\n", " };\n", "};\n", "export fn main() i32 = { return 0; };\n"), 4); cmpqrow("4arm_shadowed_reverse", strings.concat( "package a;\n", "export type more = void;\n", "export type invalid = !void;\n", "export type done = void;\n", "export fn next() (rune | done | more | invalid) = {\n", " let r: rune;\n", " return r;\n", "};\n", "package b;\n", "import a;\n", "type done = void;\n", "fn next() (rune | done) = {\n", " match (a.next()) {\n", " case let e: a.invalid => { abort(\"i\"); };\n", " case let m: a.more => { abort(\"i\"); };\n", " case let dn: a.done => { let v: done; return v; };\n", " case let r: rune => return r;\n", " };\n", "};\n", "export fn main() i32 = { return 0; };\n"), 4); cmpqrow("3arm_shadowed_callee", strings.concat( "package a;\n", "export type more = void;\n", "export type done = void;\n", "export fn next() (rune | done | more) = {\n", " let r: rune;\n", " return r;\n", "};\n", "package b;\n", "import a;\n", "type done = void;\n", "fn next() (rune | done) = {\n", " match (a.next()) {\n", " case let r: rune => return r;\n", " case let dn: a.done => { let v: done; return v; };\n", " case let m: a.more => { abort(\"i\"); };\n", " };\n", "};\n", "export fn main() i32 = { return 0; };\n"), 3); };