authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-18 21:45:21+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-03-18 21:45:21+01:00
log2ac8d90df05fdbb59a0ee9ff6609a058185f66ff
tree9e7e470a1fcc3e9b70dad781321948be194588e6
parent49d37e2d179948f526f500043c6ea9ae324e9476
parent46171bf6c89dc2c2b8f155c8511b62e5cd52e3bc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14935 from ziglang/fix-macos-build2

link: move macOS kernel inode cache invalidation to MachO linker

3 files changed, 70 insertions(+), 55 deletions(-)

src/link.zig+1-20
......@@ -418,26 +418,7 @@ pub const File = struct {
418418 .Exe => {},
419419 }
420420 switch (base.tag) {
421 .macho => if (base.file) |f| {
422 if (build_options.only_c) unreachable;
423 if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
424 if (base.options.target.cpu.arch == .aarch64) {
425 // XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
426 // Any change to the binary will effectively invalidate the kernel's cache
427 // resulting in a SIGKILL on each subsequent run. Since when doing incremental
428 // linking we're modifying a binary in-place, this will end up with the kernel
429 // killing it on every subsequent run. To circumvent it, we will copy the file
430 // into a new inode, remove the original file, and rename the copy to match
431 // the original file. This is super messy, but there doesn't seem any other
432 // way to please the XNU.
433 const emit = base.options.emit orelse return;
434 try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, emit.sub_path, .{});
435 }
436 }
437 f.close();
438 base.file = null;
439 },
440 .coff, .elf, .plan9, .wasm => if (base.file) |f| {
421 .coff, .elf, .macho, .plan9, .wasm => if (base.file) |f| {
441422 if (build_options.only_c) unreachable;
442423 if (base.intermediary_basename != null) {
443424 // The file we have open is not the final file that we want to
src/link/MachO.zig+59-27
......@@ -60,6 +60,17 @@ pub const SearchStrategy = enum {
6060 dylibs_first,
6161};
6262
63/// Mode of operation of the linker.
64pub const Mode = enum {
65 /// Incremental mode will preallocate segments/sections and is compatible with
66 /// watch and HCS modes of operation.
67 incremental,
68 /// Zld mode will link relocatables in a traditional, one-shot
69 /// fashion (default for LLVM backend). It acts as a drop-in replacement for
70 /// LLD.
71 zld,
72};
73
6374const Section = struct {
6475 header: macho.section_64,
6576 segment_index: u8,
......@@ -98,10 +109,7 @@ d_sym: ?DebugSymbols = null,
98109/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.
99110page_size: u16,
100111
101/// Mode of operation: incremental - will preallocate segments/sections and is compatible with
102/// watch and HCS modes of operation; one_shot - will link relocatables in a traditional, one-shot
103/// fashion (default for LLVM backend).
104mode: enum { incremental, one_shot },
112mode: Mode,
105113
106114dyld_info_cmd: macho.dyld_info_command = .{},
107115symtab_cmd: macho.symtab_command = .{},
......@@ -314,33 +322,42 @@ pub const default_headerpad_size: u32 = 0x1000;
314322pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
315323 assert(options.target.ofmt == .macho);
316324
317 if (options.emit == null or options.module == null) {
325 if (options.emit == null) {
318326 return createEmpty(allocator, options);
319327 }
320328
321329 const emit = options.emit.?;
330 const mode: Mode = mode: {
331 if (options.use_llvm or options.module == null or options.cache_mode == .whole)
332 break :mode .zld;
333 break :mode .incremental;
334 };
335 const sub_path = if (mode == .zld) blk: {
336 if (options.module == null) {
337 // No point in opening a file, we would not write anything to it.
338 // Initialize with empty.
339 return createEmpty(allocator, options);
340 }
341 // Open a temporary object file, not the final output file because we
342 // want to link with LLD.
343 break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{
344 emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch),
345 });
346 } else emit.sub_path;
347 errdefer if (mode == .zld) allocator.free(sub_path);
348
322349 const self = try createEmpty(allocator, options);
323 errdefer {
324 self.base.file = null;
325 self.base.destroy();
326 }
350 errdefer self.base.destroy();
327351
328 if (build_options.have_llvm and options.use_llvm and options.module != null) {
352 if (mode == .zld) {
329353 // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`,
330354 // we also want to put the intermediary object file in the cache while the
331355 // main emit directory is the cwd.
332 self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{
333 emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch),
334 });
356 self.base.intermediary_basename = sub_path;
357 return self;
335358 }
336359
337 if (self.base.intermediary_basename != null) switch (options.output_mode) {
338 .Obj => return self,
339 .Lib => if (options.link_mode == .Static) return self,
340 else => {},
341 };
342
343 const file = try emit.directory.handle.createFile(emit.sub_path, .{
360 const file = try emit.directory.handle.createFile(sub_path, .{
344361 .truncate = false,
345362 .read = true,
346363 .mode = link.determineMode(options),
......@@ -348,23 +365,21 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
348365 errdefer file.close();
349366 self.base.file = file;
350367
351 if (self.mode == .one_shot) return self;
352
353368 if (!options.strip and options.module != null) {
354369 // Create dSYM bundle.
355 log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
370 log.debug("creating {s}.dSYM bundle", .{sub_path});
356371
357372 const d_sym_path = try fmt.allocPrint(
358373 allocator,
359374 "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
360 .{emit.sub_path},
375 .{sub_path},
361376 );
362377 defer allocator.free(d_sym_path);
363378
364379 var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{});
365380 defer d_sym_bundle.close();
366381
367 const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
382 const d_sym_file = try d_sym_bundle.createFile(sub_path, .{
368383 .truncate = false,
369384 .read = true,
370385 });
......@@ -413,7 +428,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
413428 },
414429 .page_size = page_size,
415430 .mode = if (use_llvm or options.module == null or options.cache_mode == .whole)
416 .one_shot
431 .zld
417432 else
418433 .incremental,
419434 };
......@@ -447,7 +462,7 @@ pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) li
447462 }
448463
449464 switch (self.mode) {
450 .one_shot => return zld.linkWithZld(self, comp, prog_node),
465 .zld => return zld.linkWithZld(self, comp, prog_node),
451466 .incremental => return self.flushModule(comp, prog_node),
452467 }
453468}
......@@ -662,6 +677,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
662677
663678 if (codesig) |*csig| {
664679 try self.writeCodeSignature(comp, csig); // code signing always comes last
680 const emit = self.base.options.emit.?;
681 try invalidateKernelCache(emit.directory.handle, emit.sub_path);
665682 }
666683
667684 if (self.d_sym) |*d_sym| {
......@@ -691,6 +708,21 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
691708
692709 self.cold_start = false;
693710}
711
712/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
713/// Any change to the binary will effectively invalidate the kernel's cache
714/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
715/// linking we're modifying a binary in-place, this will end up with the kernel
716/// killing it on every subsequent run. To circumvent it, we will copy the file
717/// into a new inode, remove the original file, and rename the copy to match
718/// the original file. This is super messy, but there doesn't seem any other
719/// way to please the XNU.
720pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void {
721 if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
722 try dir.copyFile(sub_path, dir, sub_path, .{});
723 }
724}
725
694726inline fn conformUuid(out: *[Md5.digest_length]u8) void {
695727 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
696728 out[6] = (out[6] & 0x0F) | (3 << 4);
src/link/MachO/zld.zig+10-8
......@@ -3665,16 +3665,17 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
36653665 } else {
36663666 const page_size = macho_file.page_size;
36673667 const sub_path = options.emit.?.sub_path;
3668 if (macho_file.base.file == null) {
3669 macho_file.base.file = try directory.handle.createFile(sub_path, .{
3670 .truncate = true,
3671 .read = true,
3672 .mode = link.determineMode(options.*),
3673 });
3674 }
3668
3669 const file = try directory.handle.createFile(sub_path, .{
3670 .truncate = true,
3671 .read = true,
3672 .mode = link.determineMode(options.*),
3673 });
3674 defer file.close();
3675
36753676 var zld = Zld{
36763677 .gpa = gpa,
3677 .file = macho_file.base.file.?,
3678 .file = file,
36783679 .page_size = macho_file.page_size,
36793680 .options = options,
36803681 };
......@@ -4172,6 +4173,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
41724173
41734174 if (codesig) |*csig| {
41744175 try zld.writeCodeSignature(comp, csig); // code signing always comes last
4176 try MachO.invalidateKernelCache(directory.handle, zld.options.emit.?.sub_path);
41754177 }
41764178 }
41774179