Files
ww/test/wcc/706_fnlabel_mangle.c
Hojun-Cho ce3a25a0b4 test: contain sepwork scratch per-driver tmpdir, fix /tmp+in-repo leak (#8)
The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the
compiler's <stem>.sepwork scratch landed beside the source and was never
cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs
and fabricates phantom test failures + silent harness aborts, and for
in-repo fixture builds leaked .sepwork into the tracked tree.

Each leaking build now writes its source + output inside a per-invocation
tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and
rm -rf's the tmpdir on every exit path -- including fopen-fail and the
expected-fail reject builds (scratch is mkdir'd before the build can fail).
`ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993
byte-id comparison logic is byte-for-byte unchanged.

Two items filed separately (this commit holds the no-Makefile / no-main.c
rail):
- #13: a stale <src>.s byte-id readback (749) silently no-ops since
  separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline.
- #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no
  -o and leak main.sepwork in-tree (bounded, gitignored; own commit).

One concern -- sepwork leak hygiene -- across 228 drivers; uniform
transform applied per-file and two-round reviewed. make test: all 402
passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
2026-06-22 23:29:39 +09:00

134 lines
4.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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 <fixdir> 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;
}