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:
@@ -9544,13 +9544,14 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Fn-name used as a value (e.g. `let f = some_fn;` or
|
||||
// `... = some_fn;`). LEAQ the symbol address into AX. The
|
||||
// emitsymname helper handles ffiresolve and module-mangling
|
||||
// emitfnname helper handles ffiresolve and module-mangling
|
||||
// in one go, so a body-less FFI binding emits the C symbol
|
||||
// it was declared with via @symbol(), not the ww-side ident.
|
||||
// Bare ident → same-module by ww's resolver, hint with c.curmod.
|
||||
let rt: *node = fnretlookup(c, nm);
|
||||
if (rt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitfnname(c, nm, c.curmod);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -12142,11 +12143,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
emitline("\tCALL\t");
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// Bare `f()` — same-module by ww's resolver,
|
||||
// so c.curmod is the disambiguation hint.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
emitfnname(c, calleename, c.curmod);
|
||||
} else { if (callee.kind == nkind.N_DOT) {
|
||||
// `m.f()` — pass the explicit module bareword
|
||||
// so cross-module same-leaf exports resolve.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
let hint: str;
|
||||
hint.ptr = nil;
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
};};
|
||||
};
|
||||
emitline("(SB)\n");
|
||||
@@ -16363,33 +16376,16 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
cgeninit(c, c.a);
|
||||
c.fnname = fn_.str;
|
||||
c.curmod = fn_.module;
|
||||
c.fnret = fn_.lhs;
|
||||
|
||||
// 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. Drops the
|
||||
// `exported == 0` skip in the legacy inline form — exported fns
|
||||
// now mangle too, so cross-module same-leaf exports coexist.
|
||||
emitline("TEXT ");
|
||||
if (fn_.exported == 0) {
|
||||
if (fn_.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = fn_.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// `main` is the linker entry-point convention; even
|
||||
// when not marked `export`, it must keep its bare
|
||||
// name so w6l's _start can resolve `CALL main(SB)`.
|
||||
// Mirror of cmd/w6c/cgen.c collectmods exemption.
|
||||
let isentry: bool = streq(fn_.str, "main");
|
||||
if (!isffi && !isentry) {
|
||||
os.write(1, fn_.module.ptr, fn_.module.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str = fn_.str;
|
||||
os.write(1, nm.ptr, nm.len: u64);
|
||||
emitfnname(c, fn_.str, fn_.module);
|
||||
emitline(",$");
|
||||
|
||||
// Pre-scan total frame: only count params that land in a local
|
||||
@@ -16891,9 +16887,16 @@ type cgen = struct {
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
// when the fn is in the primary file). Drives
|
||||
// bare-IDENT call mangling — `frob()` from
|
||||
// inside lib/foo binds to `foo.frob` even when
|
||||
// other modules also export `frob`. Set in cgfn
|
||||
// before walking the body.
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
@@ -17998,11 +18001,18 @@ fn deflookuprhs(c: *cgen, name: str) *node = {
|
||||
|
||||
// ---- module-private symbol map --------------------------------------
|
||||
//
|
||||
// Non-exported top-level decls live in their originating module's
|
||||
// namespace. cgen mangles those names to `<module>.<name>` at emission
|
||||
// time, both at the def site (TEXT/DATA) and at every call/load site,
|
||||
// so two modules can each privately define `cstrlen` without colliding
|
||||
// at link time. Exported decls and FFI-bound decls keep their bare name.
|
||||
// Every non-FFI top-level fn decl lives in its module's namespace —
|
||||
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
|
||||
// and at every call/load site, so cross-module same-leaf fns (lib/os
|
||||
// `read` vs lib/io `read`, both exported) coexist at link time.
|
||||
// Non-fn decls (let/def/type) stick to the older "non-exported only"
|
||||
// rule: their export-side namespace is the user-facing data ABI and
|
||||
// mangling them changes the surface. FFI-bound decls (@symbol) keep
|
||||
// their explicit C symbol regardless of kind.
|
||||
//
|
||||
// Skip rule = {@symbol, main, empty-module}. Do NOT skip on `export`
|
||||
// for fns. Both stages must match exactly — ww2/ww3/ww4 byte-identity
|
||||
// depends on it.
|
||||
|
||||
type modent = struct {
|
||||
mname: str, // the bare ident as it appears in source
|
||||
@@ -18019,8 +18029,19 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
if (d.exported == 0) {
|
||||
if (d.module.len > 0) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
let m: *modent = amalloc(c.a, 48u64): *modent;
|
||||
m.mname = d.str;
|
||||
@@ -18080,10 +18101,38 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
// owner shape, also covers lookups with hint.len==0). Needed because
|
||||
// multiple modules can now register the same fn leaf — bare `lookup`
|
||||
// would otherwise grab whichever module was prepended last.
|
||||
fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
let m: *modent = c.mods;
|
||||
let first: str;
|
||||
first.ptr = nil;
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.module.len > 0
|
||||
&& streq(m.module, hint)) {
|
||||
return m.module;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.module;
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
};
|
||||
return first;
|
||||
};
|
||||
|
||||
// emitsymname — write the asm symbol name for `ident`. Honours, in
|
||||
// order: FFI mapping (@symbol), module mangling (private decls), bare
|
||||
// name. Use everywhere a top-level name is emitted before `(SB)` or in
|
||||
// a `TEXT name,$N` header.
|
||||
// name. Use everywhere a top-level non-fn name is emitted before `(SB)`
|
||||
// — DATA labels for top-level lets/defs, address-of-let, etc. Fn names
|
||||
// (CALL/LEAQ-of-fn/TEXT) go through emitfnname so the hint disambiguates
|
||||
// cross-module same-leaf fn exports.
|
||||
fn emitsymname(c: *cgen, ident: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
@@ -18099,6 +18148,25 @@ fn emitsymname(c: *cgen, ident: str) void = {
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// emitfnname — write the asm symbol name for a fn `ident`, threading
|
||||
// `hint` (the explicit module from a `mod.fn` use site, or c.curmod
|
||||
// for bare-IDENT calls) through modlookupforfn. Same FFI override
|
||||
// semantics as emitsymname; same dot-separator format. Use at every
|
||||
// CALL / LEAQ-of-fn / TEXT-def site.
|
||||
fn emitfnname(c: *cgen, ident: str, hint: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
os.write(1, resolved.ptr, resolved.len: u64);
|
||||
return;
|
||||
};
|
||||
let mod: str = modlookupforfn(c, ident, hint);
|
||||
if (mod.len > 0) {
|
||||
os.write(1, mod.ptr, mod.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// ---- FFI map ---------------------------------------------------------
|
||||
|
||||
fn fficollect(c: *cgen, file: *node) void = {
|
||||
|
||||
@@ -367,9 +367,16 @@ type cgen = struct {
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
// when the fn is in the primary file). Drives
|
||||
// bare-IDENT call mangling — `frob()` from
|
||||
// inside lib/foo binds to `foo.frob` even when
|
||||
// other modules also export `frob`. Set in cgfn
|
||||
// before walking the body.
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
@@ -1474,11 +1481,18 @@ fn deflookuprhs(c: *cgen, name: str) *node = {
|
||||
|
||||
// ---- module-private symbol map --------------------------------------
|
||||
//
|
||||
// Non-exported top-level decls live in their originating module's
|
||||
// namespace. cgen mangles those names to `<module>.<name>` at emission
|
||||
// time, both at the def site (TEXT/DATA) and at every call/load site,
|
||||
// so two modules can each privately define `cstrlen` without colliding
|
||||
// at link time. Exported decls and FFI-bound decls keep their bare name.
|
||||
// Every non-FFI top-level fn decl lives in its module's namespace —
|
||||
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
|
||||
// and at every call/load site, so cross-module same-leaf fns (lib/os
|
||||
// `read` vs lib/io `read`, both exported) coexist at link time.
|
||||
// Non-fn decls (let/def/type) stick to the older "non-exported only"
|
||||
// rule: their export-side namespace is the user-facing data ABI and
|
||||
// mangling them changes the surface. FFI-bound decls (@symbol) keep
|
||||
// their explicit C symbol regardless of kind.
|
||||
//
|
||||
// Skip rule = {@symbol, main, empty-module}. Do NOT skip on `export`
|
||||
// for fns. Both stages must match exactly — ww2/ww3/ww4 byte-identity
|
||||
// depends on it.
|
||||
|
||||
type modent = struct {
|
||||
mname: str, // the bare ident as it appears in source
|
||||
@@ -1495,8 +1509,19 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
if (d.exported == 0) {
|
||||
if (d.module.len > 0) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
let m: *modent = amalloc(c.a, 48u64): *modent;
|
||||
m.mname = d.str;
|
||||
@@ -1556,10 +1581,38 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
// owner shape, also covers lookups with hint.len==0). Needed because
|
||||
// multiple modules can now register the same fn leaf — bare `lookup`
|
||||
// would otherwise grab whichever module was prepended last.
|
||||
fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
let m: *modent = c.mods;
|
||||
let first: str;
|
||||
first.ptr = nil;
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.module.len > 0
|
||||
&& streq(m.module, hint)) {
|
||||
return m.module;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.module;
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
};
|
||||
return first;
|
||||
};
|
||||
|
||||
// emitsymname — write the asm symbol name for `ident`. Honours, in
|
||||
// order: FFI mapping (@symbol), module mangling (private decls), bare
|
||||
// name. Use everywhere a top-level name is emitted before `(SB)` or in
|
||||
// a `TEXT name,$N` header.
|
||||
// name. Use everywhere a top-level non-fn name is emitted before `(SB)`
|
||||
// — DATA labels for top-level lets/defs, address-of-let, etc. Fn names
|
||||
// (CALL/LEAQ-of-fn/TEXT) go through emitfnname so the hint disambiguates
|
||||
// cross-module same-leaf fn exports.
|
||||
fn emitsymname(c: *cgen, ident: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
@@ -1575,6 +1628,25 @@ fn emitsymname(c: *cgen, ident: str) void = {
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// emitfnname — write the asm symbol name for a fn `ident`, threading
|
||||
// `hint` (the explicit module from a `mod.fn` use site, or c.curmod
|
||||
// for bare-IDENT calls) through modlookupforfn. Same FFI override
|
||||
// semantics as emitsymname; same dot-separator format. Use at every
|
||||
// CALL / LEAQ-of-fn / TEXT-def site.
|
||||
fn emitfnname(c: *cgen, ident: str, hint: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
os.write(1, resolved.ptr, resolved.len: u64);
|
||||
return;
|
||||
};
|
||||
let mod: str = modlookupforfn(c, ident, hint);
|
||||
if (mod.len > 0) {
|
||||
os.write(1, mod.ptr, mod.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// ---- FFI map ---------------------------------------------------------
|
||||
|
||||
fn fficollect(c: *cgen, file: *node) void = {
|
||||
|
||||
@@ -711,33 +711,16 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
cgeninit(c, c.a);
|
||||
c.fnname = fn_.str;
|
||||
c.curmod = fn_.module;
|
||||
c.fnret = fn_.lhs;
|
||||
|
||||
// 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. Drops the
|
||||
// `exported == 0` skip in the legacy inline form — exported fns
|
||||
// now mangle too, so cross-module same-leaf exports coexist.
|
||||
emitline("TEXT ");
|
||||
if (fn_.exported == 0) {
|
||||
if (fn_.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = fn_.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// `main` is the linker entry-point convention; even
|
||||
// when not marked `export`, it must keep its bare
|
||||
// name so w6l's _start can resolve `CALL main(SB)`.
|
||||
// Mirror of cmd/w6c/cgen.c collectmods exemption.
|
||||
let isentry: bool = streq(fn_.str, "main");
|
||||
if (!isffi && !isentry) {
|
||||
os.write(1, fn_.module.ptr, fn_.module.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str = fn_.str;
|
||||
os.write(1, nm.ptr, nm.len: u64);
|
||||
emitfnname(c, fn_.str, fn_.module);
|
||||
emitline(",$");
|
||||
|
||||
// Pre-scan total frame: only count params that land in a local
|
||||
|
||||
@@ -528,13 +528,14 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Fn-name used as a value (e.g. `let f = some_fn;` or
|
||||
// `... = some_fn;`). LEAQ the symbol address into AX. The
|
||||
// emitsymname helper handles ffiresolve and module-mangling
|
||||
// emitfnname helper handles ffiresolve and module-mangling
|
||||
// in one go, so a body-less FFI binding emits the C symbol
|
||||
// it was declared with via @symbol(), not the ww-side ident.
|
||||
// Bare ident → same-module by ww's resolver, hint with c.curmod.
|
||||
let rt: *node = fnretlookup(c, nm);
|
||||
if (rt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitfnname(c, nm, c.curmod);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -3126,11 +3127,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
emitline("\tCALL\t");
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// Bare `f()` — same-module by ww's resolver,
|
||||
// so c.curmod is the disambiguation hint.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
emitfnname(c, calleename, c.curmod);
|
||||
} else { if (callee.kind == nkind.N_DOT) {
|
||||
// `m.f()` — pass the explicit module bareword
|
||||
// so cross-module same-leaf exports resolve.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
let hint: str;
|
||||
hint.ptr = nil;
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
};};
|
||||
};
|
||||
emitline("(SB)\n");
|
||||
|
||||
@@ -9544,13 +9544,14 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Fn-name used as a value (e.g. `let f = some_fn;` or
|
||||
// `... = some_fn;`). LEAQ the symbol address into AX. The
|
||||
// emitsymname helper handles ffiresolve and module-mangling
|
||||
// emitfnname helper handles ffiresolve and module-mangling
|
||||
// in one go, so a body-less FFI binding emits the C symbol
|
||||
// it was declared with via @symbol(), not the ww-side ident.
|
||||
// Bare ident → same-module by ww's resolver, hint with c.curmod.
|
||||
let rt: *node = fnretlookup(c, nm);
|
||||
if (rt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitfnname(c, nm, c.curmod);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -12142,11 +12143,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
emitline("\tCALL\t");
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// Bare `f()` — same-module by ww's resolver,
|
||||
// so c.curmod is the disambiguation hint.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
emitfnname(c, calleename, c.curmod);
|
||||
} else { if (callee.kind == nkind.N_DOT) {
|
||||
// `m.f()` — pass the explicit module bareword
|
||||
// so cross-module same-leaf exports resolve.
|
||||
calleename = callee.str;
|
||||
emitsymname(c, calleename);
|
||||
let hint: str;
|
||||
hint.ptr = nil;
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
};};
|
||||
};
|
||||
emitline("(SB)\n");
|
||||
@@ -16363,33 +16376,16 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
cgeninit(c, c.a);
|
||||
c.fnname = fn_.str;
|
||||
c.curmod = fn_.module;
|
||||
c.fnret = fn_.lhs;
|
||||
|
||||
// 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. Drops the
|
||||
// `exported == 0` skip in the legacy inline form — exported fns
|
||||
// now mangle too, so cross-module same-leaf exports coexist.
|
||||
emitline("TEXT ");
|
||||
if (fn_.exported == 0) {
|
||||
if (fn_.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = fn_.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// `main` is the linker entry-point convention; even
|
||||
// when not marked `export`, it must keep its bare
|
||||
// name so w6l's _start can resolve `CALL main(SB)`.
|
||||
// Mirror of cmd/w6c/cgen.c collectmods exemption.
|
||||
let isentry: bool = streq(fn_.str, "main");
|
||||
if (!isffi && !isentry) {
|
||||
os.write(1, fn_.module.ptr, fn_.module.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str = fn_.str;
|
||||
os.write(1, nm.ptr, nm.len: u64);
|
||||
emitfnname(c, fn_.str, fn_.module);
|
||||
emitline(",$");
|
||||
|
||||
// Pre-scan total frame: only count params that land in a local
|
||||
@@ -16891,9 +16887,16 @@ type cgen = struct {
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
// when the fn is in the primary file). Drives
|
||||
// bare-IDENT call mangling — `frob()` from
|
||||
// inside lib/foo binds to `foo.frob` even when
|
||||
// other modules also export `frob`. Set in cgfn
|
||||
// before walking the body.
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
@@ -17998,11 +18001,18 @@ fn deflookuprhs(c: *cgen, name: str) *node = {
|
||||
|
||||
// ---- module-private symbol map --------------------------------------
|
||||
//
|
||||
// Non-exported top-level decls live in their originating module's
|
||||
// namespace. cgen mangles those names to `<module>.<name>` at emission
|
||||
// time, both at the def site (TEXT/DATA) and at every call/load site,
|
||||
// so two modules can each privately define `cstrlen` without colliding
|
||||
// at link time. Exported decls and FFI-bound decls keep their bare name.
|
||||
// Every non-FFI top-level fn decl lives in its module's namespace —
|
||||
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
|
||||
// and at every call/load site, so cross-module same-leaf fns (lib/os
|
||||
// `read` vs lib/io `read`, both exported) coexist at link time.
|
||||
// Non-fn decls (let/def/type) stick to the older "non-exported only"
|
||||
// rule: their export-side namespace is the user-facing data ABI and
|
||||
// mangling them changes the surface. FFI-bound decls (@symbol) keep
|
||||
// their explicit C symbol regardless of kind.
|
||||
//
|
||||
// Skip rule = {@symbol, main, empty-module}. Do NOT skip on `export`
|
||||
// for fns. Both stages must match exactly — ww2/ww3/ww4 byte-identity
|
||||
// depends on it.
|
||||
|
||||
type modent = struct {
|
||||
mname: str, // the bare ident as it appears in source
|
||||
@@ -18019,8 +18029,19 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
if (d.exported == 0) {
|
||||
if (d.module.len > 0) {
|
||||
// Fns mangle regardless of export status — covers
|
||||
// lib/os.read vs lib/io.read collision.
|
||||
if (d.module.len > 0) {
|
||||
let isffi: bool = false;
|
||||
let a: *node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == nkind.N_ATTR) {
|
||||
let an: str = a.str;
|
||||
if (streq(an, "symbol")) { isffi = true; };
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
let m: *modent = amalloc(c.a, 48u64): *modent;
|
||||
m.mname = d.str;
|
||||
@@ -18080,10 +18101,38 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
// owner shape, also covers lookups with hint.len==0). Needed because
|
||||
// multiple modules can now register the same fn leaf — bare `lookup`
|
||||
// would otherwise grab whichever module was prepended last.
|
||||
fn modlookupforfn(c: *cgen, name: str, hint: str) str = {
|
||||
let m: *modent = c.mods;
|
||||
let first: str;
|
||||
first.ptr = nil;
|
||||
first.len = 0;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, name)) {
|
||||
if (hint.len > 0 && m.module.len > 0
|
||||
&& streq(m.module, hint)) {
|
||||
return m.module;
|
||||
};
|
||||
if (first.len == 0 && first.ptr == nil) {
|
||||
first = m.module;
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
};
|
||||
return first;
|
||||
};
|
||||
|
||||
// emitsymname — write the asm symbol name for `ident`. Honours, in
|
||||
// order: FFI mapping (@symbol), module mangling (private decls), bare
|
||||
// name. Use everywhere a top-level name is emitted before `(SB)` or in
|
||||
// a `TEXT name,$N` header.
|
||||
// name. Use everywhere a top-level non-fn name is emitted before `(SB)`
|
||||
// — DATA labels for top-level lets/defs, address-of-let, etc. Fn names
|
||||
// (CALL/LEAQ-of-fn/TEXT) go through emitfnname so the hint disambiguates
|
||||
// cross-module same-leaf fn exports.
|
||||
fn emitsymname(c: *cgen, ident: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
@@ -18099,6 +18148,25 @@ fn emitsymname(c: *cgen, ident: str) void = {
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// emitfnname — write the asm symbol name for a fn `ident`, threading
|
||||
// `hint` (the explicit module from a `mod.fn` use site, or c.curmod
|
||||
// for bare-IDENT calls) through modlookupforfn. Same FFI override
|
||||
// semantics as emitsymname; same dot-separator format. Use at every
|
||||
// CALL / LEAQ-of-fn / TEXT-def site.
|
||||
fn emitfnname(c: *cgen, ident: str, hint: str) void = {
|
||||
let resolved: str = ffiresolve(c, ident);
|
||||
if (resolved.ptr != ident.ptr) {
|
||||
os.write(1, resolved.ptr, resolved.len: u64);
|
||||
return;
|
||||
};
|
||||
let mod: str = modlookupforfn(c, ident, hint);
|
||||
if (mod.len > 0) {
|
||||
os.write(1, mod.ptr, mod.len: u64);
|
||||
os.write(1, ".".ptr, 1u64);
|
||||
};
|
||||
os.write(1, ident.ptr, ident.len: u64);
|
||||
};
|
||||
|
||||
// ---- FFI map ---------------------------------------------------------
|
||||
|
||||
fn fficollect(c: *cgen, file: *node) void = {
|
||||
|
||||
Reference in New Issue
Block a user