Commit 2e28ab153c
Changed files (5)
src
src/link/MachO/Dylib.zig
@@ -217,7 +217,7 @@ const TargetMatcher = struct {
target: CrossTarget,
target_strings: std.ArrayListUnmanaged([]const u8) = .{},
- fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
+ pub fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
var self = TargetMatcher{
.allocator = allocator,
.target = target,
@@ -239,7 +239,7 @@ const TargetMatcher = struct {
return self;
}
- fn deinit(self: *TargetMatcher) void {
+ pub fn deinit(self: *TargetMatcher) void {
for (self.target_strings.items) |t| {
self.allocator.free(t);
}
@@ -263,7 +263,7 @@ const TargetMatcher = struct {
};
}
- fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
+ pub fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
const cpu_arch = cpuArchToAppleString(target.cpu_arch.?);
const os_tag = @tagName(target.os_tag.?);
const target_abi = abiToAppleString(target.abi orelse .none);
@@ -291,7 +291,7 @@ const TargetMatcher = struct {
return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?));
}
- fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
+ pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
src/link/MachO/load_commands.zig
@@ -76,8 +76,14 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
- // LC_BUILD_VERSION
- sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
+ // LC_BUILD_VERSION or LC_VERSION_MIN_
+ if (Platform.fromOptions(options).isBuildVersionCompatible()) {
+ // LC_BUILD_VERSION
+ sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
+ } else {
+ // LC_VERSION_MIN_
+ sizeofcmds += @sizeOf(macho.version_min_command);
+ }
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
@@ -252,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an
}
}
-pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void {
- const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
- const platform_version = blk: {
- const ver = options.target.os.version_range.semver.min;
- const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8));
- break :blk platform_version;
- };
- const sdk_version: ?std.SemanticVersion = options.darwin_sdk_version orelse blk: {
- if (options.sysroot) |path| break :blk inferSdkVersionFromSdkPath(path);
- break :blk null;
+pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
+ const cmd: macho.LC = switch (platform.os_tag) {
+ .macos => .VERSION_MIN_MACOSX,
+ .ios => .VERSION_MIN_IPHONEOS,
+ .tvos => .VERSION_MIN_TVOS,
+ .watchos => .VERSION_MIN_WATCHOS,
+ else => unreachable,
};
- const sdk_version_value: u32 = if (sdk_version) |ver|
- @intCast(ver.major << 16 | ver.minor << 8)
- else
- platform_version;
- const is_simulator_abi = options.target.abi == .simulator;
+ try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
+ .cmd = cmd,
+ .version = platform.toAppleVersion(),
+ .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
+ }));
+}
+
+pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
+ const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
try lc_writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize,
- .platform = switch (options.target.os.tag) {
- .macos => .MACOS,
- .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS,
- .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS,
- .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS,
- else => unreachable,
- },
- .minos = platform_version,
- .sdk = sdk_version_value,
+ .platform = platform.toApplePlatform(),
+ .minos = platform.toAppleVersion(),
+ .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
.ntools = 1,
});
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
@@ -301,7 +302,124 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: an
}
}
-fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
+pub const Platform = struct {
+ 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 cmd = lc.cast(macho.build_version_command).?;
+ return .{
+ .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,
+ .VERSION_MIN_IPHONEOS,
+ .VERSION_MIN_TVOS,
+ .VERSION_MIN_WATCHOS,
+ => {
+ const cmd = lc.cast(macho.version_min_command).?;
+ return .{
+ .os_tag = switch (lc.cmd()) {
+ .VERSION_MIN_MACOSX => .macos,
+ .VERSION_MIN_IPHONEOS => .ios,
+ .VERSION_MIN_TVOS => .tvos,
+ .VERSION_MIN_WATCHOS => .watchos,
+ else => unreachable,
+ },
+ .abi = .none,
+ .version = appleVersionToSemanticVersion(cmd.version),
+ };
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn fromOptions(options: *const link.Options) Platform {
+ return .{
+ .os_tag = options.target.os.tag,
+ .abi = options.target.abi,
+ .version = options.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.os_tag and sup_plat[1] == plat.abi) {
+ return sup_plat[2] <= plat.toAppleVersion();
+ }
+ }
+ return false;
+ }
+
+ pub 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));
+ }
+
+ 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)),
+ };
+ }
+};
+
+const SupportedPlatforms = struct {
+ 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
+const supported_platforms = [_]SupportedPlatforms{
+ .{ .macos, .none, 0xA0E00, 0xA0800, "MACOSX_DEPLOYMENT_TARGET" },
+ .{ .ios, .none, 0xC0000, 0x70000, "IPHONEOS_DEPLOYMENT_TARGET" },
+ .{ .tvos, .none, 0xC0000, 0x70000, "TVOS_DEPLOYMENT_TARGET" },
+ .{ .watchos, .none, 0x50000, 0x20000, "WATCHOS_DEPLOYMENT_TARGET" },
+ .{ .ios, .simulator, 0xD0000, 0x80000, null },
+ .{ .tvos, .simulator, 0xD0000, 0x80000, null },
+ .{ .watchos, .simulator, 0x60000, 0x20000, null },
+};
+
+pub fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
const stem = std.fs.path.stem(path);
const start = for (stem, 0..) |c, i| {
if (std.ascii.isDigit(c)) break i;
src/link/MachO/Object.zig
@@ -940,6 +940,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo {
return di;
}
+/// Returns Options.Platform composed from the first encountered build version type load command:
+/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
+pub fn getPlatform(self: Object) ?Platform {
+ var it = LoadCommandIterator{
+ .ncmds = self.header.ncmds,
+ .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
+ };
+ while (it.next()) |cmd| {
+ switch (cmd.cmd()) {
+ .BUILD_VERSION,
+ .VERSION_MIN_MACOSX,
+ .VERSION_MIN_IPHONEOS,
+ .VERSION_MIN_TVOS,
+ .VERSION_MIN_WATCHOS,
+ => return Platform.fromLoadCommand(cmd),
+ else => {},
+ }
+ } else return null;
+}
+
pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
const size = @as(usize, @intCast(sect.size));
return self.contents[sect.offset..][0..size];
@@ -1089,5 +1109,6 @@ const Atom = @import("Atom.zig");
const DwarfInfo = @import("DwarfInfo.zig");
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
+const Platform = @import("load_commands.zig").Platform;
const SymbolWithLoc = MachO.SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/zld.zig
@@ -345,10 +345,13 @@ pub fn linkWithZld(
parent: u16,
}, .Dynamic).init(arena);
- var parse_error_ctx: union {
- none: void,
+ var parse_error_ctx: struct {
detected_arch: std.Target.Cpu.Arch,
- } = .{ .none = {} };
+ detected_os: std.Target.Os.Tag,
+ } = .{
+ .detected_arch = undefined,
+ .detected_os = undefined,
+ };
for (positionals.items) |obj| {
const in_file = try std.fs.cwd().openFile(obj.path, .{});
@@ -586,7 +589,18 @@ pub fn linkWithZld(
try lc_writer.writeStruct(macho.source_version_command{
.version = 0,
});
- try load_commands.writeBuildVersionLC(&macho_file.base.options, lc_writer);
+ {
+ const platform = load_commands.Platform.fromOptions(&macho_file.base.options);
+ const sdk_version: ?std.SemanticVersion = macho_file.base.options.darwin_sdk_version orelse blk: {
+ if (macho_file.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path);
+ break :blk null;
+ };
+ if (platform.isBuildVersionCompatible()) {
+ try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
+ } else {
+ try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
+ }
+ }
const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
try lc_writer.writeStruct(macho_file.uuid_cmd);
src/link/MachO.zig
@@ -585,7 +585,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
try lc_writer.writeStruct(macho.source_version_command{
.version = 0,
});
- try load_commands.writeBuildVersionLC(&self.base.options, lc_writer);
+ {
+ const platform = load_commands.Platform.fromOptions(&self.base.options);
+ const sdk_version: ?std.SemanticVersion = self.base.options.darwin_sdk_version orelse blk: {
+ if (self.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path);
+ break :blk null;
+ };
+ if (platform.isBuildVersionCompatible()) {
+ try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
+ } else {
+ try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
+ }
+ }
const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
try lc_writer.writeStruct(self.uuid_cmd);
@@ -797,7 +808,7 @@ fn parseObject(
const self_cpu_arch = self.base.options.target.cpu.arch;
if (self_cpu_arch != cpu_arch) {
- error_ctx.* = .{ .detected_arch = cpu_arch };
+ error_ctx.detected_arch = cpu_arch;
return error.InvalidArch;
}
}
@@ -894,7 +905,7 @@ fn parseArchive(
else => unreachable,
};
if (cpu_arch != parsed_cpu_arch) {
- error_ctx.* = .{ .detected_arch = parsed_cpu_arch };
+ error_ctx.detected_arch = parsed_cpu_arch;
return error.InvalidArch;
}
}
@@ -959,7 +970,7 @@ fn parseDylib(
else => unreachable,
};
if (self_cpu_arch != cpu_arch) {
- error_ctx.* = .{ .detected_arch = cpu_arch };
+ error_ctx.detected_arch = cpu_arch;
return error.InvalidArch;
}