//! The host half of the scheduler's machine seam: the same eight declarations as `chip.zig`, for a //! machine that is not the chip. //! //! This exists so the scheduler itself can be tested where it can be debugged. Everything above //! `context.zig` and this file is portable - the run queue, the futex, cancellation, deadline //! arithmetic - and all of it is the part where a mistake is a silent hang on the die. //! //! The clock here is **virtual**, not the wall clock, and that is the point. `ticks` returns a //! counter that only advances when the scheduler idles, so a test that sleeps three tasks for //! 5 ms, 1 ms and 3 ms finishes instantly and in a fixed order, with no tolerance windows and no //! flakiness under load. It also means the scheduler's own deadlock watchdog is testable: the //! no-deadline idle advances the clock too, so a watchdog measured in ticks still fires. const std = @import("std"); /// The chip's rate, kept identically here so the host exercises the same tick/nanosecond arithmetic /// (`* 125 / 2`) rather than a rounder number that would hide a division bug. pub const ticks_hz: u64 = 16_000_000; var virtual: u64 = 0; pub fn init() void { virtual = 0; } pub fn ticks() u64 { return virtual; } /// No interrupts to mask on the host, and the scheduler is single-threaded, so this is a shape /// rather than a mechanism. It still has to exist: it is what makes the chip's masked regions /// reachable in a host test. pub const Guard = struct { pub inline fn release(_: Guard) void {} }; pub inline fn mask() Guard { return .{}; } /// Advance the virtual clock. With a deadline, jump straight to it - nothing else can happen in /// between on a host with no interrupts, so waiting is pure delay. Without one, advance by a /// millisecond so that a caller spinning on "nothing runnable, nothing scheduled" reaches its /// watchdog instead of looping forever. pub fn idle(deadline: ?u64) void { if (deadline) |d| { if (d > virtual) virtual = d; } else { virtual += ticks_hz / 1000; } } /// Enough of `ets_printf`'s shape to typecheck the arguments, printed in Zig's own spelling. The /// crash path is the only caller, and on the host the interesting question is whether it runs at /// all, not how it looks. pub inline fn print(comptime fmt: [*:0]const u8, args: anytype) void { std.debug.print("{s} <- {any}\n", .{ std.mem.span(fmt), args }); } /// No hardware RNG. Returns zero rather than something plausible: `p4.zig` mixes this with the /// clock and its own state, and a zero here makes it obvious in a test that the hardware /// contribution is absent instead of quietly supplying the entropy the chip is being asked about. pub inline fn entropyWord() u32 { return 0; } /// Deliberately constant: a host test that asserts something about the generator's output wants a /// fixed seed, and the chip's own seed noise is `soc.cycles()`, which no host has. pub inline fn noise() u64 { return 0x0420_0cafe_0420; } /// Test-only: move the virtual clock forward by hand. pub fn advance(n: u64) void { virtual += n; }