feat(C2): unit-first JIT symbol resolution — program-owned dylibs beat process images

runJITFromObject now takes priority dylibs (the #import c unit's
linked objects first, then #library deps in declaration order) and
attaches a per-path search generator for each AHEAD of the
process-wide fallback, so a vendored symbol can never lose to a
same-named export of an image the host process happens to carry
(libz via LLVM, libsqlite3 via CoreServices). loadLibrary reports
the name dlopen succeeded on; the c-import handle records its dylib
path; temp link inputs are per-pid so concurrent runs can't clobber
each other. Flips the C0.3 shadowing pin to from_unit: true.
This commit is contained in:
agra
2026-06-12 16:56:35 +03:00
parent 2a2f43eada
commit 0bd8f3e5ce
5 changed files with 61 additions and 20 deletions

View File

@@ -198,7 +198,12 @@ pub const TargetConfig = struct {
/// Execute a precompiled object file in-process using LLVM's ORC JIT.
/// Takes ownership of obj_buf. Returns the exit code from main().
pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
/// `priority_dylibs` are consulted for symbols BEFORE the process-wide
/// search, in order: dylibs that belong to the program (the `#import c`
/// unit's linked objects, then `#library` deps in declaration order)
/// must win over a same-named export of an image the host process
/// happens to carry (libz via LLVM, libsqlite3 via CoreServices, ...).
pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef, priority_dylibs: []const [:0]const u8) !u8 {
// Create LLJIT with default builder (no custom TM needed — .o is precompiled)
var jit: c.LLVMOrcLLJITRef = null;
var err = c.LLVMOrcCreateLLJIT(&jit, null);
@@ -210,9 +215,25 @@ pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
}
defer _ = c.LLVMOrcDisposeLLJIT(jit);
// Add process symbols so JIT can find libc (printf, etc.)
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
// Program-owned dylibs first (generators run in attachment order).
// A failed generator is skipped: resolution then degrades to the
// process-wide search below, exactly the pre-priority behavior.
for (priority_dylibs) |path| {
var pgen: c.LLVMOrcDefinitionGeneratorRef = null;
err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForPath(&pgen, path.ptr, prefix, null, null);
if (err != null) {
const msg = c.LLVMGetErrorMessage(err);
defer c.LLVMDisposeErrorMessage(msg);
std.debug.print("warning: JIT could not search '{s}': {s}\n", .{ path, std.mem.span(msg) });
continue;
}
c.LLVMOrcJITDylibAddGenerator(jd, pgen);
}
// Process-wide fallback so the JIT finds libc (printf, etc.)
var gen: c.LLVMOrcDefinitionGeneratorRef = null;
err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null);
if (err != null) {