Commit 0da7c4b0c8

Andrew Kelley <andrew@ziglang.org>
2020-09-29 23:46:40
improve stage2 COFF LLD linking
* change some {} to be {s} to gain type safety * fix libraries being libfoo.lib instead of foo.lib for COFF * when linking mingw-w64, add the "always link" libs so that we generate DLL import .lib files for them as the linker code relies on. * COFF LLD linker does not support -r so we do a file copy as an alternative to the -r thing that ELF linking does. I will file an issue for the corresponding TODO upon merging this branch, to look into an optimization that possibly elides this copy when the source and destination are both cache directories. * add a CLI error message when trying to link multiple objects into one and using COFF object format.
1 parent 7c0ee42
Changed files (5)
lib/std/zig.zig
@@ -84,36 +84,36 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
                     .uefi => ".efi",
                     else => ".exe",
                 };
-                return std.fmt.allocPrint(allocator, "{}{}", .{ root_name, suffix });
+                return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
             },
             .Lib => {
                 const suffix = switch (options.link_mode orelse .Static) {
                     .Static => ".lib",
                     .Dynamic => ".dll",
                 };
-                return std.fmt.allocPrint(allocator, "{}{}{}", .{ target.libPrefix(), root_name, suffix });
+                return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
             },
-            .Obj => return std.fmt.allocPrint(allocator, "{}{}", .{ root_name, target.abi.oFileExt() }),
+            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.abi.oFileExt() }),
         },
         .elf => switch (options.output_mode) {
             .Exe => return allocator.dupe(u8, root_name),
             .Lib => {
                 switch (options.link_mode orelse .Static) {
-                    .Static => return std.fmt.allocPrint(allocator, "{}{}.a", .{
+                    .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                         target.libPrefix(), root_name,
                     }),
                     .Dynamic => {
                         if (options.version) |ver| {
-                            return std.fmt.allocPrint(allocator, "{}{}.so.{}.{}.{}", .{
+                            return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
                                 target.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
                             });
                         } else {
-                            return std.fmt.allocPrint(allocator, "{}{}.so", .{ target.libPrefix(), root_name });
+                            return std.fmt.allocPrint(allocator, "{s}{s}.so", .{ target.libPrefix(), root_name });
                         }
                     },
                 }
             },
-            .Obj => return std.fmt.allocPrint(allocator, "{}.o", .{root_name}),
+            .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
         },
         .macho => switch (options.output_mode) {
             .Exe => return allocator.dupe(u8, root_name),
@@ -122,14 +122,14 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
                     .Static => ".a",
                     .Dynamic => ".dylib",
                 };
-                return std.fmt.allocPrint(allocator, "{}{}{}", .{ target.libPrefix(), root_name, suffix });
+                return std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ target.libPrefix(), root_name, suffix });
             },
-            .Obj => return std.fmt.allocPrint(allocator, "{}.o", .{root_name}),
+            .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
         },
-        .wasm => return std.fmt.allocPrint(allocator, "{}.wasm", .{root_name}),
-        .c => return std.fmt.allocPrint(allocator, "{}.c", .{root_name}),
-        .hex => return std.fmt.allocPrint(allocator, "{}.ihex", .{root_name}),
-        .raw => return std.fmt.allocPrint(allocator, "{}.bin", .{root_name}),
+        .wasm => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
+        .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
+        .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
+        .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
     }
 }
 
src/link/Coff.zig
@@ -873,287 +873,302 @@ fn linkWithLLD(self: *Coff, 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();
-    // 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");
-    }
+    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
 
