/* * 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 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) { /* -o into a /tmp tmpdir so pos.sepwork follows the OUTPUT stem out * of the tracked fixture tree; cd stays — pos.ww imports * siblings mod1/mod2 via source-dir-first resolution. */ char td[1024]; snprintf(td, sizeof td, "/tmp/fnlbl_%d_%s", getpid(), tag); mkdir(td, 0755); char rmcmd[1088]; snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td); char cmd[2048]; 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); runwait(rmcmd); return 1; } char bin[2048]; snprintf(bin, sizeof bin, "%s/pos", td); int got = runwait(bin); runwait(rmcmd); 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); return 1; } return 0; } 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; }