wcc: c-side mangles private decls; main exempt as entry-point convention

This commit is contained in:
2026-05-11 15:29:29 +09:00
parent 895f221b6c
commit e301a198f4
7 changed files with 153 additions and 25 deletions

View File

@@ -301,6 +301,94 @@ 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. */
typedef struct Mod Mod;
struct Mod {
const char *name;
const char *module;
Mod *next;
};
static Mod *mod_map;
static int
decl_has_ffisym(Node *d)
{
for (Node *a = d->attr; a; a = a->next) {
if (a->kind != N_ATTR) continue;
if (strcmp(a->str, "symbol") == 0) return 1;
}
return 0;
}
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)
|| (d->kind == N_DEF) || (d->kind == N_LET);
if (!track) continue;
if (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
* 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;
Mod *m = amalloc(c->a, sizeof *m);
m->name = d->str;
m->module = d->module;
m->next = mod_map;
mod_map = m;
}
}
/* Returns the originating module for a name, or NULL if the name
* isn't a registered private decl. */
static const char *
mod_lookup(const char *name)
{
for (Mod *m = mod_map; m; m = m->next)
if (strcmp(m->name, name) == 0) return m->module;
return NULL;
}
/* Mangle an AST identifier into its asm linker symbol:
* - @symbol("...") binding wins (return mapped name).
* - module-private decl → <module>.<name>.
* - else → name unchanged.
* Used at every CALL/MOVQ/LEAQ site that targets an AST name. Plain
* `asym(s)` still emits `s` verbatim — use it for strlit labels and
* hard-coded runtime symbols like "rt_streq". */
static const char *
mod_mangle(Cg *c, const char *ident)
{
const char *resolved = ffi_resolve(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;
}
/* Forward decl — masym below depends on asym defined further down. */
static Adr asym(const char *s);
static Adr
masym(Cg *c, const char *ident)
{
return asym(mod_mangle(c, ident));
}
void
cg_init(Cg *c, Arena *a)
{
@@ -514,7 +602,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* the same `$symname` for both call and
* address-of via QBE; here we mirror that. */
ins2(c, A_LEAQ,
asym(ffi_resolve(n->str)), areg(D_AX));
masym(c, n->str), areg(D_AX));
break;
}
for (Sdef *s = sdefs; s; s = s->next) {
@@ -526,7 +614,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_BX));
goto ident_done;
}
ins2(c, A_MOVQ, asym(n->str), areg(D_AX));
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
}
ident_done:
break;
@@ -1253,7 +1341,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_AX));
ins1(c, A_POPQ, areg(D_SI));
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins1(c, A_CALL, asym(fn));
ins1(c, A_CALL, masym(c, fn));
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 0));
ins1(c, A_JMP, abranch(ll));
label(c, le);
@@ -1266,7 +1354,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_AX));
ins1(c, A_POPQ, areg(D_SI));
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins1(c, A_CALL, asym(fn));
ins1(c, A_CALL, masym(c, fn));
}
break;
}
@@ -1452,7 +1540,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins1(c, A_CALL, areg(D_AX));
} else {
ins1(c, A_CALL,
asym(ffi_resolve(n->lhs->str)));
masym(c, n->lhs->str));
}
} else if (n->lhs->kind == N_DOT && n->lhs->lhs &&
n->lhs->lhs->kind == N_IDENT) {
@@ -1462,7 +1550,7 @@ 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, asym(ffi_resolve(n->lhs->str)));
ins1(c, A_CALL, masym(c, n->lhs->str));
} else {
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
ins1(c, A_CALL, areg(D_AX));
@@ -1669,7 +1757,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *tu = (t && t->kind == TY_NAMED) ? t->under : t;
if (tu && tu->kind == TY_FN) {
ins2(c, A_LEAQ,
asym(ffi_resolve(n->str)), areg(D_AX));
masym(c, n->str), areg(D_AX));
break;
}
for (Sdef *s = sdefs; s; s = s->next) {
@@ -1681,7 +1769,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_BX));
goto dot_done;
}
ins2(c, A_MOVQ, asym(n->str), areg(D_AX));
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
goto dot_done;
}
int lenfld = (n->str && strcmp(n->str, "len") == 0);
@@ -2513,7 +2601,9 @@ cgfn(Cg *c, FILE *out, Node *fn)
/* TEXT directive comes first; framesize is filled at the end. */
Prog *text = newprog(c, A_TEXT);
text->to = asym(fn->str);
/* 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);
text->from.offset = 0; /* framesize patched below */
emit(c, text);
@@ -2630,7 +2720,7 @@ emit_defs(Cg *c, FILE *out, Node *file)
} else {
continue; /* skip non-integer-literal defs */
}
fprintf(out, "DATA %s(SB),\"", d->str);
fprintf(out, "DATA %s(SB),\"", mod_mangle(c, d->str));
for (int i = 0; i < 8; i++) {
unsigned b = (unsigned)((v >> (i * 8)) & 0xff);
if (b == '"' || b == '\\')
@@ -2672,6 +2762,7 @@ cg_file(Cg *c, FILE *out, Node *file)
{
if (file == NULL || file->kind != N_FILE) return;
ffi_collect(c, file);
mod_collect(c, file);
sdef_collect(c, file);
strlits = NULL;
strlit_seq = 0;