claude/input-module-keyboard-events-379361 #6

Merged
daniel merged 5 commits from claude/input-module-keyboard-events-379361 into main 2026-07-11 20:28:47 +00:00
6 changed files with 112 additions and 39 deletions
Showing only changes of commit c7b17aaa0e - Show all commits
+4 -2
View File
@@ -90,8 +90,10 @@ process) instead of silently corrupting the image
(`buildEntryStack` in `system/kernel/process.zig`); `argv[0]` is always the path (`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 or initial-ramdisk name the process was spawned as, and `system_spawn`'s optional
argument blob becomes `argv[1..]`. The runtime's `_start` argument blob becomes `argv[1..]`. The runtime's `_start`
(`library/runtime/start.zig`) hands the block to `rt_start`, which exposes it as (`library/runtime/start.zig`) hands the block to `rt_start`, which builds a
`runtime.argumentCount()` / `runtime.argument(i)`. A C runtime's `crt0` would walk `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 the identical layout unmodified — that's the compatibility being bought. The
`args` test proves the round trip. `args` test proves the round trip.
+47
View File
@@ -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;
}
};
};
+4 -5
View File
@@ -8,7 +8,8 @@
//! const runtime = @import("runtime"); //! const runtime = @import("runtime");
//! pub const panic = runtime.panic; //! pub const panic = runtime.panic;
//! comptime { _ = &runtime.start._start; } // pull the entry shim in //! 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 system = @import("system.zig");
pub const heap = @import("heap.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;`. /// Re-exported so a user binary can `pub const panic = runtime.panic;`.
pub const panic = start.panic; pub const panic = start.panic;
/// Process arguments (argc/argv, parsed from the kernel-built entry stack): /// Process entry types: the `Init` handed to `main`, and its `Arguments`.
/// `argument(0)` is the path or name this binary was spawned as. pub const process = @import("process.zig");
pub const argumentCount = start.argumentCount;
pub const argument = start.argument;
/// The heap as a `std.mem.Allocator`, for Zig `std` containers in user code. /// The heap as a `std.mem.Allocator`, for Zig `std` containers in user code.
pub const allocator = heap.allocator; pub const allocator = heap.allocator;
+51 -24
View File
@@ -4,6 +4,7 @@
const std = @import("std"); const std = @import("std");
const system = @import("system.zig"); const system = @import("system.zig");
const process = @import("process.zig");
/// The kernel enters at `_start` with rsp 16-aligned, pointing at the System V /// 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 /// 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 /// 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 /// block. Build the `process.Init` from it and dispatch to the program's `main`,
/// `main`. The heap is lazy (first alloc grows it), so there is no other runtime /// whose signature is inspected at comptime. The heap is lazy (first alloc grows
/// init to order here. /// it), so there is no other runtime init to order here.
export fn rt_start(stack: [*]const u64) callconv(.c) noreturn { export fn rt_start(stack: [*]const u64) callconv(.c) noreturn {
argument_count = stack[0]; const init: process.Init = .{ .arguments = .{
argument_vector = stack + 1; .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 const root = @import("root"); // the user binary's root source file
root.main(); const main_information = @typeInfo(@TypeOf(root.main)).@"fn";
system.exit(0);
}
/// Number of process arguments (argc). At least 1: argument 0 is the path or const call_arguments = switch (main_information.params.len) {
/// name this binary was spawned as. 0 => .{},
pub fn argumentCount() usize { 1 => arguments: {
return argument_count; 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 const ReturnType = main_information.return_type.?;
/// if out of range. The bytes live in the entry block at the top of the stack switch (@typeInfo(ReturnType)) {
/// page, NUL-terminated, valid for the process's lifetime. .noreturn => @call(.auto, root.main, call_arguments),
pub fn argument(index: usize) []const u8 { .void => {
if (index >= argument_count) return ""; @call(.auto, root.main, call_arguments);
const string: [*:0]const u8 = @ptrFromInt(argument_vector[index]); return 0;
return std.mem.span(string); },
.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. /// No runtime to unwind into — report a panic as a nonzero exit code.
+4 -4
View File
@@ -21,8 +21,8 @@ fn burnStack(depth: usize) u8 {
return touch[0] +% burnStack(depth - 1); return touch[0] +% burnStack(depth - 1);
} }
pub fn main() void { pub fn main(init: runtime.process.Init) void {
if (runtime.argumentCount() <= 1) { if (init.arguments.count <= 1) {
// First instance: spawn the second with real arguments, then exit. // First instance: spawn the second with real arguments, then exit.
_ = runtime.system.spawnWithArguments("args-echo", &.{ "alpha", "beta-42" }); _ = runtime.system.spawnWithArguments("args-echo", &.{ "alpha", "beta-42" });
return; return;
@@ -36,8 +36,8 @@ pub fn main() void {
const prefix = "args:"; const prefix = "args:";
@memcpy(buffer[0..prefix.len], prefix); @memcpy(buffer[0..prefix.len], prefix);
var len: usize = prefix.len; var len: usize = prefix.len;
for (0..runtime.argumentCount()) |i| { var iterator = init.arguments.iterate();
const argument = runtime.argument(i); while (iterator.next()) |argument| {
if (len + 1 + argument.len + 1 > buffer.len) break; if (len + 1 + argument.len + 1 > buffer.len) break;
buffer[len] = ' '; buffer[len] = ' ';
len += 1; len += 1;
@@ -48,10 +48,8 @@ fn awaitChildExit(endpoint: runtime.ipc.Handle) u32 {
return received.childProcessId(); return received.childProcessId();
} }
pub fn main() void { pub fn main(init: runtime.process.Init) void {
if (runtime.argumentCount() <= 1) return; // spawned bare (ramdisk sweep): stay silent const role = init.arguments.get(1) orelse return; // spawned bare (ramdisk sweep): stay silent
const role = runtime.argument(1);
if (std.mem.eql(u8, role, "sleeper")) { if (std.mem.eql(u8, role, "sleeper")) {
while (true) runtime.system.sleep(500); while (true) runtime.system.sleep(500);
} }