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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user