diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4c0be32922..961b64a840 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -319,7 +319,6 @@ fn linkWithLLD(self: *MachO, 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; @@ -391,215 +390,238 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { }; } - // 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 (self.base.options.output_mode == .Obj) { + // LLD's MachO driver does not support the equvialent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0]; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.items()[0].key.status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); } - } - if (is_dyn_lib) { - if ((try rpath_table.fetchPut(full_out_path, {})) == null) { - try argv.append("-rpath"); - try argv.append(full_out_path); + } else { + // 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"); + + try argv.append("-error-limit"); + try argv.append("0"); + + try argv.append("-demangle"); + + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); } - } - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append("-L"); - try argv.append(lib_dir); - } + try argv.appendSlice(self.base.options.extra_lld_args); - // Positional arguments to the linker such as object files. - try argv.appendSlice(self.base.options.objects); + 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"); + } - 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); - } + if (is_dyn_lib) { + try argv.append("-static"); + } else { + try argv.append("-dynamic"); + } - // 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); - } + if (is_dyn_lib) { + try argv.append("-dylib"); - // 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); - } + 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); - // 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); - } + 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); + } - // 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"); - } + // 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)); + } - 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); - } + try argv.append("-arch"); + try argv.append(darwinArchString(target.cpu.arch)); - 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"); - } + 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); - if (self.base.options.verbose_link) { - Compilation.dump_argv(argv.items); - } + try argv.append("-sdk_version"); + try argv.append(version_string); - const new_argv = try arena.allocSentinel(?[*:0]const u8, argv.items.len, null); - for (argv.items) |arg, i| { - new_argv[i] = try arena.dupeZ(u8, arg); - } + if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) { + try argv.append("-pie"); + } - 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}); + 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 (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); + } + + const new_argv = try arena.allocSentinel(?[*:0]const u8, 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) { diff --git a/src/main.zig b/src/main.zig index 822e777d68..d421322c17 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1337,12 +1337,12 @@ fn buildOutputType( } }; - if (output_mode == .Obj and object_format == .coff) { + if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) { const total_obj_count = c_source_files.items.len + @boolToInt(root_src_file != null) + link_objects.items.len; if (total_obj_count > 1) { - fatal("COFF does not support linking multiple objects into one", .{}); + fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)}); } }