-    try argv.append("-ERRORLIMIT:0");
-    try argv.append("-NOLOGO");
-    if (!self.base.options.strip) {
-        try argv.append("-DEBUG");
-    }
-    if (self.base.options.output_mode == .Exe) {
-        const stack_size = self.base.options.stack_size_override orelse 16777216;
-        try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size}));
-    }
+    if (self.base.options.output_mode == .Obj) {
+        // LLD's COFF 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 (target.cpu.arch == .i386) {
-        try argv.append("-MACHINE:X86");
-    } else if (target.cpu.arch == .x86_64) {
-        try argv.append("-MACHINE:X64");
-    } else if (target.cpu.arch.isARM()) {
-        if (target.cpu.arch.ptrBitWidth() == 32) {
-            try argv.append("-MACHINE:ARM");
-        } else {
-            try argv.append("-MACHINE:ARM64");
+            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;
+        };
+        try fs.cwd().copyFile(the_object_path, fs.cwd(), 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("-ERRORLIMIT:0");
+        try argv.append("-NOLOGO");
+        if (!self.base.options.strip) {
+            try argv.append("-DEBUG");
+        }
+        if (self.base.options.output_mode == .Exe) {
+            const stack_size = self.base.options.stack_size_override orelse 16777216;
+            try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size}));
         }
-    }
 
-    if (is_dyn_lib) {
-        try argv.append("-DLL");
-    }
+        if (target.cpu.arch == .i386) {
+            try argv.append("-MACHINE:X86");
+        } else if (target.cpu.arch == .x86_64) {
+            try argv.append("-MACHINE:X64");
+        } else if (target.cpu.arch.isARM()) {
+            if (target.cpu.arch.ptrBitWidth() == 32) {
+                try argv.append("-MACHINE:ARM");
+            } else {
+                try argv.append("-MACHINE:ARM64");
+            }
+        }
 
-    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
-    try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
+        if (is_dyn_lib) {
+            try argv.append("-DLL");
+        }
 
-    if (self.base.options.link_libc) {
-        if (self.base.options.libc_installation) |libc_installation| {
-            try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
+        try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
 
-            if (target.abi == .msvc) {
-                try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?}));
-                try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?}));
+        if (self.base.options.link_libc) {
+            if (self.base.options.libc_installation) |libc_installation| {
+                try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
+
+                if (target.abi == .msvc) {
+                    try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?}));
+                    try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?}));
+                }
             }
         }
-    }
 
-    for (self.base.options.lib_dirs) |lib_dir| {
-        try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir}));
-    }
+        for (self.base.options.lib_dirs) |lib_dir| {
+            try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir}));
+        }
 
-    try argv.appendSlice(self.base.options.objects);
+        try argv.appendSlice(self.base.options.objects);
 
-    for (comp.c_object_table.items()) |entry| {
-        try argv.append(entry.key.status.success.object_path);
-    }
+        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 (module_obj_path) |p| {
+            try argv.append(p);
+        }
 
