Next Generation WASM Microkernel Operating System
at trap_handler 226 lines 6.1 kB view raw
1// Copyright 2025 Jonas Kruckenberg 2// 3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or 4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or 5// http://opensource.org/licenses/MIT>, at your option. This file may not be 6// copied, modified, or distributed except according to those terms. 7 8use crate::loom::{AtomicU8, Ordering}; 9use core::mem; 10use util::loom_const_fn; 11 12/// No initialization has run yet, and no thread is currently using the Once. 13const STATUS_INCOMPLETE: u8 = 0; 14/// Some thread has previously attempted to initialize the Once, but it panicked, 15/// so the Once is now poisoned. There are no other threads currently accessing 16/// this Once. 17const STATUS_POISONED: u8 = 1; 18/// Some thread is currently attempting to run initialization. It may succeed, 19/// so all future threads need to wait for it to finish. 20const STATUS_RUNNING: u8 = 2; 21/// Initialization has completed and all future calls should finish immediately. 22const STATUS_COMPLETE: u8 = 4; 23 24pub enum ExclusiveState { 25 Incomplete, 26 Poisoned, 27 Complete, 28} 29 30/// A synchronization primitive for running one-time global initialization. 31pub struct Once { 32 status: AtomicU8, 33} 34 35impl Once { 36 loom_const_fn! { 37 #[inline] 38 #[must_use] 39 pub const fn new() -> Once { 40 Once { 41 status: AtomicU8::new(STATUS_INCOMPLETE), 42 } 43 } 44 } 45 46 #[inline] 47 pub fn is_completed(&self) -> bool { 48 self.status.load(Ordering::Acquire) == STATUS_COMPLETE 49 } 50 51 pub fn state(&mut self) -> ExclusiveState { 52 self.status.with_mut(|status| match *status { 53 STATUS_INCOMPLETE => ExclusiveState::Incomplete, 54 STATUS_POISONED => ExclusiveState::Poisoned, 55 STATUS_COMPLETE => ExclusiveState::Complete, 56 _ => unreachable!("invalid Once state"), 57 }) 58 } 59 60 /// # Panics 61 /// 62 /// Panics if the closure panics. 63 #[inline] 64 #[track_caller] 65 pub fn call_once<F>(&self, f: F) 66 where 67 F: FnOnce(), 68 { 69 // Fast path check 70 if self.is_completed() { 71 return; 72 } 73 74 let mut f = Some(f); 75 #[allow(tail_expr_drop_order, reason = "")] 76 self.call(&mut || f.take().unwrap()()); 77 } 78 79 #[cold] 80 #[track_caller] 81 fn call(&self, f: &mut impl FnMut()) { 82 loop { 83 let xchg = self.status.compare_exchange( 84 STATUS_INCOMPLETE, 85 STATUS_RUNNING, 86 Ordering::Acquire, 87 Ordering::Acquire, 88 ); 89 90 match xchg { 91 Ok(_) => { 92 let panic_guard = PanicGuard { 93 status: &self.status, 94 }; 95 96 f(); 97 98 mem::forget(panic_guard); 99 100 self.status.store(STATUS_COMPLETE, Ordering::Release); 101 102 return; 103 } 104 Err(STATUS_COMPLETE) => return, 105 Err(STATUS_RUNNING) => self.wait(), 106 Err(STATUS_POISONED) => { 107 // Panic to propagate the poison. 108 panic!("Once instance has previously been poisoned"); 109 } 110 _ => unreachable!("state is never set to invalid values"), 111 } 112 } 113 } 114 115 fn poll(&self) -> bool { 116 match self.status.load(Ordering::Acquire) { 117 STATUS_INCOMPLETE | STATUS_RUNNING => false, 118 STATUS_COMPLETE => true, 119 STATUS_POISONED => panic!("Once poisoned by panic"), 120 _ => unreachable!(), 121 } 122 } 123 124 pub fn wait(&self) { 125 while !self.poll() { 126 #[cfg(loom)] 127 crate::loom::thread::yield_now(); 128 core::hint::spin_loop(); 129 } 130 } 131} 132 133impl Default for Once { 134 fn default() -> Self { 135 Self::new() 136 } 137} 138 139struct PanicGuard<'a> { 140 status: &'a AtomicU8, 141} 142 143impl Drop for PanicGuard<'_> { 144 fn drop(&mut self) { 145 self.status.store(STATUS_POISONED, Ordering::Relaxed); 146 } 147} 148 149#[cfg(test)] 150mod tests { 151 use super::*; 152 use crate::loom::thread; 153 use std::sync::mpsc::channel; 154 155 #[test] 156 fn smoke_once() { 157 static O: std::sync::LazyLock<Once> = std::sync::LazyLock::new(|| Once::new()); 158 let mut a = 0; 159 O.call_once(|| a += 1); 160 assert_eq!(a, 1); 161 O.call_once(|| a += 1); 162 assert_eq!(a, 1); 163 } 164 165 #[test] 166 fn stampede_once() { 167 crate::loom::model(|| { 168 static O: std::sync::LazyLock<Once> = std::sync::LazyLock::new(|| Once::new()); 169 static mut RUN: bool = false; 170 171 const MAX_THREADS: usize = 4; 172 173 let (tx, rx) = channel(); 174 for _ in 0..MAX_THREADS { 175 let tx = tx.clone(); 176 thread::spawn(move || { 177 // for _ in 0..2 { 178 // thread::yield_now() 179 // } 180 unsafe { 181 O.call_once(|| { 182 assert!(!RUN); 183 RUN = true; 184 }); 185 assert!(RUN); 186 } 187 tx.send(()).unwrap(); 188 }); 189 } 190 191 unsafe { 192 O.call_once(|| { 193 assert!(!RUN); 194 RUN = true; 195 }); 196 assert!(RUN); 197 } 198 199 for _ in 0..MAX_THREADS { 200 rx.recv().unwrap(); 201 } 202 }) 203 } 204 205 #[cfg(not(loom))] 206 #[test] 207 fn wait() { 208 use crate::loom::{AtomicBool, Ordering}; 209 210 for _ in 0..50 { 211 let val = AtomicBool::new(false); 212 let once = Once::new(); 213 214 thread::scope(|s| { 215 for _ in 0..4 { 216 s.spawn(|| { 217 once.wait(); 218 assert!(val.load(Ordering::Relaxed)); 219 }); 220 } 221 222 once.call_once(|| val.store(true, Ordering::Relaxed)); 223 }); 224 } 225 } 226}