| 1 | // This file is included in the compilation unit when exporting an executable. |
| 2 | |
| 3 | const builtin = @import("builtin"); |
| 4 | const native_arch = builtin.cpu.arch; |
| 5 | const native_os = builtin.os.tag; |
| 6 | const is_wasm = native_arch.isWasm(); |
| 7 | |
| 8 | const std = @import("std.zig"); |
| 9 | const assert = std.debug.assert; |
| 10 | const uefi = std.os.uefi; |
| 11 | const elf = std.elf; |
| 12 | |
| 13 | const root = @import("root"); |
| 14 | |
| 15 | const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start"; |
| 16 | |
| 17 | comptime { |
| 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 | |
| 93 | fn 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 | |
| 109 | fn 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 | |
| 117 | fn 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 | |
| 123 | fn 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 | |
| 132 | fn 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 | |
| 165 | fn _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 | |
| 564 | fn 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 | |
| 580 | fn 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 | |
| 595 | fn 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 | |
| 599 | fn 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 | |
| 684 | fn 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 | |
| 720 | inline 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 | |
| 732 | fn 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 | |
| 757 | fn 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 |
| 778 | const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; |
| 779 | |
| 780 | const 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 | }; |
| 784 | var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); |
| 785 | |
| 786 | inline 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 | |
| 840 | inline 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 | |
| 867 | fn 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 | } |