authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-24 00:12:25+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-27 10:19:55+01:00
logc1ce1a2da70b1f3667df16212ace4c227aeaced1
treefd60fdcaf2b3ccab262529165e940fb1b2a4df17
parentc1456afca0aaf0e9e2cdb29865766d8e017b7f06
signature Commit is signed but in an unrecognized format.

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.

11 files changed, 209 insertions(+), 271 deletions(-)

lib/std/zig/LibCInstallation.zig+7-13
......@@ -96,6 +96,10 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const
9696 log.err("sys_include_dir may not be empty", .{});
9797 return error.ParseError;
9898 }
99 if (self.crt_dir == null) {
100 log.err("crt_dir may not be empty", .{});
101 return error.ParseError;
102 }
99103
100104 const os_tag = target.os.tag;
101105
......@@ -103,12 +107,6 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const
103107 log.err("cc_dir may not be empty for {s}", .{@tagName(os_tag)});
104108 return error.ParseError;
105109 }
106
107 if (self.crt_dir == null and !target.os.tag.isDarwin()) {
108 log.err("crt_dir may not be empty for {s}", .{@tagName(os_tag)});
109 return error.ParseError;
110 }
111
112110 if (self.msvc_lib_dir == null and os_tag == .windows and (target.abi == .msvc or target.abi == .itanium)) {
113111 log.err("msvc_lib_dir may not be empty for {s}-{s}", .{
114112 @tagName(os_tag),
......@@ -153,7 +151,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
153151 \\
154152 \\# The directory that contains `crt1.o` or `crt2.o`.
155153 \\# On POSIX, can be found with `cc -print-file-name=crt1.o`.
156 \\# Not needed when targeting macOS.
157154 \\crt_dir={s}
158155 \\
159156 \\# The directory that contains `vcruntime.lib`.
......@@ -193,12 +190,9 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib
193190 return error.DarwinSdkNotFound;
194191 defer gpa.free(sdk);
195192
196 self.include_dir = try fs.path.join(gpa, &.{
197 sdk, "usr/include",
198 });
199 self.sys_include_dir = try fs.path.join(gpa, &.{
200 sdk, "usr/include",
201 });
193 self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
194 self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
195 self.crt_dir = try fs.path.join(gpa, &.{ sdk, "usr/lib" });
202196 return self;
203197 } else if (is_windows) {
204198 const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.environ_map) catch |err| switch (err) {
src/Compilation.zig+7-5
......@@ -2429,11 +2429,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
24292429 // If linking against host libc installation, instead queue up jobs
24302430 // for loading those files in the linker.
24312431 if (comp.config.link_libc and is_exe_or_dyn_lib) {
2432 // If the "is darwin" check is moved below the libc_installation check below,
2433 // error.LibCInstallationMissingCrtDir is returned from lci.resolveCrtPaths().
2434 if (target.isDarwinLibC()) {
2435 // TODO delete logic from MachO flush() and queue up tasks here instead.
2436 } else if (comp.libc_installation) |lci| {
2432 if (comp.libc_installation) |lci| {
24372433 const basenames = LibCInstallation.CrtBasenames.get(.{
24382434 .target = target,
24392435 .link_libc = comp.config.link_libc,
......@@ -2456,6 +2452,12 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
24562452 }
24572453 // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`.
24582454 comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc);
2455 } else if (target.isDarwinLibC()) {
2456 try comp.oneshot_prelink_tasks.append(gpa, .{ .load_tbd = .{
2457 .root_dir = comp.dirs.zig_lib,
2458 .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd",
2459 } });
2460 // TODO delete logic from MachO flush() and queue up tasks here instead.
24592461 } else if (target.isMuslLibC()) {
24602462 if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable);
24612463
src/link.zig+122-43
......@@ -1236,15 +1236,15 @@ pub const File = struct {
12361236 }
12371237
12381238 pub fn loadInput(base: *File, input: Input) anyerror!void {
1239 if (base.tag == .lld) return;
1240 assert(!base.post_prelink);
1241
1239 if (base.tag != .lld) {
1240 assert(!base.post_prelink);
1241 }
12421242 switch (base.tag) {
1243 inline .coff, .elf, .elf2, .macho2, .wasm, .spirv => |tag| {
1243 inline else => |tag| {
12441244 dev.check(tag.devFeature());
12451245 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
12461246 },
1247 else => {},
1247 .c, .spork8, .plan9, .lld => {},
12481248 }
12491249 }
12501250
......@@ -1450,6 +1450,7 @@ pub const PrelinkTask = union(enum) {
14501450 /// Tells the linker to load a shared library, possibly one that is a
14511451 /// GNU ld script.
14521452 load_dso: Path,
1453 load_tbd: Path,
14531454};
14541455pub const ZcuTask = union(enum) {
14551456 /// 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 {
15041505 error.AlreadyReported => return, // error reported via diags
15051506 else => |e| switch (input) {
15061507 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {t}", .{e}),
1508 .tbd => |tbd| diags.addParseError(tbd.path, "failed to parse tbd: {t}", .{e}),
15071509 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}),
15081510 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}),
15091511 .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 {
15251527 assert(mem.startsWith(u8, flag, "-l"));
15261528 const lib_name = flag["-l".len..];
15271529 switch (comp.config.link_mode) {
1528 .dynamic => {
1529 const dso_path = Path.initCwd(
1530 .dynamic => loaded: {
1531 if (target.os.tag.isDarwin()) {
1532 // Prefer .tbd over .dylib.
1533 const tbd_path: Path = .initCwd(std.fmt.allocPrint(
1534 comp.arena,
1535 "{s}" ++ sep ++ "{s}{s}.tbd",
1536 .{ crt_dir, target.libPrefix(), lib_name },
1537 ) catch return diags.setAllocFailure());
1538 if (tbd_path.root_dir.handle.openFile(io, tbd_path.sub_path, .{})) |file| {
1539 errdefer file.close(io);
1540 base.loadInput(.{ .tbd = .{
1541 .path = tbd_path,
1542 .file = file,
1543 .needed = false,
1544 .weak = false,
1545 .reexport = false,
1546 } }) catch |err| switch (err) {
1547 error.AlreadyReported => return,
1548 error.Canceled => io.recancel(),
1549 else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}),
1550 };
1551 break :loaded;
1552 } else |err| switch (err) {
1553 error.FileNotFound => {},
1554 error.Canceled => io.recancel(),
1555 else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}),
1556 }
1557 }
1558
1559 // Try dynamic library.
1560 const dso_path: Path = .initCwd(std.fmt.allocPrint(
1561 comp.arena,
1562 "{s}" ++ sep ++ "{s}{s}{s}",
1563 .{ crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix() },
1564 ) catch return diags.setAllocFailure());
1565 if (base.openLoadDso(dso_path, .{
1566 .preferred_mode = .dynamic,
1567 .search_strategy = .paths_first,
1568 })) {
1569 break :loaded;
1570 } else |err| switch (err) {
1571 error.FileNotFound => {},
1572 error.AlreadyReported => return,
1573 error.Canceled => io.recancel(),
1574 else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
1575 }
1576
1577 // Also try static.
1578 const archive_path = Path.initCwd(
15301579 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1531 crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(),
1580 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
15321581 }) catch return diags.setAllocFailure(),
15331582 );
1534 base.openLoadDso(dso_path, .{
1583 if (base.openLoadArchiveQuery(archive_path, .{
15351584 .preferred_mode = .dynamic,
15361585 .search_strategy = .paths_first,
1537 }) catch |err| switch (err) {
1538 error.FileNotFound => {
1539 // Also try static.
1540 const archive_path = Path.initCwd(
1541 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1542 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1543 }) catch return diags.setAllocFailure(),
1544 );
1545 base.openLoadArchiveQuery(archive_path, .{
1546 .preferred_mode = .dynamic,
1547 .search_strategy = .paths_first,
1548 }) catch |archive_err| switch (archive_err) {
1549 error.AlreadyReported => return, // error reported via diags
1550 else => |e| diags.addParseError(dso_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }),
1551 };
1552 },
1553 error.AlreadyReported => return, // error reported via diags
1554 else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
1555 };
1586 })) {
1587 break :loaded;
1588 } else |archive_err| switch (archive_err) {
1589 error.FileNotFound => {},
1590 error.AlreadyReported => return,
1591 error.Canceled => io.recancel(),
1592 else => |e| diags.addParseError(archive_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }),
1593 }
1594
1595 diags.addError("failed to find library '{s}'", .{lib_name});
15561596 },
15571597 .static => {
15581598 const path = Path.initCwd(
......@@ -1647,6 +1687,28 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
16471687 else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}),
16481688 };
16491689 },
1690 .load_tbd => |path| {
1691 const prog_node = comp.link_prog_node.start("Parse Shared Library Stub", 0);
1692 defer prog_node.end();
1693 if (path.root_dir.handle.openFile(io, path.sub_path, .{})) |file| {
1694 errdefer file.close(io);
1695 base.loadInput(.{ .tbd = .{
1696 .path = path,
1697 .file = file,
1698 .needed = false,
1699 .weak = false,
1700 .reexport = false,
1701 } }) catch |err| switch (err) {
1702 error.AlreadyReported => return,
1703 error.Canceled => io.recancel(),
1704 else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}),
1705 };
1706 } else |err| switch (err) {
1707 error.FileNotFound => {},
1708 error.Canceled => io.recancel(),
1709 else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}),
1710 }
1711 },
16501712 }
16511713}
16521714pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void {
......@@ -1873,6 +1935,8 @@ pub const Input = union(enum) {
18731935 /// May not be a GNU ld script. Those are resolved when converting from
18741936 /// `UnresolvedInput` to `Input` values.
18751937 dso: Dso,
1938 /// Only possible when targeting Darwin.
1939 tbd: Tbd,
18761940
18771941 pub const Object = struct {
18781942 path: Path,
......@@ -1897,24 +1961,32 @@ pub const Input = union(enum) {
18971961 pub const FallbackSoname = enum { basename, full_path };
18981962 };
18991963
1964 pub const Tbd = struct {
1965 path: Path,
1966 file: Io.File,
1967 needed: bool,
1968 weak: bool,
1969 reexport: bool,
1970 };
1971
19001972 pub fn path(input: Input) Path {
19011973 return switch (input) {
19021974 .object, .archive => |obj| obj.path,
1903 inline .res, .dso => |x| x.path,
1975 inline .res, .dso, .tbd => |x| x.path,
19041976 };
19051977 }
19061978
19071979 pub fn pathAndFile(input: Input) struct { Path, Io.File } {
19081980 return switch (input) {
19091981 .object, .archive => |obj| .{ obj.path, obj.file },
1910 inline .res, .dso => |x| .{ x.path, x.file },
1982 inline .res, .dso, .tbd => |x| .{ x.path, x.file },
19111983 };
19121984 }
19131985
19141986 pub fn taskName(input: Input) []const u8 {
19151987 return switch (input) {
19161988 .object, .archive => |obj| obj.path.basename(),
1917 inline .res, .dso => |x| x.path.basename(),
1989 inline .res, .dso, .tbd => |x| x.path.basename(),
19181990 };
19191991 }
19201992};
......@@ -1947,6 +2019,15 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
19472019 man.hash.add(dso.reexport);
19482020 man.hash.add(dso.fallback_soname);
19492021 },
2022 .tbd => |tbd| {
2023 _ = try man.addInputPath(tbd.path, .{
2024 .handle = .{ .file = tbd.file },
2025 .request_handle = true,
2026 });
2027 man.hash.add(tbd.needed);
2028 man.hash.add(tbd.weak);
2029 man.hash.add(tbd.reexport);
2030 },
19502031 }
19512032 }
19522033}
......@@ -2236,16 +2317,14 @@ fn resolveLibInput(
22362317 else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }),
22372318 };
22382319 errdefer file.close(io);
2239 return finishResolveLibInput(
2240 io,
2241 resolved_inputs,
2242 archive_dedup,
2243 test_path,
2244 file,
2245 link_mode,
2246 name_query.query,
2247 .basename,
2248 );
2320 resolved_inputs.appendAssumeCapacity(.{ .tbd = .{
2321 .path = test_path,
2322 .file = file,
2323 .needed = name_query.query.needed,
2324 .weak = name_query.query.weak,
2325 .reexport = name_query.query.reexport,
2326 } });
2327 return .ok;
22492328 }
22502329
22512330 {
......@@ -2700,7 +2779,7 @@ pub fn anyObjectInputs(inputs: []const Input) bool {
27002779pub fn countObjectInputs(inputs: []const Input) usize {
27012780 var count: usize = 0;
27022781 for (inputs) |input| switch (input) {
2703 .dso => continue,
2782 .dso, .tbd => continue,
27042783 .res, .object, .archive => count += 1,
27052784 };
27062785 return count;
......@@ -2710,7 +2789,7 @@ pub fn countObjectInputs(inputs: []const Input) usize {
27102789pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
27112790 for (inputs) |input| switch (input) {
27122791 .object, .archive => |obj| return obj,
2713 .res, .dso => continue,
2792 .res, .dso, .tbd => continue,
27142793 };
27152794 return null;
27162795}
src/link/Coff.zig+1
......@@ -3912,6 +3912,7 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
39123912 coff.loadDll(dso.path, &fr) catch |err|
39133913 return coff.failLoadInput(err, &fr, dso.path);
39143914 },
3915 .tbd => unreachable,
39153916 }
39163917}
39173918
src/link/Elf.zig+2
......@@ -722,6 +722,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
722722 const argv = &self.dump_argv_list;
723723 switch (input) {
724724 .res => unreachable,
725 .tbd => unreachable,
725726 .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)),
726727 .dso => |dso| try argv.append(gpa, try dso.path.toString(comp.arena)),
727728 }
......@@ -729,6 +730,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
729730
730731 switch (input) {
731732 .res => unreachable,
733 .tbd => unreachable,
732734 .object => |obj| try parseObject(self, obj),
733735 .archive => |obj| if (self.base.isStaticLib()) {
734736 // Ignore static library inputs when generating a static library.
src/link/Elf2.zig+2-1
......@@ -7146,7 +7146,6 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void
71467146 },
71477147 };
71487148 },
7149 .res => unreachable,
71507149 .dso => |dso| {
71517150 try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1);
71527151 var fr = dso.file.reader(io, &buf);
......@@ -7163,6 +7162,8 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void
71637162 },
71647163 };
71657164 },
7165 .res => unreachable,
7166 .tbd => unreachable,
71667167 }
71677168}
71687169fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {
src/link/Lld.zig+5-1
......@@ -309,7 +309,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void {
309309
310310 try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len);
311311 for (comp.link_inputs) |input| switch (input) {
312 .dso, .archive => {}, // static archives should not contain shared libraries or other static archives
312 .dso, .tbd, .archive => {}, // static archives should not contain shared libraries or other static archives
313313 .res, .object => {
314314 const path = try input.path().toStringZ(arena);
315315 object_files.appendAssumeCapacity(path);
......@@ -561,6 +561,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
561561 argv.appendAssumeCapacity(try obj.path.toString(arena));
562562 }
563563 },
564 .tbd => unreachable,
564565 };
565566
566567 for (comp.c_objects.items) |c_object| {
......@@ -1102,6 +1103,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11021103
11031104 for (base.comp.link_inputs) |link_input| switch (link_input) {
11041105 .res => unreachable, // Windows-only
1106 .tbd => unreachable, // Darwin-only
11051107 .dso => continue,
11061108 .object, .archive => |obj| {
11071109 if (obj.must_link and !whole_archive) {
......@@ -1163,6 +1165,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11631165
11641166 for (base.comp.link_inputs) |link_input| switch (link_input) {
11651167 .res => unreachable, // Windows-only
1168 .tbd => unreachable, // Darwin-only
11661169 .object, .archive => continue,
11671170 .dso => |dso| {
11681171 const lib_as_needed = !dso.needed;
......@@ -1596,6 +1599,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
15961599 .dso => |dso| {
15971600 try argv.append(try dso.path.toString(arena));
15981601 },
1602 .tbd => unreachable,
15991603 .res => unreachable,
16001604 };
16011605 if (whole_archive) {
src/link/MachO.zig+31-161
......@@ -4,6 +4,8 @@ pub const Relocation = @import("MachO/Relocation.zig");
44
55base: link.File,
66
7all_inputs: std.ArrayList(link.Input),
8
79rpath_list: []const []const u8,
810
911/// Debug symbols bundle (or dSym).
......@@ -190,6 +192,7 @@ pub fn createEmpty(
190192 .file = null,
191193 .build_id = options.build_id,
192194 },
195 .all_inputs = .empty,
193196 .rpath_list = options.rpath_list,
194197 .pagezero_size = options.pagezero_size,
195198 .headerpad_size = options.headerpad_size,
......@@ -271,6 +274,8 @@ pub fn deinit(self: *MachO) void {
271274 const gpa = comp.gpa;
272275 const io = comp.io;
273276
277 self.all_inputs.deinit(gpa);
278
274279 if (self.d_sym) |*d_sym| {
275280 d_sym.deinit();
276281 }
......@@ -334,6 +339,11 @@ pub fn deinit(self: *MachO) void {
334339 self.thunks.deinit(gpa);
335340}
336341
342pub fn loadInput(self: *MachO, input: link.Input) link.Error!void {
343 const gpa = self.base.comp.gpa;
344 try self.all_inputs.append(gpa, input);
345}
346
337347pub fn flush(
338348 self: *MachO,
339349 arena: Allocator,
......@@ -343,6 +353,8 @@ pub fn flush(
343353 const tracy = trace(@src());
344354 defer tracy.end();
345355
356 _ = arena;
357
346358 const comp = self.base.comp;
347359 const gpa = comp.gpa;
348360 const io = comp.io;
......@@ -351,148 +363,47 @@ pub fn flush(
351363 const sub_prog_node = prog_node.start("MachO Flush", 0);
352364 defer sub_prog_node.end();
353365
354 const zcu_obj_path: ?Path = p: {
355 const zcu = comp.zcu orelse break :p null;
356 const llvm_object = zcu.llvm_object orelse break :p null;
357 break :p try comp.resolveEmitPathFlush(arena, .temp, llvm_object.out_bin_basename);
358 };
359
360366 // --verbose-link
361367 if (comp.verbose_link) try self.dumpArgv(comp);
362368
363369 if (self.getZigObject()) |zo| try zo.flush(self, tid);
364 if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path);
365 if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path);
366
367 var positionals = std.array_list.Managed(link.Input).init(gpa);
368 defer positionals.deinit();
369
370 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
371
372 for (comp.link_inputs) |link_input| switch (link_input) {
373 .dso => continue, // handled below
374 .object, .archive => positionals.appendAssumeCapacity(link_input),
375 .res => unreachable,
376 };
377
378 // This is a set of object files emitted by clang in a single `build-exe` invocation.
379 // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
380 // in this set.
381 try positionals.ensureUnusedCapacity(comp.c_objects.items.len);
382 for (comp.c_objects.items) |c_object| {
383 positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, c_object.status.success.object_path));
384 }
385
386 if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
387
388 if (comp.config.any_sanitize_thread) {
389 try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path));
390 }
391
392 if (comp.config.any_fuzz) {
393 try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false));
394 }
370 if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp);
371 if (self.base.isObject()) return relocatable.flushObject(self, comp);
395372
396 if (comp.ubsan_rt_lib) |crt_file| {
397 const path = crt_file.full_object_path;
398 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
399 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
400 } else if (comp.ubsan_rt_obj) |crt_file| {
401 const path = crt_file.full_object_path;
402 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
403 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
373 for (self.all_inputs.items) |input| {
374 self.classifyInputFile(input) catch |err|
375 diags.addParseError(input.path(), "failed to read input file: {t}", .{err});
404376 }
405377
406 for (positionals.items) |link_input| {
407 self.classifyInputFile(link_input) catch |err|
408 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
409 }
410
411 var system_libs = std.array_list.Managed(SystemLib).init(gpa);
412 defer system_libs.deinit();
413
414378 // frameworks
415 try system_libs.ensureUnusedCapacity(self.frameworks.len);
416 for (self.frameworks) |info| {
417 system_libs.appendAssumeCapacity(.{
418 .needed = info.needed,
419 .weak = info.weak,
420 .path = info.path,
421 });
422 }
423
424 // libc++ dep
425 if (comp.config.link_libcpp) {
426 try system_libs.ensureUnusedCapacity(2);
427 system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
428 system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
429 }
430
431 const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
432 (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
433
434 if (comp.config.link_libc and is_exe_or_dyn_lib) {
435 if (comp.zigc_static_lib) |zigc| {
436 const path = zigc.full_object_path;
437 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
438 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
439 }
440 }
441
442 // libc/libSystem dep
443 self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) {
444 error.MissingLibSystem => {}, // already reported
445 else => |e| return diags.fail("failed to resolve libSystem: {s}", .{@errorName(e)}),
446 };
447
448 for (comp.link_inputs) |link_input| switch (link_input) {
449 .object, .archive => continue,
450 .res => unreachable,
451 .dso => {
452 self.classifyInputFile(link_input) catch |err|
453 diags.addParseError(link_input.path(), "failed to parse input file: {t}", .{err});
454 },
455 };
456
457 for (system_libs.items) |lib| {
458 switch (Compilation.classifyFileExt(lib.path.sub_path)) {
379 for (self.frameworks) |framework| {
380 switch (Compilation.classifyFileExt(framework.path.sub_path)) {
459381 .shared_library => {
460 const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport);
382 const dso_input = try link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false);
461383 self.classifyInputFile(dso_input) catch |err|
462 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
384 diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)});
463385 },
464386 .static_library => {
465 const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden);
387 const archive_input = try link.openArchiveInput(io, diags, framework.path, false, false);
466388 self.classifyInputFile(archive_input) catch |err|
467 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
389 diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)});
468390 },
469391 else => {
470392 dso: {
471 const dso_input = link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport) catch break :dso;
393 const dso_input = link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false) catch break :dso;
472394 self.classifyInputFile(dso_input) catch break :dso;
473395 continue;
474396 }
475397 ar: {
476 const archive_input = link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden) catch break :ar;
398 const archive_input = link.openArchiveInput(io, diags, framework.path, false, false) catch break :ar;
477399 self.classifyInputFile(archive_input) catch break :ar;
478400 continue;
479401 }
480 diags.addParseError(lib.path, "unknown file extension", .{});
402 diags.addParseError(framework.path, "unknown file extension", .{});
481403 },
482404 }
483405 }
484406
485 // Finally, link against compiler_rt.
486 if (comp.compiler_rt_lib) |crt_file| {
487 const path = crt_file.full_object_path;
488 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
489 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
490 } else if (comp.compiler_rt_obj) |crt_file| {
491 const path = crt_file.full_object_path;
492 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
493 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
494 }
495
496407 try self.parseInputFiles();
497408 self.parseDependentDylibs() catch |err| {
498409 switch (err) {
......@@ -666,9 +577,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
666577 for (comp.link_inputs) |link_input| switch (link_input) {
667578 .object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
668579 .res => |res| try argv.append(try res.path.toString(arena)),
669 .dso => |dso| {
670 try argv.append(try dso.path.toString(arena));
671 },
580 .dso => |dso| try argv.append(try dso.path.toString(arena)),
581 .tbd => |tbd| try argv.append(try tbd.path.toString(arena)),
672582 };
673583
674584 for (comp.c_objects.items) |c_object| {
......@@ -754,7 +664,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
754664 }
755665
756666 for (comp.link_inputs) |link_input| switch (link_input) {
757 .dso => continue, // handled below
667 .dso, .tbd => continue, // handled below
758668 .res => unreachable, // windows only
759669 .object, .archive => |obj| {
760670 if (obj.must_link) try argv.append("-force_load"); // TODO: verify this
......@@ -788,7 +698,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
788698 for (comp.link_inputs) |link_input| switch (link_input) {
789699 .object, .archive => continue, // handled above
790700 .res => unreachable, // windows only
791 .dso => |dso| {
701 inline .dso, .tbd => |dso| {
792702 try argv.ensureUnusedCapacity(2);
793703 if (dso.needed) {
794704 argv.appendAssumeCapacity("-needed-l");
......@@ -836,46 +746,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
836746 try Compilation.dumpArgv(io, argv.items);
837747}
838748
839/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline
840pub fn resolveLibSystem(
841 self: *MachO,
842 arena: Allocator,
843 comp: *Compilation,
844 out_libs: anytype,
845) !void {
846 const io = comp.io;
847 const diags = &comp.link_diags;
848
849 var test_path = std.array_list.Managed(u8).init(arena);
850 var checked_paths = std.array_list.Managed([]const u8).init(arena);
851
852 success: {
853 if (self.sdk_layout) |sdk_layout| switch (sdk_layout) {
854 .sdk => {
855 const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" });
856 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
857 },
858 .vendored => {
859 const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" });
860 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
861 },
862 };
863
864 for (self.lib_directories) |directory| {
865 if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success;
866 }
867
868 diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
869 return error.MissingLibSystem;
870 }
871
872 const libsystem_path = Path.initCwd(try arena.dupe(u8, test_path.items));
873 try out_libs.append(.{
874 .needed = true,
875 .path = libsystem_path,
876 });
877}
878
879749pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
880750 const tracy = trace(@src());
881751 defer tracy.end();
......@@ -4497,7 +4367,7 @@ const SystemLib = struct {
44974367 .must_link = obj.must_link,
44984368 .hidden = obj.hidden,
44994369 },
4500 .dso => |dso| .{
4370 inline .dso, .tbd => |dso| .{
45014371 .path = dso.path,
45024372 .needed = dso.needed,
45034373 .weak = dso.weak,
src/link/MachO/relocatable.zig+29-46
......@@ -1,25 +1,25 @@
1pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void {
2 const gpa = comp.gpa;
1pub fn flushObject(macho_file: *MachO, comp: *Compilation) link.Error!void {
32 const io = comp.io;
43 const diags = &comp.link_diags;
54
6 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
7 var positionals = std.array_list.Managed(link.Input).init(gpa);
8 defer positionals.deinit();
9 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
10 positionals.appendSliceAssumeCapacity(comp.link_inputs);
11
12 for (comp.c_objects.items) |c_object| {
13 try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path));
14 }
15
16 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
17
18 if (macho_file.getZigObject() == null and positionals.items.len == 1) {
5 one_input: {
6 if (macho_file.getZigObject() != null) {
7 break :one_input;
8 }
9 var only_input: ?link.Input = null;
10 for (macho_file.all_inputs.items) |input| switch (input) {
11 .res => unreachable,
12 .dso, .tbd => {},
13 .object, .archive => if (only_input == null) {
14 only_input = input;
15 } else {
16 break :one_input;
17 },
18 };
1919 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
2020 // debug info segments/sections (this is apparently by design by Apple), we copy
2121 // the *only* input file over.
22 const path = positionals.items[0].path();
22 const path = (only_input orelse break :one_input).path();
2323 const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err|
2424 return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) });
2525 const stat = in_file.stat(io) catch |err|
......@@ -29,10 +29,12 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
2929 return;
3030 }
3131
32 for (positionals.items) |link_input| {
33 macho_file.classifyInputFile(link_input) catch |err|
34 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
35 }
32 for (macho_file.all_inputs.items) |input| switch (input) {
33 .res => unreachable,
34 .dso, .tbd => {},
35 .object, .archive => macho_file.classifyInputFile(input) catch |err|
36 diags.addParseError(input.path(), "failed to read input file: {t}", .{err}),
37 };
3638
3739 if (diags.hasErrors()) return error.AlreadyReported;
3840
......@@ -75,35 +77,16 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
7577 try writeHeader(macho_file, ncmds, sizeofcmds);
7678}
7779
78pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void {
80pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void {
7981 const gpa = comp.gpa;
80 const io = comp.io;
8182 const diags = &macho_file.base.comp.link_diags;
8283
83 var positionals = std.array_list.Managed(link.Input).init(gpa);
84 defer positionals.deinit();
85
86 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
87 positionals.appendSliceAssumeCapacity(comp.link_inputs);
88
89 for (comp.c_objects.items) |c_object| {
90 try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path));
91 }
92
93 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
94
95 if (comp.compiler_rt_strat == .obj) {
96 try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path));
97 }
98
99 if (comp.ubsan_rt_strat == .obj) {
100 try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path));
101 }
102
103 for (positionals.items) |link_input| {
104 macho_file.classifyInputFile(link_input) catch |err|
105 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
106 }
84 for (macho_file.all_inputs.items) |input| switch (input) {
85 .res => unreachable,
86 .dso, .tbd => {},
87 .object, .archive => macho_file.classifyInputFile(input) catch |err|
88 diags.addParseError(input.path(), "failed to read input file: {t}", .{err}),
89 };
10790
10891 if (diags.hasErrors()) return error.AlreadyReported;
10992
src/link/Wasm.zig+2
......@@ -3783,6 +3783,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
37833783 switch (input) {
37843784 .res => unreachable,
37853785 .dso => unreachable,
3786 .tbd => unreachable,
37863787 .object, .archive => |obj| {
37873788 try argv.append(gpa, try obj.path.toString(comp.arena));
37883789 },
......@@ -3792,6 +3793,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
37923793 switch (input) {
37933794 .res => unreachable,
37943795 .dso => unreachable,
3796 .tbd => unreachable,
37953797 .object => |obj| try parseObject(wasm, obj),
37963798 .archive => |obj| try parseArchive(wasm, obj),
37973799 }
src/target.zig+1-1
......@@ -510,7 +510,7 @@ pub fn libcFullLinkFlags(target: *const std.Target) []const []const u8 {
510510 },
511511 // On SerenityOS libc includes libm, libpthread, libdl, and libssp.
512512 .serenity => &.{"-lc"},
513 else => &.{},
513 else => if (target.os.tag.isDarwin()) &.{"-lSystem"} else &.{},
514514 };
515515 return result;
516516}