Commit e0e3ceac19

Jakub Konka <kubkon@jakubkonka.com>
2020-11-05 11:29:58
Re-enable system linker hack
It is now possible to force linking with system linker `ld` instead of the LLVM `lld` linker when building natively on the target. This can be done at each stage by specifying `--system-linker-hack` flag, and can be useful on platforms where `lld` fails to operate properly such as macOS 11 Big Sur on ARM64 where every binary/dylib is expected to be codesigned. Some example invocations for each stage of compilation of Zig toolchain: ``` cmake .. -DCMAKE_PREFIX_PATH=/path/to/llvm -DSYSTEM_LINKER_HACK=1 ``` ``` build/zig build test --system-linker-hack ``` ``` build/zig build --prefix $(pwd)/stage2 -Denable-llvm --system-linker-hack ``` ``` build/zig build-exe hello.zig --system-linker-hack ```
1 parent 17837af
lib/std/special/build_runner.zig
@@ -112,6 +112,8 @@ pub fn main() !void {
                 builder.verbose_cc = true;
             } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
                 builder.verbose_llvm_cpu_features = true;
+            } else if (mem.eql(u8, arg, "--system-linker-hack")) {
+                builder.system_linker_hack = true;
             } else if (mem.eql(u8, arg, "--")) {
                 builder.args = argsRest(args, arg_idx);
                 break;
@@ -213,6 +215,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void
         \\  --verbose-cimport           Enable compiler debug output for C imports
         \\  --verbose-cc                Enable compiler debug output for C compilation
         \\  --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
+        \\  --system-linker-hack        Use system linker LD instead of LLD (requires LLVM enabled)
         \\
     );
 }
