diff --git a/clang_shim.cpp b/clang_shim.cpp index 947da85..4a7d8a5 100644 --- a/clang_shim.cpp +++ b/clang_shim.cpp @@ -319,3 +319,54 @@ extern "C" LLVMMemoryBufferRef sx_clang_compile_to_object( llvm::StringRef(obj_buf.data(), obj_buf.size()), "c_import.o"); return llvm::wrap(buf.release()); } + +/* ------------------------------------------------------------------ */ +/* Exported-symbol scan over a compiled object buffer */ +/* ------------------------------------------------------------------ */ + +#include + +extern "C" SxCSymbolList *sx_clang_object_exported_symbols( + LLVMMemoryBufferRef obj, + char **out_error) +{ + using namespace llvm; + MemoryBufferRef ref( + StringRef(LLVMGetBufferStart(obj), LLVMGetBufferSize(obj)), + "sx-c-import-object"); + auto objOrErr = object::ObjectFile::createObjectFile(ref); + if (!objOrErr) { + if (out_error) + *out_error = strdup(toString(objOrErr.takeError()).c_str()); + return nullptr; + } + + std::vector names; + for (const object::SymbolRef &sym : (*objOrErr)->symbols()) { + auto flagsOrErr = sym.getFlags(); + if (!flagsOrErr) { consumeError(flagsOrErr.takeError()); continue; } + uint32_t flags = *flagsOrErr; + if (flags & object::SymbolRef::SF_Undefined) continue; + if (!(flags & object::SymbolRef::SF_Global)) continue; + if (flags & object::SymbolRef::SF_FormatSpecific) continue; + auto nameOrErr = sym.getName(); + if (!nameOrErr) { consumeError(nameOrErr.takeError()); continue; } + names.push_back(nameOrErr->str()); + } + + SxCSymbolList *list = (SxCSymbolList *)malloc(sizeof(SxCSymbolList)); + list->num_names = (int)names.size(); + list->names = (const char **)malloc(sizeof(char *) * (names.empty() ? 1 : names.size())); + for (size_t i = 0; i < names.size(); i++) + list->names[i] = strdup(names[i].c_str()); + return list; +} + +extern "C" void sx_clang_free_symbol_list(SxCSymbolList *list) +{ + if (!list) return; + for (int i = 0; i < list->num_names; i++) + free((void *)list->names[i]); + free(list->names); + free(list); +} diff --git a/clang_shim.h b/clang_shim.h index 020d9dd..6257cba 100644 --- a/clang_shim.h +++ b/clang_shim.h @@ -50,6 +50,19 @@ LLVMMemoryBufferRef sx_clang_compile_to_object( const char **args, int num_args, char **out_error); +/* --- Exported (defined, global) symbols of an object buffer --- */ + +typedef struct { + const char **names; + int num_names; +} SxCSymbolList; + +SxCSymbolList *sx_clang_object_exported_symbols( + LLVMMemoryBufferRef obj, + char **out_error); + +void sx_clang_free_symbol_list(SxCSymbolList *list); + #ifdef __cplusplus } #endif diff --git a/examples/1628-cimport-duplicate-export.sx b/examples/1628-cimport-duplicate-export.sx new file mode 100644 index 0000000..8e3919d --- /dev/null +++ b/examples/1628-cimport-duplicate-export.sx @@ -0,0 +1,20 @@ +// Two `#import c` units defining the same exported symbol is a +// compile-time diagnostic naming both sources — previously it died +// inside the JIT dylib link (or the AOT link) with raw linker spew. +// All units share one link namespace; per-unit symbol isolation is +// PLAN-C C3.2 (deferred). +#import "modules/std.sx"; + +ua :: #import c { + #source "1628-cimport-duplicate-export/a.c"; +}; +ub :: #import c { + #source "1628-cimport-duplicate-export/b.c"; +}; + +clash :: () -> i32 #foreign ua "clash"; + +main :: () -> i32 { + print("{}\n", clash()); + 0 +} diff --git a/examples/1628-cimport-duplicate-export/a.c b/examples/1628-cimport-duplicate-export/a.c new file mode 100644 index 0000000..a23599d --- /dev/null +++ b/examples/1628-cimport-duplicate-export/a.c @@ -0,0 +1 @@ +int clash(void) { return 1; } diff --git a/examples/1628-cimport-duplicate-export/b.c b/examples/1628-cimport-duplicate-export/b.c new file mode 100644 index 0000000..38a5515 --- /dev/null +++ b/examples/1628-cimport-duplicate-export/b.c @@ -0,0 +1 @@ +int clash(void) { return 2; } diff --git a/examples/expected/1628-cimport-duplicate-export.exit b/examples/expected/1628-cimport-duplicate-export.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1628-cimport-duplicate-export.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1628-cimport-duplicate-export.stderr b/examples/expected/1628-cimport-duplicate-export.stderr new file mode 100644 index 0000000..4c7f0cd --- /dev/null +++ b/examples/expected/1628-cimport-duplicate-export.stderr @@ -0,0 +1 @@ +error: C symbol 'clash' is defined by multiple '#import c' sources: 'examples/1628-cimport-duplicate-export/a.c' and 'examples/1628-cimport-duplicate-export/b.c' — all units share one link namespace diff --git a/examples/expected/1628-cimport-duplicate-export.stdout b/examples/expected/1628-cimport-duplicate-export.stdout new file mode 100644 index 0000000..e69de29 diff --git a/src/c_import.zig b/src/c_import.zig index 7e4d409..98248ae 100644 --- a/src/c_import.zig +++ b/src/c_import.zig @@ -419,6 +419,7 @@ pub fn compileCToObjects( target_config: @import("target.zig").TargetConfig, ) ![]c.LLVMMemoryBufferRef { var obj_bufs = std.ArrayList(c.LLVMMemoryBufferRef).empty; + var labels = std.ArrayList([]const u8).empty; // source path per buffer, for diagnostics var ver_maj: c_uint = 0; var ver_min: c_uint = 0; @@ -503,6 +504,7 @@ pub fn compileCToObjects( cache_path = try std.fmt.allocPrintSentinel(allocator, ".sx-cache/c-{x:0>16}.o", .{key}, 0); if (loadCachedObject(cache_path.?)) |cached| { try obj_bufs.append(allocator, cached); + try labels.append(allocator, src); continue; } } else |_| {} @@ -527,6 +529,38 @@ pub fn compileCToObjects( if (cache_path) |cp| saveCachedObject(allocator, obj_buf, io, cp); try obj_bufs.append(allocator, obj_buf); + try labels.append(allocator, src); + } + } + + // Cross-object duplicate exports are diagnosed HERE, before they + // surface as an opaque dylib/binary link failure: every `#import c` + // unit shares one link namespace (per-unit symbol isolation is + // PLAN-C C3.2, deferred). Scan failures are non-fatal — the linker + // remains the backstop. + var sym_owner = std.StringHashMap(usize).init(allocator); + defer sym_owner.deinit(); + for (obj_bufs.items, 0..) |buf, i| { + var err_msg: [*c]u8 = null; + const list = c.sx_clang_object_exported_symbols(buf, &err_msg); + if (list == null) { + if (err_msg != null) c.LLVMDisposeMessage(err_msg); + continue; + } + defer c.sx_clang_free_symbol_list(list); + const n: usize = @intCast(list.*.num_names); + for (0..n) |j| { + const nm = std.mem.span(list.*.names[j]); + const gop = try sym_owner.getOrPut(try allocator.dupe(u8, nm)); + if (gop.found_existing) { + if (gop.value_ptr.* != i) { + const disp = if (nm.len > 1 and nm[0] == '_') nm[1..] else nm; + std.debug.print("error: C symbol '{s}' is defined by multiple '#import c' sources: '{s}' and '{s}' — all units share one link namespace\n", .{ disp, labels.items[gop.value_ptr.*], labels.items[i] }); + return error.CompileError; + } + } else { + gop.value_ptr.* = i; + } } }