diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2023-03-18 21:45:21 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-18 21:45:21 +0100 |
| commit | 2ac8d90df05fdbb59a0ee9ff6609a058185f66ff (patch) | |
| tree | 9e7e470a1fcc3e9b70dad781321948be194588e6 /src | |
| parent | 49d37e2d179948f526f500043c6ea9ae324e9476 (diff) | |
| parent | 46171bf6c89dc2c2b8f155c8511b62e5cd52e3bc (diff) | |
| download | zig-2ac8d90df05fdbb59a0ee9ff6609a058185f66ff.tar.gz zig-2ac8d90df05fdbb59a0ee9ff6609a058185f66ff.zip | |
Merge pull request #14935 from ziglang/fix-macos-build2
link: move macOS kernel inode cache invalidation to MachO linker
Diffstat (limited to 'src')
| -rw-r--r-- | src/link.zig | 21 | ||||
| -rw-r--r-- | src/link/MachO.zig | 86 | ||||
| -rw-r--r-- | src/link/MachO/zld.zig | 18 |
3 files changed, 70 insertions, 55 deletions
diff --git a/src/link.zig b/src/link.zig index e68f9c97d0..a919ffa999 100644 --- a/src/link.zig +++ b/src/link.zig @@ -418,26 +418,7 @@ pub const File = struct { .Exe => {}, } switch (base.tag) { - .macho => if (base.file) |f| { - if (build_options.only_c) unreachable; - if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { - if (base.options.target.cpu.arch == .aarch64) { - // XNU starting with Big Sur running on arm64 is caching inodes of running binaries. - // Any change to the binary will effectively invalidate the kernel's cache - // resulting in a SIGKILL on each subsequent run. Since when doing incremental - // linking we're modifying a binary in-place, this will end up with the kernel - // killing it on every subsequent run. To circumvent it, we will copy the file - // into a new inode, remove the original file, and rename the copy to match - // the original file. This is super messy, but there doesn't seem any other - // way to please the XNU. - const emit = base.options.emit orelse return; - try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, emit.sub_path, .{}); - } - } - f.close(); - base.file = null; - }, - .coff, .elf, .plan9, .wasm => if (base.file) |f| { + .coff, .elf, .macho, .plan9, .wasm => if (base.file) |f| { if (build_options.only_c) unreachable; if (base.intermediary_basename != null) { // The file we have open is not the final file that we want to diff --git a/src/link/MachO.zig b/src/link/MachO.zig index eaf16e4009..2f76c49667 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -60,6 +60,17 @@ pub const SearchStrategy = enum { dylibs_first, }; +/// Mode of operation of the linker. +pub const Mode = enum { + /// Incremental mode will preallocate segments/sections and is compatible with + /// watch and HCS modes of operation. + incremental, + /// Zld mode will link relocatables in a traditional, one-shot + /// fashion (default for LLVM backend). It acts as a drop-in replacement for + /// LLD. + zld, +}; + const Section = struct { header: macho.section_64, segment_index: u8, @@ -98,10 +109,7 @@ d_sym: ?DebugSymbols = null, /// For x86_64 that's 4KB, whereas for aarch64, that's 16KB. page_size: u16, -/// Mode of operation: incremental - will preallocate segments/sections and is compatible with -/// watch and HCS modes of operation; one_shot - will link relocatables in a traditional, one-shot -/// fashion (default for LLVM backend). -mode: enum { incremental, one_shot }, +mode: Mode, dyld_info_cmd: macho.dyld_info_command = .{}, symtab_cmd: macho.symtab_command = .{}, @@ -314,33 +322,42 @@ pub const default_headerpad_size: u32 = 0x1000; pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { assert(options.target.ofmt == .macho); - if (options.emit == null or options.module == null) { + if (options.emit == null) { return createEmpty(allocator, options); } const emit = options.emit.?; + const mode: Mode = mode: { + if (options.use_llvm or options.module == null or options.cache_mode == .whole) + break :mode .zld; + break :mode .incremental; + }; + const sub_path = if (mode == .zld) blk: { + if (options.module == null) { + // No point in opening a file, we would not write anything to it. + // Initialize with empty. + return createEmpty(allocator, options); + } + // Open a temporary object file, not the final output file because we + // want to link with LLD. + break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ + emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), + }); + } else emit.sub_path; + errdefer if (mode == .zld) allocator.free(sub_path); + const self = try createEmpty(allocator, options); - errdefer { - self.base.file = null; - self.base.destroy(); - } + errdefer self.base.destroy(); - if (build_options.have_llvm and options.use_llvm and options.module != null) { + if (mode == .zld) { // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`, // we also want to put the intermediary object file in the cache while the // main emit directory is the cwd. - self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{ - emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), - }); + self.base.intermediary_basename = sub_path; + return self; } - if (self.base.intermediary_basename != null) switch (options.output_mode) { - .Obj => return self, - .Lib => if (options.link_mode == .Static) return self, - else => {}, - }; - - const file = try emit.directory.handle.createFile(emit.sub_path, .{ + const file = try emit.directory.handle.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options), @@ -348,23 +365,21 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { errdefer file.close(); self.base.file = file; - if (self.mode == .one_shot) return self; - if (!options.strip and options.module != null) { // Create dSYM bundle. - log.debug("creating {s}.dSYM bundle", .{emit.sub_path}); + log.debug("creating {s}.dSYM bundle", .{sub_path}); const d_sym_path = try fmt.allocPrint( allocator, "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF", - .{emit.sub_path}, + .{sub_path}, ); defer allocator.free(d_sym_path); var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{}); defer d_sym_bundle.close(); - const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{ + const d_sym_file = try d_sym_bundle.createFile(sub_path, .{ .truncate = false, .read = true, }); @@ -413,7 +428,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { }, .page_size = page_size, .mode = if (use_llvm or options.module == null or options.cache_mode == .whole) - .one_shot + .zld else .incremental, }; @@ -447,7 +462,7 @@ pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) li } switch (self.mode) { - .one_shot => return zld.linkWithZld(self, comp, prog_node), + .zld => return zld.linkWithZld(self, comp, prog_node), .incremental => return self.flushModule(comp, prog_node), } } @@ -662,6 +677,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No if (codesig) |*csig| { try self.writeCodeSignature(comp, csig); // code signing always comes last + const emit = self.base.options.emit.?; + try invalidateKernelCache(emit.directory.handle, emit.sub_path); } if (self.d_sym) |*d_sym| { @@ -691,6 +708,21 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No self.cold_start = false; } + +/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. +/// Any change to the binary will effectively invalidate the kernel's cache +/// resulting in a SIGKILL on each subsequent run. Since when doing incremental +/// linking we're modifying a binary in-place, this will end up with the kernel +/// killing it on every subsequent run. To circumvent it, we will copy the file +/// into a new inode, remove the original file, and rename the copy to match +/// the original file. This is super messy, but there doesn't seem any other +/// way to please the XNU. +pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void { + if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { + try dir.copyFile(sub_path, dir, sub_path, .{}); + } +} + inline fn conformUuid(out: *[Md5.digest_length]u8) void { // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats out[6] = (out[6] & 0x0F) | (3 << 4); diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index a901e4fd4b..931352545e 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -3665,16 +3665,17 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } else { const page_size = macho_file.page_size; const sub_path = options.emit.?.sub_path; - if (macho_file.base.file == null) { - macho_file.base.file = try directory.handle.createFile(sub_path, .{ - .truncate = true, - .read = true, - .mode = link.determineMode(options.*), - }); - } + + const file = try directory.handle.createFile(sub_path, .{ + .truncate = true, + .read = true, + .mode = link.determineMode(options.*), + }); + defer file.close(); + var zld = Zld{ .gpa = gpa, - .file = macho_file.base.file.?, + .file = file, .page_size = macho_file.page_size, .options = options, }; @@ -4172,6 +4173,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr if (codesig) |*csig| { try zld.writeCodeSignature(comp, csig); // code signing always comes last + try MachO.invalidateKernelCache(directory.handle, zld.options.emit.?.sub_path); } } |