lib/std/build.zig
@@ -67,6 +67,7 @@ pub const Builder = struct {
     vcpkg_root: VcpkgRoot,
     pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
     args: ?[][]const u8 = null,
+    system_linker_hack: bool,
 
     const PkgConfigError = error{
         PkgConfigCrashed,
@@ -173,6 +174,7 @@ pub const Builder = struct {
             .install_path = undefined,
             .vcpkg_root = VcpkgRoot{ .Unattempted = {} },
             .args = null,
+            .system_linker_hack = false,
         };
         try self.top_level_steps.append(&self.install_tls);
         try self.top_level_steps.append(&self.uninstall_tls);
@@ -2074,6 +2076,7 @@ pub const LibExeObjStep = struct {
         if (builder.verbose_link or self.verbose_link) zig_args.append("--verbose-link") catch unreachable;
         if (builder.verbose_cc or self.verbose_cc) zig_args.append("--verbose-cc") catch unreachable;
         if (builder.verbose_llvm_cpu_features) zig_args.append("--verbose-llvm-cpu-features") catch unreachable;
+        if (builder.system_linker_hack or self.system_linker_hack) zig_args.append("--system-linker-hack") catch unreachable;
 
         if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir");
         if (self.emit_asm) try zig_args.append("-femit-asm");
@@ -2283,10 +2286,6 @@ pub const LibExeObjStep = struct {
             }
         }
 
-        if (self.system_linker_hack) {
-            try zig_args.append("--system-linker-hack");
-        }
-
         if (self.valgrind_support) |valgrind_support| {
             if (valgrind_support) {
                 try zig_args.append("-fvalgrind");
src/link/Elf.zig
@@ -1353,14 +1353,20 @@ fn linkWithLLD(self: *Elf, 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 (self.base.options.is_native_os and self.base.options.system_linker_hack) {
+        try argv.append("ld");
+    } else {
+        // 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=0");
+    }
+
     if (is_obj) {
         try argv.append("-r");
     }
 
-    try argv.append("-error-limit=0");
-
     if (self.base.options.output_mode == .Exe) {
         try argv.append("-z");
         try argv.append(try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size}));
@@ -1616,43 +1622,63 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
         Compilation.dump_argv(argv.items);
     }
 
-    // Oh, snapplesauce! We need null terminated argv.
-    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 (self.base.options.is_native_os and self.base.options.system_linker_hack) {
+        const result = try std.ChildProcess.exec(.{ .allocator = self.base.allocator, .argv = argv.items });
+        defer {
+            self.base.allocator.free(result.stdout);
+            self.base.allocator.free(result.stderr);
+        }
+        if (result.stdout.len != 0) {
+            std.log.warn("unexpected LD stdout: {}", .{result.stdout});
+        }
+        if (result.stderr.len != 0) {
+            std.log.warn("unexpected LD stderr: {}", .{result.stderr});
+        }
+        if (result.term.Exited != 0) {
+            // TODO parse this output and surface with the Compilation API rather than
+            // directly outputting to stderr here.
+            std.debug.print("{}", .{result.stderr});
+            return error.LDReportedFailure;
+        }
+    } else {
+        // Oh, snapplesauce! We need null terminated argv.
+        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 = .{
-        .elf = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stderr_context.data.deinit();
-    var stdout_context: LLDContext = .{
-        .elf = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stdout_context.data.deinit();
-    const llvm = @import("../llvm.zig");
-    const ok = llvm.Link(
-        .ELF,
-        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});
+        var stderr_context: LLDContext = .{
+            .elf = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stderr_context.data.deinit();
+        var stdout_context: LLDContext = .{
+            .elf = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stdout_context.data.deinit();
+        const llvm = @import("../llvm.zig");
+        const ok = llvm.Link(
+            .ELF,
+            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) {
src/link/MachO.zig
@@ -532,11 +532,17 @@ 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");
 
-        try argv.append("-error-limit");
-        try argv.append("0");
+        // TODO https://github.com/ziglang/zig/issues/6971
+        if (self.base.options.is_native_os and self.base.options.system_linker_hack) {
+            try argv.append("ld");
+        } else {
+            // 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");
 
@@ -703,42 +709,63 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             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);
-        }
+        // TODO https://github.com/ziglang/zig/issues/6971
+        if (self.base.options.is_native_os and self.base.options.system_linker_hack) {
+            const result = try std.ChildProcess.exec(.{ .allocator = self.base.allocator, .argv = argv.items });
+            defer {
+                self.base.allocator.free(result.stdout);
+                self.base.allocator.free(result.stderr);
+            }
+            if (result.stdout.len != 0) {
+                std.log.warn("unexpected LD stdout: {}", .{result.stdout});
+            }
+            if (result.stderr.len != 0) {
+                std.log.warn("unexpected LD stderr: {}", .{result.stderr});
+            }
+            if (result.term.Exited != 0) {
+                // TODO parse this output and surface with the Compilation API rather than
+                // directly outputting to stderr here.
+                std.debug.print("{}", .{result.stderr});
+                return error.LDReportedFailure;
+            }
+        } else {
+            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});
+            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});
+            }
         }
     }
 
src/Compilation.zig
@@ -348,6 +348,8 @@ pub const InitOptions = struct {
     want_valgrind: ?bool = null,
     use_llvm: ?bool = null,
     use_lld: ?bool = null,
+    /// When set, this option will overwrite LLD with the system linker LD.
+    system_linker_hack: bool = false,
     use_clang: ?bool = null,
     rdynamic: bool = false,
     strip: bool = false,
@@ -775,6 +777,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .optimize_mode = options.optimize_mode,
             .use_lld = use_lld,
             .use_llvm = use_llvm,
+            .system_linker_hack = options.system_linker_hack,
             .link_libc = link_libc,
             .link_libcpp = options.link_libcpp,
             .objects = options.link_objects,
src/link.zig
@@ -57,6 +57,9 @@ pub const Options = struct {
     /// other objects.
     /// Otherwise (depending on `use_lld`) this link code directly outputs and updates the final binary.
     use_llvm: bool,
+    /// If this is true and `use_llvm` is true, this link code will use system linker `ld` instead of 
+    /// the LLD.
+    system_linker_hack: bool,
     link_libc: bool,
     link_libcpp: bool,
     function_sections: bool,
src/main.zig
@@ -317,6 +317,7 @@ const usage_build_generic =
     \\  --image-base [addr]            Set base address for executable image
     \\  -framework [name]              (darwin) link against framework
     \\  -F[dir]                        (darwin) add search path for frameworks
+    \\  --system-linker-hack           Use system linker LD instead of LLD (requires LLVM enabled)
     \\
     \\Test Options:
     \\  --test-filter [text]           Skip tests that do not match filter
@@ -474,6 +475,7 @@ fn buildOutputType(
     var image_base_override: ?u64 = null;
     var use_llvm: ?bool = null;
     var use_lld: ?bool = null;
+    var system_linker_hack = false;
     var use_clang: ?bool = null;
     var link_eh_frame_hdr = false;
     var link_emit_relocs = false;
@@ -915,6 +917,8 @@ fn buildOutputType(
                         mem.startsWith(u8, arg, "-I"))
                     {
                         try clang_argv.append(arg);
+                    } else if (mem.startsWith(u8, arg, "--system-linker-hack")) {
+                        system_linker_hack = true;
                     } else {
                         fatal("unrecognized parameter: '{}'", .{arg});
                     }
@@ -1639,6 +1643,7 @@ fn buildOutputType(
         .want_valgrind = want_valgrind,
         .use_llvm = use_llvm,
         .use_lld = use_lld,
+        .system_linker_hack = system_linker_hack,
         .use_clang = use_clang,
         .rdynamic = rdynamic,
         .linker_script = linker_script,
@@ -2160,6 +2165,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
         var override_global_cache_dir: ?[]const u8 = null;
         var override_local_cache_dir: ?[]const u8 = null;
         var child_argv = std.ArrayList([]const u8).init(arena);
+        var system_linker_hack = false;
 
         const argv_index_exe = child_argv.items.len;
         _ = try child_argv.addOne();
@@ -2200,6 +2206,10 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
                         override_global_cache_dir = args[i];
                         try child_argv.appendSlice(&[_][]const u8{ arg, args[i] });
                         continue;
+                    } else if (mem.eql(u8, arg, "--system-linker-hack")) {
+                        system_linker_hack = true;
+                        try child_argv.append(arg);
+                        continue;
                     }
                 }
                 try child_argv.append(arg);
@@ -2335,6 +2345,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
             .optimize_mode = .Debug,
             .self_exe_path = self_exe_path,
             .rand = &default_prng.random,
+            .system_linker_hack = system_linker_hack,
         }) catch |err| {
             fatal("unable to create compilation: {}", .{@errorName(err)});
         };
CMakeLists.txt
@@ -505,13 +505,24 @@ add_dependencies(zig zig_build_zig1)
 
 install(TARGETS zig DESTINATION bin)
 
-set(ZIG_INSTALL_ARGS "build"
-    --override-lib-dir "${CMAKE_SOURCE_DIR}/lib"
-    "-Dlib-files-only"
-    --prefix "${CMAKE_INSTALL_PREFIX}"
-    "-Dconfig_h=${ZIG_CONFIG_H_OUT}"
-    install
-)
+if(NOT SYSTEM_LINKER_HACK)
+  set(ZIG_INSTALL_ARGS "build"
+      --override-lib-dir "${CMAKE_SOURCE_DIR}/lib"
+      "-Dlib-files-only"
+      --prefix "${CMAKE_INSTALL_PREFIX}"
+      "-Dconfig_h=${ZIG_CONFIG_H_OUT}"
+      install
+  )
+else()
+  set(ZIG_INSTALL_ARGS "build"
+      --override-lib-dir "${CMAKE_SOURCE_DIR}/lib"
+      "-Dlib-files-only"
+      --prefix "${CMAKE_INSTALL_PREFIX}"
+      "-Dconfig_h=${ZIG_CONFIG_H_OUT}"
+      --system-linker-hack
+      install
+  )
+endif()
 
 # CODE has no effect with Visual Studio build system generator, therefore
 # when using Visual Studio build system generator we resort to running