Files
ww/test/wcc/706_fnlabel_mangle.c
Hojun-Cho 10de5a5e0e selfhost+test: cgdot LEAQ-of-fn for module-qualified N_DOT (#12)
wwstage cgdot lacked a TY_FN branch for module-qualified N_DOT
rvalues. `let p = mod1.ping` fell through to the MOVQ/LEAQ-narrow
fallback, loading 8 prologue bytes from the fn's first instruction
instead of taking its address. Cstage cgdot already handled this
case (wired during #9, f1440bf).

Mirror cstage: gate on fnretlookup(c, fld) before the localloadop
fallback; emit `LEAQ <module>.<name>(SB), AX` via emitfnname with
the hint from lhs.str.

Extend 706_fnlabel_mangle: pos.ww now stores mod1.ping/mod2.ping
into local fn-pointer slots and dispatches through them in addition
to the existing direct calls. Expected exit 56 → 112. Regression
shape: without the new branch, MOVQ leaf(SB) loads the prologue
bytes; indirect call jumps into garbage → SIGSEGV.

ww2 == ww3 == ww4 byte-identical at the new emit.
2026-05-16 00:07:00 +09:00

123 lines
3.8 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)
{
char cmd[2048];
snprintf(cmd, sizeof cmd,
"cd %s && %s build pos.ww >/dev/null 2>&1", fixdir, driver);
if (runwait(cmd) != 0) {
fprintf(stderr,
"fnlabel_mangle[%s]: pos.ww build failed\n", tag);
return 1;
}
char bin[2048];
snprintf(bin, sizeof bin, "%s/pos", fixdir);
int got = runwait(bin);
unlink(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);
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;
}