Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical. Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id).
This commit is contained in:
@@ -1167,6 +1167,43 @@ struct Mod {
|
||||
};
|
||||
static Mod *mod_map;
|
||||
|
||||
/* M1 #22: alias→import-path map built from the N_USE nodes, so a
|
||||
* qualified-ref codegen hint (`utf8.decoderune`) keys mod_map on the
|
||||
* dotted path the symbols are registered under, not the bare alias.
|
||||
* For single-level packages alias == path, so this is a no-op there. */
|
||||
typedef struct Use Use;
|
||||
struct Use {
|
||||
const char *alias;
|
||||
const char *path;
|
||||
Use *next;
|
||||
};
|
||||
static Use *use_map;
|
||||
|
||||
/*
|
||||
* Retained divergence (M1 #22): this map is file-global — when two
|
||||
* modules in the same unit bind the same leaf alias to different paths
|
||||
* (sha256's `import crypto.math` vs strconv's `import math`), the first
|
||||
* match wins. The checker's twin (use_path, check.c) is module-scoped
|
||||
* to fix exactly this for qualified-NAME resolution; the cgen mangle
|
||||
* hint is NOT, because it is unreachable with an ambiguous alias: a
|
||||
* cross-module qualified ref resolves only to an EXPORTED symbol, and
|
||||
* exported non-fn decls stay bare (mod_collect skips them, so the hint
|
||||
* is moot) while an exported-fn collision would require two same-leaf
|
||||
* packages to export the same fn name AND a third caller — not present
|
||||
* (the tree links cleanly). Left file-global to stay byte-identical
|
||||
* with wwstage's symmetric usehint (cgen.ww), which is also file-global
|
||||
* (rule 10). Module-scope both stages together if a real collision
|
||||
* surfaces — filed as the M1 cgen-hint twin follow-up.
|
||||
*/
|
||||
static const char *
|
||||
use_hint(const char *alias)
|
||||
{
|
||||
if (alias == NULL) return alias;
|
||||
for (Use *u = use_map; u; u = u->next)
|
||||
if (strcmp(u->alias, alias) == 0) return u->path;
|
||||
return alias;
|
||||
}
|
||||
|
||||
/* Top-level `let` map. Populated alongside mod_map; consulted by the
|
||||
* N_IDENT store path and the &-of path to route reads/writes through
|
||||
* a RIP-relative reference rather than dropping them as the (pre-
|
||||
@@ -1383,8 +1420,18 @@ static void
|
||||
mod_collect(Cg *c, Node *file)
|
||||
{
|
||||
mod_map = NULL;
|
||||
use_map = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
/* M1 #22: record alias→path for the qualified-ref hint. */
|
||||
if (d->kind == N_USE && d->str && d->usepath) {
|
||||
Use *u = amalloc(c->a, sizeof *u);
|
||||
u->alias = d->str;
|
||||
u->path = d->usepath;
|
||||
u->next = use_map;
|
||||
use_map = u;
|
||||
continue;
|
||||
}
|
||||
int isfn = (d->kind == N_FNDECL);
|
||||
int track = isfn || (d->kind == N_TYPEDECL)
|
||||
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
||||
@@ -1399,8 +1446,11 @@ mod_collect(Cg *c, Node *file)
|
||||
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)`. */
|
||||
if (d->str && strcmp(d->str, "main") == 0) continue;
|
||||
* 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). */
|
||||
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;
|
||||
@@ -4321,7 +4371,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (lu && lu->kind == TY_FN)
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
else
|
||||
/* #229: dotted-module value
|
||||
@@ -4330,7 +4380,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* same-leaf collision. */
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -10181,7 +10231,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* 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));
|
||||
mafn(c, n->lhs->str,
|
||||
use_hint(n->lhs->lhs->str)));
|
||||
} else {
|
||||
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
|
||||
ins1(c, A_CALL, areg(D_AX));
|
||||
@@ -11057,7 +11108,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* `mod.fn` address-of via N_DOT — pass the
|
||||
* module bareword as the disambiguation hint. */
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mafn(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
{
|
||||
@@ -11073,7 +11125,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
for (s = sdefs; s; s = s->next) {
|
||||
if (strcmp(s->name, n->str) != 0)
|
||||
continue;
|
||||
if (sdef_mod_match_hint(s, n->lhs->str))
|
||||
if (sdef_mod_match_hint(s, use_hint(n->lhs->str)))
|
||||
break;
|
||||
}
|
||||
if (s == NULL) {
|
||||
@@ -11105,10 +11157,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* branch above already uses n->lhs->str via mafn. */
|
||||
if (mqop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_CX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_CX));
|
||||
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
|
||||
}
|
||||
goto dot_done;
|
||||
@@ -14920,8 +14974,15 @@ 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 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);
|
||||
* 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`. */
|
||||
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported)
|
||||
text->to = asym("main");
|
||||
else
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
text->from.offset = 0; /* framesize patched below */
|
||||
emit(c, text);
|
||||
|
||||
@@ -15988,7 +16049,7 @@ node_fnptr_sym(Cg *c, Node *ev)
|
||||
return NULL;
|
||||
Type *du = type_chase_named(opnd->type);
|
||||
if (du == NULL || du->kind != TY_FN) return NULL;
|
||||
return mod_mangle_fn(c, opnd->str, opnd->lhs->str);
|
||||
return mod_mangle_fn(c, opnd->str, use_hint(opnd->lhs->str));
|
||||
}
|
||||
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
|
||||
Type *ou = type_chase_named(opnd->type);
|
||||
|
||||
Reference in New Issue
Block a user