feat: duplicate C exports across #import c units are a compile diagnostic
All units share one link namespace (per-unit isolation is PLAN-C C3.2, deferred), so a symbol defined by two units previously died inside the JIT dylib link or the AOT link with raw linker spew. The clang shim gains sx_clang_object_exported_symbols (llvm::object scan: defined + global, format-specific excluded) and compileCToObjects cross-checks every unit object — collisions name both source files. Scan failures are non-fatal; the linker remains the backstop. Covers JIT and native AOT; the emcc path still relies on wasm-ld's own error.
This commit is contained in:
@@ -319,3 +319,54 @@ extern "C" LLVMMemoryBufferRef sx_clang_compile_to_object(
|
|||||||
llvm::StringRef(obj_buf.data(), obj_buf.size()), "c_import.o");
|
llvm::StringRef(obj_buf.data(), obj_buf.size()), "c_import.o");
|
||||||
return llvm::wrap(buf.release());
|
return llvm::wrap(buf.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Exported-symbol scan over a compiled object buffer */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
#include <llvm/Object/ObjectFile.h>
|
||||||
|
|
||||||
|
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<std::string> 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);
|
||||||
|
}
|
||||||
|
|||||||
13
clang_shim.h
13
clang_shim.h
@@ -50,6 +50,19 @@ LLVMMemoryBufferRef sx_clang_compile_to_object(
|
|||||||
const char **args, int num_args,
|
const char **args, int num_args,
|
||||||
char **out_error);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
20
examples/1628-cimport-duplicate-export.sx
Normal file
20
examples/1628-cimport-duplicate-export.sx
Normal file
@@ -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
|
||||||
|
}
|
||||||
1
examples/1628-cimport-duplicate-export/a.c
Normal file
1
examples/1628-cimport-duplicate-export/a.c
Normal file
@@ -0,0 +1 @@
|
|||||||
|
int clash(void) { return 1; }
|
||||||
1
examples/1628-cimport-duplicate-export/b.c
Normal file
1
examples/1628-cimport-duplicate-export/b.c
Normal file
@@ -0,0 +1 @@
|
|||||||
|
int clash(void) { return 2; }
|
||||||
1
examples/expected/1628-cimport-duplicate-export.exit
Normal file
1
examples/expected/1628-cimport-duplicate-export.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
1
examples/expected/1628-cimport-duplicate-export.stderr
Normal file
1
examples/expected/1628-cimport-duplicate-export.stderr
Normal file
@@ -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
|
||||||
@@ -419,6 +419,7 @@ pub fn compileCToObjects(
|
|||||||
target_config: @import("target.zig").TargetConfig,
|
target_config: @import("target.zig").TargetConfig,
|
||||||
) ![]c.LLVMMemoryBufferRef {
|
) ![]c.LLVMMemoryBufferRef {
|
||||||
var obj_bufs = std.ArrayList(c.LLVMMemoryBufferRef).empty;
|
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_maj: c_uint = 0;
|
||||||
var ver_min: 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);
|
cache_path = try std.fmt.allocPrintSentinel(allocator, ".sx-cache/c-{x:0>16}.o", .{key}, 0);
|
||||||
if (loadCachedObject(cache_path.?)) |cached| {
|
if (loadCachedObject(cache_path.?)) |cached| {
|
||||||
try obj_bufs.append(allocator, cached);
|
try obj_bufs.append(allocator, cached);
|
||||||
|
try labels.append(allocator, src);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
@@ -527,6 +529,38 @@ pub fn compileCToObjects(
|
|||||||
|
|
||||||
if (cache_path) |cp| saveCachedObject(allocator, obj_buf, io, cp);
|
if (cache_path) |cp| saveCachedObject(allocator, obj_buf, io, cp);
|
||||||
try obj_bufs.append(allocator, obj_buf);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user