-    const resolved_subsystem: ?std.Target.SubSystem = blk: {
-        if (self.base.options.subsystem) |explicit| break :blk explicit;
-        switch (target.os.tag) {
-            .windows => {
-                if (self.base.options.module) |module| {
-                    if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib)
-                        break :blk null;
-                    if (module.stage1_flags.have_c_main or self.base.options.is_test or
-                        module.stage1_flags.have_winmain_crt_startup or
-                        module.stage1_flags.have_wwinmain_crt_startup)
-                    {
-                        break :blk .Console;
+        const resolved_subsystem: ?std.Target.SubSystem = blk: {
+            if (self.base.options.subsystem) |explicit| break :blk explicit;
+            switch (target.os.tag) {
+                .windows => {
+                    if (self.base.options.module) |module| {
+                        if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib)
+                            break :blk null;
+                        if (module.stage1_flags.have_c_main or self.base.options.is_test or
+                            module.stage1_flags.have_winmain_crt_startup or
+                            module.stage1_flags.have_wwinmain_crt_startup)
+                        {
+                            break :blk .Console;
+                        }
+                        if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain)
+                            break :blk .Windows;
                     }
-                    if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain)
-                        break :blk .Windows;
-                }
-            },
-            .uefi => break :blk .EfiApplication,
-            else => {},
-        }
-        break :blk null;
-    };
-    const Mode = enum { uefi, win32 };
-    const mode: Mode = mode: {
-        if (resolved_subsystem) |subsystem| switch (subsystem) {
-            .Console => {
-                try argv.append("-SUBSYSTEM:console");
-                break :mode .win32;
-            },
-            .EfiApplication => {
-                try argv.append("-SUBSYSTEM:efi_application");
-                break :mode .uefi;
-            },
-            .EfiBootServiceDriver => {
-                try argv.append("-SUBSYSTEM:efi_boot_service_driver");
-                break :mode .uefi;
-            },
-            .EfiRom => {
-                try argv.append("-SUBSYSTEM:efi_rom");
-                break :mode .uefi;
-            },
-            .EfiRuntimeDriver => {
-                try argv.append("-SUBSYSTEM:efi_runtime_driver");
+                },
+                .uefi => break :blk .EfiApplication,
+                else => {},
+            }
+            break :blk null;
+        };
+        const Mode = enum { uefi, win32 };
+        const mode: Mode = mode: {
+            if (resolved_subsystem) |subsystem| switch (subsystem) {
+                .Console => {
+                    try argv.append("-SUBSYSTEM:console");
+                    break :mode .win32;
+                },
+                .EfiApplication => {
+                    try argv.append("-SUBSYSTEM:efi_application");
+                    break :mode .uefi;
+                },
+                .EfiBootServiceDriver => {
+                    try argv.append("-SUBSYSTEM:efi_boot_service_driver");
+                    break :mode .uefi;
+                },
+                .EfiRom => {
+                    try argv.append("-SUBSYSTEM:efi_rom");
+                    break :mode .uefi;
+                },
+                .EfiRuntimeDriver => {
+                    try argv.append("-SUBSYSTEM:efi_runtime_driver");
+                    break :mode .uefi;
+                },
+                .Native => {
+                    try argv.append("-SUBSYSTEM:native");
+                    break :mode .win32;
+                },
+                .Posix => {
+                    try argv.append("-SUBSYSTEM:posix");
+                    break :mode .win32;
+                },
+                .Windows => {
+                    try argv.append("-SUBSYSTEM:windows");
+                    break :mode .win32;
+                },
+            } else if (target.os.tag == .uefi) {
                 break :mode .uefi;
-            },
-            .Native => {
-                try argv.append("-SUBSYSTEM:native");
-                break :mode .win32;
-            },
-            .Posix => {
-                try argv.append("-SUBSYSTEM:posix");
-                break :mode .win32;
-            },
-            .Windows => {
-                try argv.append("-SUBSYSTEM:windows");
+            } else {
                 break :mode .win32;
-            },
-        } else if (target.os.tag == .uefi) {
-            break :mode .uefi;
-        } else {
-            break :mode .win32;
-        }
-    };
+            }
+        };
 
