From 075d300342afa72f5328c0ee4232151dd0968264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20=C3=85stholm?= Date: Fri, 5 Sep 2025 17:45:22 +0200 Subject: [PATCH 1/2] Remove `std.builtin.subsystem` The subsystem detection was flaky and often incorrect and was not actually needed by the compiler or standard library. The actual subsystem won't be known until at link time, so it doesn't make sense to try to determine it at compile time. --- lib/std/builtin.zig | 26 -------------------------- lib/std/start.zig | 21 +++++++++------------ 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index a646e9ad8013b419a80b60789d11c5044c6500b1..7a0284220ca26815d96dbda8ac51dd1e768ab9ef 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -6,32 +6,6 @@ const root = @import("root"); pub const assembly = @import("builtin/assembly.zig"); -/// `explicit_subsystem` is missing when the subsystem is automatically detected, -/// so Zig standard library has the subsystem detection logic here. This should generally be -/// used rather than `explicit_subsystem`. -/// On non-Windows targets, this is `null`. -pub const subsystem: ?std.Target.SubSystem = blk: { - if (@hasDecl(builtin, "explicit_subsystem")) break :blk builtin.explicit_subsystem; - switch (builtin.os.tag) { - .windows => { - if (builtin.is_test) { - break :blk std.Target.SubSystem.Console; - } - if (@hasDecl(root, "main") or - @hasDecl(root, "WinMain") or - @hasDecl(root, "wWinMain") or - @hasDecl(root, "WinMainCRTStartup") or - @hasDecl(root, "wWinMainCRTStartup")) - { - break :blk std.Target.SubSystem.Windows; - } else { - break :blk std.Target.SubSystem.Console; - } - }, - else => break :blk null, - } -}; - /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const StackTrace = struct { diff --git a/lib/std/start.zig b/lib/std/start.zig index 7f7cc2f083d5503be4116f199454808df3a137a4..c22a36f24fd6993009a2f507da642094d7024e3d 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -734,24 +734,21 @@ pub fn call_wWinMain() std.os.windows.INT { // - u32 in PEB.ProcessParameters.dwShowWindow // Since STARTUPINFO is the bottleneck for the allowed values, we use `u16` as the // type which can coerce into i32/c_int/u32 depending on how the user defines their wWinMain - // (the Win32 docs show wWinMain with `int` as the type for nCmdShow). - const nCmdShow: u16 = nCmdShow: { - // This makes Zig match the nCmdShow behavior of a C program with a WinMain symbol: + // (the Win32 docs show wWinMain with `int` as the type for nShowCmd). + const nShowCmd: u16 = nShowCmd: { + // This makes Zig match the nShowCmd behavior of a C program with a WinMain symbol: // - With STARTF_USESHOWWINDOW set in STARTUPINFO.dwFlags of the CreateProcess call: - // - Compiled with subsystem:console -> nCmdShow is always SW_SHOWDEFAULT - // - Compiled with subsystem:windows -> nCmdShow is STARTUPINFO.wShowWindow from - // the parent CreateProcess call + // - nShowCmd is STARTUPINFO.wShowWindow from the parent CreateProcess call // - With STARTF_USESHOWWINDOW unset: - // - nCmdShow is always SW_SHOWDEFAULT + // - nShowCmd is always SW_SHOWDEFAULT const SW_SHOWDEFAULT = 10; const STARTF_USESHOWWINDOW = 1; - // root having a wWinMain means that std.builtin.subsystem will always have a non-null value. - if (std.builtin.subsystem.? == .Windows and peb.ProcessParameters.dwFlags & STARTF_USESHOWWINDOW != 0) { - break :nCmdShow @truncate(peb.ProcessParameters.dwShowWindow); + if (peb.ProcessParameters.dwFlags & STARTF_USESHOWWINDOW != 0) { + break :nShowCmd @truncate(peb.ProcessParameters.dwShowWindow); } - break :nCmdShow SW_SHOWDEFAULT; + break :nShowCmd SW_SHOWDEFAULT; }; // second parameter hPrevInstance, MSDN: "This parameter is always NULL" - return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow); + return root.wWinMain(hInstance, null, lpCmdLine, nShowCmd); } -- 2.54.0 From 54f2a7c833bf2ca0b370ebe2470bb0dd71206cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20=C3=85stholm?= Date: Fri, 5 Sep 2025 17:50:46 +0200 Subject: [PATCH 2/2] Move `std.Target.SubSystem` to `std.zig.Subsystem` Also updates the field names to conform with the rest of std. --- lib/std/Build/Step/Compile.zig | 13 +-- lib/std/Target.zig | 12 +-- lib/std/zig.zig | 28 +++++++ src/Compilation.zig | 2 +- src/link.zig | 2 +- src/link/Lld.zig | 81 +++++-------------- src/main.zig | 31 ++----- test/standalone/issue_5825/build.zig | 2 +- .../standalone/windows_entry_points/build.zig | 4 +- 9 files changed, 66 insertions(+), 109 deletions(-) diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index a5f2d696be1b92e6f1c2e09321f6131a601c028a..c82846a64047a997b1dab5a51ce54e7ebc1973fa 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -171,7 +171,7 @@ lto: ?std.zig.LtoMode = null, dll_export_fns: ?bool = null, -subsystem: ?std.Target.SubSystem = null, +subsystem: ?std.zig.Subsystem = null, /// (Windows) When targeting the MinGW ABI, use the unicode entry point (wmain/wWinMain) mingw_unicode_entry_point: bool = false, @@ -1764,16 +1764,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { if (compile.subsystem) |subsystem| { try zig_args.append("--subsystem"); - try zig_args.append(switch (subsystem) { - .Console => "console", - .Windows => "windows", - .Posix => "posix", - .Native => "native", - .EfiApplication => "efi_application", - .EfiBootServiceDriver => "efi_boot_service_driver", - .EfiRom => "efi_rom", - .EfiRuntimeDriver => "efi_runtime_driver", - }); + try zig_args.append(@tagName(subsystem)); } if (compile.mingw_unicode_entry_point) { diff --git a/lib/std/Target.zig b/lib/std/Target.zig index cbbbc863981d4b2e5507ebdc7f739b4359bfb2c2..a5af4c6511bf7517218d0708c8e139b75375e4a5 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1138,16 +1138,8 @@ pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE { }; } -pub const SubSystem = enum { - Console, - Windows, - Posix, - Native, - EfiApplication, - EfiBootServiceDriver, - EfiRom, - EfiRuntimeDriver, -}; +/// Deprecated; use 'std.zig.Subsystem' instead. To be removed after 0.16.0 is tagged. +pub const SubSystem = std.zig.Subsystem; pub const Cpu = struct { /// Architecture diff --git a/lib/std/zig.zig b/lib/std/zig.zig index dcdc727ae22638e01440e9df7a6e177bc797f401..fa70ef7fc72a095dd3ca9af0226e8256ea7c8aa4 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -349,6 +349,34 @@ pub const BuildId = union(enum) { pub const LtoMode = enum { none, full, thin }; +pub const Subsystem = enum { + console, + windows, + posix, + native, + efi_application, + efi_boot_service_driver, + efi_rom, + efi_runtime_driver, + + /// Deprecated; use '.console' instead. To be removed after 0.16.0 is tagged. + pub const Console: Subsystem = .console; + /// Deprecated; use '.windows' instead. To be removed after 0.16.0 is tagged. + pub const Windows: Subsystem = .windows; + /// Deprecated; use '.posix' instead. To be removed after 0.16.0 is tagged. + pub const Posix: Subsystem = .posix; + /// Deprecated; use '.native' instead. To be removed after 0.16.0 is tagged. + pub const Native: Subsystem = .native; + /// Deprecated; use '.efi_application' instead. To be removed after 0.16.0 is tagged. + pub const EfiApplication: Subsystem = .efi_application; + /// Deprecated; use '.efi_boot_service_driver' instead. To be removed after 0.16.0 is tagged. + pub const EfiBootServiceDriver: Subsystem = .efi_boot_service_driver; + /// Deprecated; use '.efi_rom' instead. To be removed after 0.16.0 is tagged. + pub const EfiRom: Subsystem = .efi_rom; + /// Deprecated; use '.efi_runtime_driver' instead. To be removed after 0.16.0 is tagged. + pub const EfiRuntimeDriver: Subsystem = .efi_runtime_driver; +}; + /// Renders a `std.Target.Cpu` value into a textual representation that can be parsed /// via the `-mcpu` flag passed to the Zig compiler. /// Appends the result to `buffer`. diff --git a/src/Compilation.zig b/src/Compilation.zig index 9f0ab662a461bf62596faeb8d0628aadcc848083..c6734aa3e788b10b0a4fbcbbac72f9021e4ef61a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1766,7 +1766,7 @@ pub const CreateOptions = struct { reference_trace: ?u32 = null, test_filters: []const []const u8 = &.{}, test_runner_path: ?[]const u8 = null, - subsystem: ?std.Target.SubSystem = null, + subsystem: ?std.zig.Subsystem = null, mingw_unicode_entry_point: bool = false, /// (Zig compiler development) Enable dumping linker's state as JSON. enable_link_snapshots: bool = false, diff --git a/src/link.zig b/src/link.zig index f324f23897c4d76c8cd7aa50dc84475bad6ba01f..ce1d2a68f65ce3edbd8a4a91cd7c8428a115438d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -448,7 +448,7 @@ pub const File = struct { allow_shlib_undefined: ?bool, allow_undefined_version: bool, enable_new_dtags: ?bool, - subsystem: ?std.Target.SubSystem, + subsystem: ?std.zig.Subsystem, linker_script: ?[]const u8, version_script: ?[]const u8, soname: ?[]const u8, diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 75aad965d9886ffdb586949c6f24d859f01805a1..d9c25f641d9e6e5bfea6dfdbfe31072409229ded 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -19,7 +19,7 @@ const Coff = struct { minor_subsystem_version: u16, lib_directories: []const Cache.Directory, module_definition_file: ?[]const u8, - subsystem: ?std.Target.SubSystem, + subsystem: ?std.zig.Subsystem, /// These flags are populated by `codegen.llvm.updateExports` to allow us to guess the subsystem. lld_export_flags: struct { c_main: bool, @@ -554,7 +554,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try argv.append(try allocPrint(arena, "-DEF:{s}", .{def})); } - const resolved_subsystem: ?std.Target.SubSystem = blk: { + const resolved_subsystem: ?std.zig.Subsystem = blk: { if (coff.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { .windows => { @@ -565,13 +565,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { coff.lld_export_flags.winmain_crt_startup or coff.lld_export_flags.wwinmain_crt_startup) { - break :blk .Console; + break :blk .console; } if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) - break :blk .Windows; + break :blk .windows; } }, - .uefi => break :blk .EfiApplication, + .uefi => break :blk .efi_application, else => {}, } break :blk null; @@ -580,60 +580,23 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { const Mode = enum { uefi, win32 }; const mode: Mode = mode: { if (resolved_subsystem) |subsystem| { - const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{ - coff.major_subsystem_version, coff.minor_subsystem_version, - }); - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } + try argv.append(try allocPrint(arena, "-SUBSYSTEM:{s},{d}.{d}", .{ + @tagName(subsystem), + coff.major_subsystem_version, + coff.minor_subsystem_version, + })); + break :mode switch (subsystem) { + .console, + .windows, + .posix, + .native, + => .win32, + .efi_application, + .efi_boot_service_driver, + .efi_rom, + .efi_runtime_driver, + => .uefi, + }; } else if (target.os.tag == .uefi) { break :mode .uefi; } else { diff --git a/src/main.zig b/src/main.zig index e6815bc424166f514e411728216c6e41803c49ab..ccb8935972ad869039d529a601bc41b77ce46004 100644 --- a/src/main.zig +++ b/src/main.zig @@ -893,7 +893,7 @@ fn buildOutputType( var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; - var subsystem: ?std.Target.SubSystem = null; + var subsystem: ?std.zig.Subsystem = null; var major_subsystem_version: ?u16 = null; var minor_subsystem_version: ?u16 = null; var mingw_unicode_entry_point: bool = false; @@ -1135,7 +1135,7 @@ fn buildOutputType( } n_jobs = num; } else if (mem.eql(u8, arg, "--subsystem")) { - subsystem = try parseSubSystem(args_iter.nextOrFatal()); + subsystem = try parseSubsystem(args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-O")) { mod_opts.optimize_mode = parseOptimizeMode(args_iter.nextOrFatal()); } else if (mem.cutPrefix(u8, arg, "-fentry=")) |rest| { @@ -2415,7 +2415,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-rpath") or mem.eql(u8, arg, "--rpath") or mem.eql(u8, arg, "-R")) { try create_module.rpath_list.append(arena, linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--subsystem")) { - subsystem = try parseSubSystem(linker_args_it.nextOrFatal()); + subsystem = try parseSubsystem(linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "-I") or mem.eql(u8, arg, "--dynamic-linker") or mem.eql(u8, arg, "-dynamic-linker")) @@ -2743,7 +2743,7 @@ fn buildOutputType( try symbol_wrap_set.put(arena, next_arg, {}); } else if (mem.startsWith(u8, arg, "/subsystem:")) { var split_it = mem.splitBackwardsScalar(u8, arg, ':'); - subsystem = try parseSubSystem(split_it.first()); + subsystem = try parseSubsystem(split_it.first()); } else if (mem.startsWith(u8, arg, "/implib:")) { var split_it = mem.splitBackwardsScalar(u8, arg, ':'); emit_implib = .{ .yes = split_it.first() }; @@ -6657,26 +6657,10 @@ fn warnAboutForeignBinaries( } } -fn parseSubSystem(next_arg: []const u8) !std.Target.SubSystem { - if (mem.eql(u8, next_arg, "console")) { - return .Console; - } else if (mem.eql(u8, next_arg, "windows")) { - return .Windows; - } else if (mem.eql(u8, next_arg, "posix")) { - return .Posix; - } else if (mem.eql(u8, next_arg, "native")) { - return .Native; - } else if (mem.eql(u8, next_arg, "efi_application")) { - return .EfiApplication; - } else if (mem.eql(u8, next_arg, "efi_boot_service_driver")) { - return .EfiBootServiceDriver; - } else if (mem.eql(u8, next_arg, "efi_rom")) { - return .EfiRom; - } else if (mem.eql(u8, next_arg, "efi_runtime_driver")) { - return .EfiRuntimeDriver; - } else { +fn parseSubsystem(arg: []const u8) !std.zig.Subsystem { + return std.meta.stringToEnum(std.zig.Subsystem, arg) orelse fatal("invalid: --subsystem: '{s}'. Options are:\n{s}", .{ - next_arg, + arg, \\ console \\ windows \\ posix @@ -6687,7 +6671,6 @@ fn parseSubSystem(next_arg: []const u8) !std.Target.SubSystem { \\ efi_runtime_driver \\ }); - } } /// Model a header searchlist as a group. diff --git a/test/standalone/issue_5825/build.zig b/test/standalone/issue_5825/build.zig index 3b4bb33defa7bbbd9214b4d8d3e0a7ed14985654..e102febe2dcb702f7df29a6a0b95b8f36d2d096f 100644 --- a/test/standalone/issue_5825/build.zig +++ b/test/standalone/issue_5825/build.zig @@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void { .target = target, }), }); - exe.subsystem = .Console; + exe.subsystem = .console; exe.root_module.linkSystemLibrary("kernel32", .{}); exe.root_module.linkSystemLibrary("ntdll", .{}); exe.root_module.addObject(obj); diff --git a/test/standalone/windows_entry_points/build.zig b/test/standalone/windows_entry_points/build.zig index 1889b0c8435ff665fcad98717c160bb939c2e113..c77a48ecd836dfd25cadf3c18ca3fc16101969a7 100644 --- a/test/standalone/windows_entry_points/build.zig +++ b/test/standalone/windows_entry_points/build.zig @@ -53,7 +53,7 @@ pub fn build(b: *std.Build) void { .link_libc = true, }), }); - // Note: `exe.subsystem = .Windows;` is not necessary + // Note: `exe.subsystem = .windows;` is not necessary exe.root_module.addCSourceFile(.{ .file = b.path("winmain.c") }); _ = exe.getEmittedBin(); @@ -71,7 +71,7 @@ pub fn build(b: *std.Build) void { }), }); exe.mingw_unicode_entry_point = true; - // Note: `exe.subsystem = .Windows;` is not necessary + // Note: `exe.subsystem = .windows;` is not necessary exe.root_module.addCSourceFile(.{ .file = b.path("wwinmain.c") }); _ = exe.getEmittedBin(); -- 2.54.0