diff --git a/docs/sysv.md b/docs/sysv.md index 210e51f..da8bf7c 100644 --- a/docs/sysv.md +++ b/docs/sysv.md @@ -90,8 +90,10 @@ process) instead of silently corrupting the image (`buildEntryStack` in `system/kernel/process.zig`); `argv[0]` is always the path or initial-ramdisk name the process was spawned as, and `system_spawn`'s optional argument blob becomes `argv[1..]`. The runtime's `_start` -(`library/runtime/start.zig`) hands the block to `rt_start`, which exposes it as -`runtime.argumentCount()` / `runtime.argument(i)`. A C runtime's `crt0` would walk +(`library/runtime/start.zig`) hands the block to `rt_start`, which builds a +`runtime.process.Init` from it and passes that to the program's `main` +(`pub fn main(init: runtime.process.Init)`; a parameterless `main()` is also +accepted). A C runtime's `crt0` would walk the identical layout unmodified — that's the compatibility being bought. The `args` test proves the round trip. diff --git a/library/runtime/process.zig b/library/runtime/process.zig new file mode 100644 index 0000000..9f41438 --- /dev/null +++ b/library/runtime/process.zig @@ -0,0 +1,47 @@ +//! Process-level runtime types: what a user program receives at entry. Mirrors +//! the spirit of `std.process.Init.Minimal` in danos terms — std's `Args` holds +//! no data on freestanding targets, so the type is danos's own. + +const std = @import("std"); + +/// Everything a program receives at entry. Passed to +/// `pub fn main(init: runtime.process.Init)`; programs that need nothing keep +/// `pub fn main() void`. An `environment` field is added here once the kernel +/// passes a non-empty envp (today it is always empty — see docs/sysv.md). +pub const Init = struct { + arguments: Arguments, +}; + +/// The process arguments (argc/argv), parsed from the kernel-built System V +/// entry block. The bytes live in the entry block at the top of the stack page, +/// NUL-terminated, valid for the process's lifetime. +pub const Arguments = struct { + /// argc — at least 1: argument 0 is the path or name this binary was + /// spawned as. + count: usize, + /// The argv pointers in the entry block (NULL-terminated after `count` + /// entries). + vector: [*]const [*:0]const u8, + + /// Argument `index` (0 = the program's own path/name), or null if out of + /// range. + pub fn get(arguments: Arguments, index: usize) ?[:0]const u8 { + if (index >= arguments.count) return null; + return std.mem.span(arguments.vector[index]); + } + + pub fn iterate(arguments: Arguments) Iterator { + return .{ .arguments = arguments }; + } + + pub const Iterator = struct { + arguments: Arguments, + index: usize = 0, + + pub fn next(iterator: *Iterator) ?[:0]const u8 { + const argument = iterator.arguments.get(iterator.index) orelse return null; + iterator.index += 1; + return argument; + } + }; +}; diff --git a/library/runtime/runtime.zig b/library/runtime/runtime.zig index 222eecf..b5e660a 100644 --- a/library/runtime/runtime.zig +++ b/library/runtime/runtime.zig @@ -8,7 +8,8 @@ //! const runtime = @import("runtime"); //! pub const panic = runtime.panic; //! comptime { _ = &runtime.start._start; } // pull the entry shim in -//! and a `pub fn main() void`. +//! and a `pub fn main() void` or `pub fn main(init: runtime.process.Init) void` +//! (arguments arrive via `init`). pub const system = @import("system.zig"); pub const heap = @import("heap.zig"); @@ -31,10 +32,8 @@ pub const dma = @import("dma.zig"); /// Re-exported so a user binary can `pub const panic = runtime.panic;`. pub const panic = start.panic; -/// Process arguments (argc/argv, parsed from the kernel-built entry stack): -/// `argument(0)` is the path or name this binary was spawned as. -pub const argumentCount = start.argumentCount; -pub const argument = start.argument; +/// Process entry types: the `Init` handed to `main`, and its `Arguments`. +pub const process = @import("process.zig"); /// The heap as a `std.mem.Allocator`, for Zig `std` containers in user code. pub const allocator = heap.allocator; diff --git a/library/runtime/start.zig b/library/runtime/start.zig index 2d8afcf..f9c0658 100644 --- a/library/runtime/start.zig +++ b/library/runtime/start.zig @@ -4,6 +4,7 @@ const std = @import("std"); const system = @import("system.zig"); +const process = @import("process.zig"); /// 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 @@ -20,35 +21,61 @@ pub export fn _start() callconv(.naked) noreturn { ); } -// The process-entry block, recorded by `rt_start` for the accessors below. -var argument_count: usize = 0; -var argument_vector: [*]const u64 = undefined; - /// The first Zig frame, entered with `stack` pointing at the kernel-built entry -/// block. Record argc/argv for the accessors, then hand control to the program's -/// `main`. The heap is lazy (first alloc grows it), so there is no other runtime -/// init to order here. +/// 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 { - argument_count = stack[0]; - argument_vector = stack + 1; + const init: process.Init = .{ .arguments = .{ + .count = stack[0], + .vector = @ptrCast(stack + 1), + } }; + system.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"); // the user binary's root source file - root.main(); - system.exit(0); -} + const main_information = @typeInfo(@TypeOf(root.main)).@"fn"; -/// Number of process arguments (argc). At least 1: argument 0 is the path or -/// name this binary was spawned as. -pub fn argumentCount() usize { - return argument_count; -} + 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"), + }; -/// Process argument `index` (0 = the program's own path/name), or an empty slice -/// if out of range. The bytes live in the entry block at the top of the stack -/// page, NUL-terminated, valid for the process's lifetime. -pub fn argument(index: usize) []const u8 { - if (index >= argument_count) return ""; - const string: [*:0]const u8 = @ptrFromInt(argument_vector[index]); - return std.mem.span(string); + 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"; + _ = system.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. diff --git a/system/services/args-echo/args-echo.zig b/system/services/args-echo/args-echo.zig index 8ae582e..49177cd 100644 --- a/system/services/args-echo/args-echo.zig +++ b/system/services/args-echo/args-echo.zig @@ -21,8 +21,8 @@ fn burnStack(depth: usize) u8 { return touch[0] +% burnStack(depth - 1); } -pub fn main() void { - if (runtime.argumentCount() <= 1) { +pub fn main(init: runtime.process.Init) void { + if (init.arguments.count <= 1) { // First instance: spawn the second with real arguments, then exit. _ = runtime.system.spawnWithArguments("args-echo", &.{ "alpha", "beta-42" }); return; @@ -36,8 +36,8 @@ pub fn main() void { const prefix = "args:"; @memcpy(buffer[0..prefix.len], prefix); var len: usize = prefix.len; - for (0..runtime.argumentCount()) |i| { - const argument = runtime.argument(i); + var iterator = init.arguments.iterate(); + while (iterator.next()) |argument| { if (len + 1 + argument.len + 1 > buffer.len) break; buffer[len] = ' '; len += 1; diff --git a/system/services/process-test/process-test.zig b/system/services/process-test/process-test.zig index 158d06f..ecf75bc 100644 --- a/system/services/process-test/process-test.zig +++ b/system/services/process-test/process-test.zig @@ -48,10 +48,8 @@ fn awaitChildExit(endpoint: runtime.ipc.Handle) u32 { return received.childProcessId(); } -pub fn main() void { - if (runtime.argumentCount() <= 1) return; // spawned bare (ramdisk sweep): stay silent - - const role = runtime.argument(1); +pub fn main(init: runtime.process.Init) void { + const role = init.arguments.get(1) orelse return; // spawned bare (ramdisk sweep): stay silent if (std.mem.eql(u8, role, "sleeper")) { while (true) runtime.system.sleep(500); }