Commit fa6d150441
Changed files (4)
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");
}
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);
src/Compilation.zig
@@ -558,6 +558,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
break :blk buf.items[0 .. buf.items.len - 1 :0].ptr;
} else null;
+ const strip = options.strip or !target_util.hasDebugInfo(options.target);
+
// We put everything into the cache hash that *cannot be modified during an incremental update*.
// For example, one cannot change the target between updates, but one can change source files,
// so the target goes into the cache hash, but source files do not. This is so that we can
@@ -586,7 +588,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
cache.hash.add(stack_check);
cache.hash.add(link_mode);
cache.hash.add(function_sections);
- cache.hash.add(options.strip);
+ cache.hash.add(strip);
cache.hash.add(link_libc);
cache.hash.add(options.link_libcpp);
cache.hash.add(options.output_mode);
@@ -671,7 +673,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
} else null;
errdefer if (module) |zm| zm.deinit();
- const error_return_tracing = !options.strip and switch (options.optimize_mode) {
+ const error_return_tracing = !strip and switch (options.optimize_mode) {
.Debug, .ReleaseSafe => true,
.ReleaseFast, .ReleaseSmall => false,
};
@@ -751,7 +753,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.system_libs = system_libs,
.lib_dirs = options.lib_dirs,
.rpath_list = options.rpath_list,
- .strip = options.strip,
+ .strip = strip,
.is_native_os = options.is_native_os,
.function_sections = options.function_sections orelse false,
.allow_shlib_undefined = options.linker_allow_shlib_undefined,
@@ -2415,10 +2417,11 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR
};
const root_name = mem.split(src_basename, ".").next().?;
const target = comp.getTarget();
+ const output_mode: std.builtin.OutputMode = if (target.cpu.arch.isWasm()) .Obj else .Lib;
const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{
.root_name = root_name,
.target = target,
- .output_mode = .Obj,
+ .output_mode = output_mode,
});
defer comp.gpa.free(bin_basename);
@@ -2441,7 +2444,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR
.target = target,
.root_name = root_name,
.root_pkg = &root_pkg,
- .output_mode = .Obj,
+ .output_mode = output_mode,
.rand = comp.rand,
.libc_installation = comp.bin_file.options.libc_installation,
.emit_bin = emit_bin,
BRANCH_TODO
@@ -1,4 +1,3 @@
- * MachO LLD linking
* audit the CLI options for stage2
* audit the base cache hash
* On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process.