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:
@@ -117,7 +117,62 @@ fn runJoinMode() void {
|
||||
write("thread-test: join ok\n"); // the M3 verdict marker
|
||||
}
|
||||
|
||||
// --- M4: futex mode ---------------------------------------------------------
|
||||
|
||||
const Futex = runtime.Thread.Futex;
|
||||
|
||||
var futex_word = std.atomic.Value(u32).init(0);
|
||||
var waiter_parked = std.atomic.Value(u32).init(0);
|
||||
|
||||
fn futexWaiter() void {
|
||||
write("thread-futex: waiting\n");
|
||||
waiter_parked.store(1, .release);
|
||||
// Block while the word is still 0; the waker sets it to 1 and wakes us.
|
||||
while (futex_word.load(.acquire) == 0) {
|
||||
Futex.wait(&futex_word, 0);
|
||||
}
|
||||
write("thread-futex: woke\n");
|
||||
}
|
||||
|
||||
fn runFutexMode() void {
|
||||
write("thread-futex: starting\n");
|
||||
|
||||
const waiter = runtime.Thread.spawn(.{}, futexWaiter, .{}) catch {
|
||||
write("thread-futex: FAIL spawn refused\n");
|
||||
return;
|
||||
};
|
||||
// Let the waiter reach its wait, then give it a beat to actually park in-kernel.
|
||||
var spins: usize = 0;
|
||||
while (waiter_parked.load(.acquire) == 0 and spins < 50_000_000) : (spins += 1) {
|
||||
runtime.system.yield();
|
||||
}
|
||||
runtime.system.sleep(50);
|
||||
|
||||
// The handshake: publish the value, then wake the parked waiter.
|
||||
futex_word.store(1, .release);
|
||||
write("thread-futex: waking\n");
|
||||
Futex.wake(&futex_word, 1);
|
||||
|
||||
waiter.join(); // returns once the waiter woke and printed "woke"
|
||||
|
||||
// Timeout: nobody ever wakes this word, so timedWait must report a timeout.
|
||||
var lonely = std.atomic.Value(u32).init(0);
|
||||
if (Futex.timedWait(&lonely, 0, 100_000_000)) |_| {
|
||||
write("thread-futex: FAIL timedWait did not time out\n");
|
||||
return;
|
||||
} else |_| {}
|
||||
write("thread-futex: timeout ok\n");
|
||||
|
||||
write("thread-futex: ok\n"); // the M4 verdict marker
|
||||
}
|
||||
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
const mode = init.arguments.get(1) orelse "spawn";
|
||||
if (std.mem.eql(u8, mode, "join")) runJoinMode() else runSpawnMode();
|
||||
if (std.mem.eql(u8, mode, "join")) {
|
||||
runJoinMode();
|
||||
} else if (std.mem.eql(u8, mode, "futex")) {
|
||||
runFutexMode();
|
||||
} else {
|
||||
runSpawnMode();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user