Commit 7ee6dab39f

Alex Rønne Petersen <alex@alexrp.com>
2025-08-05 22:32:35
Revert "Sema: Stop adding Windows implib link inputs for `extern "..."` syntax."
This reverts commit b461d07a5464aec86c533434dab0b58edfffb331. After some discussion in the team, we've decided that this is too disruptive, especially because the linker errors are less than helpful. That's a fixable problem, so we might reconsider this in the future, but revert it for now.
1 parent 9a158c1
Changed files (10)
src
test
standalone
simple
windows_argv
windows_bat_args
windows_spawn
src/libs/mingw.zig
@@ -1012,7 +1012,6 @@ const mingw32_winpthreads_src = [_][]const u8{
     "winpthreads" ++ path.sep_str ++ "thread.c",
 };
 
-// Note: kernel32 and ntdll are always linked even without targeting MinGW-w64.
 pub const always_link_libs = [_][]const u8{
     "api-ms-win-crt-conio-l1-1-0",
     "api-ms-win-crt-convert-l1-1-0",
@@ -1030,6 +1029,8 @@ pub const always_link_libs = [_][]const u8{
     "api-ms-win-crt-time-l1-1-0",
     "api-ms-win-crt-utility-l1-1-0",
     "advapi32",
+    "kernel32",
+    "ntdll",
     "shell32",
     "user32",
 };
src/Compilation.zig
@@ -2185,8 +2185,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),
         };
 
-        comp.windows_libs = try std.StringArrayHashMapUnmanaged(void).init(gpa, options.windows_lib_names, &.{});
-        errdefer comp.windows_libs.deinit(gpa);
+        errdefer {
+            for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);
+            comp.windows_libs.deinit(gpa);
+        }
+        try comp.windows_libs.ensureUnusedCapacity(gpa, options.windows_lib_names.len);
+        for (options.windows_lib_names) |windows_lib| comp.windows_libs.putAssumeCapacity(try gpa.dupe(u8, windows_lib), {});
 
         // Prevent some footguns by making the "any" fields of config reflect
         // the default Module settings.
@@ -2417,13 +2421,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
 
     if (comp.emit_bin != null and target.ofmt != .c) {
         if (!comp.skip_linker_dependencies) {
-            // These DLLs are always loaded into every Windows process.
-            if (target.os.tag == .windows and is_exe_or_dyn_lib) {
-                try comp.windows_libs.ensureUnusedCapacity(gpa, 2);
-                comp.windows_libs.putAssumeCapacity("kernel32", {});
-                comp.windows_libs.putAssumeCapacity("ntdll", {});
-            }
-
             // If we need to build libc for the target, add work items for it.
             // We go through the work queue so that building can be done in parallel.
             // If linking against host libc installation, instead queue up jobs
@@ -2512,7 +2509,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
 
                     // When linking mingw-w64 there are some import libs we always need.
                     try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);
-                    for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {});
+                    for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(try gpa.dupe(u8, name), {});
                 } else {
                     return error.LibCUnavailable;
                 }
@@ -2610,6 +2607,7 @@ pub fn destroy(comp: *Compilation) void {
     comp.c_object_work_queue.deinit();
     comp.win32_resource_work_queue.deinit();
 
+    for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);
     comp.windows_libs.deinit(gpa);
 
     {
@@ -7795,6 +7793,27 @@ fn getCrtPathsInner(
     };
 }
 
+pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void {
+    // Avoid deadlocking on building import libs such as kernel32.lib
+    // This can happen when the user uses `build-exe foo.obj -lkernel32` and
+    // then when we create a sub-Compilation for zig libc, it also tries to
+    // build kernel32.lib.
+    if (comp.skip_linker_dependencies) return;
+    const target = &comp.root_mod.resolved_target.result;
+    if (target.os.tag != .windows or target.ofmt == .c) return;
+
+    // This happens when an `extern "foo"` function is referenced.
+    // If we haven't seen this library yet and we're targeting Windows, we need
+    // to queue up a work item to produce the DLL import library for this.
+    const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name);
+    if (gop.found_existing) return;
+    {
+        errdefer _ = comp.windows_libs.pop();
+        gop.key_ptr.* = try comp.gpa.dupe(u8, lib_name);
+    }
+    try comp.queueJob(.{ .windows_import_lib = gop.index });
+}
+
 /// This decides the optimization mode for all zig-provided libraries, including
 /// compiler-rt, libcxx, libc, libunwind, etc.
 pub fn compilerRtOptMode(comp: Compilation) std.builtin.OptimizeMode {
src/main.zig
@@ -312,7 +312,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return jitCmd(gpa, arena, cmd_args, .{
             .cmd_name = "resinator",
             .root_src_path = "resinator/main.zig",
-            .windows_libs = &.{"advapi32"},
             .depend_on_aro = true,
             .prepend_zig_lib_dir_path = true,
             .server = use_server,
@@ -337,7 +336,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return jitCmd(gpa, arena, cmd_args, .{
             .cmd_name = "std",
             .root_src_path = "std-docs.zig",
-            .windows_libs = &.{"ws2_32"},
             .prepend_zig_lib_dir_path = true,
             .prepend_zig_exe_path = true,
             .prepend_global_cache_path = true,
@@ -3659,6 +3657,7 @@ fn buildOutputType(
             } else if (target.os.tag == .windows) {
                 try test_exec_args.appendSlice(arena, &.{
                     "--subsystem", "console",
+                    "-lkernel32",  "-lntdll",
                 });
             }
 
@@ -3862,8 +3861,7 @@ fn createModule(
                     .only_compiler_rt => continue,
                 }
 
-                // We currently prefer import libraries provided by MinGW-w64 even for MSVC.
-                if (target.os.tag == .windows) {
+                if (target.isMinGW()) {
                     const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| {
                         fatal("failed to check zig installation for DLL import libs: {s}", .{
                             @errorName(err),
@@ -5375,14 +5373,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
 
             try root_mod.deps.put(arena, "@build", build_mod);
 
-            var windows_libs: std.StringArrayHashMapUnmanaged(void) = .empty;
-
-            if (resolved_target.result.os.tag == .windows) {
-                try windows_libs.ensureUnusedCapacity(arena, 2);
-                windows_libs.putAssumeCapacity("advapi32", {});
-                windows_libs.putAssumeCapacity("ws2_32", {}); // for `--listen` (web interface)
-            }
-
             const comp = Compilation.create(gpa, arena, .{
                 .libc_installation = libc_installation,
                 .dirs = dirs,
@@ -5405,7 +5395,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
                 .cache_mode = .whole,
                 .reference_trace = reference_trace,
                 .debug_compile_errors = debug_compile_errors,
-                .windows_lib_names = windows_libs.keys(),
             }) catch |err| {
                 fatal("unable to create compilation: {s}", .{@errorName(err)});
             };
@@ -5509,7 +5498,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
 const JitCmdOptions = struct {
     cmd_name: []const u8,
     root_src_path: []const u8,
-    windows_libs: []const []const u8 = &.{},
     prepend_zig_lib_dir_path: bool = false,
     prepend_global_cache_path: bool = false,
     prepend_zig_exe_path: bool = false,
@@ -5626,13 +5614,6 @@ fn jitCmd(
             try root_mod.deps.put(arena, "aro", aro_mod);
         }
 
-        var windows_libs: std.StringArrayHashMapUnmanaged(void) = .empty;
-
-        if (resolved_target.result.os.tag == .windows) {
-            try windows_libs.ensureUnusedCapacity(arena, options.windows_libs.len);
-            for (options.windows_libs) |lib| windows_libs.putAssumeCapacity(lib, {});
-        }
-
         const comp = Compilation.create(gpa, arena, .{
             .dirs = dirs,
             .root_name = options.cmd_name,
@@ -5643,7 +5624,6 @@ fn jitCmd(
             .self_exe_path = self_exe_path,
             .thread_pool = &thread_pool,
             .cache_mode = .whole,
-            .windows_lib_names = windows_libs.keys(),
         }) catch |err| {
             fatal("unable to create compilation: {s}", .{@errorName(err)});
         };
src/Sema.zig
@@ -8906,6 +8906,14 @@ fn resolveGenericBody(
     return sema.resolveConstDefinedValue(block, src, result, reason);
 }
 
+/// Given a library name, examines if the library name should end up in
+/// `link.File.Options.windows_libs` table (for example, libc is always
+/// specified via dedicated flag `link_libc` instead),
+/// and puts it there if it doesn't exist.
+/// It also dupes the library name which can then be saved as part of the
+/// respective `Decl` (either `ExternFn` or `Var`).
+/// The liveness of the duped library name is tied to liveness of `Zcu`.
+/// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`).
 pub fn handleExternLibName(
     sema: *Sema,
     block: *Block,
@@ -8955,6 +8963,11 @@ pub fn handleExternLibName(
                 .{ lib_name, lib_name },
             );
         }
+        comp.addLinkLib(lib_name) catch |err| {
+            return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{
+                lib_name, @errorName(err),
+            });
+        };
     }
 }
 
test/standalone/simple/build.zig
@@ -50,10 +50,6 @@ pub fn build(b: *std.Build) void {
                 });
                 if (case.link_libc) exe.root_module.link_libc = true;
 
-                if (resolved_target.result.os.tag == .windows) {
-                    exe.root_module.linkSystemLibrary("advapi32", .{});
-                }
-
                 _ = exe.getEmittedBin();
 
                 step.dependOn(&exe.step);
@@ -70,10 +66,6 @@ pub fn build(b: *std.Build) void {
                 });
                 if (case.link_libc) exe.root_module.link_libc = true;
 
-                if (resolved_target.result.os.tag == .windows) {
-                    exe.root_module.linkSystemLibrary("advapi32", .{});
-                }
-
                 const run = b.addRunArtifact(exe);
                 step.dependOn(&run.step);
             }
test/standalone/windows_argv/build.zig
@@ -47,8 +47,6 @@ pub fn build(b: *std.Build) !void {
         }),
     });
 
-    fuzz.root_module.linkSystemLibrary("advapi32", .{});
-
     const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;
     const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");
 
test/standalone/windows_bat_args/build.zig
@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) !void {
         }),
     });
 
