diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 75432e06..37259d3e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1727,10 +1727,11 @@ type node = struct { exported: i32, // bool — `export` keyword present type_: *void, // filled in by checker; type.ww treats it as *tinfo tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...) + module: str, // originating module from `// MODULE: foo`; "" if none }; export fn newnode(a: *arena, k: i32, file: str, line: i32, col: i32) *node = { - let n: *node = amalloc(a, 192u64): *node; // 192 ≥ struct size + let n: *node = amalloc(a, 208u64): *node; // ≥ struct size n.kind = k; n.file = file; n.line = line; @@ -2750,6 +2751,7 @@ fn parsedef(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `def` let n: *node = newnode(p.a, N_DEF, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2768,6 +2770,7 @@ fn parselet(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `let` let n: *node = newnode(p.a, N_LET, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2835,6 +2838,7 @@ fn parsefn(p: *parser, exported: i32, attrs: *node) *node = { let pc: i32 = p.cur_col; advance(p); // past `fn` let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2864,6 +2868,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `type` let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -3787,6 +3792,7 @@ type cgen = struct { fnrets: *fnret, aliases: *aliasent, structs: *structinfo, + mods: *modent, // non-exported decls → originating module fn_name: str, fn_ret: *node, // declared return type of current fn (or nil) loop_top: i32, @@ -4025,6 +4031,12 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; if (ok) { emitline("DATA "); + if (d.exported == 0) { + if (d.module.len > 0) { + os.write(1, d.module.ptr, d.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; let nm: str = d.str; os.write(1, nm.ptr, nm.len: u64); emitline("(SB),\""); @@ -4201,6 +4213,107 @@ fn deflookup(c: *cgen, name: str) bool = { return false; }; +// ---- module-private symbol map -------------------------------------- +// +// Non-exported top-level decls live in their originating module's +// namespace. cgen mangles those names to `.` at emission +// time, both at the def site (TEXT/DATA) and at every call/load site, +// so two modules can each privately define `cstrlen` without colliding +// at link time. Exported decls and FFI-bound decls keep their bare name. + +type modent = struct { + mname: str, // the bare ident as it appears in source + module: str, // the originating module (`// MODULE: foo`) + mnext: *modent, +}; + +fn collectmods(c: *cgen, file: *node) void = { + c.mods = nil; + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + // Mirror collectfnrets' shape exactly (plain prepend in one + // branch). Earlier nested-if/early-return variants tickled a + // wwstage cgen bug that dropped most prepends. + 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 (d.kind == N_DEF) { + 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 (d.kind == N_TYPEDECL) { + 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 (d.kind == N_LET) { + 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; + }; + }; + }; + d = d.next; + }; +}; + +fn modlookup(c: *cgen, name: str) str = { + let m: *modent = c.mods; + for (m != nil) { + if (streq(m.mname, name)) { return m.module; }; + m = m.mnext; + }; + let empty: str; + empty.ptr = nil; + empty.len = 0; + return empty; +}; + +// emitsymname — write the asm symbol name for `ident`. Honours, in +// order: FFI mapping (@symbol), module mangling (private decls), bare +// name. Use everywhere a top-level name is emitted before `(SB)` or in +// a `TEXT name,$N` header. +fn emitsymname(c: *cgen, ident: str) void = { + let resolved: str = ffiresolve(c, ident); + if (resolved.ptr != ident.ptr) { + // FFI hit — emit the mapped linker symbol verbatim. + os.write(1, resolved.ptr, resolved.len: u64); + return; + }; + let mod: str = modlookup(c, ident); + if (mod.len > 0) { + os.write(1, mod.ptr, mod.len: u64); + os.write(1, ".".ptr, 1u64); + }; + os.write(1, ident.ptr, ident.len: u64); +}; + // ---- FFI map --------------------------------------------------------- fn fficollect(c: *cgen, file: *node) void = { @@ -4895,20 +5008,19 @@ fn cgexpr(c: *cgen, n: *node) void = { // Top-level `def` constant — load from its DATA symbol. if (deflookup(c, nm)) { emitline("\tMOVQ\t"); - os.write(1, nm.ptr, nm.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX — - // resolved through ffiresolve so a body-less FFI binding - // emits the C symbol it was declared with via @symbol(), - // not the ww-side ident. + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. let rt: *node = fnretlookup(c, nm); if (rt != nil) { - let resolved: str = ffiresolve(c, nm); emitline("\tLEAQ\t"); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; @@ -5246,7 +5358,7 @@ fn cgexpr(c: *cgen, n: *node) void = { if (lhs != nil) { if (lhs.kind == N_IDENT) { emitline("\tMOVQ\t"); - emitline(fld); + emitsymname(c, fld); emitline("(SB), AX\n"); return; }; @@ -5498,12 +5610,10 @@ fn cgexpr(c: *cgen, n: *node) void = { if (callee != nil) { if (callee.kind == N_IDENT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); } else { if (callee.kind == N_DOT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); };}; }; emitline("(SB)\n"); @@ -6641,6 +6751,23 @@ fn cgfn(c: *cgen, fn_: *node) void = { c.fn_ret = fn_.lhs; emitline("TEXT "); + if (fn_.exported == 0) { + if (fn_.module.len > 0) { + let isffi: bool = false; + let a: *node = fn_.attr; + for (a != nil) { + if (a.kind == N_ATTR) { + let an: str = a.str; + if (streq(an, "symbol")) { isffi = true; }; + }; + a = a.next; + }; + if (!isffi) { + os.write(1, fn_.module.ptr, fn_.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; + }; let nm: str = fn_.str; os.write(1, nm.ptr, nm.len: u64); emitline(",$"); @@ -6703,6 +6830,7 @@ export fn cgfile(c: *cgen, file: *node) void = { collectdefs(c, file); collectfnrets(c, file); fficollect(c, file); + collectmods(c, file); let d: *node = file.list; for (d != nil) { if (d.kind == N_FNDECL) { diff --git a/selfhost/cmd/wcc/ast.ww b/selfhost/cmd/wcc/ast.ww index a2e84f4b..ef86a1e1 100644 --- a/selfhost/cmd/wcc/ast.ww +++ b/selfhost/cmd/wcc/ast.ww @@ -108,10 +108,11 @@ type node = struct { exported: i32, // bool — `export` keyword present type_: *void, // filled in by checker; type.ww treats it as *tinfo tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...) + module: str, // originating module from `// MODULE: foo`; "" if none }; export fn newnode(a: *arena, k: i32, file: str, line: i32, col: i32) *node = { - let n: *node = amalloc(a, 192u64): *node; // 192 ≥ struct size + let n: *node = amalloc(a, 208u64): *node; // ≥ struct size n.kind = k; n.file = file; n.line = line; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index cedf42b2..e3afb70f 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -152,6 +152,7 @@ type cgen = struct { fnrets: *fnret, aliases: *aliasent, structs: *structinfo, + mods: *modent, // non-exported decls → originating module fn_name: str, fn_ret: *node, // declared return type of current fn (or nil) loop_top: i32, @@ -390,6 +391,12 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; if (ok) { emitline("DATA "); + if (d.exported == 0) { + if (d.module.len > 0) { + os.write(1, d.module.ptr, d.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; let nm: str = d.str; os.write(1, nm.ptr, nm.len: u64); emitline("(SB),\""); @@ -566,6 +573,107 @@ fn deflookup(c: *cgen, name: str) bool = { return false; }; +// ---- module-private symbol map -------------------------------------- +// +// Non-exported top-level decls live in their originating module's +// namespace. cgen mangles those names to `.` at emission +// time, both at the def site (TEXT/DATA) and at every call/load site, +// so two modules can each privately define `cstrlen` without colliding +// at link time. Exported decls and FFI-bound decls keep their bare name. + +type modent = struct { + mname: str, // the bare ident as it appears in source + module: str, // the originating module (`// MODULE: foo`) + mnext: *modent, +}; + +fn collectmods(c: *cgen, file: *node) void = { + c.mods = nil; + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + // Mirror collectfnrets' shape exactly (plain prepend in one + // branch). Earlier nested-if/early-return variants tickled a + // wwstage cgen bug that dropped most prepends. + 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 (d.kind == N_DEF) { + 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 (d.kind == N_TYPEDECL) { + 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 (d.kind == N_LET) { + 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; + }; + }; + }; + d = d.next; + }; +}; + +fn modlookup(c: *cgen, name: str) str = { + let m: *modent = c.mods; + for (m != nil) { + if (streq(m.mname, name)) { return m.module; }; + m = m.mnext; + }; + let empty: str; + empty.ptr = nil; + empty.len = 0; + return empty; +}; + +// emitsymname — write the asm symbol name for `ident`. Honours, in +// order: FFI mapping (@symbol), module mangling (private decls), bare +// name. Use everywhere a top-level name is emitted before `(SB)` or in +// a `TEXT name,$N` header. +fn emitsymname(c: *cgen, ident: str) void = { + let resolved: str = ffiresolve(c, ident); + if (resolved.ptr != ident.ptr) { + // FFI hit — emit the mapped linker symbol verbatim. + os.write(1, resolved.ptr, resolved.len: u64); + return; + }; + let mod: str = modlookup(c, ident); + if (mod.len > 0) { + os.write(1, mod.ptr, mod.len: u64); + os.write(1, ".".ptr, 1u64); + }; + os.write(1, ident.ptr, ident.len: u64); +}; + // ---- FFI map --------------------------------------------------------- fn fficollect(c: *cgen, file: *node) void = { @@ -1260,20 +1368,19 @@ fn cgexpr(c: *cgen, n: *node) void = { // Top-level `def` constant — load from its DATA symbol. if (deflookup(c, nm)) { emitline("\tMOVQ\t"); - os.write(1, nm.ptr, nm.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX — - // resolved through ffiresolve so a body-less FFI binding - // emits the C symbol it was declared with via @symbol(), - // not the ww-side ident. + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. let rt: *node = fnretlookup(c, nm); if (rt != nil) { - let resolved: str = ffiresolve(c, nm); emitline("\tLEAQ\t"); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; @@ -1611,7 +1718,7 @@ fn cgexpr(c: *cgen, n: *node) void = { if (lhs != nil) { if (lhs.kind == N_IDENT) { emitline("\tMOVQ\t"); - emitline(fld); + emitsymname(c, fld); emitline("(SB), AX\n"); return; }; @@ -1863,12 +1970,10 @@ fn cgexpr(c: *cgen, n: *node) void = { if (callee != nil) { if (callee.kind == N_IDENT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); } else { if (callee.kind == N_DOT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); };}; }; emitline("(SB)\n"); @@ -3006,6 +3111,23 @@ fn cgfn(c: *cgen, fn_: *node) void = { c.fn_ret = fn_.lhs; emitline("TEXT "); + if (fn_.exported == 0) { + if (fn_.module.len > 0) { + let isffi: bool = false; + let a: *node = fn_.attr; + for (a != nil) { + if (a.kind == N_ATTR) { + let an: str = a.str; + if (streq(an, "symbol")) { isffi = true; }; + }; + a = a.next; + }; + if (!isffi) { + os.write(1, fn_.module.ptr, fn_.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; + }; let nm: str = fn_.str; os.write(1, nm.ptr, nm.len: u64); emitline(",$"); @@ -3068,6 +3190,7 @@ export fn cgfile(c: *cgen, file: *node) void = { collectdefs(c, file); collectfnrets(c, file); fficollect(c, file); + collectmods(c, file); let d: *node = file.list; for (d != nil) { if (d.kind == N_FNDECL) { diff --git a/selfhost/cmd/wcc/parse.ww b/selfhost/cmd/wcc/parse.ww index 4bb42a0e..6b961fca 100644 --- a/selfhost/cmd/wcc/parse.ww +++ b/selfhost/cmd/wcc/parse.ww @@ -794,6 +794,7 @@ fn parsedef(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `def` let n: *node = newnode(p.a, N_DEF, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -812,6 +813,7 @@ fn parselet(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `let` let n: *node = newnode(p.a, N_LET, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -879,6 +881,7 @@ fn parsefn(p: *parser, exported: i32, attrs: *node) *node = { let pc: i32 = p.cur_col; advance(p); // past `fn` let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -908,6 +911,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `type` let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 2394e8e6..e2e8452b 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1727,10 +1727,11 @@ type node = struct { exported: i32, // bool — `export` keyword present type_: *void, // filled in by checker; type.ww treats it as *tinfo tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...) + module: str, // originating module from `// MODULE: foo`; "" if none }; export fn newnode(a: *arena, k: i32, file: str, line: i32, col: i32) *node = { - let n: *node = amalloc(a, 192u64): *node; // 192 ≥ struct size + let n: *node = amalloc(a, 208u64): *node; // ≥ struct size n.kind = k; n.file = file; n.line = line; @@ -2750,6 +2751,7 @@ fn parsedef(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `def` let n: *node = newnode(p.a, N_DEF, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2768,6 +2770,7 @@ fn parselet(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `let` let n: *node = newnode(p.a, N_LET, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2835,6 +2838,7 @@ fn parsefn(p: *parser, exported: i32, attrs: *node) *node = { let pc: i32 = p.cur_col; advance(p); // past `fn` let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -2864,6 +2868,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = { let pc: i32 = p.cur_col; advance(p); // past `type` let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc); + n.module = p.l.module; let id: str; expectident(p, &id); n.str = id; @@ -3787,6 +3792,7 @@ type cgen = struct { fnrets: *fnret, aliases: *aliasent, structs: *structinfo, + mods: *modent, // non-exported decls → originating module fn_name: str, fn_ret: *node, // declared return type of current fn (or nil) loop_top: i32, @@ -4025,6 +4031,12 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; if (ok) { emitline("DATA "); + if (d.exported == 0) { + if (d.module.len > 0) { + os.write(1, d.module.ptr, d.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; let nm: str = d.str; os.write(1, nm.ptr, nm.len: u64); emitline("(SB),\""); @@ -4201,6 +4213,107 @@ fn deflookup(c: *cgen, name: str) bool = { return false; }; +// ---- module-private symbol map -------------------------------------- +// +// Non-exported top-level decls live in their originating module's +// namespace. cgen mangles those names to `.` at emission +// time, both at the def site (TEXT/DATA) and at every call/load site, +// so two modules can each privately define `cstrlen` without colliding +// at link time. Exported decls and FFI-bound decls keep their bare name. + +type modent = struct { + mname: str, // the bare ident as it appears in source + module: str, // the originating module (`// MODULE: foo`) + mnext: *modent, +}; + +fn collectmods(c: *cgen, file: *node) void = { + c.mods = nil; + if (file == nil) { return; }; + let d: *node = file.list; + for (d != nil) { + // Mirror collectfnrets' shape exactly (plain prepend in one + // branch). Earlier nested-if/early-return variants tickled a + // wwstage cgen bug that dropped most prepends. + 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 (d.kind == N_DEF) { + 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 (d.kind == N_TYPEDECL) { + 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 (d.kind == N_LET) { + 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; + }; + }; + }; + d = d.next; + }; +}; + +fn modlookup(c: *cgen, name: str) str = { + let m: *modent = c.mods; + for (m != nil) { + if (streq(m.mname, name)) { return m.module; }; + m = m.mnext; + }; + let empty: str; + empty.ptr = nil; + empty.len = 0; + return empty; +}; + +// emitsymname — write the asm symbol name for `ident`. Honours, in +// order: FFI mapping (@symbol), module mangling (private decls), bare +// name. Use everywhere a top-level name is emitted before `(SB)` or in +// a `TEXT name,$N` header. +fn emitsymname(c: *cgen, ident: str) void = { + let resolved: str = ffiresolve(c, ident); + if (resolved.ptr != ident.ptr) { + // FFI hit — emit the mapped linker symbol verbatim. + os.write(1, resolved.ptr, resolved.len: u64); + return; + }; + let mod: str = modlookup(c, ident); + if (mod.len > 0) { + os.write(1, mod.ptr, mod.len: u64); + os.write(1, ".".ptr, 1u64); + }; + os.write(1, ident.ptr, ident.len: u64); +}; + // ---- FFI map --------------------------------------------------------- fn fficollect(c: *cgen, file: *node) void = { @@ -4895,20 +5008,19 @@ fn cgexpr(c: *cgen, n: *node) void = { // Top-level `def` constant — load from its DATA symbol. if (deflookup(c, nm)) { emitline("\tMOVQ\t"); - os.write(1, nm.ptr, nm.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX — - // resolved through ffiresolve so a body-less FFI binding - // emits the C symbol it was declared with via @symbol(), - // not the ww-side ident. + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. let rt: *node = fnretlookup(c, nm); if (rt != nil) { - let resolved: str = ffiresolve(c, nm); emitline("\tLEAQ\t"); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, nm); emitline("(SB), AX\n"); return; }; @@ -5246,7 +5358,7 @@ fn cgexpr(c: *cgen, n: *node) void = { if (lhs != nil) { if (lhs.kind == N_IDENT) { emitline("\tMOVQ\t"); - emitline(fld); + emitsymname(c, fld); emitline("(SB), AX\n"); return; }; @@ -5498,12 +5610,10 @@ fn cgexpr(c: *cgen, n: *node) void = { if (callee != nil) { if (callee.kind == N_IDENT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); } else { if (callee.kind == N_DOT) { calleename = callee.str; - let resolved: str = ffiresolve(c, calleename); - os.write(1, resolved.ptr, resolved.len: u64); + emitsymname(c, calleename); };}; }; emitline("(SB)\n"); @@ -6641,6 +6751,23 @@ fn cgfn(c: *cgen, fn_: *node) void = { c.fn_ret = fn_.lhs; emitline("TEXT "); + if (fn_.exported == 0) { + if (fn_.module.len > 0) { + let isffi: bool = false; + let a: *node = fn_.attr; + for (a != nil) { + if (a.kind == N_ATTR) { + let an: str = a.str; + if (streq(an, "symbol")) { isffi = true; }; + }; + a = a.next; + }; + if (!isffi) { + os.write(1, fn_.module.ptr, fn_.module.len: u64); + os.write(1, ".".ptr, 1u64); + }; + }; + }; let nm: str = fn_.str; os.write(1, nm.ptr, nm.len: u64); emitline(",$"); @@ -6703,6 +6830,7 @@ export fn cgfile(c: *cgen, file: *node) void = { collectdefs(c, file); collectfnrets(c, file); fficollect(c, file); + collectmods(c, file); let d: *node = file.list; for (d != nil) { if (d.kind == N_FNDECL) {