package label_test; // Module-scoped label-emission observers, both driver stages. Ports of // the retired native carriers test/wcc/706_fnlabel_mangle.c and // test/wcc/750_mklabel_modscoped.c; every assertion preserved. // // fnlabel (#9 + #12) — the on-disk fixture tree // test/wcc/data/fnlabelmangle/ pins all four label-emit sites: two // modules each `export fn ping` + private `fn helper` + `fn fpi` // (LEAQ N_IDENT), root takes `let p = mod.ping` (LEAQ N_DOT) and // calls both directly (CALL N_DOT) and through the ptrs. Build+run // exit 112 (25+31+25+31) on BOTH stages; a link-time label collapse // or a missing TY_FN LEAQ branch lands a wrong body. Strengthened // beyond the carrier: cs==ww byte-id over the sepwork .s concat (the // carrier never asserted it). // // mklabel (#13) — mklabel emits `.__`, // never leaf-only labels that w6a's a_intern collapses across units: // two_modules_same_leaf | mod1.locate/mod2.locate both stamp // | _match_next_1; run exit 0 (mis-jump // | exits 10/20) AND BOTH module-qualified // | labels appear in the .s concat // bytes_strings_contains| the bytes.contains/strings.contains // | latent pair coexists (run 0; 11/12) // same_module_same_leaf | per-fn labelseq keeps same-shape fns in // | ONE module distinct (run 0; 30/31) // // Dropped C machinery, not assertions: the ww_ww-absent skip gate // (the Make target declares both drivers) and the per-path cleanup // ledger (testenv.clean asserts the removal). The .s concat order is // strengthened from the carrier's shell glob to byte-sorted listdir. import os; import os.exec; import strings; import testenv; import time; fn fail(label: str, why: str) void = { let m: str = strings.concat("label FAIL: ", label, " -- ", why, "\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; fn tmo() time.duration = { return (240i64 * (time.second: i64)): time.duration; }; // -1 encodes an abnormal (non-EXIT) termination, never a valid code. fn runcode(dir: str, name: str, argv: []str) i32 = { let co: testenv.commandout; testenv.runcommand(dir, dir, name, argv, tmo(), &co); if (co.termination != exec.termination.EXIT) { return -1; }; return co.code; }; // Every per-package .s under `sepdir`, concatenated in byte-sorted // order; "" when the directory is missing or holds no .s. fn catasm(sepdir: str) str = { if (!testenv.isdir(sepdir)) { return ""; }; let names: []str = testenv.listdir(sepdir); let out: str = ""; let i: i32 = 0; for (i < names.len) { if (strings.hassuffix(names[i], ".s")) { out = strings.concat(out, testenv.readfile(strings.concat(sepdir, "/", names[i]))); }; i += 1; }; return out; }; @test fn fnlabel() void = { let fixdir: str = strings.concat(testenv.repo(), "/test/wcc/data/fnlabelmangle"); let td: str = testenv.fresh(); let drvs: []str = ["ww", "ww_ww"]; let tags: []str = ["cstage", "wwstage"]; let asms: []str = ["", ""]; let s: i32 = 0; for (s < 2) { let stem: str = strings.concat(td, "/pos.", tags[s]); let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, strings.concat(fixdir, "/pos.ww")]; if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { fail("fnlabel", strings.concat(tags[s], " pos.ww build failed")); }; let rav: []str = [stem]; if (runcode(td, strings.concat("run_", tags[s]), rav) != 112) { fail("fnlabel", strings.concat(tags[s], " exit != 112 -- fn ", "labels likely collapsed at link, or LEAQ-of-fn N_DOT ", "branch missing in cgdot")); }; asms[s] = catasm(strings.concat(stem, ".sepwork")); s += 1; }; if (asms[0].len == 0 || asms[1].len == 0) { fail("fnlabel", "empty .s concat"); }; if (!testenv.same(asms[0], asms[1])) { fail("fnlabel", "cstage.s != wwstage.s (byte-id break)"); }; testenv.clean(td); }; fn mod1locate() str = { return strings.concat( "package mod1;\n", "export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n", " match (needle) {\n", " case let c: u8 => {\n", " let i: i32 = 0;\n", " for (i < s.len) {\n", " if (s[i] == c) { return i; };\n", " i += 1;\n", " };\n", " return;\n", " };\n", " case let sub: []u8 => { return; };\n", " };\n", " return;\n", "};\n"); }; fn mod2locate() str = { return strings.concat( "package mod2;\n", "export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n", " match (needle) {\n", " case let c: u8 => {\n", " let i: i32 = 0;\n", " for (i < s.len) {\n", " if (s[i] == c) { return i + 100; };\n", " i += 1;\n", " };\n", " return;\n", " };\n", " case let sub: []u8 => { return; };\n", " };\n", " return;\n", "};\n"); }; fn locatemain() str = { return strings.concat( "package main;\n", "import mod1;\n", "import mod2;\n", "export fn main() i32 = {\n", " let arr: [3]u8 = [65u8, 66u8, 67u8];\n", " let s: []u8 = arr[0:3];\n", " let n: u8 = 66u8;\n", " let needle: (u8 | []u8) = n;\n", " let a: (i32 | void) = mod1.locate(s, needle);\n", " let b: (i32 | void) = mod2.locate(s, needle);\n", " let av: i32 = match (a) { case let v: i32 => yield v; ", "case void => yield -1; };\n", " let bv: i32 = match (b) { case let v: i32 => yield v; ", "case void => yield -1; };\n", " if (av != 1) { return 10; };\n", " if (bv != 101) { return 20; };\n", " return 0;\n", "};\n"); }; fn containsmain() str = { return strings.concat( "package main;\n", "import bytes;\n", "import strings;\n", "export fn main() i32 = {\n", " let s: str = \"abc\";\n", " let b: []u8 = strings.toutf8(s);\n", " let n: (u8 | []u8) = 98u8;\n", " if (!bytes.contains(b, n)) { return 11; };\n", " if (!strings.contains(s, 'b')) { return 12; };\n", " return 0;\n", "};\n"); }; fn sameleafmain() str = { return strings.concat( "package main;\n", "fn a(needle: (u8 | []u8)) i32 = {\n", " match (needle) {\n", " case let c: u8 => return 1;\n", " case let s: []u8 => return 2;\n", " };\n", " return 0;\n", "};\n", "fn b(needle: (u8 | []u8)) i32 = {\n", " match (needle) {\n", " case let c: u8 => return 3;\n", " case let s: []u8 => return 4;\n", " };\n", " return 0;\n", "};\n", "export fn main() i32 = {\n", " let x: (u8 | []u8) = 7u8;\n", " if (a(x) != 1) { return 30; };\n", " if (b(x) != 3) { return 31; };\n", " return 0;\n", "};\n"); }; // dirs/rels/srcs describe the row's tree; every row builds main.ww, // runs to exit 0 (mis-jump polarities live in the sources), and needs // both required label substrings in the .s concat. fn mkrow(label: str, dirs: []str, rels: []str, srcs: []str, req: []str) void = { let drvs: []str = ["ww", "ww_ww"]; let tags: []str = ["cs", "ws"]; let s: i32 = 0; for (s < 2) { let td: str = testenv.fresh(); let d: i32 = 0; for (d < dirs.len) { assert(os.mkdir(strings.concat(td, "/", dirs[d]), 493) == 0); d += 1; }; let f: i32 = 0; for (f < rels.len) { testenv.writefile(strings.concat(td, "/", rels[f]), srcs[f]); f += 1; }; let av: []str = [testenv.driver(drvs[s]), "build", "-o", "main", "main.ww"]; if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { fail(label, strings.concat(drvs[s], " build failed")); }; let rav: []str = [strings.concat(td, "/main")]; if (runcode(td, strings.concat("run_", tags[s]), rav) != 0) { fail(label, strings.concat(drvs[s], " run-exit != 0 ", "(collapsed label mis-jump -- #13)")); }; let all: str = catasm(strings.concat(td, "/main.sepwork")); if (all.len == 0) { fail(label, "empty .s concat"); }; let k: i32 = 0; for (k < req.len) { if (!testenv.has(all, req[k])) { fail(label, strings.concat("label substring `", req[k], "` missing from the emitted .s")); }; k += 1; }; testenv.clean(td); s += 1; }; }; @test fn two_modules_same_leaf() void = { let dirs: []str = ["mod1", "mod2"]; let rels: []str = ["mod1/mod1.ww", "mod2/mod2.ww", "main.ww"]; let srcs: []str = [mod1locate(), mod2locate(), locatemain()]; let req: []str = ["mod1.locate_match_next_1", "mod2.locate_match_next_1"]; mkrow("two_modules_same_leaf", dirs, rels, srcs, req); }; @test fn bytes_strings_contains() void = { let dirs: []str = []; let rels: []str = ["main.ww"]; let srcs: []str = [containsmain()]; let req: []str = ["bytes.contains_", "strings.contains_"]; mkrow("bytes_strings_contains", dirs, rels, srcs, req); }; @test fn same_module_same_leaf() void = { let dirs: []str = []; let rels: []str = ["main.ww"]; let srcs: []str = [sameleafmain()]; let req: []str = ["main.a_match_next_1", "main.b_match_next_1"]; mkrow("same_module_same_leaf", dirs, rels, srcs, req); };