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;

View File

@@ -94,6 +94,27 @@ skipws(Lex *l)
continue;
}
if (c == '/' && lpeek(l, 1) == '/') {
lget(l); lget(l); /* consume '//' */
/* `// MODULE: foo` directive emitted by the ww
* driver before each source-file's section in
* combined.ww. Captured so cgen can mangle
* non-exported symbols by module. */
if (lpeek(l, 0) == ' '
&& lpeek(l, 1) == 'M' && lpeek(l, 2) == 'O'
&& lpeek(l, 3) == 'D' && lpeek(l, 4) == 'U'
&& lpeek(l, 5) == 'L' && lpeek(l, 6) == 'E'
&& lpeek(l, 7) == ':' && lpeek(l, 8) == ' ') {
for (int i = 0; i < 9; i++) lget(l);
u64 start = l->pos;
while ((c = lpeek(l, 0)) >= 0
&& c != '\n' && c != '\r')
lget(l);
u64 n = l->pos - start;
char *m = amalloc(l->a, n + 1);
memcpy(m, l->src + start, n);
m[n] = '\0';
l->module = m;
}
while ((c = lpeek(l, 0)) >= 0 && c != '\n')
lget(l);
continue;

View File

@@ -1174,6 +1174,10 @@ parsefile(Parser *p)
advance(p);
continue;
}
/* Stamp the lex's current `// MODULE: foo` directive on the
* decl. cgen uses it to mangle non-exported names so two
* modules can each privately define `cstrlen`/`streq`/etc. */
if (d != NULL) d->module = p->l->module;
if (head == NULL) head = d;
else tail->next = d;
tail = d;

View File

@@ -196,6 +196,7 @@ struct Lex {
i32 col;
Arena *a; /* token-text arena */
int errs;
const char *module; /* current `// MODULE: foo` directive, or NULL */
};
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
@@ -303,6 +304,11 @@ struct Node {
int export;
Type *type; /* filled in by checker */
const char *tsuffix; /* typed numeric literal suffix */
const char *module; /* `// MODULE: foo` directive at the
* decl's section in combined.ww.
* NULL for nested nodes; only top-
* level decls (fn/def/type/let)
* carry it. */
};
Node *newnode(Arena*, Nkind, Pos);

View File

@@ -4238,6 +4238,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == N_FNDECL) {
if (d.exported == 0) {
if (d.module.len > 0) {
if (!streq(d.str, "main")) {
let m: *modent = amalloc(c.a, 48u64): *modent;
m.mname = d.str;
m.module = d.module;
@@ -4246,6 +4247,7 @@ fn collectmods(c: *cgen, file: *node) void = {
};
};
};
};
if (d.kind == N_DEF) {
if (d.exported == 0) {
if (d.module.len > 0) {

View File

@@ -598,6 +598,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == N_FNDECL) {
if (d.exported == 0) {
if (d.module.len > 0) {
if (!streq(d.str, "main")) {
let m: *modent = amalloc(c.a, 48u64): *modent;
m.mname = d.str;
m.module = d.module;
@@ -606,6 +607,7 @@ fn collectmods(c: *cgen, file: *node) void = {
};
};
};
};
if (d.kind == N_DEF) {
if (d.exported == 0) {
if (d.module.len > 0) {

View File

@@ -4238,6 +4238,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == N_FNDECL) {
if (d.exported == 0) {
if (d.module.len > 0) {
if (!streq(d.str, "main")) {
let m: *modent = amalloc(c.a, 48u64): *modent;
m.mname = d.str;
m.module = d.module;
@@ -4246,6 +4247,7 @@ fn collectmods(c: *cgen, file: *node) void = {
};
};
};
};
if (d.kind == N_DEF) {
if (d.exported == 0) {
if (d.module.len > 0) {