zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob bca0922f (24401B) - Raw


      1 // This file is included in the compilation unit when exporting an executable.
      2 
      3 const root = @import("root");
      4 const std = @import("std.zig");
      5 const builtin = @import("builtin");
      6 const assert = std.debug.assert;
      7 const uefi = std.os.uefi;
      8 const elf = std.elf;
      9 const native_arch = builtin.cpu.arch;
     10 const native_os = builtin.os.tag;
     11 
     12 const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";
     13 
     14 // The self-hosted compiler is not fully capable of handling all of this start.zig file.
     15 // Until then, we have simplified logic here for self-hosted. TODO remove this once
     16 // self-hosted is capable enough to handle all of the real start.zig logic.
     17 pub const simplified_logic =
     18     builtin.zig_backend == .stage2_x86 or
     19     builtin.zig_backend == .stage2_aarch64 or
     20     builtin.zig_backend == .stage2_arm or
     21     builtin.zig_backend == .stage2_sparc64 or
     22     builtin.cpu.arch == .spirv32 or
     23     builtin.cpu.arch == .spirv64;
     24 
     25 comptime {
     26     // No matter what, we import the root file, so that any export, test, comptime
     27     // decls there get run.
     28     _ = root;
     29 
     30     if (simplified_logic) {
     31         if (builtin.output_mode == .Exe) {
     32             if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) {
     33                 if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
     34                     @export(main2, .{ .name = "main" });
     35                 }
     36             } else if (builtin.os.tag == .windows) {
     37                 if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) {
     38                     @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
     39                 }
     40             } else if (builtin.os.tag == .opencl) {
     41                 if (@hasDecl(root, "main"))
     42                     @export(spirvMain2, .{ .name = "main" });
     43             } else {
     44                 if (!@hasDecl(root, "_start")) {
     45                     @export(_start2, .{ .name = "_start" });
     46                 }
     47             }
     48         }
     49     } else {
     50         if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
     51             if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
     52                 @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
     53             }
     54         } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
     55             if (builtin.link_libc and @hasDecl(root, "main")) {
     56                 if (native_arch.isWasm()) {
     57                     @export(mainWithoutEnv, .{ .name = "main" });
     58                 } else if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
     59                     @export(main, .{ .name = "main" });
     60                 }
     61             } else if (native_os == .windows) {
     62                 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
     63                     !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
     64                 {
     65                     @export(WinStartup, .{ .name = "wWinMainCRTStartup" });
     66                 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
     67                     !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
     68                 {
     69                     @compileError("WinMain not supported; declare wWinMain or main instead");
     70                 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
     71                     !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
     72                 {
     73                     @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
     74                 }
     75             } else if (native_os == .uefi) {
     76                 if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
     77             } else if (native_os == .wasi) {
     78                 const wasm_start_sym = switch (builtin.wasi_exec_model) {
     79                     .reactor => "_initialize",
     80                     .command => "_start",
     81                 };
     82                 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
     83                     // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
     84                     // case it's not required to provide an entrypoint such as main.
     85                     @export(wasi_start, .{ .name = wasm_start_sym });
     86                 }
     87             } else if (native_arch.isWasm() and native_os == .freestanding) {
     88                 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
     89                 // case it's not required to provide an entrypoint such as main.
     90                 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(wasm_freestanding_start, .{ .name = start_sym_name });
     91             } else if (native_os != .other and native_os != .freestanding) {
     92                 if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
     93             }
     94         }
     95     }
     96 }
     97 
     98 // Simplified start code for stage2 until it supports more language features ///
     99 
    100 fn main2() callconv(.C) c_int {
    101     root.main();
    102     return 0;
    103 }
    104 
    105 fn _start2() callconv(.C) noreturn {
    106     callMain2();
    107 }
    108 
    109 fn callMain2() noreturn {
    110     @setAlignStack(16);
    111     root.main();
    112     exit2(0);
    113 }
    114 
    115 fn spirvMain2() callconv(.Kernel) void {
    116     root.main();
    117 }
    118 
    119 fn wWinMainCRTStartup2() callconv(.C) noreturn {
    120     root.main();
    121     exit2(0);
    122 }
    123 
    124 fn exit2(code: usize) noreturn {
    125     switch (native_os) {
    126         .linux => switch (builtin.cpu.arch) {
    127             .x86_64 => {
    128                 asm volatile ("syscall"
    129                     :
    130                     : [number] "{rax}" (231),
    131                       [arg1] "{rdi}" (code),
    132                     : "rcx", "r11", "memory"
    133                 );
    134             },
    135             .arm => {
    136                 asm volatile ("svc #0"
    137                     :
    138                     : [number] "{r7}" (1),
    139                       [arg1] "{r0}" (code),
    140                     : "memory"
    141                 );
    142             },
    143             .aarch64 => {
    144                 asm volatile ("svc #0"
    145                     :
    146                     : [number] "{x8}" (93),
    147                       [arg1] "{x0}" (code),
    148                     : "memory", "cc"
    149                 );
    150             },
    151             .sparc64 => {
    152                 asm volatile ("ta 0x6d"
    153                     :
    154                     : [number] "{g1}" (1),
    155                       [arg1] "{o0}" (code),
    156                     : "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory"
    157                 );
    158             },
    159             else => @compileError("TODO"),
    160         },
    161         // exits(0)
    162         .plan9 => std.os.plan9.exits(null),
    163         .windows => {
    164             std.os.windows.ntdll.RtlExitUserProcess(@as(u32, @truncate(code)));
    165         },
    166         else => @compileError("TODO"),
    167     }
    168     unreachable;
    169 }
    170 
    171 ////////////////////////////////////////////////////////////////////////////////
    172 
    173 fn _DllMainCRTStartup(
    174     hinstDLL: std.os.windows.HINSTANCE,
    175     fdwReason: std.os.windows.DWORD,
    176     lpReserved: std.os.windows.LPVOID,
    177 ) callconv(std.os.windows.WINAPI) std.os.windows.BOOL {
    178     if (!builtin.single_threaded and !builtin.link_libc) {
    179         _ = @import("start_windows_tls.zig");
    180     }
    181 
    182     if (@hasDecl(root, "DllMain")) {
    183         return root.DllMain(hinstDLL, fdwReason, lpReserved);
    184     }
    185 
    186     return std.os.windows.TRUE;
    187 }
    188 
    189 fn wasm_freestanding_start() callconv(.C) void {
    190     // This is marked inline because for some reason LLVM in
    191     // release mode fails to inline it, and we want fewer call frames in stack traces.
    192     _ = @call(.always_inline, callMain, .{});
    193 }
    194 
    195 fn wasi_start() callconv(.C) void {
    196     // The function call is marked inline because for some reason LLVM in
    197     // release mode fails to inline it, and we want fewer call frames in stack traces.
    198     switch (builtin.wasi_exec_model) {
    199         .reactor => _ = @call(.always_inline, callMain, .{}),
    200         .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{})),
    201     }
    202 }
    203 
    204 fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv(.C) usize {
    205     uefi.handle = handle;
    206     uefi.system_table = system_table;
    207 
    208     switch (@typeInfo(@TypeOf(root.main)).Fn.return_type.?) {
    209         noreturn => {
    210             root.main();
    211         },
    212         void => {
    213             root.main();
    214             return 0;
    215         },
    216         usize => {
    217             return root.main();
    218         },
    219         uefi.Status => {
    220             return @intFromEnum(root.main());
    221         },
    222         else => @compileError("expected return type of main to be 'void', 'noreturn', 'usize', or 'std.os.uefi.Status'"),
    223     }
    224 }
    225 
    226 fn _start() callconv(.Naked) noreturn {
    227     // TODO set Top of Stack on non x86_64-plan9
    228     if (native_os == .plan9 and native_arch == .x86_64) {
    229         // from /sys/src/libc/amd64/main9.s
    230         std.os.plan9.tos = asm volatile (""
    231             : [tos] "={rax}" (-> *std.os.plan9.Tos),
    232         );
    233     }
    234 
    235     // Note that we maintain a very low level of trust with regards to ABI guarantees at this point.
    236     // We will redundantly align the stack, clear the link register, etc. While e.g. the Linux
    237     // kernel is usually good about upholding the ABI guarantees, the same cannot be said of dynamic
    238     // linkers; musl's ldso, for example, opts to not align the stack when invoking the dynamic
    239     // linker explicitly.
    240     asm volatile (switch (native_arch) {
    241             .x86_64 =>
    242             \\ xorl %%ebp, %%ebp
    243             \\ movq %%rsp, %%rdi
    244             \\ andq $-16, %%rsp
    245             \\ callq %[posixCallMainAndExit:P]
    246             ,
    247             .x86 =>
    248             \\ xorl %%ebp, %%ebp
    249             \\ movl %%esp, %%eax
    250             \\ andl $-16, %%esp
    251             \\ subl $12, %%esp
    252             \\ pushl %%eax
    253             \\ calll %[posixCallMainAndExit:P]
    254             ,
    255             .aarch64, .aarch64_be =>
    256             \\ mov fp, #0
    257             \\ mov lr, #0
    258             \\ mov x0, sp
    259             \\ and sp, x0, #-16
    260             \\ b %[posixCallMainAndExit]
    261             ,
    262             .arm, .armeb, .thumb, .thumbeb =>
    263             \\ mov fp, #0
    264             \\ mov lr, #0
    265             \\ mov a1, sp
    266             \\ and sp, #-16
    267             \\ b %[posixCallMainAndExit]
    268             ,
    269             .loongarch32, .loongarch64 =>
    270             \\ move $fp, $zero
    271             \\ move $a0, $sp
    272             \\ bstrins.d $sp, $zero, 3, 0
    273             \\ b %[posixCallMainAndExit]
    274             ,
    275             .riscv32, .riscv64 =>
    276             \\ li s0, 0
    277             \\ li ra, 0
    278             \\ mv a0, sp
    279             \\ andi sp, sp, -16
    280             \\ tail %[posixCallMainAndExit]@plt
    281             ,
    282             .m68k =>
    283             // Note that the - 8 is needed because pc in the jsr instruction points into the middle
    284             // of the jsr instruction. (The lea is 6 bytes, the jsr is 4 bytes.)
    285             \\ suba.l %%fp, %%fp
    286             \\ move.l %%sp, -(%%sp)
    287             \\ lea %[posixCallMainAndExit] - . - 8, %%a0
    288             \\ jsr (%%pc, %%a0)
    289             ,
    290             .mips, .mipsel =>
    291             \\ move $fp, $0
    292             \\ bal 1f
    293             \\ .gpword .
    294             \\ .gpword %[posixCallMainAndExit]
    295             \\ 1:
    296             // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64.
    297             \\ lw $gp, 0($ra)
    298             \\ subu $gp, $ra, $gp
    299             \\ lw $25, 4($ra)
    300             \\ addu $25, $25, $gp
    301             \\ move $ra, $0
    302             \\ move $a0, $sp
    303             \\ and $sp, -8
    304             \\ subu $sp, $sp, 16
    305             \\ jalr $25
    306             ,
    307             .mips64, .mips64el =>
    308             \\ move $fp, $0
    309             // This is needed because early MIPS versions don't support misaligned loads. Without
    310             // this directive, the hidden `nop` inserted to fill the delay slot after `bal` would
    311             // cause the two doublewords to be aligned to 4 bytes instead of 8.
    312             \\ .balign 8
    313             \\ bal 1f
    314             \\ .gpdword .
    315             \\ .gpdword %[posixCallMainAndExit]
    316             \\ 1:
    317             // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64.
    318             \\ ld $gp, 0($ra)
    319             \\ dsubu $gp, $ra, $gp
    320             \\ ld $25, 8($ra)
    321             \\ daddu $25, $25, $gp
    322             \\ move $ra, $0
    323             \\ move $a0, $sp
    324             \\ and $sp, -16
    325             \\ dsubu $sp, $sp, 16
    326             \\ jalr $25
    327             ,
    328             .powerpc, .powerpcle =>
    329             // Set up the initial stack frame, and clear the back chain pointer.
    330             \\ mr 3, 1
    331             \\ clrrwi 1, 1, 4
    332             \\ li 0, 0
    333             \\ stwu 1, -16(1)
    334             \\ stw 0, 0(1)
    335             \\ mtlr 0
    336             \\ b %[posixCallMainAndExit]
    337             ,
    338             .powerpc64, .powerpc64le =>
    339             // Set up the ToC and initial stack frame, and clear the back chain pointer.
    340             \\ addis 2, 12, .TOC. - %[_start]@ha
    341             \\ addi 2, 2, .TOC. - %[_start]@l
    342             \\ mr 3, 1
    343             \\ clrrdi 1, 1, 4
    344             \\ li 0, 0
    345             \\ stdu 0, -32(1)
    346             \\ mtlr 0
    347             \\ b %[posixCallMainAndExit]
    348             ,
    349             .s390x =>
    350             // Set up the stack frame (register save area and cleared back-chain slot).
    351             \\ lgr %%r2, %%r15
    352             \\ lghi %%r0, -16
    353             \\ ngr %%r15, %%r0
    354             \\ aghi %%r15, -160
    355             \\ lghi %%r0, 0
    356             \\ stg  %%r0, 0(%%r15)
    357             \\ jg %[posixCallMainAndExit]
    358             ,
    359             .sparc64 =>
    360             // argc is stored after a register window (16 registers * 8 bytes) plus the stack bias
    361             // (2047 bytes).
    362             \\ mov %%g0, %%fp
    363             \\ add %%sp, 2175, %%o0
    364             \\ add %%sp, 2047, %%sp
    365             \\ and %%sp, -16, %%sp
    366             \\ sub %%sp, 2047, %%sp
    367             \\ ba,a %[posixCallMainAndExit]
    368             ,
    369             else => @compileError("unsupported arch"),
    370         }
    371         :
    372         : [_start] "X" (_start),
    373           [posixCallMainAndExit] "X" (&posixCallMainAndExit),
    374     );
    375 }
    376 
    377 fn WinStartup() callconv(std.os.windows.WINAPI) noreturn {
    378     @setAlignStack(16);
    379     if (!builtin.single_threaded and !builtin.link_libc) {
    380         _ = @import("start_windows_tls.zig");
    381     }
    382 
    383     std.debug.maybeEnableSegfaultHandler();
    384 
    385     std.os.windows.ntdll.RtlExitUserProcess(callMain());
    386 }
    387 
    388 fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn {
    389     @setAlignStack(16);
    390     if (!builtin.single_threaded and !builtin.link_libc) {
    391         _ = @import("start_windows_tls.zig");
    392     }
    393 
    394     std.debug.maybeEnableSegfaultHandler();
    395 
    396     const result: std.os.windows.INT = call_wWinMain();
    397     std.os.windows.ntdll.RtlExitUserProcess(@as(std.os.windows.UINT, @bitCast(result)));
    398 }
    399 
    400 fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn {
    401     // We're not ready to panic until thread local storage is initialized.
    402     @setRuntimeSafety(false);
    403     // Code coverage instrumentation might try to use thread local variables.
    404     @disableInstrumentation();
    405     const argc = argc_argv_ptr[0];
    406     const argv = @as([*][*:0]u8, @ptrCast(argc_argv_ptr + 1));
    407 
    408     const envp_optional: [*:null]?[*:0]u8 = @ptrCast(@alignCast(argv + argc + 1));
    409     var envp_count: usize = 0;
    410     while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
    411     const envp = @as([*][*:0]u8, @ptrCast(envp_optional))[0..envp_count];
    412 
    413     if (native_os == .linux) {
    414         // Find the beginning of the auxiliary vector
    415         const auxv: [*]elf.Auxv = @ptrCast(@alignCast(envp.ptr + envp_count + 1));
    416 
    417         var at_hwcap: usize = 0;
    418         const phdrs = init: {
    419             var i: usize = 0;
    420             var at_phdr: usize = 0;
    421             var at_phnum: usize = 0;
    422             while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
    423                 switch (auxv[i].a_type) {
    424                     elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
    425                     elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
    426                     elf.AT_HWCAP => at_hwcap = auxv[i].a_un.a_val,
    427                     else => continue,
    428                 }
    429             }
    430             break :init @as([*]elf.Phdr, @ptrFromInt(at_phdr))[0..at_phnum];
    431         };
    432 
    433         // Apply the initial relocations as early as possible in the startup process. We cannot
    434         // make calls yet on some architectures (e.g. MIPS) *because* they haven't been applied yet,
    435         // so this must be fully inlined.
    436         if (builtin.position_independent_executable) {
    437             @call(.always_inline, std.os.linux.pie.relocate, .{phdrs});
    438         }
    439 
    440         // This must be done after PIE relocations have been applied or we may crash
    441         // while trying to access the global variable (happens on MIPS at least).
    442         std.os.linux.elf_aux_maybe = auxv;
    443 
    444         if (!builtin.single_threaded) {
    445             // ARMv6 targets (and earlier) have no support for TLS in hardware.
    446             // FIXME: Elide the check for targets >= ARMv7 when the target feature API
    447             // becomes less verbose (and more usable).
    448             if (comptime native_arch.isARM()) {
    449                 if (at_hwcap & std.os.linux.HWCAP.TLS == 0) {
    450                     // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls
    451                     // For the time being use a simple trap instead of a @panic call to
    452                     // keep the binary bloat under control.
    453                     @trap();
    454                 }
    455             }
    456 
    457             // Initialize the TLS area.
    458             std.os.linux.tls.initStaticTLS(phdrs);
    459         }
    460 
    461         // The way Linux executables represent stack size is via the PT_GNU_STACK
    462         // program header. However the kernel does not recognize it; it always gives 8 MiB.
    463         // Here we look for the stack size in our program headers and use setrlimit
    464         // to ask for more stack space.
    465         expandStackSize(phdrs);
    466     }
    467 
    468     std.posix.exit(callMainWithArgs(argc, argv, envp));
    469 }
    470 
    471 fn expandStackSize(phdrs: []elf.Phdr) void {
    472     for (phdrs) |*phdr| {
    473         switch (phdr.p_type) {
    474             elf.PT_GNU_STACK => {
    475                 assert(phdr.p_memsz % std.mem.page_size == 0);
    476 
    477                 // Silently fail if we are unable to get limits.
    478                 const limits = std.posix.getrlimit(.STACK) catch break;
    479 
    480                 // Clamp to limits.max .
    481                 const wanted_stack_size = @min(phdr.p_memsz, limits.max);
    482 
    483                 if (wanted_stack_size > limits.cur) {
    484                     std.posix.setrlimit(.STACK, .{
    485                         .cur = wanted_stack_size,
    486                         .max = limits.max,
    487                     }) catch {
    488                         // Because we could not increase the stack size to the upper bound,
    489                         // depending on what happens at runtime, a stack overflow may occur.
    490                         // However it would cause a segmentation fault, thanks to stack probing,
    491                         // so we do not have a memory safety issue here.
    492                         // This is intentional silent failure.
    493                         // This logic should be revisited when the following issues are addressed:
    494                         // https://github.com/ziglang/zig/issues/157
    495                         // https://github.com/ziglang/zig/issues/1006
    496                     };
    497                 }
    498                 break;
    499             },
    500             else => {},
    501         }
    502     }
    503 }
    504 
    505 inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
    506     std.os.argv = argv[0..argc];
    507     std.os.environ = envp;
    508 
    509     std.debug.maybeEnableSegfaultHandler();
    510     maybeIgnoreSigpipe();
    511 
    512     return callMain();
    513 }
    514 
    515 fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.C) c_int {
    516     var env_count: usize = 0;
    517     while (c_envp[env_count] != null) : (env_count += 1) {}
    518     const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count];
    519 
    520     if (builtin.os.tag == .linux) {
    521         const at_phdr = std.c.getauxval(elf.AT_PHDR);
    522         const at_phnum = std.c.getauxval(elf.AT_PHNUM);
    523         const phdrs = (@as([*]elf.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum];
    524         expandStackSize(phdrs);
    525     }
    526 
    527     return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp);
    528 }
    529 
    530 fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.C) c_int {
    531     std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@as(usize, @intCast(c_argc))];
    532     return callMain();
    533 }
    534 
    535 // General error message for a malformed return type
    536 const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
    537 
    538 pub inline fn callMain() u8 {
    539     const ReturnType = @typeInfo(@TypeOf(root.main)).Fn.return_type.?;
    540 
    541     switch (ReturnType) {
    542         void => {
    543             root.main();
    544             return 0;
    545         },
    546         noreturn, u8 => {
    547             return root.main();
    548         },
    549         else => {
    550             if (@typeInfo(ReturnType) != .ErrorUnion) @compileError(bad_main_ret);
    551 
    552             const result = root.main() catch |err| {
    553                 if (builtin.zig_backend == .stage2_riscv64) {
    554                     std.debug.print("error: failed with error\n", .{});
    555                     return 1;
    556                 }
    557                 std.log.err("{s}", .{@errorName(err)});
    558                 if (@errorReturnTrace()) |trace| {
    559                     std.debug.dumpStackTrace(trace.*);
    560                 }
    561                 return 1;
    562             };
    563 
    564             return switch (@TypeOf(result)) {
    565                 void => 0,
    566                 u8 => result,
    567                 else => @compileError(bad_main_ret),
    568             };
    569         },
    570     }
    571 }
    572 
    573 pub fn call_wWinMain() std.os.windows.INT {
    574     const peb = std.os.windows.peb();
    575     const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).Fn.params[0].type.?;
    576     const hInstance = @as(MAIN_HINSTANCE, @ptrCast(peb.ImageBaseAddress));
    577     const lpCmdLine: [*:0]u16 = @ptrCast(peb.ProcessParameters.CommandLine.Buffer);
    578 
    579     // There are various types used for the 'show window' variable through the Win32 APIs:
    580     // - u16 in STARTUPINFOA.wShowWindow / STARTUPINFOW.wShowWindow
    581     // - c_int in ShowWindow
    582     // - u32 in PEB.ProcessParameters.dwShowWindow
    583     // Since STARTUPINFO is the bottleneck for the allowed values, we use `u16` as the
    584     // type which can coerce into i32/c_int/u32 depending on how the user defines their wWinMain
    585     // (the Win32 docs show wWinMain with `int` as the type for nCmdShow).
    586     const nCmdShow: u16 = nCmdShow: {
    587         // This makes Zig match the nCmdShow behavior of a C program with a WinMain symbol:
    588         // - With STARTF_USESHOWWINDOW set in STARTUPINFO.dwFlags of the CreateProcess call:
    589         //   - Compiled with subsystem:console -> nCmdShow is always SW_SHOWDEFAULT
    590         //   - Compiled with subsystem:windows -> nCmdShow is STARTUPINFO.wShowWindow from
    591         //     the parent CreateProcess call
    592         // - With STARTF_USESHOWWINDOW unset:
    593         //   - nCmdShow is always SW_SHOWDEFAULT
    594         const SW_SHOWDEFAULT = 10;
    595         const STARTF_USESHOWWINDOW = 1;
    596         // root having a wWinMain means that std.builtin.subsystem will always have a non-null value.
    597         if (std.builtin.subsystem.? == .Windows and peb.ProcessParameters.dwFlags & STARTF_USESHOWWINDOW != 0) {
    598             break :nCmdShow @truncate(peb.ProcessParameters.dwShowWindow);
    599         }
    600         break :nCmdShow SW_SHOWDEFAULT;
    601     };
    602 
    603     // second parameter hPrevInstance, MSDN: "This parameter is always NULL"
    604     return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow);
    605 }
    606 
    607 fn maybeIgnoreSigpipe() void {
    608     const have_sigpipe_support = switch (builtin.os.tag) {
    609         .linux,
    610         .plan9,
    611         .solaris,
    612         .netbsd,
    613         .openbsd,
    614         .haiku,
    615         .macos,
    616         .ios,
    617         .watchos,
    618         .tvos,
    619         .visionos,
    620         .dragonfly,
    621         .freebsd,
    622         => true,
    623 
    624         else => false,
    625     };
    626 
    627     if (have_sigpipe_support and !std.options.keep_sigpipe) {
    628         const posix = std.posix;
    629         const act: posix.Sigaction = .{
    630             // Set handler to a noop function instead of `SIG.IGN` to prevent
    631             // leaking signal disposition to a child process.
    632             .handler = .{ .handler = noopSigHandler },
    633             .mask = posix.empty_sigset,
    634             .flags = 0,
    635         };
    636         posix.sigaction(posix.SIG.PIPE, &act, null);
    637     }
    638 }
    639 
    640 fn noopSigHandler(_: i32) callconv(.C) void {}