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

@@ -598,12 +598,15 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
if ((frame & 15) != 0) { frame = (frame + 15) & ~15; };
// Emit the TEXT label via emitfnname so the def site picks up the
// same skip rule (FFI / `main` / empty-module) and the same module
// hint (this fn's own module) that the call sites use. M1 #32: the
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
// bare directly, mirroring collectmods' skip; without this its
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
// onto an IMPORTED package's now-registered `pkg.main`.
// same module hint (this fn's own module) the call sites use. M1 #32:
// the ROOT-unit main (imported==0) is the bare `_start` entry — emit
// it bare directly, mirroring collectmods' skip. Irreducibly name-
// based: a `package main` primary's decls are MODULED "main" (the
// package clause sets curmod; cmd/ww/main.c:394), so main is NOT a
// bare-module decl — dropping 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, nmod empty), resolved
// in modlookupforfn, NOT here.
emitline("TEXT ");
if (syntax.streq(fn_.str, "main") && fn_.imported == 0) {
emitbytes(fn_.str.ptr, fn_.str.len: u64);