1// This file is included in the compilation unit when exporting an executable.
2
3const builtin = @import("builtin");
4const native_arch = builtin.cpu.arch;
5const native_os = builtin.os.tag;
6const is_wasm = native_arch.isWasm();
7
8const std = @import("std.zig");
9const assert = std.debug.assert;
10const uefi = std.os.uefi;
11const elf = std.elf;
12
13const root = @import("root");
14
15const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";
16
17comptime {
18 // No matter what, we import the root file, so that any export, test, comptime
19 // decls there get run.
20 _ = root;
21
22 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
23 const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup";
24 if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) {
25 @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup });
26 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "DllMain")) {
27 if (!@typeInfo(@TypeOf(root.DllMain)).@"fn".attrs.@"callconv".eql(.winapi)) {
28 @export(&DllMain, .{ .name = "DllMain" });
29 }
30 }
31 } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
32 if (builtin.link_libc and @hasDecl(root, "main")) {
33 if (is_wasm) {
34 @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });
35 } else if (!@typeInfo(@TypeOf(root.main)).@"fn".attrs.@"callconv".eql(.c)) {
36 @export(&main, .{ .name = "main" });
37 }
38 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {
39 if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".attrs.@"callconv".eql(.c)) {
40 @export(&wWinMain, .{ .name = "wWinMain" });
41 }
42 } else if (native_os == .windows) {
43 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
44 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
45 {
46 @export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
47 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
48 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
49 {
50 @compileError("WinMain not supported; declare wWinMain or main instead");
51 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
52 !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
53 {
54 @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
55 }
56 } else if (native_os == .uefi) {
57 if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
58 } else if (native_os == .wasi) {
59 const wasm_start_sym = switch (builtin.wasi_exec_model) {
60 .reactor => "_initialize",
61 .command => "_start",
62 };
63 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
64 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
65 // case it's not required to provide an entrypoint such as main.
66 @export(&startWasi, .{ .name = wasm_start_sym });
67 }
68 } else if (is_wasm and native_os == .freestanding) {
69 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
70 // case it's not required to provide an entrypoint such as main.
71 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
72 } else switch (native_os) {
73 .other,
74 .freestanding,
75 .vulkan,
76 .opengl,
77 .opencl,
78
79 .@"3ds",
80 .wiiu,
81 .@"switch",
82 .gba,
83
84 .psx,
85 .psp,
86 .vita,
87 => {},
88 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
89 }
90 }
91}
92
93fn DllMainCRTStartup(
94 hinstDLL: std.os.windows.HINSTANCE,
95 fdwReason: std.os.windows.DWORD,
96 lpReserved: std.os.windows.LPVOID,
97) callconv(.winapi) std.os.windows.BOOL {
98 if (!builtin.single_threaded) {
99 _ = @import("os/windows/tls.zig");
100 }
101
102 if (@hasDecl(root, "DllMain")) {
103 return root.DllMain(@ptrCast(hinstDLL), fdwReason, lpReserved);
104 }
105
106 return .TRUE;
107}
108
109fn DllMain(
110 hinstDLL: std.os.windows.HINSTANCE,
111 fdwReason: std.os.windows.DWORD,
112 lpReserved: std.os.windows.LPVOID,
113) callconv(.winapi) std.os.windows.BOOL {
114 return root.DllMain(@ptrCast(hinstDLL), fdwReason, lpReserved);
115}
116
117fn wasm_freestanding_start() callconv(.c) void {
118 // This is marked inline because for some reason LLVM in
119 // release mode fails to inline it, and we want fewer call frames in stack traces.
120 _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global });
121}
122
123fn startWasi() callconv(.c) void {
124 // The function call is marked inline because for some reason LLVM in
125 // release mode fails to inline it, and we want fewer call frames in stack traces.
126 switch (builtin.wasi_exec_model) {
127 .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }),
128 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
129 }
130}
131
132fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv(.c) usize {
133 uefi.handle = handle;
134 uefi.system_table = system_table;
135
136 switch (@typeInfo(@TypeOf(root.main)).@"fn".return_type.?) {
137 noreturn => {
138 root.main();
139 },
140 void => {
141 root.main();
142 return 0;
143 },
144 uefi.Status => {
145 return @backingInt(root.main());
146 },
147 uefi.Error!void => {
148 root.main() catch |err| switch (err) {
149 error.Unexpected => @panic("EfiMain: unexpected error"),
150 else => {
151 const status = uefi.Status.fromError(@errorCast(err));
152 return @backingInt(status);
153 },
154 };
155
156 return 0;
157 },
158 else => @compileError(
159 "expected return type of main to be 'void', 'noreturn', " ++
160 "'uefi.Status', or 'uefi.Error!void'",
161 ),
162 }
163}
164
165fn _start() callconv(.naked) noreturn {
166 // TODO set Top of Stack on non x86_64-plan9
167 if (native_os == .plan9 and native_arch == .x86_64) {
168 // from /sys/src/libc/amd64/main9.s
169 std.os.plan9.tos = asm volatile (""
170 : [tos] "={rax}" (-> *std.os.plan9.Tos),
171 );
172 }
173
174 // This is the first userspace frame. Prevent DWARF-based unwinders from unwinding further. We
175 // prevent FP-based unwinders from unwinding further by zeroing the register below.
176 if (builtin.unwind_tables != .none or !builtin.strip_debug_info) asm volatile (switch (native_arch) {
177 .aarch64, .aarch64_be => ".cfi_undefined lr",
178 .alpha => ".cfi_undefined $26",
179 .arc, .arceb => ".cfi_undefined blink",
180 .arm, .armeb, .thumb, .thumbeb => "", // https://github.com/llvm/llvm-project/issues/115891
181 .csky => ".cfi_undefined lr",
182 .hexagon => ".cfi_undefined r31",
183 .kvx => ".cfi_undefined r14",
184 .loongarch32, .loongarch64 => if (builtin.zig_backend == .stage2_loongarch)
185 ""
186 else
187 ".cfi_undefined 1",
188 .m68k => ".cfi_undefined %%pc",
189 .m88k => ".cfi_undefined %%r1",
190 .microblaze, .microblazeel => "", // No CFI support.
191 .mips, .mipsel, .mips64, .mips64el => ".cfi_undefined $ra",
192 .or1k => ".cfi_undefined r9",
193 .powerpc, .powerpcle, .powerpc64, .powerpc64le => ".cfi_undefined lr",
194 .riscv32, .riscv32be, .riscv64, .riscv64be => if (builtin.zig_backend == .stage2_riscv64)
195 ""
196 else
197 ".cfi_undefined ra",
198 .s390x => ".cfi_undefined %%r14",
199 .sh, .sheb => ".cfi_undefined pr",
200 .sparc, .sparc64 => ".cfi_undefined %%i7",
201 .x86 => ".cfi_undefined %%eip",
202 .x86_64 => ".cfi_undefined %%rip",
203 .xtensa, .xtensaeb => "", // No CFI support.
204 else => @compileError("unsupported arch"),
205 });
206
207 // Move this to the riscv prong below when this is resolved: https://github.com/ziglang/zig/issues/20918
208 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend != .stage2_riscv64) asm volatile (
209 \\ .weak __global_pointer$
210 \\ .hidden __global_pointer$
211 \\ .option push
212 \\ .option norelax
213 \\ lla gp, __global_pointer$
214 \\ .option pop
215 );
216
217 // Note that we maintain a very low level of trust with regards to ABI guarantees at this point.
218 // We will redundantly align the stack, clear the link register, etc. While e.g. the Linux
219 // kernel is usually good about upholding the ABI guarantees, the same cannot be said of dynamic
220 // linkers; musl's ldso, for example, opts to not align the stack when invoking the dynamic
221 // linker explicitly.
222 if (builtin.zig_backend == .stage2_loongarch) {
223 // TODO: need "X" constraint support
224 asm volatile (switch (native_arch) {
225 .loongarch32 =>
226 \\ move $fp, $zero
227 \\ move $ra, $zero
228 \\ move $a0, $sp
229 \\ srli.w $sp, $sp, 4
230 \\ slli.w $sp, $sp, 4
231 \\ jirl $ra, %[posixCallMainAndExit], 0
232 ,
233 .loongarch64 =>
234 \\ move $fp, $zero
235 \\ move $ra, $zero
236 \\ move $a0, $sp
237 \\ bstrins.d $sp, $zero, 3, 0
238 \\ jirl $ra, %[posixCallMainAndExit], 0
239 ,
240 else => unreachable,
241 }
242 :
243 : [posixCallMainAndExit] "r" (&posixCallMainAndExit),
244 : .{ .r1 = true, .r4 = true, .r22 = true });
245 unreachable;
246 }
247
248 asm volatile (switch (native_arch) {
249 .x86_64 =>
250 \\ xorl %%ebp, %%ebp
251 \\ movq %%rsp, %%rdi
252 \\ andq $-16, %%rsp
253 \\ callq %[posixCallMainAndExit:P]
254 ,
255 .x86 =>
256 \\ xorl %%ebp, %%ebp
257 \\ movl %%esp, %%eax
258 \\ andl $-16, %%esp
259 \\ subl $12, %%esp
260 \\ pushl %%eax
261 \\ calll %[posixCallMainAndExit:P]
262 ,
263 .aarch64, .aarch64_be =>
264 \\ mov fp, #0
265 \\ mov lr, #0
266 \\ mov x0, sp
267 \\ and sp, x0, #-16
268 \\ b %[posixCallMainAndExit]
269 ,
270 .alpha =>
271 // $15 = FP, $26 = LR, $29 = GP, $30 = SP
272 \\ br $29, 1f
273 \\1:
274 \\ ldgp $29, 0($29)
275 \\ mov 0, $15
276 \\ mov 0, $26
277 \\ mov $30, $16
278 \\ ldi $1, -16
279 \\ and $30, $30, $1
280 \\ br $31, %[posixCallMainAndExit]
281 ,
282 .arc, .arceb =>
283 // ARC v1 and v2 had a very low stack alignment requirement of 4; v3 increased it to 16.
284 \\ mov fp, 0
285 \\ mov blink, 0
286 \\ mov r0, sp
287 \\ and sp, sp, -16
288 \\ b %[posixCallMainAndExit]
289 ,
290 .arm, .armeb, .thumb, .thumbeb =>
291 // Note that this code must work for Thumb-1.
292 // r7 = FP (local), r11 = FP (unwind)
293 \\ movs v1, #0
294 \\ mov r7, v1
295 \\ mov r11, v1
296 \\ mov lr, v1
297 \\ mov a1, sp
298 \\ subs v1, #16
299 \\ ands v1, a1
300 \\ mov sp, v1
301 \\ b %[posixCallMainAndExit]
302 ,
303 .csky =>
304 // The CSKY ABI assumes that `gb` is set to the address of the GOT in order for
305 // position-independent code to work. We depend on this in `std.pie` to locate
306 // `_DYNAMIC` as well.
307 // r8 = FP
308 \\ grs t0, 1f
309 \\1:
310 \\ lrw gb, 1b@GOTPC
311 \\ addu gb, t0
312 \\ movi r8, 0
313 \\ movi lr, 0
314 \\ mov a0, sp
315 \\ andni sp, sp, 7
316 \\ lrw t1, %[posixCallMainAndExit]@GOTOFF
317 \\ addu t1, gb
318 \\ jmp t1
319 ,
320 .hexagon =>
321 // r29 = SP, r30 = FP, r31 = LR
322 \\ r30 = #0
323 \\ r31 = #0
324 \\ r0 = r29
325 \\ r29 = and(r29, #-8)
326 \\ memw(r29 + #-8) = r29
327 \\ r29 = add(r29, #-8)
328 \\ jump %[posixCallMainAndExit]
329 ,
330 .kvx =>
331 \\ make $fp = 0
332 \\ ;;
333 \\ set $ra = $fp
334 \\ copyd $r0 = $sp
335 \\ andd $sp = $sp, -32
336 \\ ;;
337 \\ goto %[posixCallMainAndExit]
338 ,
339 .loongarch32 =>
340 \\ move $fp, $zero
341 \\ move $ra, $zero
342 \\ move $a0, $sp
343 \\ srli.w $sp, $sp, 4
344 \\ slli.w $sp, $sp, 4
345 \\ b %[posixCallMainAndExit]
346 ,
347 .loongarch64 =>
348 \\ move $fp, $zero
349 \\ move $ra, $zero
350 \\ move $a0, $sp
351 \\ bstrins.d $sp, $zero, 3, 0
352 \\ b %[posixCallMainAndExit]
353 ,
354 .or1k =>
355 // r1 = SP, r2 = FP, r9 = LR
356 \\ l.ori r2, r0, 0
357 \\ l.ori r9, r0, 0
358 \\ l.ori r3, r1, 0
359 \\ l.addi r13, r0, -4
360 \\ l.and r1, r1, r13
361 \\ l.j %[posixCallMainAndExit]
362 \\ l.nop
363 ,
364 .riscv32, .riscv32be, .riscv64, .riscv64be =>
365 \\ li fp, 0
366 \\ li ra, 0
367 \\ mv a0, sp
368 \\ andi sp, sp, -16
369 \\ tail %[posixCallMainAndExit]
370 ,
371 .m68k =>
372 // Note that the - 8 is needed because pc in the jsr instruction points into the middle
373 // of the jsr instruction. (The lea is 6 bytes, the jsr is 4 bytes.)
374 \\ suba.l %%fp, %%fp
375 \\ move.l %%sp, %%a0
376 \\ move.l %%a0, %%d0
377 \\ and.l #-4, %%d0
378 \\ move.l %%d0, %%sp
379 \\ move.l %%a0, -(%%sp)
380 \\ lea %[posixCallMainAndExit] - . - 8, %%a0
381 \\ jsr (%%pc, %%a0)
382 ,
383 .m88k =>
384 // r1 = LR, r30 = FP, r31 = SP
385 \\ or %%r0, %%r0, %%r0
386 \\ or %%r0, %%r0, %%r0
387 \\ or %%30, %%r0, %%r0
388 \\ or %%r1, %%r0, %%r0
389 \\ or %%r2, %%r31, %%r0
390 \\ clr %%r31, %%r31, 4<0>
391 \\ br %[posixCallMainAndExit]
392 ,
393 .microblaze, .microblazeel =>
394 // r1 = SP, r15 = LR, r19 = FP, r20 = GP
395 \\ ori r15, r0, 0
396 \\ ori r19, r0, 0
397 \\ mfs r20, rpc
398 \\ addi r20, r20, _GLOBAL_OFFSET_TABLE_ + 8
399 \\ ori r5, r1, 0
400 \\ andi r1, r1, -4
401 \\ bri %[posixCallMainAndExit]
402 ,
403 .mips, .mipsel =>
404 \\ move $fp, $zero
405 \\ bal 1f
406 \\ .gpword .
407 \\ .gpword %[posixCallMainAndExit]
408 \\1:
409 // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64.
410 \\ lw $gp, 0($ra)
411 \\ nop
412 \\ subu $gp, $ra, $gp
413 \\ lw $t9, 4($ra)
414 \\ nop
415 \\ addu $t9, $t9, $gp
416 \\ move $ra, $zero
417 \\ move $a0, $sp
418 \\ and $sp, -8
419 \\ subu $sp, $sp, 16
420 \\ jr $t9
421 ,
422 .mips64, .mips64el => switch (builtin.abi) {
423 .gnuabin32, .muslabin32, .abin32 =>
424 \\ move $fp, $zero
425 \\ bal 1f
426 \\ .gpword .
427 \\ .gpword %[posixCallMainAndExit]
428 \\1:
429 // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64.
430 \\ lw $gp, 0($ra)
431 \\ subu $gp, $ra, $gp
432 \\ lw $t9, 4($ra)
433 \\ addu $t9, $t9, $gp
434 \\ move $ra, $zero
435 \\ move $a0, $sp
436 \\ and $sp, -16
437 \\ subu $sp, $sp, 16
438 \\ jr $t9
439 ,
440 else =>
441 \\ move $fp, $zero
442 // This is needed because early MIPS versions don't support misaligned loads. Without
443 // this directive, the hidden `nop` inserted to fill the delay slot after `bal` would
444 // cause the two doublewords to be aligned to 4 bytes instead of 8.
445 \\ .balign 8
446 \\ bal 1f
447 \\ .gpdword .
448 \\ .gpdword %[posixCallMainAndExit]
449 \\1:
450 // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64.
451 \\ ld $gp, 0($ra)
452 \\ dsubu $gp, $ra, $gp
453 \\ ld $t9, 8($ra)
454 \\ daddu $t9, $t9, $gp
455 \\ move $ra, $zero
456 \\ move $a0, $sp
457 \\ and $sp, -16
458 \\ dsubu $sp, $sp, 16
459 \\ jr $t9
460 ,
461 },
462 .powerpc, .powerpcle =>
463 // Set up the initial stack frame, and clear the back chain pointer.
464 // r1 = SP, r31 = FP
465 \\ mr 3, 1
466 \\ clrrwi 1, 1, 4
467 \\ li 0, 0
468 \\ stwu 1, -16(1)
469 \\ stw 0, 0(1)
470 \\ li 31, 0
471 \\ mtlr 31
472 \\ b %[posixCallMainAndExit]
473 ,
474 .powerpc64, .powerpc64le =>
475 // Set up the ToC and initial stack frame, and clear the back chain pointer.
476 // r1 = SP, r2 = ToC, r31 = FP
477 \\ addis 2, 12, .TOC. - %[_start]@ha
478 \\ addi 2, 2, .TOC. - %[_start]@l
479 \\ mr 3, 1
480 \\ clrrdi 1, 1, 4
481 \\ li 0, 0
482 \\ stdu 0, -32(1)
483 \\ li 31, 0
484 \\ mtlr 31
485 \\ b %[posixCallMainAndExit]
486 \\ nop
487 ,
488 .s390x =>
489 // Set up the stack frame (register save area and cleared back-chain slot).
490 // r11 = FP, r14 = LR, r15 = SP
491 \\ lghi %%r11, 0
492 \\ lghi %%r14, 0
493 \\ lgr %%r2, %%r15
494 \\ lghi %%r0, -16
495 \\ ngr %%r15, %%r0
496 \\ aghi %%r15, -160
497 \\ lghi %%r0, 0
498 \\ stg %%r0, 0(%%r15)
499 \\ jg %[posixCallMainAndExit]
500 ,
501 .sh, .sheb =>
502 // r14 = FP, r15 = SP, pr = LR
503 \\ mov #0, r0
504 \\ lds r0, pr
505 \\ mov r0, r14
506 \\ mov r15, r4
507 \\ mov #-4, r0
508 \\ and r0, r15
509 \\ mova 2f, r0
510 \\ mov.l 2f, r1
511 \\ add r0, r1
512 \\ jmp @r1
513 \\ nop
514 \\ .balign 4
515 \\1:
516 \\ .long %[posixCallMainAndExit] - .
517 ,
518 .sparc =>
519 // argc is stored after a register window (16 registers * 4 bytes).
520 // i7 = LR
521 \\ mov %%g0, %%fp
522 \\ mov %%g0, %%i7
523 \\ add %%sp, 64, %%o0
524 \\ and %%sp, -8, %%sp
525 \\ ba,a %[posixCallMainAndExit]
526 ,
527 .sparc64 =>
528 // argc is stored after a register window (16 registers * 8 bytes) plus the stack bias
529 // (2047 bytes).
530 // i7 = LR
531 \\ mov %%g0, %%fp
532 \\ mov %%g0, %%i7
533 \\ add %%sp, 2175, %%o0
534 \\ add %%sp, 2047, %%sp
535 \\ and %%sp, -16, %%sp
536 \\ sub %%sp, 2047, %%sp
537 \\ ba,a %[posixCallMainAndExit]
538 ,
539 .xtensa, .xtensaeb => if (builtin.abi == .call0)
540 // a0 = LR, a15 = FP, a1 = SP
541 \\ movi a0, 0
542 \\ movi a15, 0
543 \\ mov a2, sp
544 \\ movi a8, -16
545 \\ and sp, sp, a8
546 \\ call0 %[posixCallMainAndExit]
547 else
548 // a0 = LR, a7 = FP, a1 = SP
549 \\ movi a0, 0
550 \\ movi a7, 0
551 \\ mov a6, sp
552 \\ movi a8, -16
553 \\ and sp, sp, a8
554 \\ call4 %[posixCallMainAndExit]
555 ,
556 else => @compileError("unsupported arch"),
557 }
558 :
559 : [_start] "X" (&_start),
560 [posixCallMainAndExit] "X" (&posixCallMainAndExit),
561 );
562}
563
564fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
565 // Switch from the x87 fpu state set by windows to the state expected by the gnu abi.
566 if (builtin.cpu.arch.isX86() and builtin.abi == .gnu) asm volatile ("fninit");
567
568 if (!builtin.single_threaded and !builtin.link_libc) {
569 _ = @import("os/windows/tls.zig");
570 }
571
572 std.Thread.maybeAttachSignalStack();
573 std.debug.maybeEnableSegfaultHandler();
574
575 std.os.windows.ntdll.RtlExitUserProcess(
576 callMain(std.os.windows.peb().ProcessParameters.CommandLine.slice(), .global),
577 );
578}
579
580fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
581 // Switch from the x87 fpu state set by windows to the state expected by the gnu abi.
582 if (builtin.cpu.arch.isX86() and builtin.abi == .gnu) asm volatile ("fninit");
583
584 if (!builtin.single_threaded and !builtin.link_libc) {
585 _ = @import("os/windows/tls.zig");
586 }
587
588 std.Thread.maybeAttachSignalStack();
589 std.debug.maybeEnableSegfaultHandler();
590
591 const result: std.os.windows.INT = call_wWinMain();
592 std.os.windows.ntdll.RtlExitUserProcess(@as(std.os.windows.UINT, @bitCast(result)));
593}
594
595fn wWinMain(hInstance: *anyopaque, hPrevInstance: ?*anyopaque, pCmdLine: [*:0]u16, nCmdShow: c_int) callconv(.c) c_int {
596 return root.wWinMain(@ptrCast(hInstance), @ptrCast(hPrevInstance), pCmdLine, @intCast(nCmdShow));
597}
598
599fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
600 // We're not ready to panic until thread local storage is initialized.
601 @setRuntimeSafety(false);
602 // Code coverage instrumentation might try to use thread local variables.
603 @disableInstrumentation();
604 const argc = argc_argv_ptr[0];
605 const argv: [*][*:0]u8 = @ptrCast(argc_argv_ptr + 1);
606
607 const envp_optional: [*:null]?[*:0]u8 = @ptrCast(@alignCast(argv + argc + 1));
608 var envp_count: usize = 0;
609 while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
610 const envp = envp_optional[0..envp_count :null];
611
612 // Find the beginning of the auxiliary vector
613 const auxv: [*]elf.Auxv = @ptrCast(@alignCast(envp.ptr + envp_count + 1));
614
615 var at_hwcap: usize = 0;
616 const phdrs = init: {
617 var i: usize = 0;
618 var at_phdr: usize = 0;
619 var at_phnum: usize = 0;
620 while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
621 switch (auxv[i].a_type) {
622 elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
623 elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
624 elf.AT_HWCAP => at_hwcap = auxv[i].a_un.a_val,
625 else => continue,
626 }
627 }
628 break :init @as([*]elf.ElfN.Phdr, @ptrFromInt(at_phdr))[0..at_phnum];
629 };
630
631 // Apply the initial relocations as early as possible in the startup process. We cannot
632 // make calls yet on some architectures (e.g. MIPS) *because* they haven't been applied yet,
633 // so this must be fully inlined.
634 if (builtin.link_mode == .static and builtin.position_independent_executable) {
635 @call(.always_inline, std.pie.relocate, .{phdrs});
636 }
637
638 if (native_os == .linux) {
639 // This must be done after PIE relocations have been applied or we may crash
640 // while trying to access the global variable (happens on MIPS at least).
641 std.os.linux.elf_aux_maybe = auxv;
642
643 if (!builtin.single_threaded) {
644 // ARMv6 targets (and earlier) have no support for TLS in hardware.
645 // FIXME: Elide the check for targets >= ARMv7 when the target feature API
646 // becomes less verbose (and more usable).
647 if (comptime native_arch.isArm()) {
648 if (at_hwcap & std.os.linux.HWCAP.TLS == 0) {
649 // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls
650 // For the time being use a simple trap instead of a @panic call to
651 // keep the binary bloat under control.
652 @trap();
653 }
654 }
655
656 // Initialize the TLS area.
657 std.os.linux.tls.initStatic(phdrs);
658 }
659
660 // The way Linux executables represent stack size is via the PT.GNU_STACK
661 // program header. However the kernel does not recognize it; it always gives 8 MiB.
662 // Here we look for the stack size in our program headers and use setrlimit
663 // to ask for more stack space.
664 expandStackSize(phdrs);
665 }
666
667 const opt_init_array_start = @extern([*]const *const fn () callconv(.c) void, .{
668 .name = "__init_array_start",
669 .linkage = .weak,
670 });
671 const opt_init_array_end = @extern([*]const *const fn () callconv(.c) void, .{
672 .name = "__init_array_end",
673 .linkage = .weak,
674 });
675 if (opt_init_array_start) |init_array_start| {
676 const init_array_end = opt_init_array_end.?;
677 const slice = init_array_start[0 .. init_array_end - init_array_start];
678 for (slice) |func| func();
679 }
680
681 std.process.exit(callMainWithArgs(argc, argv, envp));
682}
683
684fn expandStackSize(phdrs: []elf.ElfN.Phdr) void {
685 @disableInstrumentation();
686 for (phdrs) |*phdr| {
687 switch (phdr.type) {
688 .GNU_STACK => {
689 if (phdr.memsz == 0) break;
690 assert(phdr.memsz % std.heap.page_size_min == 0);
691
692 // Silently fail if we are unable to get limits.
693 const limits = std.posix.getrlimit(.STACK) catch break;
694
695 // Clamp to limits.max .
696 const wanted_stack_size = @min(phdr.memsz, limits.max);
697
698 if (wanted_stack_size > limits.cur) {
699 std.posix.setrlimit(.STACK, .{
700 .cur = wanted_stack_size,
701 .max = limits.max,
702 }) catch {
703 // Because we could not increase the stack size to the upper bound,
704 // depending on what happens at runtime, a stack overflow may occur.
705 // However it would cause a segmentation fault, thanks to stack probing,
706 // so we do not have a memory safety issue here.
707 // This is intentional silent failure.
708 // This logic should be revisited when the following issues are addressed:
709 // https://github.com/ziglang/zig/issues/157
710 // https://github.com/ziglang/zig/issues/1006
711 };
712 }
713 break;
714 },
715 else => {},
716 }
717 }
718}
719
720inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
721 const env_block: std.process.Environ.Block = .{ .slice = envp };
722 if (std.Options.debug_threaded_io) |t| {
723 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
724 t.environ = .{ .process_environ = .{ .block = env_block } };
725 t.environ_initialized = env_block.isEmpty();
726 }
727 std.Thread.maybeAttachSignalStack();
728 std.debug.maybeEnableSegfaultHandler();
729 return callMain(argv[0..argc], env_block);
730}
731
732fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
733 var env_count: usize = 0;
734 while (c_envp[env_count] != null) : (env_count += 1) {}
735 const envp = c_envp[0..env_count :null];
736
737 switch (builtin.os.tag) {
738 .linux => {
739 const at_phdr = std.c.getauxval(elf.AT_PHDR);
740 const at_phnum = std.c.getauxval(elf.AT_PHNUM);
741 const phdrs = (@as([*]elf.ElfN.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum];
742 expandStackSize(phdrs);
743 },
744 .windows => {
745 // On Windows, we ignore libc environment and argv and get those
746 // values in their intended encoding from the PEB instead.
747 std.Thread.maybeAttachSignalStack();
748 std.debug.maybeEnableSegfaultHandler();
749 return callMain(std.os.windows.peb().ProcessParameters.CommandLine.slice(), .global);
750 },
751 else => {},
752 }
753
754 return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), @ptrCast(envp));
755}
756
757fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
758 const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];
759 const environ: [:null]?[*:0]u8 = switch (builtin.os.tag) {
760 .wasi, .emscripten => environ: {
761 const c_environ = std.c.environ;
762 var env_count: usize = 0;
763 while (c_environ[env_count] != null) : (env_count += 1) {}
764 break :environ c_environ[0..env_count :null];
765 },
766 else => &.{},
767 };
768 const env_block: std.process.Environ.Block = .{ .slice = environ };
769 if (std.Options.debug_threaded_io) |t| {
770 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
771 t.environ = .{ .process_environ = .{ .block = env_block } };
772 t.environ_initialized = env_block.isEmpty();
773 }
774 return callMain(argv, env_block);
775}
776
777/// General error message for a malformed return type
778const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
779
780const use_safe_allocator = !is_wasm and switch (builtin.mode) {
781 .debug, .safe => true,
782 .fast, .small => !builtin.link_libc and builtin.single_threaded, // Also not ideal.
783};
784var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
785
786inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 {
787 const fn_info = @typeInfo(@TypeOf(root.main)).@"fn";
788 if (fn_info.param_types.len == 0) return wrapMain(root.main());
789 if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
790 .args = .{ .vector = args },
791 .environ = .{ .block = environ },
792 }));
793
794 const gpa = if (use_safe_allocator)
795 safe_allocator.allocator()
796 else if (builtin.link_libc)
797 std.heap.c_allocator
798 else if (is_wasm)
799 std.heap.wasm_allocator
800 else if (!builtin.single_threaded)
801 std.heap.smp_allocator
802 else
803 comptime unreachable;
804
805 defer if (use_safe_allocator) {
806 _ = safe_allocator.deinit(); // Leaks do not affect return code.
807 };
808
809 const arena_backing_allocator = if (is_wasm) gpa else std.heap.page_allocator;
810
811 var arena_allocator = std.heap.ArenaAllocator.init(arena_backing_allocator);
812 defer arena_allocator.deinit();
813
814 var threaded: std.Io.Threaded = .init(gpa, .{
815 .argv0 = .init(.{ .vector = args }),
816 .environ = .{ .block = environ },
817 });
818 defer threaded.deinit();
819
820 var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err|
821 std.process.fatal("failed to parse environment variables: {t}", .{err});
822 defer environ_map.deinit();
823
824 const preopens = std.process.Preopens.init(arena_allocator.allocator()) catch |err|
825 std.process.fatal("failed to init preopens: {t}", .{err});
826
827 return wrapMain(root.main(.{
828 .minimal = .{
829 .args = .{ .vector = args },
830 .environ = .{ .block = environ },
831 },
832 .arena = &arena_allocator,
833 .gpa = gpa,
834 .io = threaded.io(),
835 .environ_map = &environ_map,
836 .preopens = preopens,
837 }));
838}
839
840inline fn wrapMain(result: anytype) u8 {
841 const ReturnType = @TypeOf(result);
842 switch (ReturnType) {
843 void => return 0,
844 noreturn => unreachable,
845 u8 => return result,
846 else => {},
847 }
848 if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret);
849
850 const unwrapped_result = result catch |err| {
851 std.log.err("{t}", .{err});
852 switch (native_os) {
853 .freestanding, .other => {},
854 else => if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace),
855 }
856 return 1;
857 };
858
859 return switch (@TypeOf(unwrapped_result)) {
860 noreturn => unreachable,
861 void => 0,
862 u8 => unwrapped_result,
863 else => @compileError(bad_main_ret),
864 };
865}
866
867fn call_wWinMain() std.os.windows.INT {
868 const peb = std.os.windows.peb();
869 const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".param_types[0].?;
870 const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress);
871 const lpCmdLine: [*:0]u16 = @ptrCast(peb.ProcessParameters.CommandLine.Buffer);
872
873 // There are various types used for the 'show window' variable through the Win32 APIs:
874 // - u16 in STARTUPINFOA.wShowWindow / STARTUPINFOW.wShowWindow
875 // - c_int in ShowWindow
876 // - u32 in PEB.ProcessParameters.dwShowWindow
877 // Since STARTUPINFO is the bottleneck for the allowed values, we use `u16` as the
878 // type which can coerce into i32/c_int/u32 depending on how the user defines their wWinMain
879 // (the Win32 docs show wWinMain with `int` as the type for nShowCmd).
880 const nShowCmd: u16 = nShowCmd: {
881 // This makes Zig match the nShowCmd behavior of a C program with a WinMain symbol:
882 // - With STARTF_USESHOWWINDOW set in STARTUPINFO.dwFlags of the CreateProcess call:
883 // - nShowCmd is STARTUPINFO.wShowWindow from the parent CreateProcess call
884 // - With STARTF_USESHOWWINDOW unset:
885 // - nShowCmd is always SW_SHOWDEFAULT
886 const SW_SHOWDEFAULT = 10;
887 if (peb.ProcessParameters.dwFlags & std.os.windows.STARTF_USESHOWWINDOW != 0) {
888 break :nShowCmd @truncate(peb.ProcessParameters.dwShowWindow);
889 }
890 break :nShowCmd SW_SHOWDEFAULT;
891 };
892
893 // second parameter hPrevInstance, MSDN: "This parameter is always NULL"
894 return root.wWinMain(hInstance, null, lpCmdLine, nShowCmd);
895}