//! The user-space process entry shim. Every user binary roots `_start` here (via //! `entry = _start` in build.zig); the shared compilation root, root.zig, forces //! this file to be analysed with `comptime { _ = &runtime.start._start; }`, so //! the whole runtime is linked in. const std = @import("std"); const logging = @import("logging"); const process = @import("process"); /// The kernel enters at `_start` with rsp 16-aligned, pointing at the System V /// process-entry block it built: argc, argv pointers, NULL, envp terminator, the /// auxiliary vector, then the strings (see system/kernel/process.zig, /// `buildEntryStack`). Capture that address in rdi — the first SysV argument — /// before `call` disturbs the stack; the call's pushed return address also puts /// rsp ≡ 8 (mod 16), satisfying the ABI before any Zig frame runs. The `ud2` is a /// safety net if `rt_start` ever returns. pub export fn _start() callconv(.naked) noreturn { asm volatile ( \\mov %%rsp, %%rdi \\call rt_start \\ud2 ); } /// The first Zig frame, entered with `stack` pointing at the kernel-built entry /// block. Build the `process.Init` from it and dispatch to the program's `main`, /// whose signature is inspected at comptime. The heap is lazy (first alloc grows /// it), so there is no other runtime init to order here. export fn rt_start(stack: [*]const u64) callconv(.c) noreturn { const init: process.Init = .{ .arguments = .{ .count = stack[0], .vector = @ptrCast(stack + 1), } }; process.exit(callMain(init)); } /// Comptime-dispatch on root.main's signature, in the spirit of std's start.zig: /// zero parameters or one `process.Init`; returns void, noreturn, u8, !void, or !u8. fn callMain(init: process.Init) u8 { const root = @import("root"); // root.zig, re-exporting the program's main const main_information = @typeInfo(@TypeOf(root.main)).@"fn"; const call_arguments = switch (main_information.params.len) { 0 => .{}, 1 => arguments: { const Parameter = main_information.params[0].type orelse @compileError("main's parameter must be runtime.process.Init (not anytype)"); if (Parameter != process.Init) @compileError("main's parameter must be runtime.process.Init, found " ++ @typeName(Parameter)); break :arguments .{init}; }, else => @compileError("main takes no parameters or a single runtime.process.Init"), }; const ReturnType = main_information.return_type.?; switch (@typeInfo(ReturnType)) { .noreturn => @call(.auto, root.main, call_arguments), .void => { @call(.auto, root.main, call_arguments); return 0; }, .int => { if (ReturnType != u8) @compileError("main's integer return type must be u8, found " ++ @typeName(ReturnType)); return @call(.auto, root.main, call_arguments); }, .error_union => { const payload = @call(.auto, root.main, call_arguments) catch |err| { var buffer: [128]u8 = undefined; const line = std.fmt.bufPrint(&buffer, "main returned error: {s}\n", .{@errorName(err)}) catch "main returned an error\n"; _ = logging.write(line); return 1; // distinct from panic's 127 }; if (@TypeOf(payload) == void) return 0; if (@TypeOf(payload) == u8) return payload; @compileError("main's error-union payload must be void or u8, found " ++ @typeName(@TypeOf(payload))); }, else => @compileError("main must return void, noreturn, u8, !void, or !u8, found " ++ @typeName(ReturnType)), } } /// No runtime to unwind into — report a panic as a nonzero exit code. pub const panic = std.debug.FullPanic(struct { fn panic(_: []const u8, _: ?usize) noreturn { process.exit(127); } }.panic);