diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 1f8b7e8f33..2ca6fe9d0a 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -2085,11 +2085,13 @@ pub fn GenericCommandWithData(comptime Cmd: type) type { pub fn createLoadDylibCommand( allocator: Allocator, + cmd_id: LC, name: []const u8, timestamp: u32, current_version: u32, compatibility_version: u32, ) !GenericCommandWithData(dylib_command) { + assert(cmd_id == .LOAD_DYLIB or cmd_id == .LOAD_WEAK_DYLIB or cmd_id == .REEXPORT_DYLIB or cmd_id == .ID_DYLIB); const cmdsize = @intCast(u32, mem.alignForwardGeneric( u64, @sizeOf(dylib_command) + name.len + 1, // +1 for nul @@ -2097,7 +2099,7 @@ pub fn createLoadDylibCommand( )); var dylib_cmd = emptyGenericCommandWithData(dylib_command{ - .cmd = .LOAD_DYLIB, + .cmd = cmd_id, .cmdsize = cmdsize, .dylib = .{ .name = @sizeOf(dylib_command), diff --git a/src/link.zig b/src/link.zig index c3d1d216c0..da6e8c53ed 100644 --- a/src/link.zig +++ b/src/link.zig @@ -21,6 +21,7 @@ const TypedValue = @import("TypedValue.zig"); pub const SystemLib = struct { needed: bool = false, + weak: bool = false, }; pub const CacheMode = enum { incremental, whole }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d660d2ce7e..a406923329 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -52,6 +52,11 @@ pub const SearchStrategy = enum { dylibs_first, }; +const SystemLib = struct { + needed: bool = false, + weak: bool = false, +}; + base: File, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. @@ -768,7 +773,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } // Shared and static libraries passed via `-l` flag. - var candidate_libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena); + var candidate_libs = std.StringArrayHashMap(SystemLib).init(arena); const system_lib_names = self.base.options.system_libs.keys(); for (system_lib_names) |system_lib_name| { @@ -781,7 +786,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } const system_lib_info = self.base.options.system_libs.get(system_lib_name).?; - try candidate_libs.put(system_lib_name, system_lib_info); + try candidate_libs.put(system_lib_name, .{ + .needed = system_lib_info.needed, + .weak = system_lib_info.weak, + }); } var lib_dirs = std.ArrayList([]const u8).init(arena); @@ -793,7 +801,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } } - var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena); + var libs = std.StringArrayHashMap(SystemLib).init(arena); // Assume ld64 default -search_paths_first if no strategy specified. const search_strategy = self.base.options.search_strategy orelse .paths_first; @@ -890,7 +898,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No for (framework_dirs.items) |dir| { for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { if (try resolveFramework(arena, dir, f_name, ext)) |full_path| { - try libs.put(full_path, self.base.options.frameworks.get(f_name).?); + const info = self.base.options.frameworks.get(f_name).?; + try libs.put(full_path, .{ + .needed = info.needed, + .weak = info.weak, + }); continue :outer; } } @@ -1026,9 +1038,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try argv.append("-lc"); for (self.base.options.system_libs.keys()) |l_name| { - const needed = self.base.options.system_libs.get(l_name).?.needed; - const arg = if (needed) + const info = self.base.options.system_libs.get(l_name).?; + const arg = if (info.needed) try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name}) + else if (info.weak) + try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name}) else try std.fmt.allocPrint(arena, "-l{s}", .{l_name}); try argv.append(arg); @@ -1039,9 +1053,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } for (self.base.options.frameworks.keys()) |framework| { - const needed = self.base.options.frameworks.get(framework).?.needed; - const arg = if (needed) + const info = self.base.options.frameworks.get(framework).?; + const arg = if (info.needed) try std.fmt.allocPrint(arena, "-needed_framework {s}", .{framework}) + else if (info.weak) + try std.fmt.allocPrint(arena, "-weak_framework {s}", .{framework}) else try std.fmt.allocPrint(arena, "-framework {s}", .{framework}); try argv.append(arg); @@ -1063,7 +1079,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No Compilation.dump_argv(argv.items); } - var dependent_libs = std.fifo.LinearFifo(Dylib.Id, .Dynamic).init(self.base.allocator); + var dependent_libs = std.fifo.LinearFifo(struct { + id: Dylib.Id, + parent: u16, + }, .Dynamic).init(self.base.allocator); defer dependent_libs.deinit(); try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs); try self.parseAndForceLoadStaticArchives(must_link_archives.keys()); @@ -1389,13 +1408,18 @@ const ParseDylibError = error{ const DylibCreateOpts = struct { syslibroot: ?[]const u8, - dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic), id: ?Dylib.Id = null, - is_dependent: bool = false, - is_needed: bool = false, + dependent: bool = false, + needed: bool = false, + weak: bool = false, }; -pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool { +pub fn parseDylib( + self: *MachO, + path: []const u8, + dependent_libs: anytype, + opts: DylibCreateOpts, +) ParseDylibError!bool { const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => return false, else => |e| return e, @@ -1405,12 +1429,19 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy const name = try self.base.allocator.dupe(u8, path); errdefer self.base.allocator.free(name); + const dylib_id = @intCast(u16, self.dylibs.items.len); var dylib = Dylib{ .name = name, .file = file, + .weak = opts.weak, }; - dylib.parse(self.base.allocator, self.base.options.target, opts.dependent_libs) catch |err| switch (err) { + dylib.parse( + self.base.allocator, + self.base.options.target, + dylib_id, + dependent_libs, + ) catch |err| switch (err) { error.EndOfStream, error.NotDylib => { try file.seekTo(0); @@ -1420,7 +1451,13 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy }; defer lib_stub.deinit(); - try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub, opts.dependent_libs); + try dylib.parseFromStub( + self.base.allocator, + self.base.options.target, + lib_stub, + dylib_id, + dependent_libs, + ); }, else => |e| return e, }; @@ -1438,13 +1475,12 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy } } - const dylib_id = @intCast(u16, self.dylibs.items.len); try self.dylibs.append(self.base.allocator, dylib); try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id); const should_link_dylib_even_if_unreachable = blk: { - if (self.base.options.dead_strip_dylibs and !opts.is_needed) break :blk false; - break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id)); + if (self.base.options.dead_strip_dylibs and !opts.needed) break :blk false; + break :blk !(opts.dependent or self.referenced_dylibs.contains(dylib_id)); }; if (should_link_dylib_even_if_unreachable) { @@ -1467,9 +1503,8 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const if (try self.parseObject(full_path)) continue; if (try self.parseArchive(full_path, false)) continue; - if (try self.parseDylib(full_path, .{ + if (try self.parseDylib(full_path, dependent_libs, .{ .syslibroot = syslibroot, - .dependent_libs = dependent_libs, })) continue; log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); @@ -1494,17 +1529,17 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi fn parseLibs( self: *MachO, lib_names: []const []const u8, - lib_infos: []const Compilation.SystemLib, + lib_infos: []const SystemLib, syslibroot: ?[]const u8, dependent_libs: anytype, ) !void { for (lib_names) |lib, i| { const lib_info = lib_infos[i]; log.debug("parsing lib path '{s}'", .{lib}); - if (try self.parseDylib(lib, .{ + if (try self.parseDylib(lib, dependent_libs, .{ .syslibroot = syslibroot, - .dependent_libs = dependent_libs, - .is_needed = lib_info.needed, + .needed = lib_info.needed, + .weak = lib_info.weak, })) continue; if (try self.parseArchive(lib, false)) continue; @@ -1522,20 +1557,21 @@ fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: any const arena = arena_alloc.allocator(); defer arena_alloc.deinit(); - while (dependent_libs.readItem()) |*id| { - defer id.deinit(self.base.allocator); + while (dependent_libs.readItem()) |*dep_id| { + defer dep_id.id.deinit(self.base.allocator); - if (self.dylibs_map.contains(id.name)) continue; + if (self.dylibs_map.contains(dep_id.id.name)) continue; + const weak = self.dylibs.items[dep_id.parent].weak; const has_ext = blk: { - const basename = fs.path.basename(id.name); + const basename = fs.path.basename(dep_id.id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; }; - const extension = if (has_ext) fs.path.extension(id.name) else ""; + const extension = if (has_ext) fs.path.extension(dep_id.id.name) else ""; const without_ext = if (has_ext) blk: { - const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable; - break :blk id.name[0..index]; - } else id.name; + const index = mem.lastIndexOfScalar(u8, dep_id.id.name, '.') orelse unreachable; + break :blk dep_id.id.name[0..index]; + } else dep_id.id.name; for (&[_][]const u8{ extension, ".tbd" }) |ext| { const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext }); @@ -1543,15 +1579,15 @@ fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: any log.debug("trying dependency at fully resolved path {s}", .{full_path}); - const did_parse_successfully = try self.parseDylib(full_path, .{ - .id = id.*, + const did_parse_successfully = try self.parseDylib(full_path, dependent_libs, .{ + .id = dep_id.id, .syslibroot = syslibroot, - .is_dependent = true, - .dependent_libs = dependent_libs, + .dependent = true, + .weak = weak, }); if (did_parse_successfully) break; } else { - log.warn("unable to resolve dependency {s}", .{id.name}); + log.warn("unable to resolve dependency {s}", .{dep_id.id.name}); } } } @@ -3441,6 +3477,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void { const dylib_id = dylib.id orelse unreachable; var dylib_cmd = try macho.createLoadDylibCommand( self.base.allocator, + if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, dylib_id.name, dylib_id.timestamp, dylib_id.current_version, @@ -4885,13 +4922,13 @@ fn populateMissingMetadata(self: *MachO) !void { std.builtin.Version{ .major = 1, .minor = 0, .patch = 0 }; var dylib_cmd = try macho.createLoadDylibCommand( self.base.allocator, + .ID_DYLIB, install_name, 2, current_version.major << 16 | current_version.minor << 8 | current_version.patch, compat_version.major << 16 | compat_version.minor << 8 | compat_version.patch, ); errdefer dylib_cmd.deinit(self.base.allocator); - dylib_cmd.inner.cmd = .ID_DYLIB; try self.load_commands.append(self.base.allocator, .{ .dylib = dylib_cmd }); self.load_commands_dirty = true; } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 801f810f80..ba519aac0a 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -30,6 +30,7 @@ dysymtab_cmd_index: ?u16 = null, id_cmd_index: ?u16 = null, id: ?Id = null, +weak: bool = false, /// Parsed symbol table represented as hash map of symbols' /// names. We can and should defer creating *Symbols until @@ -141,7 +142,13 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void { } } -pub fn parse(self: *Dylib, allocator: Allocator, target: std.Target, dependent_libs: anytype) !void { +pub fn parse( + self: *Dylib, + allocator: Allocator, + target: std.Target, + dylib_id: u16, + dependent_libs: anytype, +) !void { log.debug("parsing shared library '{s}'", .{self.name}); self.library_offset = try fat.getLibraryOffset(self.file.reader(), target); @@ -163,12 +170,18 @@ pub fn parse(self: *Dylib, allocator: Allocator, target: std.Target, dependent_l return error.MismatchedCpuArchitecture; } - try self.readLoadCommands(allocator, reader, dependent_libs); + try self.readLoadCommands(allocator, reader, dylib_id, dependent_libs); try self.parseId(allocator); try self.parseSymbols(allocator); } -fn readLoadCommands(self: *Dylib, allocator: Allocator, reader: anytype, dependent_libs: anytype) !void { +fn readLoadCommands( + self: *Dylib, + allocator: Allocator, + reader: anytype, + dylib_id: u16, + dependent_libs: anytype, +) !void { const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; try self.load_commands.ensureUnusedCapacity(allocator, self.header.?.ncmds); @@ -190,7 +203,7 @@ fn readLoadCommands(self: *Dylib, allocator: Allocator, reader: anytype, depende if (should_lookup_reexports) { // Parse install_name to dependent dylib. var id = try Id.fromLoadCommand(allocator, cmd.dylib); - try dependent_libs.writeItem(id); + try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id }); } }, else => { @@ -338,6 +351,7 @@ pub fn parseFromStub( allocator: Allocator, target: std.Target, lib_stub: LibStub, + dylib_id: u16, dependent_libs: anytype, ) !void { if (lib_stub.inner.len == 0) return error.EmptyStubFile; @@ -417,7 +431,7 @@ pub fn parseFromStub( log.debug(" (found re-export '{s}')", .{lib}); var dep_id = try Id.default(allocator, lib); - try dependent_libs.writeItem(dep_id); + try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); } } } @@ -522,7 +536,7 @@ pub fn parseFromStub( log.debug(" (found re-export '{s}')", .{lib}); var dep_id = try Id.default(allocator, lib); - try dependent_libs.writeItem(dep_id); + try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); } } } diff --git a/src/main.zig b/src/main.zig index 749534416f..ae1b95a4aa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -443,9 +443,12 @@ const usage_build_generic = \\ --subsystem [subsystem] (Windows) /SUBSYSTEM: to the linker \\ --stack [size] Override default stack size \\ --image-base [addr] Set base address for executable image + \\ -weak-l[lib] (Darwin) link against system library and mark it and all referenced symbols as weak + \\ -weak_library [lib] \\ -framework [name] (Darwin) link against framework \\ -needed_framework [name] (Darwin) link against framework (even if unused) \\ -needed_library [lib] (Darwin) link against system library (even if unused) + \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature @@ -916,7 +919,12 @@ fn buildOutputType( const path = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); }; - try frameworks.put(gpa, path, .{ .needed = false }); + try frameworks.put(gpa, path, .{}); + } else if (mem.eql(u8, arg, "-weak_framework")) { + const path = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; + try frameworks.put(gpa, path, .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { const path = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); @@ -962,7 +970,7 @@ fn buildOutputType( }; // We don't know whether this library is part of libc or libc++ until // we resolve the target, so we simply append to the list for now. - try system_libs.put(next_arg, .{ .needed = false }); + try system_libs.put(next_arg, .{}); } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l") or mem.eql(u8, arg, "-needed_library")) @@ -971,6 +979,11 @@ fn buildOutputType( fatal("expected parameter after {s}", .{arg}); }; try system_libs.put(next_arg, .{ .needed = true }); + } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { + const next_arg = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; + try system_libs.put(next_arg, .{ .weak = true }); } else if (mem.eql(u8, arg, "-D") or mem.eql(u8, arg, "-isystem") or mem.eql(u8, arg, "-I") or @@ -1300,9 +1313,11 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-l")) { // We don't know whether this library is part of libc or libc++ until // we resolve the target, so we simply append to the list for now. - try system_libs.put(arg["-l".len..], .{ .needed = false }); + try system_libs.put(arg["-l".len..], .{}); } else if (mem.startsWith(u8, arg, "-needed-l")) { try system_libs.put(arg["-needed-l".len..], .{ .needed = true }); + } else if (mem.startsWith(u8, arg, "-weak-l")) { + try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); } else if (mem.startsWith(u8, arg, "-D") or mem.startsWith(u8, arg, "-I")) { @@ -1596,7 +1611,7 @@ fn buildOutputType( try clang_argv.appendSlice(it.other_args); }, .framework_dir => try framework_dirs.append(it.only_arg), - .framework => try frameworks.put(gpa, it.only_arg, .{ .needed = false }), + .framework => try frameworks.put(gpa, it.only_arg, .{}), .nostdlibinc => want_native_include_dirs = false, .strip => strip = true, .exec_model => { @@ -1879,12 +1894,18 @@ fn buildOutputType( ) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; - } else if (mem.eql(u8, arg, "-framework") or mem.eql(u8, arg, "-weak_framework")) { + } else if (mem.eql(u8, arg, "-framework")) { i += 1; if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - try frameworks.put(gpa, linker_args.items[i], .{ .needed = false }); + try frameworks.put(gpa, linker_args.items[i], .{}); + } else if (mem.eql(u8, arg, "-weak_framework")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + try frameworks.put(gpa, linker_args.items[i], .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { i += 1; if (i >= linker_args.items.len) { @@ -1897,6 +1918,14 @@ fn buildOutputType( fatal("expected linker arg after '{s}'", .{arg}); } try system_libs.put(linker_args.items[i], .{ .needed = true }); + } else if (mem.startsWith(u8, arg, "-weak-l")) { + try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); + } else if (mem.eql(u8, arg, "-weak_library")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + try system_libs.put(linker_args.items[i], .{ .weak = true }); } else if (mem.eql(u8, arg, "-compatibility_version")) { i += 1; if (i >= linker_args.items.len) {