nx_cond.nx source
↩ module page · 97 lines · 3111 B
1// nx_cond.nx -- condition variable on top of nx_mutex + futex.
2//
3// Condition variables let a thread block until another thread
4// signals "the condition you were waiting on is now true". The
5// classic producer/consumer pattern:
6//
7// producer: consumer:
8// mutex.lock mutex.lock
9// queue.push(item) while queue.empty:
10// cond.signal cond.wait(mutex) # atomic unlock+sleep
11// mutex.unlock item = queue.pop
12// mutex.unlock
13//
14// Implementation uses Linux futex with a sequence counter. Each
15// nx_cond_wait reads the current seq, drops the mutex, blocks on
16// futex(seq), then reacquires the mutex on wakeup. signal/broadcast
17// bump seq + futex_wake.
18//
19// Pairs with nx_mutex (the lock you must hold when calling wait /
20// signal) + nx_atom (eventual SMP correctness).
21
22// nx_safety_envelope:
23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
24// sil_target: SIL1
25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
26// verdict: NOT_YET_EVALUATED
27
28import "syscalls.nx"
29import "nx_mutex.nx"
30
31const NX_SYS_FUTEX_2: i64 = 98
32
33const NX_FUTEX_WAIT_2: i64 = 0
34const NX_FUTEX_WAKE_2: i64 = 1
35const NX_FUTEX_PRIVATE_2: i64 = 128
36
37struct NxCond {
38 seq: i64,
39}
40
41const NX_COND_BYTES: i64 = 8
42
43func nx_cond_new() -> *NxCond {
44 let raw: *u8 = sys_mmap(NX_COND_BYTES)
45 let c: *NxCond = raw as *NxCond
46 c.seq = 0
47 return c
48}
49
50// Atomic-unlock-and-wait pattern. Caller MUST hold `m`; we drop
51// it, wait for the next seq bump, then reacquire.
52func nx_cond_wait(c: *NxCond, m: *NxMutex) -> i64 {
53 let seq_at_wait: i64 = c.seq
54 nx_mutex_unlock(m)
55 let op: i64 = NX_FUTEX_WAIT_2 | NX_FUTEX_PRIVATE_2
56 __syscall(NX_SYS_FUTEX_2, c as i64, op, seq_at_wait, 0, 0, 0)
57 nx_mutex_lock(m)
58 return 0
59}
60
61// Wake one waiter. Bumps the seq counter so any waiter that
62// hasn't yet entered the syscall sees a different value and
63// returns -EAGAIN immediately.
64func nx_cond_signal(c: *NxCond) -> i64 {
65 c.seq = c.seq + 1
66 let op: i64 = NX_FUTEX_WAKE_2 | NX_FUTEX_PRIVATE_2
67 return __syscall(NX_SYS_FUTEX_2, c as i64, op, 1, 0, 0, 0)
68}
69
70// Wake all waiters.
71func nx_cond_broadcast(c: *NxCond) -> i64 {
72 c.seq = c.seq + 1
73 let op: i64 = NX_FUTEX_WAKE_2 | NX_FUTEX_PRIVATE_2
74 return __syscall(NX_SYS_FUTEX_2, c as i64, op, 0x7FFFFFFF, 0, 0, 0)
75}
76
77// ---- self-test ---------------------------------------------------
78
79func main() -> i64 {
80 let c: *NxCond = nx_cond_new()
81 if c.seq != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
82
83 nx_cond_signal(c)
84 if c.seq != 1 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
85
86 nx_cond_signal(c)
87 if c.seq != 2 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
88
89 nx_cond_broadcast(c)
90 if c.seq != 3 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
91
92 // We don't exercise nx_cond_wait in the self-test because it
93 // would block indefinitely without a paired signaller. Real
94 // tests live in the multi-threaded harness once SMP arrives.
95
96 return 0
97}