nx_thread_spawn_test.nx source
↩ module page · 116 lines · 4208 B
1// nx_thread_spawn_test.nx -- end-to-end test for __thread_clone:
2// real OS thread spawn + atomic counter bumping + spin-join.
3//
4// Strategy: parent allocates a shared counter (mmap'd page so child
5// sees the same memory), spawns a worker thread that bumps the
6// counter N times via atomic FAA, then spin-waits until the counter
7// hits N. No futex/condvar needed for this smoke -- just proves the
8// child trampoline works and that atomics propagate cross-thread.
9//
10// Per cardinal end-to-end-or-dont-ship: this test EXERCISES the
11// trampoline rather than just compiling it. Runs under
12// qemu-riscv64-static which emulates clone() correctly.
13
14import "nx_kernel_v2.nx"
15import "nx_log.nx"
16import "nx_atom.nx"
17import "nx_thread.nx"
18import "nx_hw.nx"
19
20const ITERATIONS_PER_THREAD: i64 = 10000
21
22// Contention oversubscription multiplier: spawn STRESS_FACTOR x cpu_count
23// threads so the race test produces real contention even on single-CPU
24// emulators (qemu-user reports 1 CPU regardless of host) -- per
25// [[feedback-dynamic-hw-sizing-no-hardcoded-thread-counts]] never
26// hardcode thread counts; always derive from hw + a factor.
27const STRESS_FACTOR: i64 = 4
28
29// Worker function -- each spawned thread calls this with a pointer
30// to the shared counter. Bumps it ITERATIONS_PER_THREAD times via
31// atomic fetch-and-add at SEQ_CST ordering.
32func worker_bump(arg: *u8) -> i64 {
33 let counter: *i64 = arg as *i64
34 var i: i64 = 0
35 while i < ITERATIONS_PER_THREAD {
36 nx_atom_faa_i64(counter, 1, NX_MO_SEQ_CST)
37 i = i + 1
38 }
39 return 0
40}
41
42func main() -> nx_exit {
43 println("=== nx_thread_spawn end-to-end smoke ===" as *u8)
44
45 // Shared counter -- mmap'd so it survives address-space sharing.
46 let raw: *u8 = sys_mmap(64)
47 let counter: *i64 = raw as *i64
48 *counter = 0
49
50 println("Spawning 1 worker thread that bumps counter 10000 times..." as *u8)
51
52 let tid: i64 = nx_thread_spawn_fn(worker_bump, raw, 65536)
53 if tid <= 0 {
54 println("FAIL: nx_thread_spawn returned non-positive TID" as *u8)
55 return 1
56 }
57 println("Thread spawned (positive TID). Spin-joining on counter..." as *u8)
58
59 // Spin-wait until counter reaches the expected value. A real
60 // join would use SYS_wait4 + CLONE_CHILD_CLEARTID/futex, but
61 // this is sufficient for the smoke and proves the child runs.
62 var spins: i64 = 0
63 while nx_atom_load_i64(counter, NX_MO_SEQ_CST) < ITERATIONS_PER_THREAD {
64 spins = spins + 1
65 if spins > 100000000 {
66 println("FAIL: spin timeout (counter < 10000 after 100M spins)" as *u8)
67 return 2
68 }
69 }
70
71 let final: i64 = nx_atom_load_i64(counter, NX_MO_SEQ_CST)
72 if final != ITERATIONS_PER_THREAD {
73 println("FAIL: counter overshot ITERATIONS_PER_THREAD" as *u8)
74 return 3
75 }
76
77 println("PASS T1: 1 thread, counter == 10000." as *u8)
78
79 // ===== T2: hw-derived thread count racing the same counter =====
80 let n_threads: i64 = nx_hw_worker_count() * STRESS_FACTOR
81 println("" as *u8)
82 println("Spawning hw-derived workers (cpu_count * STRESS_FACTOR) racing same counter:" as *u8)
83 print_i64(n_threads); println(" threads" as *u8)
84 *counter = 0
85
86 var spawned: i64 = 0
87 var t: i64 = 0
88 while t < n_threads {
89 let tid_n: i64 = nx_thread_spawn_fn(worker_bump, raw, 65536)
90 if tid_n <= 0 {
91 println("FAIL: spawn N failed" as *u8)
92 return 4
93 }
94 spawned = spawned + 1
95 t = t + 1
96 }
97
98 let expected: i64 = ITERATIONS_PER_THREAD * n_threads
99 var spins2: i64 = 0
100 while nx_atom_load_i64(counter, NX_MO_SEQ_CST) < expected {
101 spins2 = spins2 + 1
102 if spins2 > 200000000 {
103 println("FAIL: T2 spin timeout" as *u8)
104 return 5
105 }
106 }
107 let final2: i64 = nx_atom_load_i64(counter, NX_MO_SEQ_CST)
108 if final2 != expected {
109 println("FAIL: T2 counter overshot" as *u8)
110 return 6
111 }
112 println("PASS T2: hw-sized racers, counter == ITERATIONS * n_threads (no lost updates)." as *u8)
113 println("" as *u8)
114 println("Real concurrency confirmed: atomic FAA arbitrates dynamic racers." as *u8)
115 return 0
116}