From cf74d59e870b75f9231df8e167abbe4fb5f5f3e9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:30:57 +0900 Subject: [PATCH] test: port the fn/mklabel mangle observers to ww test/xmod/label_test.ww replaces 706_fnlabel_mangle.c and 750_mklabel_modscoped.c with every assertion preserved (fnlabelmangle fixture run-112 both stages; three mklabel rows with run exits and module-qualified label needles). Strengthened: fnlabel adds the cs==ww sepwork .s byte-id the carrier never asserted. --- Makefile | 3 +- test/wcc/706_fnlabel_mangle.c | 158 ------------ test/wcc/750_mklabel_modscoped.c | 399 ------------------------------- test/xmod/label_test.ww | 280 ++++++++++++++++++++++ 4 files changed, 282 insertions(+), 558 deletions(-) delete mode 100644 test/wcc/706_fnlabel_mangle.c delete mode 100644 test/wcc/750_mklabel_modscoped.c create mode 100644 test/xmod/label_test.ww diff --git a/Makefile b/Makefile index 2ef60386..a90420c4 100644 --- a/Makefile +++ b/Makefile @@ -372,7 +372,8 @@ SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) # label-mangle runs, cross-module typecheck rejects, enumerator # capacity, directive adjacency). Compiler/driver gates like test/sep: # they run under test-compiler. -XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww +XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \ + test/xmod/label_test.ww XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/wcc/706_fnlabel_mangle.c b/test/wcc/706_fnlabel_mangle.c deleted file mode 100644 index ae63b06f..00000000 --- a/test/wcc/706_fnlabel_mangle.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 706_fnlabel_mangle — cgen mangles fn labels by module so two - * modules can each `export fn ping` (and each define a private - * `fn helper`) without colliding at link time. - * - * The fixture in test/wcc/data/fnlabelmangle/ pins all four - * label-emit sites #9 / #12 touch: - * mod{1,2}/mod{1,2}.ww - * export fn ping = bareval + helper() + fpi(); - * fn helper — private, same-leaf across modules - * fn fpi — `let h = helper; h();` pins LEAQ N_IDENT - * pos.ww - * let p1 = mod1.ping; let p2 = mod2.ping; // LEAQ N_DOT × 2 - * return mod1.ping() + mod2.ping() // CALL N_DOT × 2 - * + p1() + p2(); // through fn ptr - * - * mod1.ping = 3 + 11 + 11 = 25 - * mod2.ping = 5 + 13 + 13 = 31 - * total = 25 + 31 + 25 + 31 = 112 - * - * Pre-#9 (cgen emitted bare `TEXT ping` for both modules' exports), - * the linker collapsed both `ping` symbols and one dispatch landed - * on the wrong body. The bare-IDENT helper / `let h = helper` call- - * sites would also pick whichever module the lookup found first, - * silently miscompiling fpi's intra-module call. - * - * Pre-#12 wwstage cgdot's `let p = mod.ping` fell through to the - * MOVQ leaf(SB) module-qualified-value fallback, loading 8 bytes - * of fn-prologue into AX; `p()` then jumped into mid-prologue - * garbage. Cstage cgdot already had a TY_FN LEAQ branch from #9. - * - * Coverage notes: - * - The skip rule's three remaining cases (@symbol / main / empty- - * module) are not pinned by a dedicated row because every passing - * run of `make test` already exercises them: the bootstrap and - * stdlib pull through `@symbol("rt_syscall")` bindings (lib/fmt, - * lib/log), every test program links a bare `main`, and the inline- - * source tests (700, 701, ...) compile fixtures with no module - * directive. A regression in any of those rules would cascade - * across the suite, not show up here. - * - * Both stages run the positive case — cstage and wwstage cgen must - * agree on the mangling rule for ww2/ww3/ww4 byte-identity to hold. - */ -#include -#include -#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; -} - -static int -run_pos(const char *driver, const char *fixdir, const char *tag) -{ - /* Keep the output and caller-owned pos.sepwork outside the tracked - * fixture tree; cd stays so pos.ww imports siblings mod1/mod2. */ - char td[1024]; - snprintf(td, sizeof td, "/tmp/fnlbl_%d_%s", getpid(), tag); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "fnlabel_mangle[%s]: mkdir %s: %s\n", - tag, td, strerror(errno)); - return 1; - } - char bin[2048], sepdir[2048], rmcmd[2112]; - snprintf(bin, sizeof bin, "%s/pos", td); - snprintf(sepdir, sizeof sepdir, "%s.sepwork", bin); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); - - char cmd[2048]; - int failed = 0, got; - snprintf(cmd, sizeof cmd, - "cd %s && %s build -o %s/pos pos.ww >/dev/null 2>&1", - fixdir, driver, td); - if (runwait(cmd) != 0) { - fprintf(stderr, - "fnlabel_mangle[%s]: pos.ww build failed\n", tag); - failed = 1; - goto cleanup; - } - got = runwait(bin); - if (got != 112) { - fprintf(stderr, - "fnlabel_mangle[%s]: pos.ww exit=%d want=112 — " - "fn labels likely collapsed at link, or LEAQ-of-fn " - "N_DOT branch missing in cgdot\n", tag, got); - failed = 1; - } - -cleanup: - { - int cleanup_failed = 0; - if (runwait(rmcmd) != 0) { - fprintf(stderr, "fnlabel_mangle[%s]: remove %s failed\n", - tag, sepdir); - cleanup_failed = 1; - } - if (unlink(bin) != 0 && errno != ENOENT) { - fprintf(stderr, "fnlabel_mangle[%s]: unlink %s: %s\n", - tag, bin, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(td) != 0) { - fprintf(stderr, "fnlabel_mangle[%s]: rmdir %s: %s\n", - tag, td, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed; -} - -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]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - char wdrv[1024]; - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - char fixdir[1024]; - if (getcwd(fixdir, sizeof fixdir) == NULL) return 1; - size_t cwd_n = strlen(fixdir); - const char *rel = "/test/wcc/data/fnlabelmangle"; - if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1; - memcpy(fixdir + cwd_n, rel, strlen(rel) + 1); - - int fail = 0; - fail += run_pos(cdrv, fixdir, "cstage"); - fail += run_pos(wdrv, fixdir, "wwstage"); - - if (fail) { - fprintf(stderr, - "fnlabel_mangle: %d case(s) failed\n", fail); - return 1; - } - printf("fnlabel_mangle: 2/2 ok\n"); - return 0; -} diff --git a/test/wcc/750_mklabel_modscoped.c b/test/wcc/750_mklabel_modscoped.c deleted file mode 100644 index fe211b9a..00000000 --- a/test/wcc/750_mklabel_modscoped.c +++ /dev/null @@ -1,399 +0,0 @@ -/* - * 750_mklabel_modscoped — sentinel for task #13. mklabel pre-fix - * formatted labels as `__` (just the leaf). Two - * top-level fns in different modules sharing a leaf (e.g. `bytes.index` - * + `strings.index`) emitted colliding labels into the same combined - * .s; w6a's a_intern collapsed the names and the LAST definition's - * `s->addr` won — earlier fns' JNE/JMP rel32 resolved into the later - * fn's body. Silent miscompile in cstage; wwstage's mklabel had the - * same shape so the symmetric path was equally exposed (the wedge - * just happened to fire under cstage's combined link first). - * - * Fix (rule 10): `.__` mirrors the - * existing TEXT-directive convention (`TEXT bytes.index,$N`). w6a - * already accepts `.` in label-cont (lex.c:18) so the format is a - * valid Plan-9 symbol. - * - * row | what it pins - * ----------------------------|-------------------------------- - * two_modules_same_leaf | original wedge. mod1.locate / - * | mod2.locate both contain `match` - * | so both stamp `_match_next_1`. - * | Pre-fix run aborts on mis-jump - * | (exit=10); post-fix exit=0 and - * | mod1.locate_match_next_1 + - * | mod2.locate_match_next_1 BOTH - * | appear in the .s. - * bytes_strings_contains | the latent that flips live once - * | task #11 lands strings.index. - * | Force both bytes.contains and - * | strings.contains into the link; - * | confirm post-fix bytes.contains_* - * | and strings.contains_* labels - * | are distinct. - * same_module_same_leaf | non-regression: two fns in the - * | same module with the same `match` - * | shape still get unique labels via - * | the per-fn `labelseq` counter. - */ -#include -#include -#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; - int want_exit; - int n_files; - const char *files[8]; /* {path, src, path, src, ...} */ - const char *entry; /* file to `ww build` (relative) */ - int n_required; - const char *required[6]; /* substrings every emitted .s must contain */ -}; - -static const struct row rows[] = { - /* Original wedge shape. mod1.locate and mod2.locate both run a - * `match (needle) { case u8 / case []u8 }` over a `for` loop — - * both stamp `_match_next_1` and `_loop_2` etc. Pre-fix the - * second TEXT's labels overwrite the first's s->addr; mod1.locate - * misjumps and returns the wrong byte offset. Post-fix the - * module-qualified labels coexist. */ - { "two_modules_same_leaf", 0, 3, - { "mod1/mod1.ww", - "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", - "mod2/mod2.ww", - "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", - "main.ww", - "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" }, - "main.ww", 2, - { "mod1.locate_match_next_1", "mod2.locate_match_next_1" } }, - - /* The pre-located bytes.contains / strings.contains latent. Both - * libs already export `contains`; force both into the link so the - * pair coexists in main.s. */ - { "bytes_strings_contains", 0, 1, - { "main.ww", - "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" }, - "main.ww", 2, - { "bytes.contains_", "strings.contains_" } }, - - /* Non-regression: same module, two fns with the same `match` - * shape. Per-fn labelseq still gives unique labels — both fns' - * labels carry the same `pkg.fn_` prefix but differ in the seq - * suffix. */ - { "same_module_same_leaf", 0, 1, - { "main.ww", - "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" }, - "main.ww", 2, - { "main.a_match_next_1", "main.b_match_next_1" } }, -}; - -/* Write `src` to `/`, mkdir'ing intermediate dirs. */ -static int -write_one(const char *dir, const char *rel, const char *src, - int *parent_owned) -{ - char path[512]; - snprintf(path, sizeof path, "%s/%s", dir, rel); - /* mkdir parent if rel has a slash. */ - char *slash = strchr(path + strlen(dir) + 1, '/'); - if (slash) { - *slash = '\0'; - if (mkdir(path, 0755) != 0) return -1; - *parent_owned = 1; - *slash = '/'; - } - FILE *f = fopen(path, "wb"); - if (!f) return -1; - int failed = fputs(src, f) == EOF; - if (fclose(f) != 0) failed = 1; - return failed ? -1 : 0; -} - -static char * -slurp(const char *path, size_t *outsz) -{ - FILE *f = fopen(path, "rb"); - if (!f) return NULL; - fseek(f, 0, SEEK_END); - long n = ftell(f); - fseek(f, 0, SEEK_SET); - if (n < 0) { fclose(f); return NULL; } - char *buf = malloc((size_t)n + 1); - if (!buf) { fclose(f); return NULL; } - size_t got = fread(buf, 1, (size_t)n, f); - fclose(f); - buf[got] = '\0'; - if (outsz) *outsz = got; - return buf; -} - -static int -run_row(const char *driver, const struct row *r, int idx, const char *tag) -{ - char tmpdir[96]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/mklabel_%s_%d_%d", - tag, getpid(), idx); - if (mkdir(tmpdir, 0755) != 0) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: cannot acquire temp directory: %s\n", - tag, r->label, strerror(errno)); - return 1; - } - int fail = 0; - int parent_owned[4] = { 0 }; - char entry_bin[512], stem[512], alls[600], cmd[2048]; - snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry); - char *dot = strrchr(entry_bin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - snprintf(stem, sizeof stem, "%s/%s", tmpdir, r->entry); - char *d2 = strrchr(stem, '.'); - if (d2 && strcmp(d2, ".ww") == 0) *d2 = '\0'; - snprintf(alls, sizeof alls, "%s/all.s", tmpdir); - - for (int k = 0; k < r->n_files; k++) { - if (write_one(tmpdir, r->files[2 * k], - r->files[2 * k + 1], &parent_owned[k]) != 0) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: write %s failed\n", - tag, r->label, r->files[2 * k]); - fail++; - goto cleanup; - } - } - - /* #93 sep layout: emit asm to .sepwork/.s; pin - * all outputs stay under tmpdir. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; %s build " - "-o \"${b%%.ww}\" \"$b\" >/dev/null 2>&1", - tmpdir, r->entry, driver); - if (runwait(cmd) != 0) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: build failed\n", - tag, r->label); - fail++; - goto cleanup; - } - - /* Runtime check. */ - int got = runwait(entry_bin); - if (got != r->want_exit) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: rc=%d want=%d\n", - tag, r->label, got, r->want_exit); - fail++; - } - - /* Required-substring check on the emitted .s. #93: root + every - * imported pkg now compile to separate .sepwork/.s; the - * cross-module label substrings (mod1.x / strings.x) live in the - * per-package files, so concat ALL of them (sorted glob) — a single - * __root.s read would FALSE-FAIL the multi-package rows. */ - char catcmd[1200]; - snprintf(catcmd, sizeof catcmd, "cat %s.sepwork/*.s > %s 2>/dev/null", - stem, alls); - (void)runwait(catcmd); - size_t sz = 0; - char *asm_buf = slurp(alls, &sz); - if (!asm_buf) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: cannot read %s\n", - tag, r->label, alls); - fail++; - } else { - for (int k = 0; k < r->n_required; k++) { - if (strstr(asm_buf, r->required[k]) == NULL) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: missing label " - "substring \"%s\" in %s\n", - tag, r->label, r->required[k], alls); - fail++; - } - } - free(asm_buf); - } - -cleanup: ; - /* The retained build scratch is the only recursively removed path. */ - int cleanup_fail = 0; - char sepwork[600]; - snprintf(sepwork, sizeof sepwork, "%s.sepwork", stem); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", sepwork); - if (runwait(cmd) != 0) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: remove %s failed\n", - tag, r->label, sepwork); - cleanup_fail = 1; - } - const char *outputs[] = { alls, entry_bin }; - for (size_t k = 0; k < sizeof outputs / sizeof outputs[0]; k++) { - if (unlink(outputs[k]) != 0 && errno != ENOENT) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: unlink %s: %s\n", - tag, r->label, outputs[k], strerror(errno)); - cleanup_fail = 1; - } - } - for (int k = 0; k < r->n_files; k++) { - char path[512]; - snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]); - char *slash = strchr(path + strlen(tmpdir) + 1, '/'); - if (slash && !parent_owned[k]) continue; - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: unlink %s: %s\n", - tag, r->label, path, strerror(errno)); - cleanup_fail = 1; - } - } - for (int k = r->n_files - 1; k >= 0; k--) { - char path[512]; - snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]); - char *slash = strrchr(path, '/'); - if (parent_owned[k] && slash && slash > path + strlen(tmpdir)) { - *slash = '\0'; - if (rmdir(path) != 0 && errno != ENOENT) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: rmdir %s: %s\n", - tag, r->label, path, strerror(errno)); - cleanup_fail = 1; - } - } - } - if (rmdir(tmpdir) != 0) { - fprintf(stderr, - "mklabel_modscoped[%s][%s]: rmdir %s: %s\n", - tag, r->label, tmpdir, strerror(errno)); - cleanup_fail = 1; - } - if (cleanup_fail) fail++; - return fail; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - 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; - } - - char cdrv[640], wdrv[640]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - int have_ww = (access(wdrv, X_OK) == 0); - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - total++; - fail += run_row(cdrv, &rows[i], i, "cs"); - if (have_ww) { - total++; - fail += run_row(wdrv, &rows[i], i, "ws"); - } - } - - if (fail) { - fprintf(stderr, - "mklabel_modscoped: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("mklabel_modscoped: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/xmod/label_test.ww b/test/xmod/label_test.ww new file mode 100644 index 00000000..e09ec65b --- /dev/null +++ b/test/xmod/label_test.ww @@ -0,0 +1,280 @@ +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; +}; + +// ---- fnlabel (#9 + #12) ------------------------------------------------ + +@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); +}; + +// ---- mklabel (#13) ----------------------------------------------------- + +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); +};