From c1ce1a2da70b1f3667df16212ace4c227aeaced1 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 24 Sep 2026 00:12:25 +0100 Subject: [PATCH] compiler: move darwin libSystem resolution to the frontend Native libSystem detection happens via `std.zig.LibCInstallation`. In that type, the `crt_dir` field is now mandatory on Darwin (bringing Darwin into alignment with other OSes): it holds the directory containing `libSystem.dylib`/`libSystem.tbd`. (This is also the directory containing `crt1.o`, not that we need that.) In the compiler, a new field is added to `link.Input` to represent a tbd file. These are essentially text-based representations of a dylib's ABI, so the `tbd` field contains very similar information to the `dso` field. The handling for non-native libSystem is trivial compared to most cross libs, because we don't actually need to build anything (the linker can directly consume the tbd file we ship!), so the logic in `Compilation` just directly queues a single `.load_tbd` prelink task. Frameworks are not yet integrated with the frontend link input logic. The legacy Mach-O linker is now at least vaguely integrated with prelink: instead of building its own list of link inputs, it implements `loadInput` by just adding the `link.Input` to an `ArrayList` which it consumes in `flush`. This just helped to avoid duplicating code with the frontend. --- lib/std/zig/LibCInstallation.zig | 20 ++-- src/Compilation.zig | 12 +- src/link.zig | 169 +++++++++++++++++++-------- src/link/Coff.zig | 1 + src/link/Elf.zig | 2 + src/link/Elf2.zig | 3 +- src/link/Lld.zig | 6 +- src/link/MachO.zig | 192 +++++-------------------------- src/link/MachO/relocatable.zig | 75 +++++------- src/link/Wasm.zig | 2 + src/target.zig | 2 +- 11 files changed, 211 insertions(+), 273 deletions(-) diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index fe44e3a307ccaa56671b2dbfe50b25afc4f4f935..a96bbaea26e8cdabad6aba221584916ee4c7d9cf 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -96,6 +96,10 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const log.err("sys_include_dir may not be empty", .{}); return error.ParseError; } + if (self.crt_dir == null) { + log.err("crt_dir may not be empty", .{}); + return error.ParseError; + } const os_tag = target.os.tag; @@ -103,12 +107,6 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const log.err("cc_dir may not be empty for {s}", .{@tagName(os_tag)}); return error.ParseError; } - - if (self.crt_dir == null and !target.os.tag.isDarwin()) { - log.err("crt_dir may not be empty for {s}", .{@tagName(os_tag)}); - return error.ParseError; - } - if (self.msvc_lib_dir == null and os_tag == .windows and (target.abi == .msvc or target.abi == .itanium)) { log.err("msvc_lib_dir may not be empty for {s}-{s}", .{ @tagName(os_tag), @@ -153,7 +151,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { \\ \\# The directory that contains `crt1.o` or `crt2.o`. \\# On POSIX, can be found with `cc -print-file-name=crt1.o`. - \\# Not needed when targeting macOS. \\crt_dir={s} \\ \\# The directory that contains `vcruntime.lib`. @@ -193,12 +190,9 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib return error.DarwinSdkNotFound; defer gpa.free(sdk); - self.include_dir = try fs.path.join(gpa, &.{ - sdk, "usr/include", - }); - self.sys_include_dir = try fs.path.join(gpa, &.{ - sdk, "usr/include", - }); + self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" }); + self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" }); + self.crt_dir = try fs.path.join(gpa, &.{ sdk, "usr/lib" }); return self; } else if (is_windows) { const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.environ_map) catch |err| switch (err) { diff --git a/src/Compilation.zig b/src/Compilation.zig index d73e142ffdad6067e611eb702072b4e77f6ef143..7bdc84b74d4fd56c2dc21be583691de40de3989f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2429,11 +2429,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // If linking against host libc installation, instead queue up jobs // for loading those files in the linker. if (comp.config.link_libc and is_exe_or_dyn_lib) { - // If the "is darwin" check is moved below the libc_installation check below, - // error.LibCInstallationMissingCrtDir is returned from lci.resolveCrtPaths(). - if (target.isDarwinLibC()) { - // TODO delete logic from MachO flush() and queue up tasks here instead. - } else if (comp.libc_installation) |lci| { + if (comp.libc_installation) |lci| { const basenames = LibCInstallation.CrtBasenames.get(.{ .target = target, .link_libc = comp.config.link_libc, @@ -2456,6 +2452,12 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`. comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc); + } else if (target.isDarwinLibC()) { + try comp.oneshot_prelink_tasks.append(gpa, .{ .load_tbd = .{ + .root_dir = comp.dirs.zig_lib, + .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd", + } }); + // TODO delete logic from MachO flush() and queue up tasks here instead. } else if (target.isMuslLibC()) { if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable); diff --git a/src/link.zig b/src/link.zig index c6d077c7772a51836a9d331f0120299745f8b542..247ca5d4b9250e2dd94408d34d2e12786483376a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1236,15 +1236,15 @@ pub const File = struct { } pub fn loadInput(base: *File, input: Input) anyerror!void { - if (base.tag == .lld) return; - assert(!base.post_prelink); - + if (base.tag != .lld) { + assert(!base.post_prelink); + } switch (base.tag) { - inline .coff, .elf, .elf2, .macho2, .wasm, .spirv => |tag| { + inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input); }, - else => {}, + .c, .spork8, .plan9, .lld => {}, } } @@ -1450,6 +1450,7 @@ pub const PrelinkTask = union(enum) { /// Tells the linker to load a shared library, possibly one that is a /// GNU ld script. load_dso: Path, + load_tbd: Path, }; pub const ZcuTask = union(enum) { /// Sent once per update, as the very first `ZcuTask` in the update. Indicates that all per-file @@ -1504,6 +1505,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { error.AlreadyReported => return, // error reported via diags else => |e| switch (input) { .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {t}", .{e}), + .tbd => |tbd| diags.addParseError(tbd.path, "failed to parse tbd: {t}", .{e}), .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}), .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}), .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}), @@ -1525,34 +1527,72 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { assert(mem.startsWith(u8, flag, "-l")); const lib_name = flag["-l".len..]; switch (comp.config.link_mode) { - .dynamic => { - const dso_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadDso(dso_path, .{ + .dynamic => loaded: { + if (target.os.tag.isDarwin()) { + // Prefer .tbd over .dylib. + const tbd_path: Path = .initCwd(std.fmt.allocPrint( + comp.arena, + "{s}" ++ sep ++ "{s}{s}.tbd", + .{ crt_dir, target.libPrefix(), lib_name }, + ) catch return diags.setAllocFailure()); + if (tbd_path.root_dir.handle.openFile(io, tbd_path.sub_path, .{})) |file| { + errdefer file.close(io); + base.loadInput(.{ .tbd = .{ + .path = tbd_path, + .file = file, + .needed = false, + .weak = false, + .reexport = false, + } }) catch |err| switch (err) { + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}), + }; + break :loaded; + } else |err| switch (err) { + error.FileNotFound => {}, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}), + } + } + + // Try dynamic library. + const dso_path: Path = .initCwd(std.fmt.allocPrint( + comp.arena, + "{s}" ++ sep ++ "{s}{s}{s}", + .{ crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix() }, + ) catch return diags.setAllocFailure()); + if (base.openLoadDso(dso_path, .{ .preferred_mode = .dynamic, .search_strategy = .paths_first, - }) catch |err| switch (err) { - error.FileNotFound => { - // Also try static. - const archive_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadArchiveQuery(archive_path, .{ - .preferred_mode = .dynamic, - .search_strategy = .paths_first, - }) catch |archive_err| switch (archive_err) { - error.AlreadyReported => return, // error reported via diags - else => |e| diags.addParseError(dso_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }), - }; - }, - error.AlreadyReported => return, // error reported via diags + })) { + break :loaded; + } else |err| switch (err) { + error.FileNotFound => {}, + error.AlreadyReported => return, + error.Canceled => io.recancel(), else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}), - }; + } + + // Also try static. + const archive_path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ + crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), + }) catch return diags.setAllocFailure(), + ); + if (base.openLoadArchiveQuery(archive_path, .{ + .preferred_mode = .dynamic, + .search_strategy = .paths_first, + })) { + break :loaded; + } else |archive_err| switch (archive_err) { + error.FileNotFound => {}, + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(archive_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }), + } + + diags.addError("failed to find library '{s}'", .{lib_name}); }, .static => { const path = Path.initCwd( @@ -1647,6 +1687,28 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}), }; }, + .load_tbd => |path| { + const prog_node = comp.link_prog_node.start("Parse Shared Library Stub", 0); + defer prog_node.end(); + if (path.root_dir.handle.openFile(io, path.sub_path, .{})) |file| { + errdefer file.close(io); + base.loadInput(.{ .tbd = .{ + .path = path, + .file = file, + .needed = false, + .weak = false, + .reexport = false, + } }) catch |err| switch (err) { + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}), + }; + } else |err| switch (err) { + error.FileNotFound => {}, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}), + } + }, } } pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void { @@ -1873,6 +1935,8 @@ pub const Input = union(enum) { /// May not be a GNU ld script. Those are resolved when converting from /// `UnresolvedInput` to `Input` values. dso: Dso, + /// Only possible when targeting Darwin. + tbd: Tbd, pub const Object = struct { path: Path, @@ -1897,24 +1961,32 @@ pub const Input = union(enum) { pub const FallbackSoname = enum { basename, full_path }; }; + pub const Tbd = struct { + path: Path, + file: Io.File, + needed: bool, + weak: bool, + reexport: bool, + }; + pub fn path(input: Input) Path { return switch (input) { .object, .archive => |obj| obj.path, - inline .res, .dso => |x| x.path, + inline .res, .dso, .tbd => |x| x.path, }; } pub fn pathAndFile(input: Input) struct { Path, Io.File } { return switch (input) { .object, .archive => |obj| .{ obj.path, obj.file }, - inline .res, .dso => |x| .{ x.path, x.file }, + inline .res, .dso, .tbd => |x| .{ x.path, x.file }, }; } pub fn taskName(input: Input) []const u8 { return switch (input) { .object, .archive => |obj| obj.path.basename(), - inline .res, .dso => |x| x.path.basename(), + inline .res, .dso, .tbd => |x| x.path.basename(), }; } }; @@ -1947,6 +2019,15 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { man.hash.add(dso.reexport); man.hash.add(dso.fallback_soname); }, + .tbd => |tbd| { + _ = try man.addInputPath(tbd.path, .{ + .handle = .{ .file = tbd.file }, + .request_handle = true, + }); + man.hash.add(tbd.needed); + man.hash.add(tbd.weak); + man.hash.add(tbd.reexport); + }, } } } @@ -2236,16 +2317,14 @@ fn resolveLibInput( else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = name_query.query.needed, + .weak = name_query.query.weak, + .reexport = name_query.query.reexport, + } }); + return .ok; } { @@ -2700,7 +2779,7 @@ pub fn anyObjectInputs(inputs: []const Input) bool { pub fn countObjectInputs(inputs: []const Input) usize { var count: usize = 0; for (inputs) |input| switch (input) { - .dso => continue, + .dso, .tbd => continue, .res, .object, .archive => count += 1, }; return count; @@ -2710,7 +2789,7 @@ pub fn countObjectInputs(inputs: []const Input) usize { pub fn firstObjectInput(inputs: []const Input) ?Input.Object { for (inputs) |input| switch (input) { .object, .archive => |obj| return obj, - .res, .dso => continue, + .res, .dso, .tbd => continue, }; return null; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index dbdea4966de0b5ba91b7e2b026a7b80c419d5854..754c3ad9a830078489427c9794455642527b7f20 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -3912,6 +3912,7 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { coff.loadDll(dso.path, &fr) catch |err| return coff.failLoadInput(err, &fr, dso.path); }, + .tbd => unreachable, } } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 3cbcc53361033b74e8ea1c69189f6909d3be7ad6..5a3aa2648a58c61e59be964f0058525efa650fd9 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -722,6 +722,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { const argv = &self.dump_argv_list; switch (input) { .res => unreachable, + .tbd => unreachable, .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)), .dso => |dso| try argv.append(gpa, try dso.path.toString(comp.arena)), } @@ -729,6 +730,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { switch (input) { .res => unreachable, + .tbd => unreachable, .object => |obj| try parseObject(self, obj), .archive => |obj| if (self.base.isStaticLib()) { // Ignore static library inputs when generating a static library. diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 5967cfe48e03369cd10f620035179e99b4f41904..26d3d346f62289221cf549306e04f98b74c0809e 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -7146,7 +7146,6 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, }; }, - .res => unreachable, .dso => |dso| { try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1); var fr = dso.file.reader(io, &buf); @@ -7163,6 +7162,8 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, }; }, + .res => unreachable, + .tbd => unreachable, } } fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { diff --git a/src/link/Lld.zig b/src/link/Lld.zig index c56557dfb02336a36ab88ec32170b705a64d5e53..64a9aeeebf2e94889cd258ba0edc877a4518c1f4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -309,7 +309,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void { try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len); for (comp.link_inputs) |input| switch (input) { - .dso, .archive => {}, // static archives should not contain shared libraries or other static archives + .dso, .tbd, .archive => {}, // static archives should not contain shared libraries or other static archives .res, .object => { const path = try input.path().toStringZ(arena); object_files.appendAssumeCapacity(path); @@ -561,6 +561,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { argv.appendAssumeCapacity(try obj.path.toString(arena)); } }, + .tbd => unreachable, }; for (comp.c_objects.items) |c_object| { @@ -1102,6 +1103,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only + .tbd => unreachable, // Darwin-only .dso => continue, .object, .archive => |obj| { if (obj.must_link and !whole_archive) { @@ -1163,6 +1165,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only + .tbd => unreachable, // Darwin-only .object, .archive => continue, .dso => |dso| { const lib_as_needed = !dso.needed; @@ -1596,6 +1599,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { .dso => |dso| { try argv.append(try dso.path.toString(arena)); }, + .tbd => unreachable, .res => unreachable, }; if (whole_archive) { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dbabc88a512ff37af5fd0caa9ece4562b8e06378..db7d1733859235990e5cc60fcd840b5fa8cd30dc 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -4,6 +4,8 @@ pub const Relocation = @import("MachO/Relocation.zig"); base: link.File, +all_inputs: std.ArrayList(link.Input), + rpath_list: []const []const u8, /// Debug symbols bundle (or dSym). @@ -190,6 +192,7 @@ pub fn createEmpty( .file = null, .build_id = options.build_id, }, + .all_inputs = .empty, .rpath_list = options.rpath_list, .pagezero_size = options.pagezero_size, .headerpad_size = options.headerpad_size, @@ -271,6 +274,8 @@ pub fn deinit(self: *MachO) void { const gpa = comp.gpa; const io = comp.io; + self.all_inputs.deinit(gpa); + if (self.d_sym) |*d_sym| { d_sym.deinit(); } @@ -334,6 +339,11 @@ pub fn deinit(self: *MachO) void { self.thunks.deinit(gpa); } +pub fn loadInput(self: *MachO, input: link.Input) link.Error!void { + const gpa = self.base.comp.gpa; + try self.all_inputs.append(gpa, input); +} + pub fn flush( self: *MachO, arena: Allocator, @@ -343,6 +353,8 @@ pub fn flush( const tracy = trace(@src()); defer tracy.end(); + _ = arena; + const comp = self.base.comp; const gpa = comp.gpa; const io = comp.io; @@ -351,148 +363,47 @@ pub fn flush( const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); - const zcu_obj_path: ?Path = p: { - const zcu = comp.zcu orelse break :p null; - const llvm_object = zcu.llvm_object orelse break :p null; - break :p try comp.resolveEmitPathFlush(arena, .temp, llvm_object.out_bin_basename); - }; - // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flush(self, tid); - if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path); - if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path); + if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp); + if (self.base.isObject()) return relocatable.flushObject(self, comp); - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - - for (comp.link_inputs) |link_input| switch (link_input) { - .dso => continue, // handled below - .object, .archive => positionals.appendAssumeCapacity(link_input), - .res => unreachable, - }; - - // This is a set of object files emitted by clang in a single `build-exe` invocation. - // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up - // in this set. - try positionals.ensureUnusedCapacity(comp.c_objects.items.len); - for (comp.c_objects.items) |c_object| { - positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, c_object.status.success.object_path)); + for (self.all_inputs.items) |input| { + self.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}); } - if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (comp.config.any_sanitize_thread) { - try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path)); - } - - if (comp.config.any_fuzz) { - try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false)); - } - - if (comp.ubsan_rt_lib) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } else if (comp.ubsan_rt_obj) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } - - for (positionals.items) |link_input| { - self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } - - var system_libs = std.array_list.Managed(SystemLib).init(gpa); - defer system_libs.deinit(); - // frameworks - try system_libs.ensureUnusedCapacity(self.frameworks.len); - for (self.frameworks) |info| { - system_libs.appendAssumeCapacity(.{ - .needed = info.needed, - .weak = info.weak, - .path = info.path, - }); - } - - // libc++ dep - if (comp.config.link_libcpp) { - try system_libs.ensureUnusedCapacity(2); - system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path }); - system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path }); - } - - const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or - (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); - - if (comp.config.link_libc and is_exe_or_dyn_lib) { - if (comp.zigc_static_lib) |zigc| { - const path = zigc.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } - } - - // libc/libSystem dep - self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) { - error.MissingLibSystem => {}, // already reported - else => |e| return diags.fail("failed to resolve libSystem: {s}", .{@errorName(e)}), - }; - - for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive => continue, - .res => unreachable, - .dso => { - self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to parse input file: {t}", .{err}); - }, - }; - - for (system_libs.items) |lib| { - switch (Compilation.classifyFileExt(lib.path.sub_path)) { + for (self.frameworks) |framework| { + switch (Compilation.classifyFileExt(framework.path.sub_path)) { .shared_library => { - const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport); + const dso_input = try link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false); self.classifyInputFile(dso_input) catch |err| - diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); + diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)}); }, .static_library => { - const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden); + const archive_input = try link.openArchiveInput(io, diags, framework.path, false, false); self.classifyInputFile(archive_input) catch |err| - diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); + diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)}); }, else => { dso: { - const dso_input = link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport) catch break :dso; + const dso_input = link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false) catch break :dso; self.classifyInputFile(dso_input) catch break :dso; continue; } ar: { - const archive_input = link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden) catch break :ar; + const archive_input = link.openArchiveInput(io, diags, framework.path, false, false) catch break :ar; self.classifyInputFile(archive_input) catch break :ar; continue; } - diags.addParseError(lib.path, "unknown file extension", .{}); + diags.addParseError(framework.path, "unknown file extension", .{}); }, } } - // Finally, link against compiler_rt. - if (comp.compiler_rt_lib) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } else if (comp.compiler_rt_obj) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } - try self.parseInputFiles(); self.parseDependentDylibs() catch |err| { switch (err) { @@ -666,9 +577,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), .res => |res| try argv.append(try res.path.toString(arena)), - .dso => |dso| { - try argv.append(try dso.path.toString(arena)); - }, + .dso => |dso| try argv.append(try dso.path.toString(arena)), + .tbd => |tbd| try argv.append(try tbd.path.toString(arena)), }; for (comp.c_objects.items) |c_object| { @@ -754,7 +664,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } for (comp.link_inputs) |link_input| switch (link_input) { - .dso => continue, // handled below + .dso, .tbd => continue, // handled below .res => unreachable, // windows only .object, .archive => |obj| { if (obj.must_link) try argv.append("-force_load"); // TODO: verify this @@ -788,7 +698,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => continue, // handled above .res => unreachable, // windows only - .dso => |dso| { + inline .dso, .tbd => |dso| { try argv.ensureUnusedCapacity(2); if (dso.needed) { argv.appendAssumeCapacity("-needed-l"); @@ -836,46 +746,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try Compilation.dumpArgv(io, argv.items); } -/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline -pub fn resolveLibSystem( - self: *MachO, - arena: Allocator, - comp: *Compilation, - out_libs: anytype, -) !void { - const io = comp.io; - const diags = &comp.link_diags; - - var test_path = std.array_list.Managed(u8).init(arena); - var checked_paths = std.array_list.Managed([]const u8).init(arena); - - success: { - if (self.sdk_layout) |sdk_layout| switch (sdk_layout) { - .sdk => { - const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" }); - if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; - }, - .vendored => { - const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" }); - if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; - }, - }; - - for (self.lib_directories) |directory| { - if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; - } - - diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); - return error.MissingLibSystem; - } - - const libsystem_path = Path.initCwd(try arena.dupe(u8, test_path.items)); - try out_libs.append(.{ - .needed = true, - .path = libsystem_path, - }); -} - pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const tracy = trace(@src()); defer tracy.end(); @@ -4497,7 +4367,7 @@ const SystemLib = struct { .must_link = obj.must_link, .hidden = obj.hidden, }, - .dso => |dso| .{ + inline .dso, .tbd => |dso| .{ .path = dso.path, .needed = dso.needed, .weak = dso.weak, diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 38420414382928237d93b16b5c8648e4c91fb6f5..1b679cc9058932f34ea8098032ef75af49295059 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -1,25 +1,25 @@ -pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { - const gpa = comp.gpa; +pub fn flushObject(macho_file: *MachO, comp: *Compilation) link.Error!void { const io = comp.io; const diags = &comp.link_diags; - // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - positionals.appendSliceAssumeCapacity(comp.link_inputs); - - for (comp.c_objects.items) |c_object| { - try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); - } - - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (macho_file.getZigObject() == null and positionals.items.len == 1) { + one_input: { + if (macho_file.getZigObject() != null) { + break :one_input; + } + var only_input: ?link.Input = null; + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => if (only_input == null) { + only_input = input; + } else { + break :one_input; + }, + }; // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all // debug info segments/sections (this is apparently by design by Apple), we copy // the *only* input file over. - const path = positionals.items[0].path(); + const path = (only_input orelse break :one_input).path(); const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); const stat = in_file.stat(io) catch |err| @@ -29,10 +29,12 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat return; } - for (positionals.items) |link_input| { - macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => macho_file.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}), + }; if (diags.hasErrors()) return error.AlreadyReported; @@ -75,35 +77,16 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat try writeHeader(macho_file, ncmds, sizeofcmds); } -pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { +pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void { const gpa = comp.gpa; - const io = comp.io; const diags = &macho_file.base.comp.link_diags; - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - positionals.appendSliceAssumeCapacity(comp.link_inputs); - - for (comp.c_objects.items) |c_object| { - try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); - } - - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (comp.compiler_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path)); - } - - if (comp.ubsan_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path)); - } - - for (positionals.items) |link_input| { - macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => macho_file.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}), + }; if (diags.hasErrors()) return error.AlreadyReported; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e83fa056802e10d9c961ebdd3f77ea9355de6739..ea00a2f31b74610c151499948658af9e85002903 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3783,6 +3783,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { switch (input) { .res => unreachable, .dso => unreachable, + .tbd => unreachable, .object, .archive => |obj| { try argv.append(gpa, try obj.path.toString(comp.arena)); }, @@ -3792,6 +3793,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { switch (input) { .res => unreachable, .dso => unreachable, + .tbd => unreachable, .object => |obj| try parseObject(wasm, obj), .archive => |obj| try parseArchive(wasm, obj), } diff --git a/src/target.zig b/src/target.zig index 5e36be5fd4f7c7ad799f619e775bcc611747003f..6234c11a146b18c45b6b36ecf12138edebb99a5c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -510,7 +510,7 @@ pub fn libcFullLinkFlags(target: *const std.Target) []const []const u8 { }, // On SerenityOS libc includes libm, libpthread, libdl, and libssp. .serenity => &.{"-lc"}, - else => &.{}, + else => if (target.os.tag.isDarwin()) &.{"-lSystem"} else &.{}, }; return result; } -- 2.54.0