cstage+selfhost+test: mangle fn labels by module (#9)
Both stages emitted fn TEXT labels by leaf only; lib/os and lib/io
exporting the same leaves (read, write, close) collided at link.
lib/fmt + lib/log worked around with @symbol("rt_syscall") stubs.
Drop d->export from the fn skip rule in mod_collect (both stages) so
exported fns mangle as <module>.<name>. Let/def/type keep current
behavior. Skip retained for {@symbol, main, empty-module}.
Add cur_mod thread through cgfn + mod_lookup_for_fn(name, hint) at
all 4 label-emit sites (TEXT def, LEAQ N_IDENT, CALL N_IDENT, CALL
N_DOT). Wwstage mirror: emitfnname + modlookupforfn + curmod.
Invariant comment pinned in both stages.
ww2 == ww3 == ww4 byte-identical at the new label format.
706_fnlabel_mangle covers same-leaf cross-module CALL + private-leaf
cur_mod disambiguation through a fn-pointer rvalue.
Wwstage LEAQ-of-fn N_DOT (`let p = mod.fn` rvalue) is a pre-existing
gap; deferred to a follow-up. fmt/log rt_syscall stubs untouched
here; cleanup follows.
This commit is contained in:
118
test/wcc/706_fnlabel_mangle.c
Normal file
118
test/wcc/706_fnlabel_mangle.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 three of the four
|
||||
* label-emit sites #9 touches:
|
||||
* 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
|
||||
* return mod1.ping() + mod2.ping(); // CALL N_DOT × 2
|
||||
*
|
||||
* mod1.ping = 3 + 11 + 11 = 25
|
||||
* mod2.ping = 5 + 13 + 13 = 31
|
||||
* total = 56
|
||||
*
|
||||
* Pre-fix (cgen emitted bare `TEXT ping` for both modules' exports;
|
||||
* non-exported `helper` already mangled, so that half is incidental
|
||||
* regression coverage), the linker collapsed both `ping` symbols and
|
||||
* one dispatch landed on the wrong body. The bare-IDENT helper /
|
||||
* `let h = helper` callsites would also pick whichever module the
|
||||
* lookup found first, silently miscompiling fpi's intra-module call.
|
||||
*
|
||||
* Coverage notes:
|
||||
* - LEAQ N_DOT for fn rvalue (`let p = mod1.ping`) is not exercised.
|
||||
* wwstage cgdot has no working LEAQ-of-fn N_DOT branch — a row
|
||||
* would diverge across stages. Tracked as a follow-up.
|
||||
* - 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 != 56) {
|
||||
fprintf(stderr,
|
||||
"fnlabel_mangle[%s]: pos.ww exit=%d want=56 — "
|
||||
"fn labels likely collapsed at link\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;
|
||||
}
|
||||
19
test/wcc/data/fnlabelmangle/mod1/mod1.ww
Normal file
19
test/wcc/data/fnlabelmangle/mod1/mod1.ww
Normal file
@@ -0,0 +1,19 @@
|
||||
// Two modules each `export fn ping` and each define a private
|
||||
// `helper` with the same leaf. Together with mod2 they pin three of
|
||||
// the four label-emit sites #9 touches:
|
||||
// - CALL N_DOT : main calls mod1.ping / mod2.ping
|
||||
// - CALL N_IDENT : ping bare-calls helper inside its own module
|
||||
// - LEAQ N_IDENT : fpi takes `helper` by value, then calls it
|
||||
//
|
||||
// The LEAQ N_DOT path (`let p = mod1.ping`) is intentionally not
|
||||
// exercised here — wwstage cgdot has no working LEAQ-of-fn N_DOT
|
||||
// branch, so a row would diverge across stages.
|
||||
|
||||
fn helper() i32 = { return 11i32; };
|
||||
|
||||
fn fpi() i32 = {
|
||||
let h: fn() i32 = helper;
|
||||
return h();
|
||||
};
|
||||
|
||||
export fn ping() i32 = { return 3i32 + helper() + fpi(); };
|
||||
11
test/wcc/data/fnlabelmangle/mod2/mod2.ww
Normal file
11
test/wcc/data/fnlabelmangle/mod2/mod2.ww
Normal file
@@ -0,0 +1,11 @@
|
||||
// Sibling of mod1.ww — same leaves (`ping`, `helper`, `fpi`),
|
||||
// distinct values. See mod1.ww for the coverage-rationale comment.
|
||||
|
||||
fn helper() i32 = { return 13i32; };
|
||||
|
||||
fn fpi() i32 = {
|
||||
let h: fn() i32 = helper;
|
||||
return h();
|
||||
};
|
||||
|
||||
export fn ping() i32 = { return 5i32 + helper() + fpi(); };
|
||||
18
test/wcc/data/fnlabelmangle/pos.ww
Normal file
18
test/wcc/data/fnlabelmangle/pos.ww
Normal file
@@ -0,0 +1,18 @@
|
||||
// Positive case: import both modules, dispatch through the explicit
|
||||
// module qualifier. Each module's ping = bare-value + helper() (CALL
|
||||
// N_IDENT) + fpi() (which exercises LEAQ N_IDENT via `let h = helper`).
|
||||
//
|
||||
// mod1.ping = 3 + 11 + 11 = 25
|
||||
// mod2.ping = 5 + 13 + 13 = 31
|
||||
// total = 56
|
||||
//
|
||||
// Pre-fix the four `helper` / `ping` symbols would collapse at link
|
||||
// or the hint-less lookup would silently grab the wrong module's
|
||||
// body — exit would land at 50/62/some other value, never 56.
|
||||
|
||||
use mod1;
|
||||
use mod2;
|
||||
|
||||
fn main() i32 = {
|
||||
return mod1.ping() + mod2.ping();
|
||||
};
|
||||
Reference in New Issue
Block a user