Nothing to see here, move along
at main 359 lines 10 kB view raw
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}