threads(M4): futex_wait/futex_wake, the blocking primitive

New private syscalls futex_wait(addr, expected, timeout_ns)=40 and
futex_wake(addr, count)=41. A waiter is a .blocked task tagged with
Task.futex_addr (no queue linkage); futex_wait reads the user word under the big
lock and parks only if it still equals expected, so a concurrent wake can't slip
between the check and the block. futex_wake scans the task table and readies up
to count waiters in the same address space. A timed wait also sets wake_at so the
existing wakeExpired times it out; futex_addr staying non-zero (only futex_wake
clears it) distinguishes timeout from a real wake. Waiters park in-kernel, so an
idle core still halts (no busy-wait).

runtime.Thread.Futex mirrors std.Thread.Futex (wait/timedWait/wake). thread-test
gains a futex mode: a waiter parks, the main thread wakes it (serial order
waiting/waking/woke, asserted by the case regex), and timedWait reports a
timeout.

Gate thread-futex PASS (3x); 18 guardrail cases green incl. sleep/event/ipc
blocking paths; build + host tests clean.
This commit is contained in:
2026-07-20 21:30:57 +01:00
parent 0730e77530
commit b3a8147bd7
8 changed files with 272 additions and 14 deletions
+33
View File
@@ -11,6 +11,7 @@
//! A binary must be built multi-threaded (`addThreadedUserBinary`) before it may spawn.
const std = @import("std");
const abi = @import("abi");
const sc = @import("system-call.zig");
const system = @import("system.zig");
const ipc = @import("ipc.zig");
@@ -104,6 +105,28 @@ pub const Thread = struct {
pub fn currentCore() Id {
return @intCast(sc.systemCall0(.current_core));
}
/// `std.Thread.Futex`-shaped block/wake on a `u32` atomic — the primitive the
/// blocking `Mutex`/`Condition`/`Semaphore` are built on. Waiters park in the
/// kernel (no busy-wait), so an idle core still halts (docs/halting.md).
pub const Futex = struct {
/// Block while `ptr.* == expect`. Returns when woken by `wake`, or promptly if
/// the value already differs (safe against spurious returns, as in std): the
/// caller re-checks its condition in a loop.
pub fn wait(ptr: *const std.atomic.Value(u32), expect: u32) void {
_ = futexWait(@intFromPtr(ptr), expect, 0);
}
/// As `wait`, but returns `error.Timeout` if `timeout_ns` elapses first.
pub fn timedWait(ptr: *const std.atomic.Value(u32), expect: u32, timeout_ns: u64) error{Timeout}!void {
if (futexWait(@intFromPtr(ptr), expect, timeout_ns) == abi.futex_timed_out) return error.Timeout;
}
/// Wake up to `max_waiters` threads blocked on `ptr`.
pub fn wake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
_ = futexWake(@intFromPtr(ptr), max_waiters);
}
};
};
/// thread_spawn(entry, stack_top, arg, exit_endpoint) -> tid, or a wrapped error.
@@ -122,3 +145,13 @@ fn exitThread() noreturn {
_ = sc.systemCall0(.thread_exit);
unreachable;
}
/// futex_wait(addr, expect, timeout_ns) -> status (abi.futex_*).
fn futexWait(addr: usize, expect: u32, timeout_ns: u64) usize {
return sc.systemCall3(.futex_wait, addr, expect, timeout_ns);
}
/// futex_wake(addr, count) -> number woken.
fn futexWake(addr: usize, count: u32) usize {
return sc.systemCall2(.futex_wake, addr, count);
}