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:
126
cmd/w6c/cgen.c
126
cmd/w6c/cgen.c
@@ -451,10 +451,13 @@ ffi_collect(Cg *c, Node *file)
|
||||
}
|
||||
}
|
||||
|
||||
/* Module-private symbol map. Mirrors selfhost/cmd/wcc/cgen.ww. Each
|
||||
* non-exported, non-FFI top-level decl is mangled to <module>.<name>
|
||||
* at emission time so two modules can each privately define the same
|
||||
* helper without colliding at link time. */
|
||||
/* Module-private symbol map. Mirrors selfhost/cmd/wcc/cgen.ww. Every
|
||||
* non-FFI top-level fn decl is mangled to <module>.<name> at emission
|
||||
* time so two modules can each define the same fn leaf — including
|
||||
* exported ones (lib/os and lib/io both ship `read`/`write`/`close`)
|
||||
* — without colliding at link time. Non-fn decls (let/def/type) keep
|
||||
* the older "non-exported only" rule: their export-side namespace is
|
||||
* the user-facing data ABI and mangling them changes the surface. */
|
||||
typedef struct Mod Mod;
|
||||
struct Mod {
|
||||
const char *name;
|
||||
@@ -601,16 +604,24 @@ decl_has_ffisym(Node *d)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void
|
||||
mod_collect(Cg *c, Node *file)
|
||||
{
|
||||
mod_map = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
int track = (d->kind == N_FNDECL) || (d->kind == N_TYPEDECL)
|
||||
int isfn = (d->kind == N_FNDECL);
|
||||
int track = isfn || (d->kind == N_TYPEDECL)
|
||||
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
||||
if (!track) continue;
|
||||
if (d->export) continue;
|
||||
/* Non-fn decls (let/def/type) still skip exported entries —
|
||||
* their export-side namespace is the user-facing data ABI
|
||||
* and mangling them changes the surface. Fns mangle
|
||||
* unconditionally so cross-module same-leaf exports
|
||||
* (os.read vs io.read) coexist at link time. */
|
||||
if (!isfn && d->export) continue;
|
||||
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
|
||||
@@ -626,7 +637,10 @@ mod_collect(Cg *c, Node *file)
|
||||
}
|
||||
|
||||
/* Returns the originating module for a name, or NULL if the name
|
||||
* isn't a registered private decl. */
|
||||
* isn't a registered private decl. By-name only — works for non-fn
|
||||
* refs (let/def/type) where the mod_collect skip rule keeps each leaf
|
||||
* unique across the program. Fn refs go through mod_lookup_for_fn
|
||||
* since multiple modules can now export the same fn leaf. */
|
||||
static const char *
|
||||
mod_lookup(const char *name)
|
||||
{
|
||||
@@ -635,6 +649,29 @@ mod_lookup(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Hint-aware variant for fn names. Walks mod_map looking for a
|
||||
* (name, hint) pair; returns NULL if there's no leaf-name match at
|
||||
* all, the hinted module if a match exists, or the first leaf match
|
||||
* when the caller had no hint. The hint comes from AST shape:
|
||||
* - N_DOT call `m.fn(...)`: hint = the SK_USE module ident's str.
|
||||
* - bare N_IDENT call `fn(...)`: hint = c->cur_mod (current fn's
|
||||
* module — bare names resolve same-module by ww's rules).
|
||||
* Falling back to the first leaf match preserves the legacy single-
|
||||
* owner shape for callers that don't (yet) thread a hint. */
|
||||
static const char *
|
||||
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)
|
||||
return m->module;
|
||||
if (first == NULL) first = m->module;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/* Collect every top-level `let` whose declared type we can store
|
||||
* in a single .data slot. Names not in this map fall through to
|
||||
* the old "drop assignment" path; with a clear link-time
|
||||
@@ -664,6 +701,19 @@ let_islet(const char *name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Glue `<module>.<ident>` into a fresh arena buffer. */
|
||||
static const char *
|
||||
mod_join(Cg *c, const char *mod, const char *ident)
|
||||
{
|
||||
size_t mn = strlen(mod), in = strlen(ident);
|
||||
char *buf = amalloc(c->a, mn + 1 + in + 1);
|
||||
memcpy(buf, mod, mn);
|
||||
buf[mn] = '.';
|
||||
memcpy(buf + mn + 1, ident, in);
|
||||
buf[mn + 1 + in] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Mangle an AST identifier into its asm linker symbol:
|
||||
* - @symbol("...") binding wins (return mapped name).
|
||||
* - module-private decl → <module>.<name>.
|
||||
@@ -678,13 +728,22 @@ mod_mangle(Cg *c, const char *ident)
|
||||
if (resolved != ident) return resolved;
|
||||
const char *mod = mod_lookup(ident);
|
||||
if (mod == NULL) return ident;
|
||||
size_t mn = strlen(mod), in = strlen(ident);
|
||||
char *buf = amalloc(c->a, mn + 1 + in + 1);
|
||||
memcpy(buf, mod, mn);
|
||||
buf[mn] = '.';
|
||||
memcpy(buf + mn + 1, ident, in);
|
||||
buf[mn + 1 + in] = '\0';
|
||||
return buf;
|
||||
return mod_join(c, mod, ident);
|
||||
}
|
||||
|
||||
/* Fn-flavoured mangle: same shape as mod_mangle but consults
|
||||
* mod_lookup_for_fn so the right module wins when multiple modules
|
||||
* register the same fn leaf. `hint` is the explicit module from a
|
||||
* N_DOT call site (or c->cur_mod for bare-ident calls); pass NULL
|
||||
* to get the legacy first-match-wins behaviour. */
|
||||
static const char *
|
||||
mod_mangle_fn(Cg *c, const char *ident, const char *hint)
|
||||
{
|
||||
const char *resolved = ffi_resolve(ident);
|
||||
if (resolved != ident) return resolved;
|
||||
const char *mod = mod_lookup_for_fn(ident, hint);
|
||||
if (mod == NULL) return ident;
|
||||
return mod_join(c, mod, ident);
|
||||
}
|
||||
|
||||
/* Forward decl — masym below depends on asym defined further down. */
|
||||
@@ -696,6 +755,15 @@ masym(Cg *c, const char *ident)
|
||||
return asym(mod_mangle(c, ident));
|
||||
}
|
||||
|
||||
/* Fn-name address builder. Use at every CALL/LEAQ site whose target
|
||||
* is a top-level fn — passes the hint so cross-module same-leaf
|
||||
* exports resolve to the right module. */
|
||||
static Adr
|
||||
mafn(Cg *c, const char *ident, const char *hint)
|
||||
{
|
||||
return asym(mod_mangle_fn(c, ident, hint));
|
||||
}
|
||||
|
||||
void
|
||||
cg_init(Cg *c, Arena *a)
|
||||
{
|
||||
@@ -1496,9 +1564,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* of a body-less FFI binding yields the C
|
||||
* symbol, not the ww-side ident. Hare emits
|
||||
* the same `$symname` for both call and
|
||||
* address-of via QBE; here we mirror that. */
|
||||
* address-of via QBE; here we mirror that.
|
||||
* Bare ident → same-module by ww's resolver,
|
||||
* so c->cur_mod is the right disambiguation
|
||||
* hint. */
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->str), areg(D_AX));
|
||||
mafn(c, n->str, c->cur_mod), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
for (Sdef *s = sdefs; s; s = s->next) {
|
||||
@@ -4216,8 +4287,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(D_BP, loff), areg(D_AX));
|
||||
ins1(c, A_CALL, areg(D_AX));
|
||||
} else {
|
||||
/* Bare `f()` — same-module by ww's resolver
|
||||
* rules. Hint with c->cur_mod so the right
|
||||
* fn wins when the leaf collides with another
|
||||
* module's exported same-leaf fn. */
|
||||
ins1(c, A_CALL,
|
||||
masym(c, n->lhs->str));
|
||||
mafn(c, n->lhs->str, c->cur_mod));
|
||||
}
|
||||
} else if (n->lhs->kind == N_DOT && n->lhs->lhs &&
|
||||
n->lhs->lhs->kind == N_IDENT) {
|
||||
@@ -4227,7 +4302,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* function pointer — we load the field and indirect. */
|
||||
Type *bt = n->lhs->lhs->type;
|
||||
if (bt == NULL || bt == ty_err) {
|
||||
ins1(c, A_CALL, masym(c, n->lhs->str));
|
||||
/* `m.fn()` — explicit module qualifier. Pass
|
||||
* the bareword as the hint so cross-module
|
||||
* same-leaf exports resolve correctly. */
|
||||
ins1(c, A_CALL,
|
||||
mafn(c, n->lhs->str, n->lhs->lhs->str));
|
||||
} else {
|
||||
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
|
||||
ins1(c, A_CALL, areg(D_AX));
|
||||
@@ -4759,8 +4838,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
Type *t = n->type;
|
||||
Type *tu = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (tu && tu->kind == TY_FN) {
|
||||
/* `mod.fn` address-of via N_DOT — pass the
|
||||
* module bareword as the disambiguation hint. */
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->str), areg(D_AX));
|
||||
mafn(c, n->str, n->lhs->str), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
for (Sdef *s = sdefs; s; s = s->next) {
|
||||
@@ -6470,6 +6551,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
/* fresh per-fn state */
|
||||
c->head = c->tail = NULL;
|
||||
c->fnname = fn->str;
|
||||
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL;
|
||||
c->labelseq = 0;
|
||||
cg_stack_arg_cursor = 0;
|
||||
ndefers = 0;
|
||||
@@ -6482,9 +6564,9 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
|
||||
/* TEXT directive comes first; framesize is filled at the end. */
|
||||
Prog *text = newprog(c, A_TEXT);
|
||||
/* Mangle the label for non-exported, non-FFI decls; mod_mangle
|
||||
* does the FFI/module lookup in one step. */
|
||||
text->to = masym(c, fn->str);
|
||||
/* Mangle the label using the fn's own module as the hint — picks
|
||||
* the right entry when multiple modules export the same leaf. */
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
text->from.offset = 0; /* framesize patched below */
|
||||
emit(c, text);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user