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

@@ -270,15 +270,21 @@ pub fn main(init: std.process.Init) !void {
defer c_handle.unload(io);
timer.record("c-import");
// dlopen #library dependencies so JIT can resolve foreign symbols
// dlopen #library dependencies so JIT can resolve foreign symbols.
// Program-owned dylibs (the #import c unit first, then #library
// deps in declaration order) also become PRIORITY search targets
// for the JIT, consulted before the process-wide fallback.
const libs = extractLibraries(allocator, root) catch std.process.exit(1);
var lib_handles = std.ArrayList(*anyopaque).empty;
var priority_dylibs = std.ArrayList([:0]const u8).empty;
if (c_handle.dylib_path) |cp| priority_dylibs.append(allocator, cp) catch {};
defer {
for (lib_handles.items) |h| _ = std.c.dlclose(h);
}
for (libs) |lib_name| {
if (loadLibrary(allocator, lib_name, target_config.lib_paths)) |handle| {
lib_handles.append(allocator, handle) catch {};
if (loadLibrary(allocator, lib_name, target_config.lib_paths)) |loaded| {
lib_handles.append(allocator, loaded.handle) catch {};
priority_dylibs.append(allocator, loaded.path) catch {};
} else {
const e = std.c.dlerror();
if (e) |msg| std.debug.print("warning: could not load library '{s}': {s}\n", .{ lib_name, std.mem.span(msg) });
@@ -304,7 +310,7 @@ pub fn main(init: std.process.Init) !void {
const marker = "--- build done ---\n";
_ = std.c.write(1, marker.ptr, marker.len);
}
const exit_code = sx.target.runJITFromObject(obj_buf) catch {
const exit_code = sx.target.runJITFromObject(obj_buf, priority_dylibs.items) catch {
// JIT failed — fall back to AOT
timer.record("jit-fail");
runAOT(allocator, io, path, target_config, &timer, enable_cache, stdlib_paths) catch std.process.exit(1);
@@ -924,8 +930,15 @@ fn extractFrameworks(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]
return try fws.toOwnedSlice(allocator);
}
const LoadedLibrary = struct {
handle: *anyopaque,
/// The name dlopen succeeded on (full path or bare name) — reused
/// verbatim as the JIT's priority search target for this library.
path: [:0]const u8,
};
/// Try to dlopen a library by name, searching user paths, host paths, and common naming conventions.
fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_paths: []const []const u8) ?*anyopaque {
fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_paths: []const []const u8) ?LoadedLibrary {
const is_macos = comptime @import("builtin").os.tag == .macos;
const suffixes: []const []const u8 = if (is_macos) &.{ ".dylib", ".so" } else &.{ ".so", ".dylib" };
@@ -944,7 +957,7 @@ fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_path
for (paths) |dir| {
for (suffixes) |sfx| {
const full = std.fmt.allocPrintSentinel(allocator, "{s}/lib{s}{s}", .{ dir, lib_name, sfx }, 0) catch continue;
if (std.c.dlopen(full.ptr, .{ .NOW = true })) |h| return h;
if (std.c.dlopen(full.ptr, .{ .NOW = true })) |h| return .{ .handle = h, .path = full };
}
}
}
@@ -952,7 +965,7 @@ fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_path
// Fallback: bare name (let dlopen search its default paths)
for (suffixes) |sfx| {
const bare = std.fmt.allocPrintSentinel(allocator, "lib{s}{s}", .{ lib_name, sfx }, 0) catch continue;
if (std.c.dlopen(bare.ptr, .{ .NOW = true })) |h| return h;
if (std.c.dlopen(bare.ptr, .{ .NOW = true })) |h| return .{ .handle = h, .path = bare };
}
return null;