diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 0f225be4..7b8925a6 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 . + * 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 → .. + * - 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; diff --git a/cmd/wcc/lex.c b/cmd/wcc/lex.c index 71dc5102..4f3ae509 100644 --- a/cmd/wcc/lex.c +++ b/cmd/wcc/lex.c @@ -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; diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index 9ded6210..fc879044 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -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; diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index 2b298b9a..cd7f90b4 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -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); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 37259d3e..a017c1ad 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -4238,11 +4238,13 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == N_FNDECL) { if (d.exported == 0) { if (d.module.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; - m.mname = d.str; - m.module = d.module; - m.mnext = c.mods; - c.mods = m; + if (!streq(d.str, "main")) { + let m: *modent = amalloc(c.a, 48u64): *modent; + m.mname = d.str; + m.module = d.module; + m.mnext = c.mods; + c.mods = m; + }; }; }; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index e3afb70f..6dc39b07 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -598,11 +598,13 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == N_FNDECL) { if (d.exported == 0) { if (d.module.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; - m.mname = d.str; - m.module = d.module; - m.mnext = c.mods; - c.mods = m; + if (!streq(d.str, "main")) { + let m: *modent = amalloc(c.a, 48u64): *modent; + m.mname = d.str; + m.module = d.module; + m.mnext = c.mods; + c.mods = m; + }; }; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e2e8452b..789f56cf 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -4238,11 +4238,13 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == N_FNDECL) { if (d.exported == 0) { if (d.module.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; - m.mname = d.str; - m.module = d.module; - m.mnext = c.mods; - c.mods = m; + if (!streq(d.str, "main")) { + let m: *modent = amalloc(c.a, 48u64): *modent; + m.mname = d.str; + m.module = d.module; + m.mnext = c.mods; + c.mods = m; + }; }; }; };