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:
@@ -41304,12 +41304,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);
|
||||
@@ -45224,23 +45227,32 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.nmod.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
if (!isffi) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
// #84: register bare-module (package-less `//ww:module-
|
||||
// reset`) fns TOO (nmod.len==0) — previously the
|
||||
// `nmod.len > 0` gate skipped ALL bare fns, so a bare fn
|
||||
// never entered c.mods and a bare-ident ref to it first-
|
||||
// matched an imported same-leaf fn (modlookupforfn) → a
|
||||
// #40 residual / #263-class silent dead-dup. With the bare
|
||||
// entry present, modlookupforfn prefers it on an empty
|
||||
// hint. main is the linker entry 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 ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||
// cstage cgen.c mod_collect.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -45248,6 +45260,11 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
// like fns — `export def MAX` becomes `<mod>.MAX`, not a bare
|
||||
// `MAX` two packages could clash on under sep-compile. No export
|
||||
// guard: every decl with a module mangles identically.
|
||||
// #85 (deferred): the `nmod.len > 0` gate below retains the
|
||||
// bare-NON-fn skip — the sibling of #84 (a package-less let/def/
|
||||
// type leaf colliding with an imported same-kind export) needs a
|
||||
// different fix (modlookup is hint-LESS) and has no corpus repro;
|
||||
// retained until then (rule-11).
|
||||
if (d.kind == syntax.nkind.N_DEF) {
|
||||
if (d.nmod.len > 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
@@ -45323,12 +45340,27 @@ fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (syntax.streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.nmod.len > 0
|
||||
&& syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
if (m.nmod.len == 0) {
|
||||
// #84: a bare-module entry (now registered). A bare-ident
|
||||
// ref (hint.len==0: the caller is itself in the bare/root
|
||||
// module) resolves to it — bare wins over an imported
|
||||
// same-leaf fn, by construction, order-independent. A
|
||||
// non-empty hint 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.len == 0) {
|
||||
let empty: str;
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
return empty;
|
||||
};
|
||||
} else {
|
||||
if (hint.len > 0 && syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
};
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
|
||||
@@ -3852,23 +3852,32 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.nmod.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
if (!isffi) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
// #84: register bare-module (package-less `//ww:module-
|
||||
// reset`) fns TOO (nmod.len==0) — previously the
|
||||
// `nmod.len > 0` gate skipped ALL bare fns, so a bare fn
|
||||
// never entered c.mods and a bare-ident ref to it first-
|
||||
// matched an imported same-leaf fn (modlookupforfn) → a
|
||||
// #40 residual / #263-class silent dead-dup. With the bare
|
||||
// entry present, modlookupforfn prefers it on an empty
|
||||
// hint. main is the linker entry 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 ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||
// cstage cgen.c mod_collect.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -3876,6 +3885,11 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
// like fns — `export def MAX` becomes `<mod>.MAX`, not a bare
|
||||
// `MAX` two packages could clash on under sep-compile. No export
|
||||
// guard: every decl with a module mangles identically.
|
||||
// #85 (deferred): the `nmod.len > 0` gate below retains the
|
||||
// bare-NON-fn skip — the sibling of #84 (a package-less let/def/
|
||||
// type leaf colliding with an imported same-kind export) needs a
|
||||
// different fix (modlookup is hint-LESS) and has no corpus repro;
|
||||
// retained until then (rule-11).
|
||||
if (d.kind == syntax.nkind.N_DEF) {
|
||||
if (d.nmod.len > 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
@@ -3951,12 +3965,27 @@ fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (syntax.streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.nmod.len > 0
|
||||
&& syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
if (m.nmod.len == 0) {
|
||||
// #84: a bare-module entry (now registered). A bare-ident
|
||||
// ref (hint.len==0: the caller is itself in the bare/root
|
||||
// module) resolves to it — bare wins over an imported
|
||||
// same-leaf fn, by construction, order-independent. A
|
||||
// non-empty hint 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.len == 0) {
|
||||
let empty: str;
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
return empty;
|
||||
};
|
||||
} else {
|
||||
if (hint.len > 0 && syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
};
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -41304,12 +41304,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);
|
||||
@@ -45224,23 +45227,32 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.nmod.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
let isffi: bool = false;
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (syntax.streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
if (!isffi) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
// #84: register bare-module (package-less `//ww:module-
|
||||
// reset`) fns TOO (nmod.len==0) — previously the
|
||||
// `nmod.len > 0` gate skipped ALL bare fns, so a bare fn
|
||||
// never entered c.mods and a bare-ident ref to it first-
|
||||
// matched an imported same-leaf fn (modlookupforfn) → a
|
||||
// #40 residual / #263-class silent dead-dup. With the bare
|
||||
// entry present, modlookupforfn prefers it on an empty
|
||||
// hint. main is the linker entry 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 ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||
// cstage cgen.c mod_collect.
|
||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -45248,6 +45260,11 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
// like fns — `export def MAX` becomes `<mod>.MAX`, not a bare
|
||||
// `MAX` two packages could clash on under sep-compile. No export
|
||||
// guard: every decl with a module mangles identically.
|
||||
// #85 (deferred): the `nmod.len > 0` gate below retains the
|
||||
// bare-NON-fn skip — the sibling of #84 (a package-less let/def/
|
||||
// type leaf colliding with an imported same-kind export) needs a
|
||||
// different fix (modlookup is hint-LESS) and has no corpus repro;
|
||||
// retained until then (rule-11).
|
||||
if (d.kind == syntax.nkind.N_DEF) {
|
||||
if (d.nmod.len > 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
|
||||
@@ -45323,12 +45340,27 @@ fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (syntax.streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.nmod.len > 0
|
||||
&& syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
if (m.nmod.len == 0) {
|
||||
// #84: a bare-module entry (now registered). A bare-ident
|
||||
// ref (hint.len==0: the caller is itself in the bare/root
|
||||
// module) resolves to it — bare wins over an imported
|
||||
// same-leaf fn, by construction, order-independent. A
|
||||
// non-empty hint 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.len == 0) {
|
||||
let empty: str;
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
return empty;
|
||||
};
|
||||
} else {
|
||||
if (hint.len > 0 && syntax.streq(m.nmod, hint)) {
|
||||
return m.nmod;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.nmod;
|
||||
};
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
|
||||
Reference in New Issue
Block a user