-    switch (mode) {
-        .uefi => try argv.appendSlice(&[_][]const u8{
-            "-BASE:0",
-            "-ENTRY:EfiMain",
-            "-OPT:REF",
-            "-SAFESEH:NO",
-            "-MERGE:.rdata=.data",
-            "-ALIGN:32",
-            "-NODEFAULTLIB",
-            "-SECTION:.xdata,D",
-        }),
-        .win32 => {
-            if (link_in_crt) {
-                if (target.abi.isGnu()) {
-                    try argv.append("-lldmingw");
-
-                    if (target.cpu.arch == .i386) {
-                        try argv.append("-ALTERNATENAME:__image_base__=___ImageBase");
-                    } else {
-                        try argv.append("-ALTERNATENAME:__image_base__=__ImageBase");
-                    }
+        switch (mode) {
+            .uefi => try argv.appendSlice(&[_][]const u8{
+                "-BASE:0",
+                "-ENTRY:EfiMain",
+                "-OPT:REF",
+                "-SAFESEH:NO",
+                "-MERGE:.rdata=.data",
+                "-ALIGN:32",
+                "-NODEFAULTLIB",
+                "-SECTION:.xdata,D",
+            }),
+            .win32 => {
+                if (link_in_crt) {
+                    if (target.abi.isGnu()) {
+                        try argv.append("-lldmingw");
+
+                        if (target.cpu.arch == .i386) {
+                            try argv.append("-ALTERNATENAME:__image_base__=___ImageBase");
+                        } else {
+                            try argv.append("-ALTERNATENAME:__image_base__=__ImageBase");
+                        }
 
-                    if (is_dyn_lib) {
-                        try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.o"));
-                    } else {
-                        try argv.append(try comp.get_libc_crt_file(arena, "crt2.o"));
-                    }
+                        if (is_dyn_lib) {
+                            try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.o"));
+                        } else {
+                            try argv.append(try comp.get_libc_crt_file(arena, "crt2.o"));
+                        }
 
-                    try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib"));
-                    try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib"));
-                    try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib"));
+                        try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib"));
+                        try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib"));
+                        try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib"));
 
-                    for (mingw.always_link_libs) |name| {
-                        if (!self.base.options.system_libs.contains(name)) {
-                            const lib_basename = try allocPrint(arena, "{s}.lib", .{name});
-                            try argv.append(try comp.get_libc_crt_file(arena, lib_basename));
+                        for (mingw.always_link_libs) |name| {
+                            if (!self.base.options.system_libs.contains(name)) {
+                                const lib_basename = try allocPrint(arena, "{s}.lib", .{name});
+                                try argv.append(try comp.get_libc_crt_file(arena, lib_basename));
+                            }
+                        }
+                    } else {
+                        const lib_str = switch (self.base.options.link_mode) {
+                            .Dynamic => "",
+                            .Static => "lib",
+                        };
+                        const d_str = switch (self.base.options.optimize_mode) {
+                            .Debug => "d",
+                            else => "",
+                        };
+                        switch (self.base.options.link_mode) {
+                            .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})),
+                            .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})),
                         }
-                    }
-                } else {
-                    const lib_str = switch (self.base.options.link_mode) {
-                        .Dynamic => "",
-                        .Static => "lib",
-                    };
-                    const d_str = switch (self.base.options.optimize_mode) {
-                        .Debug => "d",
-                        else => "",
-                    };
-                    switch (self.base.options.link_mode) {
-                        .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})),
-                        .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})),
-                    }
 
-                    try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str }));
-                    try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str }));
+                        try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str }));
+                        try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str }));
 
-                    //Visual C++ 2015 Conformance Changes
-                    //https://msdn.microsoft.com/en-us/library/bb531344.aspx
-                    try argv.append("legacy_stdio_definitions.lib");
+                        //Visual C++ 2015 Conformance Changes
+                        //https://msdn.microsoft.com/en-us/library/bb531344.aspx
+                        try argv.append("legacy_stdio_definitions.lib");
 
-                    // msvcrt depends on kernel32 and ntdll
-                    try argv.append("kernel32.lib");
-                    try argv.append("ntdll.lib");
-                }
-            } else {
-                try argv.append("-NODEFAULTLIB");
-                if (!is_lib) {
-                    if (self.base.options.module) |module| {
-                        if (module.stage1_flags.have_winmain) {
-                            try argv.append("-ENTRY:WinMain");
-                        } else if (module.stage1_flags.have_wwinmain) {
-                            try argv.append("-ENTRY:wWinMain");
-                        } else if (module.stage1_flags.have_wwinmain_crt_startup) {
-                            try argv.append("-ENTRY:wWinMainCRTStartup");
+                        // msvcrt depends on kernel32 and ntdll
+                        try argv.append("kernel32.lib");
+                        try argv.append("ntdll.lib");
+                    }
+                } else {
+                    try argv.append("-NODEFAULTLIB");
+                    if (!is_lib) {
+                        if (self.base.options.module) |module| {
+                            if (module.stage1_flags.have_winmain) {
+                                try argv.append("-ENTRY:WinMain");
+                            } else if (module.stage1_flags.have_wwinmain) {
+                                try argv.append("-ENTRY:wWinMain");
+                            } else if (module.stage1_flags.have_wwinmain_crt_startup) {
+                                try argv.append("-ENTRY:wWinMainCRTStartup");
+                            } else {
+                                try argv.append("-ENTRY:WinMainCRTStartup");
+                            }
                         } else {
                             try argv.append("-ENTRY:WinMainCRTStartup");
                         }
-                    } else {
-                        try argv.append("-ENTRY:WinMainCRTStartup");
                     }
                 }
-            }
-        },
-    }
+            },
+        }
 
