aboutsummaryrefslogtreecommitdiff
path: root/src/link
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-09-29 00:26:18 -0700
committerAndrew Kelley <andrew@ziglang.org>2020-09-29 00:26:18 -0700
commitfa6d150441d1d8679a77c5e9a6071fa952851376 (patch)
treeaddec8faed4b228955e4e0a9f095d582f40625e2 /src/link
parent41f6627521f7ff03962ff73944f5be3722346385 (diff)
downloadzig-fa6d150441d1d8679a77c5e9a6071fa952851376.tar.gz
zig-fa6d150441d1d8679a77c5e9a6071fa952851376.zip
stage2: MachO LLD Linking
Diffstat (limited to 'src/link')
-rw-r--r--src/link/Elf.zig18
-rw-r--r--src/link/MachO.zig370
2 files changed, 377 insertions, 11 deletions
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
index 88f5040761..dc664b14be 100644
--- a/src/link/Elf.zig
+++ b/src/link/Elf.zig
@@ -1241,6 +1241,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
break :blk full_obj_path;
} else null;
+ const is_obj = self.base.options.output_mode == .Obj;
const is_lib = self.base.options.output_mode == .Lib;
const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
@@ -1248,6 +1249,9 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib;
const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe;
const target = self.base.options.target;
+ const gc_sections = self.base.options.gc_sections orelse !is_obj;
+ const stack_size = self.base.options.stack_size_override orelse 16777216;
+ const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
// Here we want to determine whether we can save time by not invoking LLD when the
// output is unchanged. None of the linker options or the object files that are being
@@ -1279,8 +1283,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
try man.addOptionalFile(module_obj_path);
// We can skip hashing libc and libc++ components that we are in charge of building from Zig
// installation sources because they are always a product of the compiler version + target information.
- man.hash.addOptional(self.base.options.stack_size_override);
- man.hash.addOptional(self.base.options.gc_sections);
+ man.hash.add(stack_size);
+ man.hash.add(gc_sections);
man.hash.add(self.base.options.eh_frame_hdr);
man.hash.add(self.base.options.rdynamic);
man.hash.addListOfBytes(self.base.options.extra_lld_args);
@@ -1304,7 +1308,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
man.hash.addOptional(self.base.options.version);
}
man.hash.addStringSet(self.base.options.system_libs);
- man.hash.addOptional(self.base.options.allow_shlib_undefined);
+ man.hash.add(allow_shlib_undefined);
man.hash.add(self.base.options.bind_global_refs_locally);
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
@@ -1332,8 +1336,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
};
}
- const is_obj = self.base.options.output_mode == .Obj;
-
// Create an LLD command line and invoke it.
var argv = std.ArrayList([]const u8).init(self.base.allocator);
defer argv.deinit();
@@ -1347,9 +1349,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
if (self.base.options.output_mode == .Exe) {
try argv.append("-z");
- const stack_size = self.base.options.stack_size_override orelse 16777216;
- const arg = try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size});
- try argv.append(arg);
+ try argv.append(try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size}));
}
if (self.base.options.linker_script) |linker_script| {
@@ -1357,7 +1357,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
try argv.append(linker_script);
}
- const gc_sections = self.base.options.gc_sections orelse !is_obj;
if (gc_sections) {
try argv.append("--gc-sections");
}
@@ -1577,7 +1576,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
}
}
- const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
if (allow_shlib_undefined) {
try argv.append("--allow-shlib-undefined");
}
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index c38c6b34ff..146f2616c2 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -17,6 +17,8 @@ const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
const link = @import("../link.zig");
const File = link.File;
+const Cache = @import("../Cache.zig");
+const target_util = @import("../target.zig");
pub const base_tag: File.Tag = File.Tag.macho;
@@ -180,8 +182,12 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*MachO {
pub fn flush(self: *MachO, comp: *Compilation) !void {
if (build_options.have_llvm and self.base.options.use_lld) {
- return error.MachOLLDLinkingUnimplemented;
+ return self.linkWithLLD(comp);
} else {
+ switch (self.base.options.effectiveOutputMode()) {
+ .Exe, .Obj => {},
+ .Lib => return error.TODOImplementWritingLibFiles,
+ }
return self.flushModule(comp);
}
}
@@ -282,6 +288,368 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
}
}
+fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
+ defer arena_allocator.deinit();
+ const arena = &arena_allocator.allocator;
+
+ const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
+
+ // If there is no Zig code to compile, then we should skip flushing the output file because it
+ // will not be part of the linker line anyway.
+ const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
+ const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
+ if (use_stage1) {
+ const obj_basename = try std.zig.binNameAlloc(arena, .{
+ .root_name = self.base.options.root_name,
+ .target = self.base.options.target,
+ .output_mode = .Obj,
+ });
+ const o_directory = self.base.options.module.?.zig_cache_artifact_directory;
+ const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename});
+ break :blk full_obj_path;
+ }
+
+ try self.flushModule(comp);
+ const obj_basename = self.base.intermediary_basename.?;
+ const full_obj_path = try directory.join(arena, &[_][]const u8{obj_basename});
+ break :blk full_obj_path;
+ } else null;
+
+ const is_obj = self.base.options.output_mode == .Obj;
+ const is_lib = self.base.options.output_mode == .Lib;
+ const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
+ const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
+ const have_dynamic_linker = self.base.options.link_libc and
+ self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib;
+ const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe;
+ const target = self.base.options.target;
+ const stack_size = self.base.options.stack_size_override orelse 16777216;
+ const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
+
+ const id_symlink_basename = "lld.id";
+
+ var man: Cache.Manifest = undefined;
+ defer if (!self.base.options.disable_lld_caching) man.deinit();
+
+ var digest: [Cache.hex_digest_len]u8 = undefined;
+
+ if (!self.base.options.disable_lld_caching) {
+ man = comp.cache_parent.obtain();
+
+ // We are about to obtain this lock, so here we give other processes a chance first.
+ self.base.releaseLock();
+
+ try man.addOptionalFile(self.base.options.linker_script);
+ try man.addOptionalFile(self.base.options.version_script);
+ try man.addListOfFiles(self.base.options.objects);
+ for (comp.c_object_table.items()) |entry| {
+ _ = try man.addFile(entry.key.status.success.object_path, null);
+ }
+ try man.addOptionalFile(module_obj_path);
+ // We can skip hashing libc and libc++ components that we are in charge of building from Zig
+ // installation sources because they are always a product of the compiler version + target information.
+ man.hash.add(stack_size);
+ man.hash.add(self.base.options.rdynamic);
+ man.hash.addListOfBytes(self.base.options.extra_lld_args);
+ man.hash.addListOfBytes(self.base.options.lib_dirs);
+ man.hash.addListOfBytes(self.base.options.framework_dirs);
+ man.hash.addListOfBytes(self.base.options.frameworks);
+ man.hash.addListOfBytes(self.base.options.rpath_list);
+ man.hash.add(self.base.options.is_compiler_rt_or_libc);
+ man.hash.add(self.base.options.z_nodelete);
+ man.hash.add(self.base.options.z_defs);
+ if (is_dyn_lib) {
+ man.hash.addOptional(self.base.options.version);
+ }
+ man.hash.addStringSet(self.base.options.system_libs);
+ man.hash.add(allow_shlib_undefined);
+ man.hash.add(self.base.options.bind_global_refs_locally);
+
+ // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
+ _ = try man.hit();
+ digest = man.final();
+
+ var prev_digest_buf: [digest.len]u8 = undefined;
+ const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: {
+ log.debug("MachO LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) });
+ // Handle this as a cache miss.
+ break :blk prev_digest_buf[0..0];
+ };
+ if (mem.eql(u8, prev_digest, &digest)) {
+ log.debug("MachO LLD digest={} match - skipping invocation", .{digest});
+ // Hot diggity dog! The output binary is already there.
+ self.base.lock = man.toOwnedLock();
+ return;
+ }
+ log.debug("MachO LLD prev_digest={} new_digest={}", .{ prev_digest, digest });
+
+ // We are about to change the output file to be different, so we invalidate the build hash now.
+ directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
+ error.FileNotFound => {},
+ else => |e| return e,
+ };
+ }
+
+ // Create an LLD command line and invoke it.
+ var argv = std.ArrayList([]const u8).init(self.base.allocator);
+ defer argv.deinit();
+ // Even though we're calling LLD as a library it thinks the first argument is its own exe name.
+ try argv.append("lld");
+ if (is_obj) {
+ try argv.append("-r");
+ }
+
+ try argv.append("-error-limit");
+ try argv.append("0");
+
+ try argv.append("-demangle");
+
+ if (self.base.options.rdynamic) {
+ try argv.append("--export-dynamic");
+ }
+
+ try argv.appendSlice(self.base.options.extra_lld_args);
+
+ if (self.base.options.z_nodelete) {
+ try argv.append("-z");
+ try argv.append("nodelete");
+ }
+ if (self.base.options.z_defs) {
+ try argv.append("-z");
+ try argv.append("defs");
+ }
+
+ if (is_dyn_lib) {
+ try argv.append("-static");
+ } else {
+ try argv.append("-dynamic");
+ }
+
+ if (is_dyn_lib) {
+ try argv.append("-dylib");
+
+ if (self.base.options.version) |ver| {
+ const compat_vers = try std.fmt.allocPrint(arena, "{d}.0.0", .{ver.major});
+ try argv.append("-compatibility_version");
+ try argv.append(compat_vers);
+
+ const cur_vers = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
+ try argv.append("-current_version");
+ try argv.append(cur_vers);
+ }
+
+ // TODO getting an error when running an executable when doing this rpath thing
+ //Buf *dylib_install_name = buf_sprintf("@rpath/lib%s.%" ZIG_PRI_usize ".dylib",
+ // buf_ptr(g->root_out_name), g->version_major);
+ //try argv.append("-install_name");
+ //try argv.append(buf_ptr(dylib_install_name));
+ }
+
+ try argv.append("-arch");
+ try argv.append(darwinArchString(target.cpu.arch));
+
+ switch (target.os.tag) {
+ .macosx => {
+ try argv.append("-macosx_version_min");
+ },
+ .ios, .tvos, .watchos => switch (target.cpu.arch) {
+ .i386, .x86_64 => {
+ try argv.append("-ios_simulator_version_min");
+ },
+ else => {
+ try argv.append("-iphoneos_version_min");
+ },
+ },
+ else => unreachable,
+ }
+ const ver = target.os.version_range.semver.min;
+ const version_string = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
+ try argv.append(version_string);
+
+ try argv.append("-sdk_version");
+ try argv.append(version_string);
+
+ if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) {
+ try argv.append("-pie");
+ }
+
+ const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
+ try argv.append("-o");
+ try argv.append(full_out_path);
+
+ // rpaths
+ var rpath_table = std.StringHashMap(void).init(self.base.allocator);
+ defer rpath_table.deinit();
+ for (self.base.options.rpath_list) |rpath| {
+ if ((try rpath_table.fetchPut(rpath, {})) == null) {
+ try argv.append("-rpath");
+ try argv.append(rpath);
+ }
+ }
+ if (is_dyn_lib) {
+ if ((try rpath_table.fetchPut(full_out_path, {})) == null) {
+ try argv.append("-rpath");
+ try argv.append(full_out_path);
+ }
+ }
+
+ for (self.base.options.lib_dirs) |lib_dir| {
+ try argv.append("-L");
+ try argv.append(lib_dir);
+ }
+
+ // Positional arguments to the linker such as object files.
+ try argv.appendSlice(self.base.options.objects);
+
+ for (comp.c_object_table.items()) |entry| {
+ try argv.append(entry.key.status.success.object_path);
+ }
+ if (module_obj_path) |p| {
+ try argv.append(p);
+ }
+
+ // compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce
+ if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
+ try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
+ }
+
+ // Shared libraries.
+ const system_libs = self.base.options.system_libs.items();
+ try argv.ensureCapacity(argv.items.len + system_libs.len);
+ for (system_libs) |entry| {
+ const link_lib = entry.key;
+ // By this time, we depend on these libs being dynamically linked libraries and not static libraries
+ // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
+ // case we want to avoid prepending "-l".
+ const ext = Compilation.classifyFileExt(link_lib);
+ const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib});
+ argv.appendAssumeCapacity(arg);
+ }
+
+ // libc++ dep
+ if (!is_obj and self.base.options.link_libcpp) {
+ try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
+ try argv.append(comp.libcxx_static_lib.?.full_object_path);
+ }
+
+ // On Darwin, libSystem has libc in it, but also you have to use it
+ // to make syscalls because the syscall numbers are not documented
+ // and change between versions. So we always link against libSystem.
+ // LLD craps out if you do -lSystem cross compiling, so until that
+ // codebase gets some love from the new maintainers we're left with
+ // this dirty hack.
+ if (self.base.options.is_native_os) {
+ try argv.append("-lSystem");
+ }
+
+ for (self.base.options.framework_dirs) |framework_dir| {
+ try argv.append("-F");
+ try argv.append(framework_dir);
+ }
+ for (self.base.options.frameworks) |framework| {
+ try argv.append("-framework");
+ try argv.append(framework);
+ }
+
+ if (allow_shlib_undefined) {
+ try argv.append("-undefined");
+ try argv.append("dynamic_lookup");
+ }
+ if (self.base.options.bind_global_refs_locally) {
+ try argv.append("-Bsymbolic");
+ }
+
+ if (self.base.options.verbose_link) {
+ Compilation.dump_argv(argv.items);
+ }
+
+ // TODO allocSentinel crashed stage1 so this is working around it.
+ const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
+ new_argv_with_sentinel[argv.items.len] = null;
+ const new_argv = new_argv_with_sentinel[0..argv.items.len :null];
+ for (argv.items) |arg, i| {
+ new_argv[i] = try arena.dupeZ(u8, arg);
+ }
+
+ var stderr_context: LLDContext = .{
+ .macho = self,
+ .data = std.ArrayList(u8).init(self.base.allocator),
+ };
+ defer stderr_context.data.deinit();
+ var stdout_context: LLDContext = .{
+ .macho = self,
+ .data = std.ArrayList(u8).init(self.base.allocator),
+ };
+ defer stdout_context.data.deinit();
+ const llvm = @import("../llvm.zig");
+ const ok = llvm.Link(
+ .MachO,
+ new_argv.ptr,
+ new_argv.len,
+ append_diagnostic,
+ @ptrToInt(&stdout_context),
+ @ptrToInt(&stderr_context),
+ );
+ if (stderr_context.oom or stdout_context.oom) return error.OutOfMemory;
+ if (stdout_context.data.items.len != 0) {
+ std.log.warn("unexpected LLD stdout: {}", .{stdout_context.data.items});
+ }
+ if (!ok) {
+ // TODO parse this output and surface with the Compilation API rather than
+ // directly outputting to stderr here.
+ std.debug.print("{}", .{stderr_context.data.items});
+ return error.LLDReportedFailure;
+ }
+ if (stderr_context.data.items.len != 0) {
+ std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items});
+ }
+
+ if (!self.base.options.disable_lld_caching) {
+ // Update the dangling symlink with the digest. If it fails we can continue; it only
+ // means that the next invocation will have an unnecessary cache miss.
+ directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| {
+ std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)});
+ };
+ // Again failure here only means an unnecessary cache miss.
+ man.writeManifest() catch |err| {
+ std.log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)});
+ };
+ // We hang on to this lock so that the output file path can be used without
+ // other processes clobbering it.
+ self.base.lock = man.toOwnedLock();
+ }
+}
+
+const LLDContext = struct {
+ data: std.ArrayList(u8),
+ macho: *MachO,
+ oom: bool = false,
+};
+
+fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void {
+ const lld_context = @intToPtr(*LLDContext, context);
+ const msg = ptr[0..len];
+ lld_context.data.appendSlice(msg) catch |err| switch (err) {
+ error.OutOfMemory => lld_context.oom = true,
+ };
+}
+
+fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 {
+ return switch (arch) {
+ .aarch64, .aarch64_be, .aarch64_32 => "arm64",
+ .thumb, .arm => "arm",
+ .thumbeb, .armeb => "armeb",
+ .powerpc => "ppc",
+ .powerpc64 => "ppc64",
+ .powerpc64le => "ppc64le",
+ else => @tagName(arch),
+ };
+}
+
pub fn deinit(self: *MachO) void {
self.offset_table.deinit(self.base.allocator);
self.string_table.deinit(self.base.allocator);