aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2023-03-18 21:45:21 +0100
committerGitHub <noreply@github.com>2023-03-18 21:45:21 +0100
commit2ac8d90df05fdbb59a0ee9ff6609a058185f66ff (patch)
tree9e7e470a1fcc3e9b70dad781321948be194588e6 /src
parent49d37e2d179948f526f500043c6ea9ae324e9476 (diff)
parent46171bf6c89dc2c2b8f155c8511b62e5cd52e3bc (diff)
downloadzig-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.zig21
-rw-r--r--src/link/MachO.zig86
-rw-r--r--src/link/MachO/zld.zig18
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);
}
}