-    if (!is_obj) {
         // 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);
             try argv.append(comp.libunwind_static_lib.?.full_object_path);
         }
-    }
 
-    // compiler-rt and libc
-    if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
-        if (!self.base.options.link_libc) {
-            try argv.append(comp.libc_static_lib.?.full_object_path);
+        // compiler-rt and libc
+        if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
+            if (!self.base.options.link_libc) {
+                try argv.append(comp.libc_static_lib.?.full_object_path);
+            }
+            // MSVC compiler_rt is missing some stuff, so we build it unconditionally but
+            // and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
+            try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
         }
-        // MSVC compiler_rt is missing some stuff, so we build it unconditionally but
-        // and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
-        try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
-    }
 
-    for (self.base.options.system_libs.items()) |entry| {
-        const lib_basename = try allocPrint(arena, "{s}.lib", .{entry.key});
-        if (comp.crt_files.get(lib_basename)) |crt_file| {
-            try argv.append(crt_file.full_object_path);
-        } else {
-            try argv.append(lib_basename);
+        for (self.base.options.system_libs.items()) |entry| {
+            const lib_basename = try allocPrint(arena, "{s}.lib", .{entry.key});
+            if (comp.crt_files.get(lib_basename)) |crt_file| {
+                try argv.append(crt_file.full_object_path);
+            } else {
+                try argv.append(lib_basename);
+            }
         }
-    }
 
-    if (self.base.options.verbose_link) {
-        Compilation.dump_argv(argv.items);
-    }
+        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);
-    }
+        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 = .{
-        .coff = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stderr_context.data.deinit();
-    var stdout_context: LLDContext = .{
-        .coff = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stdout_context.data.deinit();
-    const llvm = @import("../llvm.zig");
-    const ok = llvm.Link(
-        .COFF,
-        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 = .{
+            .coff = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stderr_context.data.deinit();
+        var stdout_context: LLDContext = .{
+            .coff = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stdout_context.data.deinit();
+        const llvm = @import("../llvm.zig");
+        const ok = llvm.Link(
+            .COFF,
+            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/Compilation.zig
@@ -879,6 +879,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             try comp.work_queue.ensureUnusedCapacity(static_lib_jobs.len + 1);
             comp.work_queue.writeAssumeCapacity(&static_lib_jobs);
             comp.work_queue.writeItemAssumeCapacity(crt_job);
+
+            // When linking mingw-w64 there are some import libs we always need.
+            for (mingw.always_link_libs) |name| {
+                try comp.bin_file.options.system_libs.put(comp.gpa, name, .{});
+            }
         }
         // Generate Windows import libs.
         if (comp.getTarget().os.tag == .windows) {
src/link.zig
@@ -321,7 +321,7 @@ pub const File = struct {
             // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
             // Until then, we do `lld -r -o output.o input.o` even though the output is the same
             // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file
-            // to the final location.
+            // to the final location. See also the corresponding TODO in Coff linking.
             const full_out_path = try emit.directory.join(comp.gpa, &[_][]const u8{emit.sub_path});
             defer comp.gpa.free(full_out_path);
             assert(comp.c_object_table.count() == 1);
src/main.zig
@@ -1314,8 +1314,8 @@ fn buildOutputType(
         }
     }
 
-    const object_format: ?std.Target.ObjectFormat = blk: {
-        const ofmt = target_ofmt orelse break :blk null;
+    const object_format: std.Target.ObjectFormat = blk: {
+        const ofmt = target_ofmt orelse break :blk target_info.target.getObjectFormat();
         if (mem.eql(u8, ofmt, "elf")) {
             break :blk .elf;
         } else if (mem.eql(u8, ofmt, "c")) {
@@ -1337,6 +1337,15 @@ fn buildOutputType(
         }
     };
 
+    if (output_mode == .Obj and object_format == .coff) {
+        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", .{});
+        }
+    }
+
     var cleanup_emit_bin_dir: ?fs.Dir = null;
     defer if (cleanup_emit_bin_dir) |*dir| dir.close();