Nothing to see here, move along
1#![no_std]
2#![no_main]
3
4use core::sync::atomic::{AtomicU64, Ordering};
5use lancer_user::net;
6use lancer_user::show;
7use lancer_user::syscall;
8
9const TAG_NOTIFICATION: u64 = 2;
10const TAG_SCHED_CONTEXT: u64 = 5;
11const TAG_FRAME: u64 = 11;
12const NOTIFY_BIT_DEATH: u64 = 1;
13const RIGHTS_ALL: u64 = 0xFFFF;
14
15const SLOT_UNTYPED: u64 = 30;
16const SLOT_DEATH_NOTIF: u64 = 31;
17const SLOTS_PER_THREAD: u64 = 3;
18
19const DEFAULT_N: usize = 4;
20const STACK_PAGE_SIZE: u64 = 4096;
21const STACK_STRIDE: u64 = 8192;
22const STACK_BASE: u64 = 0x0000_7000_0000_0000;
23
24const CHURN_ITERATIONS: u64 = 10000;
25
26const ROOT_SIZE_BITS: u64 = 10;
27const SLOTS_PER_CNODE: u64 = 1 << ROOT_SIZE_BITS;
28const FLAT_FIRST_SLOT: u64 = 32;
29const EXPANDED_FIRST_SLOT: u64 = SLOTS_PER_CNODE;
30const FLAT_MAX_THREADS: usize = ((SLOTS_PER_CNODE - FLAT_FIRST_SLOT) / SLOTS_PER_THREAD) as usize;
31
32const BOOT_CAP_RANGE: u64 = 31;
33
34static SHARED_COUNTER: AtomicU64 = AtomicU64::new(0);
35static FIRST_THREAD_SLOT: AtomicU64 = AtomicU64::new(FLAT_FIRST_SLOT);
36static THREAD_CAPACITY: AtomicU64 = AtomicU64::new(FLAT_MAX_THREADS as u64);
37
38#[unsafe(no_mangle)]
39pub extern "C" fn thread_entry(_thread_id: u64) -> ! {
40 SHARED_COUNTER.fetch_add(1, Ordering::SeqCst);
41 syscall::exit()
42}
43
44fn max_threads() -> usize {
45 THREAD_CAPACITY.load(Ordering::Relaxed) as usize
46}
47
48fn thread_slots(i: usize) -> (u64, u64, u64) {
49 let base = FIRST_THREAD_SLOT.load(Ordering::Relaxed) + (i as u64) * SLOTS_PER_THREAD;
50 (base, base + 1, base + 2)
51}
52
53fn expand_cspace(child_cnode_count: u64) {
54 const STAGING: u64 = 900;
55
56 let r = syscall::cnode_create(ROOT_SIZE_BITS, STAGING);
57 assert!(r >= 0, "cnode_create staging failed: {r}");
58
59 (0..BOOT_CAP_RANGE).for_each(|slot| {
60 let _ = syscall::cnode_copy_to(slot, STAGING, slot, RIGHTS_ALL);
61 });
62
63 (0..BOOT_CAP_RANGE).for_each(|slot| {
64 let _ = syscall::cnode_delete(slot);
65 });
66
67 let r = syscall::cnode_copy(STAGING, 0, RIGHTS_ALL);
68 assert!(r >= 0, "cnode_copy staging->0 failed: {r}");
69 syscall::cnode_delete(STAGING);
70
71 (1..=child_cnode_count).for_each(|i| {
72 let r = syscall::cnode_create(ROOT_SIZE_BITS, i);
73 assert!(r >= 0, "cnode_create child {i} failed: {r}");
74 });
75
76 let new_guard_bits: u64 = 64 - ROOT_SIZE_BITS * 2;
77 let r = syscall::cspace_set_guard(new_guard_bits);
78 assert!(r >= 0, "cspace_set_guard failed: {r}");
79
80 let total_thread_slots = child_cnode_count * SLOTS_PER_CNODE;
81 FIRST_THREAD_SLOT.store(EXPANDED_FIRST_SLOT, Ordering::Relaxed);
82 THREAD_CAPACITY.store(total_thread_slots / SLOTS_PER_THREAD, Ordering::Relaxed);
83}
84
85fn ensure_capacity(needed: usize) {
86 match needed <= FLAT_MAX_THREADS {
87 true => {}
88 false => {
89 let thread_slots_needed = needed as u64 * SLOTS_PER_THREAD;
90 let child_cnodes = thread_slots_needed.div_ceil(SLOTS_PER_CNODE);
91 show!(thread_test, "expanding cspace: {} child cnodes for {} threads", child_cnodes, needed);
92 expand_cspace(child_cnodes);
93 show!(thread_test, "cspace expanded, capacity: {} threads", max_threads());
94 }
95 }
96}
97
98#[derive(Clone, Copy, PartialEq)]
99enum Mode {
100 Normal(usize),
101 Stress,
102}
103
104fn parse_args(args: &[u8]) -> Mode {
105 let trimmed = &args[..args.iter().position(|&b| b == 0).unwrap_or(args.len())];
106 match trimmed.windows(6).any(|w| w == b"stress") {
107 true => Mode::Stress,
108 false => Mode::Normal(parse_n(trimmed)),
109 }
110}
111
112fn parse_n(args: &[u8]) -> usize {
113 let digits_start = match (0..args.len())
114 .rev()
115 .find(|&i| args[i] == b' ' || args[i] == b'/')
116 {
117 Some(pos) => pos + 1,
118 None => return DEFAULT_N,
119 };
120
121 let digits = &args[digits_start..];
122 match digits.is_empty() || !digits[0].is_ascii_digit() {
123 true => DEFAULT_N,
124 false => {
125 let val = digits
126 .iter()
127 .take_while(|b| b.is_ascii_digit())
128 .fold(0usize, |acc, &b| {
129 acc.saturating_mul(10).saturating_add((b - b'0') as usize)
130 });
131 match val {
132 0 => DEFAULT_N,
133 _ => val,
134 }
135 }
136 }
137}
138
139fn ok(r: i64) -> Result<u64, ()> {
140 match r < 0 {
141 true => Err(()),
142 false => Ok(r as u64),
143 }
144}
145
146fn spawn_thread(i: usize) -> Result<u64, ()> {
147 let (mem_slot, proc_slot, sched_slot) = thread_slots(i);
148 let stack_vaddr = STACK_BASE + (i as u64) * STACK_STRIDE;
149
150 ok(syscall::untyped_retype(SLOT_UNTYPED, TAG_FRAME, 0, mem_slot, 1))?;
151
152 let result = spawn_thread_inner(i, mem_slot, proc_slot, sched_slot, stack_vaddr);
153 if result.is_err() {
154 destroy_thread(i);
155 }
156 result
157}
158
159fn spawn_thread_inner(
160 _i: usize,
161 mem_slot: u64,
162 proc_slot: u64,
163 sched_slot: u64,
164 stack_vaddr: u64,
165) -> Result<u64, ()> {
166 ok(syscall::frame_map(mem_slot, stack_vaddr, 1))?;
167
168 let stack_top = stack_vaddr + STACK_PAGE_SIZE;
169 let child_pid = ok(syscall::thread_create(
170 SLOT_UNTYPED, proc_slot, thread_entry as *const () as u64, stack_top, _i as u64,
171 ))?;
172
173 syscall::proc_bind_death_notif(proc_slot, SLOT_DEATH_NOTIF, NOTIFY_BIT_DEATH);
174
175 ok(syscall::untyped_retype(SLOT_UNTYPED, TAG_SCHED_CONTEXT, 0, sched_slot, 1))?;
176 ok(syscall::sched_configure(sched_slot, 5_000_000, 10_000_000, 100))?;
177 ok(syscall::sched_attach(sched_slot, child_pid))?;
178 ok(syscall::proc_start(proc_slot, _i as u64))?;
179
180 Ok(child_pid)
181}
182
183fn wait_all(target: u64) {
184 core::iter::repeat(())
185 .take_while(|()| SHARED_COUNTER.load(Ordering::SeqCst) < target)
186 .for_each(|()| {
187 syscall::notify_wait(SLOT_DEATH_NOTIF);
188 });
189}
190
191fn destroy_thread(i: usize) {
192 let (mem_slot, proc_slot, sched_slot) = thread_slots(i);
193 syscall::proc_destroy(proc_slot);
194 syscall::cap_revoke(sched_slot);
195 syscall::cap_revoke(mem_slot);
196}
197
198fn destroy_threads(count: u64) {
199 (0..count).for_each(|i| destroy_thread(i as usize));
200}
201
202fn create_death_notif() {
203 let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_NOTIFICATION, 0, SLOT_DEATH_NOTIF, 1);
204 assert!(r >= 0, "untyped_retype death notif failed");
205}
206
207fn query_debug_info() -> Option<(u64, u64, u64, u64)> {
208 let (rax, rsi, rdx, r8) = syscall::debug_info();
209 match (rax as i64) < 0 {
210 true => None,
211 false => Some((rax, rsi, rdx, r8)),
212 }
213}
214
215fn run_normal(n: usize) {
216 ensure_capacity(n);
217 let max = max_threads();
218 let n = n.min(max);
219
220 show!(thread_test, "spawning {n} threads (cap slots: {max} max)");
221
222 SHARED_COUNTER.store(0, Ordering::SeqCst);
223
224 create_death_notif();
225
226 let spawned = (0..n)
227 .try_fold(0u64, |count, i| match spawn_thread(i) {
228 Ok(_) => Ok(count + 1),
229 Err(()) => Err(count),
230 })
231 .unwrap_or_else(|count| count);
232
233 show!(thread_test, "{spawned} threads started, waiting");
234
235 wait_all(spawned);
236
237 let val = SHARED_COUNTER.load(Ordering::SeqCst);
238
239 destroy_threads(spawned);
240 syscall::cap_revoke(SLOT_DEATH_NOTIF);
241
242 match val == spawned {
243 true => show!(thread_test, "results: {val}/{spawned} ok"),
244 false => show!(thread_test, error, "results: {val}/{spawned} FAILED"),
245 };
246}
247
248fn run_stress() {
249 show!(thread_test, "stress mode starting");
250
251 ensure_capacity(10000);
252
253 let before = query_debug_info();
254
255 let start_ms = syscall::clock_monotonic_ms();
256
257 let max_concurrent = stress_phase1_concurrent();
258
259 let churn_count = stress_phase2_churn();
260
261 let elapsed_ms = syscall::clock_monotonic_ms() - start_ms;
262
263 let after = query_debug_info();
264
265 show!(thread_test, "--- stress results ---");
266 show!(thread_test, "max concurrent: {}", max_concurrent);
267 show!(thread_test, "churn completed: {}", churn_count);
268 show!(thread_test, "elapsed: {} ms", elapsed_ms);
269 match (before, after) {
270 (Some((pb, ob, fb, _)), Some((pa, oa, fa, tc))) => {
271 show!(thread_test, "before: pids={} objs={} frames={}", pb, ob, fb);
272 show!(thread_test, "after: pids={} objs={} frames={}", pa, oa, fa);
273 show!(thread_test, "last tick cycles: {}", tc);
274 let frame_delta = (fb as i64) - (fa as i64);
275 match frame_delta.abs() <= 2 {
276 true => show!(thread_test, "frame leak check: OK (delta={})", frame_delta),
277 false => show!(
278 thread_test,
279 error,
280 "frame leak check: LEAK delta={}",
281 frame_delta
282 ),
283 };
284 }
285 _ => show!(
286 thread_test,
287 "(debug_info unavailable -- test kernel required)"
288 ),
289 }
290}
291
292fn stress_phase1_concurrent() -> u64 {
293 let max = max_threads();
294 SHARED_COUNTER.store(0, Ordering::SeqCst);
295
296 create_death_notif();
297
298 let spawned = (0..max).take_while(|&i| spawn_thread(i).is_ok()).count() as u64;
299
300 show!(thread_test, "phase1: spawned {} / {} max", spawned, max);
301
302 wait_all(spawned);
303 destroy_threads(spawned);
304 syscall::cap_revoke(SLOT_DEATH_NOTIF);
305
306 spawned
307}
308
309fn stress_phase2_churn() -> u64 {
310 SHARED_COUNTER.store(0, Ordering::SeqCst);
311 let mut completed = 0u64;
312
313 (0..CHURN_ITERATIONS).for_each(|iter| {
314 create_death_notif();
315 match spawn_thread(0) {
316 Ok(_) => {
317 core::iter::repeat(())
318 .take_while(|()| SHARED_COUNTER.load(Ordering::SeqCst) <= completed)
319 .for_each(|()| {
320 syscall::notify_wait(SLOT_DEATH_NOTIF);
321 });
322 completed += 1;
323
324 destroy_thread(0);
325 }
326 Err(()) => {
327 show!(thread_test, "phase2: churn iter {} spawn failed", iter);
328 }
329 }
330 syscall::cap_revoke(SLOT_DEATH_NOTIF);
331 });
332
333 show!(
334 thread_test,
335 "phase2: {} sequential churn iterations",
336 completed
337 );
338 completed
339}
340
341#[unsafe(no_mangle)]
342pub extern "C" fn lancer_main() -> ! {
343 let mode = match net::init() {
344 Some((rx, _tx)) => {
345 let mut buf = [0u8; 64];
346 let len = net::recv_args(&rx, &mut buf);
347 parse_args(&buf[..len])
348 }
349 None => Mode::Normal(DEFAULT_N),
350 };
351
352 match mode {
353 Mode::Normal(n) => run_normal(n),
354 Mode::Stress => run_stress(),
355 };
356
357 syscall::send(1, [1, 0, 0, 0, 0, 0]);
358 syscall::exit()
359}