diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 895ec1cc2e..1e41458c70 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1,5 +1,4 @@ base: File, -entry_name: ?[]const u8, /// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. llvm_object: ?*LlvmObject = null, @@ -47,9 +46,9 @@ atoms: std.ArrayListUnmanaged(Atom) = .{}, sdk_layout: ?SdkLayout, /// Size of the __PAGEZERO segment. -pagezero_vmsize: u64, +pagezero_vmsize: ?u64, /// Minimum space for future expansion of the load commands. -headerpad_size: u32, +headerpad_size: ?u32, /// Set enough space as if all paths were MATPATHLEN. headerpad_max_install_names: bool, /// Remove dylibs that are unreachable by the entry point or exported symbols. @@ -61,6 +60,8 @@ install_name: ?[]const u8, /// Path to entitlements file. entitlements: ?[]const u8, compatibility_version: ?std.SemanticVersion, +/// Entry name +entry_name: ?[]const u8, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, @@ -128,8 +129,8 @@ pub fn createEmpty( .build_id = options.build_id, .rpath_list = options.rpath_list, }, - .pagezero_vmsize = options.pagezero_size orelse default_pagezero_vmsize, - .headerpad_size = options.headerpad_size orelse default_headerpad_size, + .pagezero_vmsize = options.pagezero_size, + .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, .sdk_layout = options.darwin_sdk_layout, @@ -249,9 +250,173 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node /// --verbose-link output fn dumpArgv(self: *MachO, comp: *Compilation) !void { - _ = self; - _ = comp; - @panic("TODO dumpArgv"); + const gpa = self.base.comp.gpa; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const target = self.base.comp.root_mod.resolved_target.result; + const directory = self.base.emit.directory; + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); + const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, path }); + } else { + break :blk path; + } + } else null; + + var argv = std.ArrayList([]const u8).init(arena); + + try argv.append("zig"); + + if (self.base.isStaticLib()) { + try argv.append("ar"); + } else { + try argv.append("ld"); + } + + if (self.base.isObject()) { + try argv.append("-r"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + if (self.base.isRelocatable()) { + for (comp.objects) |obj| { + try argv.append(obj.path); + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + } else { + if (!self.base.isStatic()) { + try argv.append("-dynamic"); + } + + if (self.base.isDynLib()) { + try argv.append("-dylib"); + + if (self.install_name) |install_name| { + try argv.append("-install_name"); + try argv.append(install_name); + } + } + + { + const platform = Platform.fromTarget(target); + try argv.append("-platform_version"); + try argv.append(@tagName(platform.os_tag)); + try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version})); + + const sdk_version: ?std.SemanticVersion = self.inferSdkVersion(); + if (sdk_version) |ver| { + try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor })); + } else { + try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version})); + } + } + + if (comp.sysroot) |syslibroot| { + try argv.append("-syslibroot"); + try argv.append(syslibroot); + } + + for (self.base.rpath_list) |rpath| { + try argv.append("-rpath"); + try argv.append(rpath); + } + + if (self.pagezero_vmsize) |size| { + try argv.append("-pagezero_size"); + try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size})); + } + + if (self.headerpad_size) |size| { + try argv.append("-headerpad_size"); + try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size})); + } + + if (self.headerpad_max_install_names) { + try argv.append("-headerpad_max_install_names"); + } + + if (self.base.gc_sections) { + try argv.append("-dead_strip"); + } + + if (self.dead_strip_dylibs) { + try argv.append("-dead_strip_dylibs"); + } + + if (self.entry_name) |entry_name| { + try argv.appendSlice(&.{ "-e", entry_name }); + } + + for (comp.objects) |obj| { + // TODO: verify this + if (obj.must_link) { + try argv.append("-force_load"); + } + try argv.append(obj.path); + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path); + if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path); + + if (comp.config.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + try argv.append("-lSystem"); + + for (comp.system_libs.keys()) |l_name| { + const info = comp.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); + } + + for (self.frameworks) |framework| { + const name = std.fs.path.stem(framework.path); + const arg = if (framework.needed) + try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) + else if (framework.weak) + try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name}) + else + try std.fmt.allocPrint(arena, "-framework {s}", .{name}); + try argv.append(arg); + } + + if (self.base.isDynLib() and self.base.allow_shlib_undefined) { + try argv.append("-undefined"); + try argv.append("dynamic_lookup"); + } + } + + Compilation.dump_argv(argv.items); } /// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. @@ -1034,38 +1199,6 @@ pub const Section = struct { free_list: std.ArrayListUnmanaged(Atom.Index) = .{}, }; -const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata); - -const LazySymbolMetadata = struct { - const State = enum { unused, pending_flush, flushed }; - text_atom: Atom.Index = undefined, - data_const_atom: Atom.Index = undefined, - text_state: State = .unused, - data_const_state: State = .unused, -}; - -const DeclMetadata = struct { - atom: Atom.Index, - section: u8, - /// A list of all exports aliases of this Decl. - /// TODO do we actually need this at all? - exports: std.ArrayListUnmanaged(u32) = .{}, - - fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 { - for (m.exports.items) |exp| { - if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp; - } - return null; - } - - fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 { - for (m.exports.items) |*exp| { - if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp; - } - return null; - } -}; - const HotUpdateState = struct { mach_task: ?std.os.darwin.MachTask = null, }; @@ -1091,18 +1224,32 @@ pub const null_sym = macho.nlist_64{ }; pub const Platform = struct { - platform: macho.PLATFORM, - version: Version, + os_tag: std.Target.Os.Tag, + abi: std.Target.Abi, + version: std.SemanticVersion, /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to /// the extracted minimum platform version. pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform { switch (lc.cmd()) { .BUILD_VERSION => { - const lc_cmd = lc.cast(macho.build_version_command).?; + const cmd = lc.cast(macho.build_version_command).?; return .{ - .platform = lc_cmd.platform, - .version = .{ .value = lc_cmd.minos }, + .os_tag = switch (cmd.platform) { + .MACOS => .macos, + .IOS, .IOSSIMULATOR => .ios, + .TVOS, .TVOSSIMULATOR => .tvos, + .WATCHOS, .WATCHOSSIMULATOR => .watchos, + else => @panic("TODO"), + }, + .abi = switch (cmd.platform) { + .IOSSIMULATOR, + .TVOSSIMULATOR, + .WATCHOSSIMULATOR, + => .simulator, + else => .none, + }, + .version = appleVersionToSemanticVersion(cmd.minos), }; }, .VERSION_MIN_MACOSX, @@ -1110,22 +1257,45 @@ pub const Platform = struct { .VERSION_MIN_TVOS, .VERSION_MIN_WATCHOS, => { - const lc_cmd = lc.cast(macho.version_min_command).?; + const cmd = lc.cast(macho.version_min_command).?; return .{ - .platform = switch (lc.cmd()) { - .VERSION_MIN_MACOSX => .MACOS, - .VERSION_MIN_IPHONEOS => .IOS, - .VERSION_MIN_TVOS => .TVOS, - .VERSION_MIN_WATCHOS => .WATCHOS, + .os_tag = switch (lc.cmd()) { + .VERSION_MIN_MACOSX => .macos, + .VERSION_MIN_IPHONEOS => .ios, + .VERSION_MIN_TVOS => .tvos, + .VERSION_MIN_WATCHOS => .watchos, else => unreachable, }, - .version = .{ .value = lc_cmd.version }, + .abi = .none, + .version = appleVersionToSemanticVersion(cmd.version), }; }, else => unreachable, } } + pub fn fromTarget(target: std.Target) Platform { + return .{ + .os_tag = target.os.tag, + .abi = target.abi, + .version = target.os.version_range.semver.min, + }; + } + + pub fn toAppleVersion(plat: Platform) u32 { + return semanticVersionToAppleVersion(plat.version); + } + + pub fn toApplePlatform(plat: Platform) macho.PLATFORM { + return switch (plat.os_tag) { + .macos => .MACOS, + .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS, + .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS, + .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS, + else => unreachable, + }; + } + pub fn isBuildVersionCompatible(plat: Platform) bool { inline for (supported_platforms) |sup_plat| { if (sup_plat[0] == plat.platform) { @@ -1134,76 +1304,152 @@ pub const Platform = struct { } return false; } -}; -pub const Version = struct { - value: u32, - - pub fn major(v: Version) u16 { - return @as(u16, @truncate(v.value >> 16)); - } - - pub fn minor(v: Version) u8 { - return @as(u8, @truncate(v.value >> 8)); - } - - pub fn patch(v: Version) u8 { - return @as(u8, @truncate(v.value)); - } - - pub fn parse(raw: []const u8) ?Version { - var parsed: [3]u16 = [_]u16{0} ** 3; - var count: usize = 0; - var it = std.mem.splitAny(u8, raw, "."); - while (it.next()) |comp| { - if (count >= 3) return null; - parsed[count] = std.fmt.parseInt(u16, comp, 10) catch return null; - count += 1; + pub fn isVersionMinCompatible(plat: Platform) bool { + inline for (supported_platforms) |sup_plat| { + if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { + return sup_plat[3] <= plat.toAppleVersion(); + } } - if (count == 0) return null; - const maj = parsed[0]; - const min = std.math.cast(u8, parsed[1]) orelse return null; - const pat = std.math.cast(u8, parsed[2]) orelse return null; - return Version.new(maj, min, pat); + return false; } - pub fn new(maj: u16, min: u8, pat: u8) Version { - return .{ .value = (@as(u32, @intCast(maj)) << 16) | (@as(u32, @intCast(min)) << 8) | pat }; + pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) { + return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } }; } - pub fn format( - v: Version, + const FmtCtx = struct { + platform: Platform, + cpu_arch: std.Target.Cpu.Arch, + }; + + pub fn formatTarget( + ctx: FmtCtx, comptime unused_fmt_string: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = unused_fmt_string; _ = options; - try writer.print("{d}.{d}.{d}", .{ - v.major(), - v.minor(), - v.patch(), - }); + try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) }); + if (ctx.platform.abi != .none) { + try writer.print("-{s}", .{@tagName(ctx.platform.abi)}); + } + } + + /// Caller owns the memory. + pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 { + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)}); + return buffer.toOwnedSlice(); + } + + pub fn eqlTarget(plat: Platform, other: Platform) bool { + return plat.os_tag == other.os_tag and plat.abi == other.abi; } }; const SupportedPlatforms = struct { - macho.PLATFORM, // Platform identifier + std.Target.Os.Tag, + std.Target.Abi, u32, // Min platform version for which to emit LC_BUILD_VERSION u32, // Min supported platform version - ?[]const u8, // Env var to look for }; // Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52 +// zig fmt: off const supported_platforms = [_]SupportedPlatforms{ - .{ .MACOS, 0xA0E00, 0xA0800, "MACOSX_DEPLOYMENT_TARGET" }, - .{ .IOS, 0xC0000, 0x70000, "IPHONEOS_DEPLOYMENT_TARGET" }, - .{ .TVOS, 0xC0000, 0x70000, "TVOS_DEPLOYMENT_TARGET" }, - .{ .WATCHOS, 0x50000, 0x20000, "WATCHOS_DEPLOYMENT_TARGET" }, - .{ .IOSSIMULATOR, 0xD0000, 0x80000, null }, - .{ .TVOSSIMULATOR, 0xD0000, 0x80000, null }, - .{ .WATCHOSSIMULATOR, 0x60000, 0x20000, null }, + .{ .macos, .none, 0xA0E00, 0xA0800 }, + .{ .ios, .none, 0xC0000, 0x70000 }, + .{ .tvos, .none, 0xC0000, 0x70000 }, + .{ .watchos, .none, 0x50000, 0x20000 }, + .{ .ios, .simulator, 0xD0000, 0x80000 }, + .{ .tvos, .simulator, 0xD0000, 0x80000 }, + .{ .watchos, .simulator, 0x60000, 0x20000 }, }; +// zig fmt: on + +inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { + const major = version.major; + const minor = version.minor; + const patch = version.patch; + return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch)); +} + +pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion { + return .{ + .major = @as(u16, @truncate(version >> 16)), + .minor = @as(u8, @truncate(version >> 8)), + .patch = @as(u8, @truncate(version)), + }; +} + +fn inferSdkVersion(self: *MachO) ?std.SemanticVersion { + const comp = self.base.comp; + const gpa = comp.gpa; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const sdk_layout = self.sdk_layout orelse return null; + const sdk_dir = switch (sdk_layout) { + .sdk => comp.sysroot.?, + .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null, + }; + if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| { + return parseSdkVersion(ver); + } else |_| { + // Read from settings should always succeed when vendored. + if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version"); + } + + // infer from pathname + const stem = std.fs.path.stem(sdk_dir); + const start = for (stem, 0..) |c, i| { + if (std.ascii.isDigit(c)) break i; + } else stem.len; + const end = for (stem[start..], start..) |c, i| { + if (std.ascii.isDigit(c) or c == '.') continue; + break i; + } else stem.len; + return parseSdkVersion(stem[start..end]); +} + +// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. +// Use property `MinimalDisplayName` to determine version. +// The file/property is also available with vendored libc. +fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { + const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" }); + const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16)); + const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); + if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; + return error.SdkVersionFailure; +} + +// Versions reported by Apple aren't exactly semantically valid as they usually omit +// the patch component, so we parse SDK value by hand. +fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion { + var parsed: std.SemanticVersion = .{ + .major = 0, + .minor = 0, + .patch = 0, + }; + + const parseNext = struct { + fn parseNext(it: anytype) ?u16 { + const nn = it.next() orelse return null; + return std.fmt.parseInt(u16, nn, 10) catch null; + } + }.parseNext; + + var it = std.mem.splitAny(u8, raw, "."); + parsed.major = parseNext(&it) orelse return null; + parsed.minor = parseNext(&it) orelse return null; + parsed.patch = parseNext(&it) orelse 0; + return parsed; +} /// When allocating, the ideal_capacity is calculated by /// actual_capacity + (actual_capacity / ideal_factor)