/* * 752_modparam_callee — sentinel for task #16 (sub-bug of #4d): * wwstage's callee_variadic_param helper (selfhost/cmd/wcc/ * cgenutil.ww) took an N_DOT callee but routed both arms through * bare-leaf fnparamslookup. Post-#4d that walk is same-module-first * then head-walk fallback — for a cross-module N_DOT call from a * caller whose c.curmod doesn't match either side, the head-walk * returns whichever module's same-leaf fn sits at the head of * c.fnrets. When that head-side fn has a divergent variadic-vs- * non-variadic shape the callee_variadic_param result fires the * gather machinery against the wrong-module shape: arg-prep * builds a slice for `xs: T...` while the CALL still targets the * non-variadic module-qualified label. Label correct, ABI wrong. * * Surfaced by attempting strings.contains' Hare-shaped * `(needles: (str | rune)...)` graduation while bytes.contains * stays at `(needle: (u8 | []u8))` — caller's * `bytes.contains(b, n)` site picked strings.contains' variadic * params for arg-prep. * * Cstage carries no sister bug: cmd/w6c/cgen.c reads the callee * fn-type from the typed `n->lhs->type` (TY_FN sig) and walks * `callee_params->variadic` directly, module-aware via the typed * AST. Mirror of #4d / #28 / #31 / #34: cstage sidesteps every * bare-leaf table. * * Fix: route the N_DOT arm through fnparamslookupmod(c, leaf, * callee.lhs.str). The N_IDENT arm stays on bare fnparamslookup * (already same-module-first post-#4d). * * row | what it pins * ---------------------------------------|------------------------- * cross_module_same_leaf_variadic_vs_scalar * | primary wedge. A.foo * | variadic, B.foo scalar * | same leaf. Caller in * | a third package calls * | B.foo with a tagged * | arg; pre-fix wwstage * | preps the call site as * | variadic gather and * | mis-marshals — rc=11. * | Post-fix both stages * | rc=0. * bare_leaf_no_collision | control: variadic fn * | declared in only one * | module, called via * | bare leaf (N_IDENT) * | from the same module. * | Confirms the N_IDENT * | arm still graduates * | through bare * | fnparamslookup * | (post-#4d). * * The standalone same_module_same_leaf runtime control now lives in * test/wcc/data/r75_modparam_same_module_same_leaf/case.ww. This wrapper * retains only the two package-layout rows that require separately named * imported source files and therefore are not expressible by case.ww. */ #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) */ }; static const struct row rows[] = { /* Primary wedge. alpha.foo is variadic (i32...), beta.foo is * non-variadic with a tagged-union param. Caller in `main` * imports both and calls beta.foo with a tagged (u8|[]u8). The * combined-source layout puts `beta` after `alpha` so beta.foo * sits at the head of c.fnrets among non-main fns; with * c.curmod="main" the bare-leaf walk falls through same-module * to head-walk and picks beta.foo here — but reverse the import * order or any other ordering perturbation flips it to alpha.foo. * Either fnparamslookup result is wrong half the time; the * N_DOT arm must route through fnparamslookupmod to be stable. * Pre-#16 the head-pick happens to be the variadic side and * arg-prep builds an `i32...` gather slice while CALL targets * beta.foo's scalar ABI. */ { "cross_module_same_leaf_variadic_vs_scalar", 0, 3, { "alpha/alpha.ww", "package alpha;\n" "export fn foo(a: i32, xs: i32...) i32 = {\n" "\treturn a + xs.len;\n" "};\n", "beta/beta.ww", "package beta;\n" "export fn foo(a: i32, n: (u8 | []u8)) i32 = {\n" "\tmatch (n) {\n" "\tcase let c: u8 => return a + (c: i32);\n" "\tcase let s: []u8 => return a + s.len;\n" "\t};\n" "\treturn 0;\n" "};\n", "main.ww", "package main;\n" "import alpha;\n" "import beta;\n" "export fn main() i32 = {\n" "\tlet n: (u8 | []u8) = 7u8;\n" "\tlet r: i32 = beta.foo(10, n);\n" "\tif (r != 17) { return 11; };\n" "\treturn 0;\n" "};\n" }, "main.ww" }, /* Control. A variadic fn exists in only one imported module; * the caller calls it via N_DOT. No same-leaf collision, so * fnparamslookupmod's same-module hint and the head-walk * fallback agree. Pre+post fix both stages rc=0. */ { "bare_leaf_no_collision", 0, 2, { "alpha/alpha.ww", "package alpha;\n" "export fn sum(a: i32, xs: i32...) i32 = {\n" "\tlet t: i32 = a;\n" "\tlet i: i32 = 0;\n" "\tfor (i < xs.len) { t += xs[i]; i += 1; };\n" "\treturn t;\n" "};\n", "main.ww", "package main;\n" "import alpha;\n" "export fn main() i32 = {\n" "\tlet r: i32 = alpha.sum(1, 2, 3, 4);\n" "\tif (r != 10) { return 40; };\n" "\treturn 0;\n" "};\n" }, "main.ww" }, }; 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); 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 rc = fputs(src, f) == EOF ? -1 : 0; if (fclose(f) != 0) rc = -1; return rc; } static int cleanup_row(const char *tmpdir, const struct row *r, const int *parent_owned) { char path[512], cmd[640], entry[512]; int rc = 0; for (int k = 0; k < r->n_files; k++) { 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) rc = -1; } snprintf(entry, sizeof entry, "%s/%s", tmpdir, r->entry); char *dot = strrchr(entry, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; if (unlink(entry) != 0 && errno != ENOENT) rc = -1; snprintf(cmd, sizeof cmd, "rm -rf %s.sepwork", entry); if (runwait(cmd) != 0) rc = -1; for (int k = 0; k < r->n_files; k++) { snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]); char *slash = strchr(path + strlen(tmpdir) + 1, '/'); if (slash && parent_owned[k]) { *slash = '\0'; if (rmdir(path) != 0 && errno != ENOENT) rc = -1; } } if (rmdir(tmpdir) != 0) rc = -1; return rc; } static int run_row(const char *driver, const struct row *r, int idx, const char *tag) { (void)idx; char tmpdir[] = "/tmp/modparam_XXXXXX"; if (mkdtemp(tmpdir) == NULL) { fprintf(stderr, "modparam_callee[%s][%s]: mkdtemp failed\n", tag, r->label); return 1; } int fail = 0; int parent_owned[4] = { 0 }; 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, "modparam_callee[%s][%s]: write %s failed\n", tag, r->label, r->files[2 * k]); fail++; goto cleanup; } } char cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>&1", tmpdir, driver, r->entry); if (runwait(cmd) != 0) { fprintf(stderr, "modparam_callee[%s][%s]: build failed\n", tag, r->label); fail++; goto cleanup; } char entry_bin[512]; snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry); char *dot = strrchr(entry_bin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(entry_bin); if (got != r->want_exit) { fprintf(stderr, "modparam_callee[%s][%s]: rc=%d want=%d\n", tag, r->label, got, r->want_exit); fail++; } cleanup: if (cleanup_row(tmpdir, r, parent_owned) != 0) { fprintf(stderr, "modparam_callee[%s][%s]: cleanup failed\n", tag, r->label); if (fail == 0) 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, "modparam_callee: %d/%d fixtures failed\n", fail, total); return 1; } printf("modparam_callee: %d/%d ok\n", total, total); return 0; }