wcc,ww: bare-module fn mangles bare, not an imported same-leaf (M4 E2, #84)

A package-less primary's bare fn (module="") whose leaf collided with an
imported module's same-leaf exported fn was mis-mangled to the imported
qualified name (a user `fn run` emitted as `test.run`), producing a
dead-duplicate symbol the linker silently shadowed -- a #263-class silent
miscompile, gate-blind and symmetric across both stages. cgen now registers
bare-module fns and resolves a bare-ident reference to its own bare leaf:
mod_lookup_for_fn prefers the bare entry when the call carries no module
hint and skips bare entries when it does, so the moduled-caller path stays
byte-identical. The moduled `main` entry carve-out is an orthogonal rule
(the linker entry is force-bared) and is retained. Prereq for the @test
user-`run` coexist (#80). The bare non-fn (let/def/type) sibling is the
same class but hint-less; deferred as #85, noted at the retained skip.
This commit is contained in:
2026-06-17 21:25:06 +09:00
parent e7cb9c9d83
commit 08cfb5b2dd
7 changed files with 452 additions and 96 deletions

View File

@@ -1458,18 +1458,31 @@ mod_collect(Cg *c, Node *file)
* like fns — `export def MAX` becomes `<mod>.MAX`, not a bare
* `MAX` that two packages could clash on under sep-compile. No
* export skip: every decl with a module mangles identically. */
if (d->module == NULL || d->module[0] == '\0') continue;
if (decl_has_ffisym(d)) continue;
/* `main` is the linker entry-point convention. Even when not
* marked `export`, it must keep its bare name so w6l can
* resolve `_start`'s `CALL main(SB)`. M1 #32: only the ROOT
* unit's main stays bare; an IMPORTED `fn main` mangles on its
* path (closes #31's dup-main by construction). */
int bare = (d->module == NULL || d->module[0] == '\0');
/* #84: register bare-module (package-less `//ww:module-reset`)
* FNS too — previously ALL bare decls were skipped here, so a
* bare fn never entered mod_map and a bare-ident ref to it
* first-matched an imported module's same-leaf fn (mod_lookup_
* for_fn) → a #40 residual / #263-class silent dead-dup. With
* the bare entry present, mod_lookup_for_fn prefers it on a NULL
* hint. Non-fn bare decls keep the legacy skip (their collision
* class is outside #84's fn scope; pre-bind proven). */
/* #85 (deferred): the bare-NON-fn sibling (a package-less let/def/
* type leaf colliding with an imported same-kind export) needs a
* different fix — mod_lookup (non-fn) is hint-LESS — and has no
* corpus repro; retained skip until then (rule-11). */
if (bare && !isfn) continue;
/* `main` is the linker entry-point convention. A `package main`
* primary's decls are MODULED "main", so main is NOT bare here;
* skip it (cgfn force-emits the bare `main` label). M1 #32: only
* the ROOT main (imported==0) stays bare; an IMPORTED `fn main`
* mangles on its path (closes #31's dup-main by construction). */
if (d->str && strcmp(d->str, "main") == 0 && !d->imported)
continue;
Mod *m = amalloc(c->a, sizeof *m);
m->name = d->str;
m->module = d->module;
m->module = bare ? NULL : d->module;
m->next = mod_map;
mod_map = m;
}
@@ -1502,8 +1515,18 @@ mod_lookup_for_fn(const char *name, const char *hint)
const char *first = NULL;
for (Mod *m = mod_map; m; m = m->next) {
if (strcmp(m->name, name) != 0) continue;
if (hint != NULL && m->module != NULL
&& strcmp(m->module, hint) == 0)
if (m->module == NULL) {
/* #84: a bare-module entry (now registered). A bare-ident
* ref (hint==NULL: the caller is itself in the bare/root
* module) resolves to it — bare wins over an imported
* same-leaf fn, by construction, order-independent. With a
* non-NULL hint the caller named a module, so a bare decl
* is irrelevant: skip it, leaving the legacy first-imported
* -match untouched (byte-id-neutral for moduled callers). */
if (hint == NULL) return NULL;
continue;
}
if (hint != NULL && strcmp(m->module, hint) == 0)
return m->module;
if (first == NULL) first = m->module;
}
@@ -14931,9 +14954,13 @@ cgfn(Cg *c, FILE *out, Node *fn)
/* Mangle the label using the fn's own module as the hint — picks
* the right entry when multiple modules export the same leaf. M1 #32:
* the ROOT-unit main (imported==0) is the bare `_start` entry — emit
* it bare directly, mirroring mod_collect's skip; without this its
* NULL cur_mod would fall through mafn's first-leaf match onto an
* IMPORTED package's now-registered `pkg.main`. */
* it bare directly, mirroring mod_collect's skip. This is irreducibly
* name-based: a `package main` primary's decls are MODULED "main"
* (the package clause sets cur_mod; cmd/ww/main.c:394), so main is
* NOT a bare-module decl — removing this carve-out mangles it to
* `main.main` (undefined `main`). #84's bare-module handling is the
* orthogonal axis (a package-LESS `//ww:module-reset` fn, cur_mod
* NULL), resolved in mod_lookup_for_fn, NOT here. */
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported)
text->to = asym("main");
else