Skip to content

Commit f9b8da3

Browse files
committed
Make entry points/inferred Windows subsystem conform better with conventions
Previously, start.zig would always export wWinMainCRTStartup as the entry symbol, regardless of whether `main` or `wWinMain` was used as the main. This meant that the linker was unable to differentiate between main/wWinMain and so anything with wWinMainCRTStartup would get the `.console` subsystem inferred. In other words, the added test would fail for `winmain_inferred` as it would have the `.console` subsystem instead of the expected `.windows` subsystem. This commit changes the relevant logic: - Only export wWinMainCRTStartup when using wWinMain - Export wmainCRTStartup for non-wWinMain main functions when libc is not linked - When libc is linked, then the libc runtime entry point is used and `main` is exported (this part hasn't changed) - Infer `.windows` subsystem when exporting WinMainCRTStartup/wWinMainCRTStartup/WinMain/wWinMain - Infer `.console` subsystem when exporting main/mainCRTStartup/wmainCRTStartup This is a breaking change in the sense that a compiler built before this commit will be unable to compile a non-wWinMain exe using the updated `start.zig`. This is because the previous code did not look for mainCRTStartup/wmainCRTStartup at all, so it'd fall back to assuming the entry point is wWinMainCRTStartup which is no longer exported when using a normal main and therefore hit `error: lld-link: <root>: undefined symbol: wWinMainCRTStartup`
1 parent 74d2536 commit f9b8da3

File tree

8 files changed

+224
-10
lines changed

8 files changed

+224
-10
lines changed

lib/std/start.zig

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ comptime {
3737
@export(&main2, .{ .name = "main" });
3838
}
3939
} else if (builtin.os.tag == .windows) {
40-
if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) {
41-
@export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
40+
if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "WinMainCRTStartup") and
41+
!@hasDecl(root, "mainCRTStartup") and !@hasDecl(root, "wmainCRTStartup"))
42+
{
43+
@export(&WinStartup2, .{ .name = "wmainCRTStartup" });
4244
}
4345
} else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) {
4446
if (@hasDecl(root, "main"))
@@ -65,7 +67,7 @@ comptime {
6567
if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
6668
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
6769
{
68-
@export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
70+
@export(&WinStartup, .{ .name = "wmainCRTStartup" });
6971
} else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
7072
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
7173
{
@@ -113,7 +115,7 @@ fn spirvMain2() callconv(.kernel) void {
113115
root.main();
114116
}
115117

116-
fn wWinMainCRTStartup2() callconv(.c) noreturn {
118+
fn WinStartup2() callconv(.c) noreturn {
117119
std.posix.exit(callMain());
118120
}
119121

src/codegen/llvm.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,8 @@ pub const Object = struct {
16421642
if (name.eqlSlice("main", ip)) flags.c_main = true;
16431643
if (name.eqlSlice("WinMain", ip)) flags.winmain = true;
16441644
if (name.eqlSlice("wWinMain", ip)) flags.wwinmain = true;
1645+
if (name.eqlSlice("mainCRTStartup", ip)) flags.main_crt_startup = true;
1646+
if (name.eqlSlice("wmainCRTStartup", ip)) flags.wmain_crt_startup = true;
16451647
if (name.eqlSlice("WinMainCRTStartup", ip)) flags.winmain_crt_startup = true;
16461648
if (name.eqlSlice("wWinMainCRTStartup", ip)) flags.wwinmain_crt_startup = true;
16471649
if (name.eqlSlice("DllMainCRTStartup", ip)) flags.dllmain_crt_startup = true;

src/link/Lld.zig

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const Coff = struct {
2525
c_main: bool,
2626
winmain: bool,
2727
wwinmain: bool,
28+
main_crt_startup: bool,
29+
wmain_crt_startup: bool,
2830
winmain_crt_startup: bool,
2931
wwinmain_crt_startup: bool,
3032
dllmain_crt_startup: bool,
@@ -64,6 +66,8 @@ const Coff = struct {
6466
.c_main = false,
6567
.winmain = false,
6668
.wwinmain = false,
69+
.main_crt_startup = false,
70+
.wmain_crt_startup = false,
6771
.winmain_crt_startup = false,
6872
.wwinmain_crt_startup = false,
6973
.dllmain_crt_startup = false,
@@ -562,12 +566,14 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
562566
if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib)
563567
break :blk null;
564568
if (coff.lld_export_flags.c_main or comp.config.is_test or
565-
coff.lld_export_flags.winmain_crt_startup or
566-
coff.lld_export_flags.wwinmain_crt_startup)
569+
coff.lld_export_flags.main_crt_startup or
570+
coff.lld_export_flags.wmain_crt_startup)
567571
{
568572
break :blk .console;
569573
}
570-
if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain)
574+
if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain or
575+
coff.lld_export_flags.winmain_crt_startup or
576+
coff.lld_export_flags.wwinmain_crt_startup)
571577
break :blk .windows;
572578
}
573579
},
@@ -660,13 +666,19 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
660666
try argv.append("-NODEFAULTLIB");
661667
if (!is_lib and entry_name == null) {
662668
if (comp.zcu != null) {
663-
if (coff.lld_export_flags.winmain_crt_startup) {
669+
if (coff.lld_export_flags.wwinmain_crt_startup) {
670+
try argv.append("-ENTRY:wWinMainCRTStartup");
671+
} else if (coff.lld_export_flags.winmain_crt_startup) {
664672
try argv.append("-ENTRY:WinMainCRTStartup");
673+
} else if (coff.lld_export_flags.wmain_crt_startup) {
674+
try argv.append("-ENTRY:wmainCRTStartup");
675+
} else if (coff.lld_export_flags.main_crt_startup) {
676+
try argv.append("-ENTRY:mainCRTStartup");
665677
} else {
666-
try argv.append("-ENTRY:wWinMainCRTStartup");
678+
try argv.append("-ENTRY:wmainCRTStartup");
667679
}
668680
} else {
669-
try argv.append("-ENTRY:wWinMainCRTStartup");
681+
try argv.append("-ENTRY:wmainCRTStartup");
670682
}
671683
}
672684
}

test/standalone/build.zig.zon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@
120120
.windows_spawn = .{
121121
.path = "windows_spawn",
122122
},
123+
.windows_subsystem = .{
124+
.path = "windows_subsystem",
125+
},
123126
.windows_argv = .{
124127
.path = "windows_argv",
125128
},
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
const std = @import("std");
2+
const builtin = @import("builtin");
3+
4+
pub fn build(b: *std.Build) void {
5+
const test_step = b.step("test", "Test it");
6+
b.default_step = test_step;
7+
8+
const optimize: std.builtin.OptimizeMode = .Debug;
9+
const target = b.graph.host;
10+
11+
if (builtin.os.tag != .windows) return;
12+
13+
const expected_console_subsystem = std.fmt.comptimePrint("{}\n", .{@intFromEnum(std.coff.Subsystem.WINDOWS_CUI)});
14+
const expected_windows_subsystem = std.fmt.comptimePrint("{}\n", .{@intFromEnum(std.coff.Subsystem.WINDOWS_GUI)});
15+
16+
// Normal Zig main, no libc linked
17+
{
18+
const main_mod = b.createModule(.{
19+
.root_source_file = b.path("main.zig"),
20+
.optimize = optimize,
21+
.target = target,
22+
});
23+
24+
const main_inferred = b.addExecutable(.{
25+
.name = "main_inferred",
26+
.root_module = main_mod,
27+
});
28+
const run_inferred = b.addRunArtifact(main_inferred);
29+
run_inferred.expectStdErrEqual(expected_console_subsystem);
30+
run_inferred.expectExitCode(0);
31+
test_step.dependOn(&run_inferred.step);
32+
33+
const main_console = b.addExecutable(.{
34+
.name = "main_console",
35+
.root_module = main_mod,
36+
});
37+
main_console.subsystem = .console;
38+
const run_console = b.addRunArtifact(main_console);
39+
run_console.expectStdErrEqual(expected_console_subsystem);
40+
run_console.expectExitCode(0);
41+
test_step.dependOn(&run_console.step);
42+
43+
const main_windows = b.addExecutable(.{
44+
.name = "main_windows",
45+
.root_module = main_mod,
46+
});
47+
main_windows.subsystem = .windows;
48+
const run_windows = b.addRunArtifact(main_windows);
49+
run_windows.expectStdErrEqual(expected_windows_subsystem);
50+
run_windows.expectExitCode(0);
51+
test_step.dependOn(&run_windows.step);
52+
}
53+
54+
// Normal Zig main, libc linked
55+
{
56+
const main_link_libc_mod = b.createModule(.{
57+
.root_source_file = b.path("main.zig"),
58+
.optimize = optimize,
59+
.target = target,
60+
.link_libc = true,
61+
});
62+
63+
const main_link_libc_inferred = b.addExecutable(.{
64+
.name = "main_link_libc_inferred",
65+
.root_module = main_link_libc_mod,
66+
});
67+
const run_inferred = b.addRunArtifact(main_link_libc_inferred);
68+
run_inferred.expectStdErrEqual(expected_console_subsystem);
69+
run_inferred.expectExitCode(0);
70+
test_step.dependOn(&run_inferred.step);
71+
72+
const main_link_libc_console = b.addExecutable(.{
73+
.name = "main_link_libc_console",
74+
.root_module = main_link_libc_mod,
75+
});
76+
main_link_libc_console.subsystem = .console;
77+
const run_console = b.addRunArtifact(main_link_libc_console);
78+
run_console.expectStdErrEqual(expected_console_subsystem);
79+
run_console.expectExitCode(0);
80+
test_step.dependOn(&run_console.step);
81+
82+
const main_link_libc_windows = b.addExecutable(.{
83+
.name = "main_link_libc_windows",
84+
.root_module = main_link_libc_mod,
85+
});
86+
main_link_libc_windows.subsystem = .windows;
87+
const run_windows = b.addRunArtifact(main_link_libc_windows);
88+
run_windows.expectStdErrEqual(expected_windows_subsystem);
89+
run_windows.expectExitCode(0);
90+
test_step.dependOn(&run_windows.step);
91+
}
92+
93+
// wWinMain
94+
{
95+
const winmain_mod = b.createModule(.{
96+
.root_source_file = b.path("winmain.zig"),
97+
.optimize = optimize,
98+
.target = target,
99+
});
100+
101+
const winmain_inferred = b.addExecutable(.{
102+
.name = "winmain_inferred",
103+
.root_module = winmain_mod,
104+
});
105+
const run_inferred = b.addRunArtifact(winmain_inferred);
106+
run_inferred.expectStdErrEqual(expected_windows_subsystem);
107+
run_inferred.expectExitCode(0);
108+
test_step.dependOn(&run_inferred.step);
109+
110+
const winmain_console = b.addExecutable(.{
111+
.name = "winmain_console",
112+
.root_module = winmain_mod,
113+
});
114+
winmain_console.subsystem = .console;
115+
const run_console = b.addRunArtifact(winmain_console);
116+
run_console.expectStdErrEqual(expected_console_subsystem);
117+
run_console.expectExitCode(0);
118+
test_step.dependOn(&run_console.step);
119+
120+
const winmain_windows = b.addExecutable(.{
121+
.name = "winmain_windows",
122+
.root_module = winmain_mod,
123+
});
124+
winmain_windows.subsystem = .windows;
125+
const run_windows = b.addRunArtifact(winmain_windows);
126+
run_windows.expectStdErrEqual(expected_windows_subsystem);
127+
run_windows.expectExitCode(0);
128+
test_step.dependOn(&run_windows.step);
129+
}
130+
131+
// exported callconv(.c) main, libc must be linked
132+
{
133+
const cmain_mod = b.createModule(.{
134+
.root_source_file = b.path("cmain.zig"),
135+
.optimize = optimize,
136+
.target = target,
137+
.link_libc = true,
138+
});
139+
140+
const cmain_inferred = b.addExecutable(.{
141+
.name = "cmain_inferred",
142+
.root_module = cmain_mod,
143+
});
144+
const run_inferred = b.addRunArtifact(cmain_inferred);
145+
run_inferred.expectStdErrEqual(expected_console_subsystem);
146+
run_inferred.expectExitCode(0);
147+
test_step.dependOn(&run_inferred.step);
148+
149+
const cmain_console = b.addExecutable(.{
150+
.name = "cmain_console",
151+
.root_module = cmain_mod,
152+
});
153+
cmain_console.subsystem = .console;
154+
const run_console = b.addRunArtifact(cmain_console);
155+
run_console.expectStdErrEqual(expected_console_subsystem);
156+
run_console.expectExitCode(0);
157+
test_step.dependOn(&run_console.step);
158+
159+
const cmain_windows = b.addExecutable(.{
160+
.name = "cmain_windows",
161+
.root_module = cmain_mod,
162+
});
163+
cmain_windows.subsystem = .windows;
164+
const run_windows = b.addRunArtifact(cmain_windows);
165+
run_windows.expectStdErrEqual(expected_windows_subsystem);
166+
run_windows.expectExitCode(0);
167+
test_step.dependOn(&run_windows.step);
168+
}
169+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const std = @import("std");
2+
3+
pub export fn main() callconv(.c) c_int {
4+
std.debug.print("{}\n", .{std.os.windows.peb().ImageSubSystem});
5+
return 0;
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const std = @import("std");
2+
3+
pub fn main() void {
4+
std.debug.print("{}\n", .{std.os.windows.peb().ImageSubSystem});
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const std = @import("std");
2+
3+
pub fn wWinMain(
4+
inst: std.os.windows.HINSTANCE,
5+
prev: ?std.os.windows.HINSTANCE,
6+
cmd_line: std.os.windows.LPWSTR,
7+
cmd_show: c_int,
8+
) std.os.windows.INT {
9+
_ = inst;
10+
_ = prev;
11+
_ = cmd_line;
12+
_ = cmd_show;
13+
std.debug.print("{}\n", .{std.os.windows.peb().ImageSubSystem});
14+
return 0;
15+
}

0 commit comments

Comments
 (0)