-    test_exe.root_module.linkSystemLibrary("advapi32", .{});
-
     const run = b.addRunArtifact(test_exe);
     run.addArtifactArg(echo_args);
     run.expectExitCode(0);
@@ -46,8 +44,6 @@ pub fn build(b: *std.Build) !void {
         }),
     });
 
-    fuzz.root_module.linkSystemLibrary("advapi32", .{});
-
     const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;
     const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");
 
test/standalone/windows_spawn/build.zig
@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) void {
         }),
     });
 
-    main.root_module.linkSystemLibrary("advapi32", .{});
-
     const run = b.addRunArtifact(main);
     run.addArtifactArg(hello);
     run.expectExitCode(0);
test/tests.zig
@@ -2238,7 +2238,6 @@ const ModuleTestOptions = struct {
     desc: []const u8,
     optimize_modes: []const OptimizeMode,
     include_paths: []const []const u8,
-    windows_libs: []const []const u8,
     skip_single_threaded: bool,
     skip_non_native: bool,
     skip_freebsd: bool,
@@ -2373,10 +2372,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
 
         for (options.include_paths) |include_path| these_tests.root_module.addIncludePath(b.path(include_path));
 
-        if (target.os.tag == .windows) {
-            for (options.windows_libs) |lib| these_tests.root_module.linkSystemLibrary(lib, .{});
-        }
-
         const qualified_name = b.fmt("{s}-{s}-{s}-{s}{s}{s}{s}{s}{s}{s}", .{
             options.name,
             triple_txt,
@@ -2672,10 +2667,6 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
         }),
     });
 
-    if (b.graph.host.result.os.tag == .windows) {
-        incr_check.root_module.linkSystemLibrary("advapi32", .{});
-    }
-
     var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });
     defer dir.close();
 
build.zig
@@ -452,7 +452,6 @@ pub fn build(b: *std.Build) !void {
         .desc = "Run the behavior tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{},
-        .windows_libs = &.{},
         .skip_single_threaded = skip_single_threaded,
         .skip_non_native = skip_non_native,
         .skip_freebsd = skip_freebsd,
@@ -475,7 +474,6 @@ pub fn build(b: *std.Build) !void {
         .desc = "Run the @cImport tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{"test/c_import"},
-        .windows_libs = &.{},
         .skip_single_threaded = true,
         .skip_non_native = skip_non_native,
         .skip_freebsd = skip_freebsd,
@@ -496,7 +494,6 @@ pub fn build(b: *std.Build) !void {
         .desc = "Run the compiler_rt tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{},
-        .windows_libs = &.{},
         .skip_single_threaded = true,
         .skip_non_native = skip_non_native,
         .skip_freebsd = skip_freebsd,
@@ -518,7 +515,6 @@ pub fn build(b: *std.Build) !void {
         .desc = "Run the zigc tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{},
-        .windows_libs = &.{},
         .skip_single_threaded = true,
         .skip_non_native = skip_non_native,
         .skip_freebsd = skip_freebsd,
@@ -540,12 +536,6 @@ pub fn build(b: *std.Build) !void {
         .desc = "Run the standard library tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{},
-        .windows_libs = &.{
-            "advapi32",
-            "crypt32",
-            "iphlpapi",
-            "ws2_32",
-        },
         .skip_single_threaded = skip_single_threaded,
         .skip_non_native = skip_non_native,
         .skip_freebsd = skip_freebsd,
@@ -743,12 +733,6 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu
     compiler_mod.addImport("aro", aro_mod);
     compiler_mod.addImport("aro_translate_c", aro_translate_c_mod);
 
-    if (options.target.result.os.tag == .windows) {
-        compiler_mod.linkSystemLibrary("advapi32", .{});
-        compiler_mod.linkSystemLibrary("crypt32", .{});
-        compiler_mod.linkSystemLibrary("ws2_32", .{});
-    }
-
     return compiler_mod;
 }
 
@@ -1446,10 +1430,6 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
         }),
     });
 
-    if (b.graph.host.result.os.tag == .windows) {
-        doctest_exe.root_module.linkSystemLibrary("advapi32", .{});
-    }
-
     var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
         std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
             b.build_root, @errorName(err),