1 //! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime 2 //! > project and is not intended for general use. APIs are not strictly 3 //! > reviewed for safety and usage outside of Wasmtime may have bugs. If 4 //! > you're interested in using this feel free to file an issue on the 5 //! > Wasmtime repository to start a discussion about doing so, but otherwise 6 //! > be aware that your usage of this crate is not supported. 7 8 #![no_std] 9 10 #[cfg(any(feature = "std", unix, windows))] 11 #[macro_use] 12 extern crate std; 13 extern crate alloc; 14 15 use alloc::boxed::Box; 16 use anyhow::Error; 17 use core::cell::Cell; 18 use core::marker::PhantomData; 19 use core::ops::Range; 20 21 cfg_if::cfg_if! { 22 if #[cfg(not(feature = "std"))] { 23 mod nostd; 24 use nostd as imp; 25 } else if #[cfg(windows)] { 26 mod windows; 27 use windows as imp; 28 } else if #[cfg(unix)] { 29 mod unix; 30 use unix as imp; 31 } else { 32 compile_error!("fibers are not supported on this platform"); 33 } 34 } 35 36 // Our own stack switcher routines are used on Unix and no_std 37 // platforms, but not on Windows (it has its own fiber API). 38 #[cfg(any(unix, not(feature = "std")))] 39 pub(crate) mod stackswitch; 40 41 /// Represents an execution stack to use for a fiber. 42 pub struct FiberStack(imp::FiberStack); 43 44 fn _assert_send_sync() { 45 fn _assert_send<T: Send>() {} 46 fn _assert_sync<T: Sync>() {} 47 48 _assert_send::<FiberStack>(); 49 _assert_sync::<FiberStack>(); 50 } 51 52 pub type Result<T, E = imp::Error> = core::result::Result<T, E>; 53 54 impl FiberStack { 55 /// Creates a new fiber stack of the given size. 56 pub fn new(size: usize, zeroed: bool) -> Result<Self> { 57 Ok(Self(imp::FiberStack::new(size, zeroed)?)) 58 } 59 60 /// Creates a new fiber stack of the given size. 61 pub fn from_custom(custom: Box<dyn RuntimeFiberStack>) -> Result<Self> { 62 Ok(Self(imp::FiberStack::from_custom(custom)?)) 63 } 64 65 /// Creates a new fiber stack with the given pointer to the bottom of the 66 /// stack plus how large the guard size and stack size are. 67 /// 68 /// The bytes from `bottom` to `bottom.add(guard_size)` should all be 69 /// guaranteed to be unmapped. The bytes from `bottom.add(guard_size)` to 70 /// `bottom.add(guard_size + len)` should be addressable. 71 /// 72 /// # Safety 73 /// 74 /// This is unsafe because there is no validation of the given pointer. 75 /// 76 /// The caller must properly allocate the stack space with a guard page and 77 /// make the pages accessible for correct behavior. 78 pub unsafe fn from_raw_parts(bottom: *mut u8, guard_size: usize, len: usize) -> Result<Self> { 79 Ok(Self(unsafe { 80 imp::FiberStack::from_raw_parts(bottom, guard_size, len)? 81 })) 82 } 83 84 /// Gets the top of the stack. 85 /// 86 /// Returns `None` if the platform does not support getting the top of the 87 /// stack. 88 pub fn top(&self) -> Option<*mut u8> { 89 self.0.top() 90 } 91 92 /// Returns the range of where this stack resides in memory if the platform 93 /// supports it. 94 pub fn range(&self) -> Option<Range<usize>> { 95 self.0.range() 96 } 97 98 /// Is this a manually-managed stack created from raw parts? If so, it is up 99 /// to whoever created it to manage the stack's memory allocation. 100 pub fn is_from_raw_parts(&self) -> bool { 101 self.0.is_from_raw_parts() 102 } 103 104 /// Returns the range of memory that the guard page(s) reside in. 105 pub fn guard_range(&self) -> Option<Range<*mut u8>> { 106 self.0.guard_range() 107 } 108 } 109 110 /// A creator of RuntimeFiberStacks. 111 pub unsafe trait RuntimeFiberStackCreator: Send + Sync { 112 /// Creates a new RuntimeFiberStack with the specified size, guard pages should be included, 113 /// memory should be zeroed. 114 /// 115 /// This is useful to plugin previously allocated memory instead of mmap'ing a new stack for 116 /// every instance. 117 fn new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn RuntimeFiberStack>, Error>; 118 } 119 120 /// A fiber stack backed by custom memory. 121 pub unsafe trait RuntimeFiberStack: Send + Sync { 122 /// The top of the allocated stack. 123 fn top(&self) -> *mut u8; 124 /// The valid range of the stack without guard pages. 125 fn range(&self) -> Range<usize>; 126 /// The range of the guard page(s) 127 fn guard_range(&self) -> Range<*mut u8>; 128 } 129 130 pub struct Fiber<'a, Resume, Yield, Return> { 131 stack: Option<FiberStack>, 132 inner: imp::Fiber, 133 done: Cell<bool>, 134 _phantom: PhantomData<&'a (Resume, Yield, Return)>, 135 } 136 137 pub struct Suspend<Resume, Yield, Return> { 138 inner: imp::Suspend, 139 _phantom: PhantomData<(Resume, Yield, Return)>, 140 } 141 142 enum RunResult<Resume, Yield, Return> { 143 Executing, 144 Resuming(Resume), 145 Yield(Yield), 146 Returned(Return), 147 #[cfg(feature = "std")] 148 Panicked(Box<dyn core::any::Any + Send>), 149 } 150 151 impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> { 152 /// Creates a new fiber which will execute `func` on the given stack. 153 /// 154 /// This function returns a `Fiber` which, when resumed, will execute `func` 155 /// to completion. When desired the `func` can suspend itself via 156 /// `Fiber::suspend`. 157 pub fn new( 158 stack: FiberStack, 159 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return + 'a, 160 ) -> Result<Self> { 161 let inner = imp::Fiber::new(&stack.0, func)?; 162 163 Ok(Self { 164 stack: Some(stack), 165 inner, 166 done: Cell::new(false), 167 _phantom: PhantomData, 168 }) 169 } 170 171 /// Resumes execution of this fiber. 172 /// 173 /// This function will transfer execution to the fiber and resume from where 174 /// it last left off. 175 /// 176 /// Returns `true` if the fiber finished or `false` if the fiber was 177 /// suspended in the middle of execution. 178 /// 179 /// # Panics 180 /// 181 /// Panics if the current thread is already executing a fiber or if this 182 /// fiber has already finished. 183 /// 184 /// Note that if the fiber itself panics during execution then the panic 185 /// will be propagated to this caller. 186 pub fn resume(&self, val: Resume) -> Result<Return, Yield> { 187 assert!(!self.done.replace(true), "cannot resume a finished fiber"); 188 let result = Cell::new(RunResult::Resuming(val)); 189 self.inner.resume(&self.stack().0, &result); 190 match result.into_inner() { 191 RunResult::Resuming(_) | RunResult::Executing => unreachable!(), 192 RunResult::Yield(y) => { 193 self.done.set(false); 194 Err(y) 195 } 196 RunResult::Returned(r) => Ok(r), 197 #[cfg(feature = "std")] 198 RunResult::Panicked(_payload) => { 199 use std::panic; 200 panic::resume_unwind(_payload); 201 } 202 } 203 } 204 205 /// Returns whether this fiber has finished executing. 206 pub fn done(&self) -> bool { 207 self.done.get() 208 } 209 210 /// Gets the stack associated with this fiber. 211 pub fn stack(&self) -> &FiberStack { 212 self.stack.as_ref().unwrap() 213 } 214 215 /// When this fiber has finished executing, reclaim its stack. 216 pub fn into_stack(mut self) -> FiberStack { 217 assert!(self.done()); 218 self.stack.take().unwrap() 219 } 220 } 221 222 impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> { 223 /// Suspend execution of a currently running fiber. 224 /// 225 /// This function will switch control back to the original caller of 226 /// `Fiber::resume`. This function will then return once the `Fiber::resume` 227 /// function is called again. 228 /// 229 /// # Panics 230 /// 231 /// Panics if the current thread is not executing a fiber from this library. 232 pub fn suspend(&mut self, value: Yield) -> Resume { 233 self.inner 234 .switch::<Resume, Yield, Return>(RunResult::Yield(value)) 235 } 236 237 fn execute( 238 inner: imp::Suspend, 239 initial: Resume, 240 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return, 241 ) { 242 let mut suspend = Suspend { 243 inner, 244 _phantom: PhantomData, 245 }; 246 247 #[cfg(feature = "std")] 248 { 249 use std::panic::{self, AssertUnwindSafe}; 250 let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend))); 251 suspend.inner.switch::<Resume, Yield, Return>(match result { 252 Ok(result) => RunResult::Returned(result), 253 Err(panic) => RunResult::Panicked(panic), 254 }); 255 } 256 // Note that it is sound to omit the `catch_unwind` here: it 257 // will not result in unwinding going off the top of the fiber 258 // stack, because the code on the fiber stack is invoked via 259 // an extern "C" boundary which will panic on unwinds. 260 #[cfg(not(feature = "std"))] 261 { 262 let result = (func)(initial, &mut suspend); 263 suspend 264 .inner 265 .switch::<Resume, Yield, Return>(RunResult::Returned(result)); 266 } 267 } 268 } 269 270 impl<A, B, C> Drop for Fiber<'_, A, B, C> { 271 fn drop(&mut self) { 272 debug_assert!(self.done.get(), "fiber dropped without finishing"); 273 } 274 } 275 276 #[cfg(all(test))] 277 mod tests { 278 use super::{Fiber, FiberStack}; 279 use alloc::string::ToString; 280 use std::cell::Cell; 281 use std::rc::Rc; 282 283 fn fiber_stack(size: usize) -> FiberStack { 284 FiberStack::new(size, false).unwrap() 285 } 286 287 #[test] 288 fn small_stacks() { 289 Fiber::<(), (), ()>::new(fiber_stack(0), |_, _| {}) 290 .unwrap() 291 .resume(()) 292 .unwrap(); 293 Fiber::<(), (), ()>::new(fiber_stack(1), |_, _| {}) 294 .unwrap() 295 .resume(()) 296 .unwrap(); 297 } 298 299 #[test] 300 fn smoke() { 301 let hit = Rc::new(Cell::new(false)); 302 let hit2 = hit.clone(); 303 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, _| { 304 hit2.set(true); 305 }) 306 .unwrap(); 307 assert!(!hit.get()); 308 fiber.resume(()).unwrap(); 309 assert!(hit.get()); 310 } 311 312 #[test] 313 fn suspend_and_resume() { 314 let hit = Rc::new(Cell::new(false)); 315 let hit2 = hit.clone(); 316 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, s| { 317 s.suspend(()); 318 hit2.set(true); 319 s.suspend(()); 320 }) 321 .unwrap(); 322 assert!(!hit.get()); 323 assert!(fiber.resume(()).is_err()); 324 assert!(!hit.get()); 325 assert!(fiber.resume(()).is_err()); 326 assert!(hit.get()); 327 assert!(fiber.resume(()).is_ok()); 328 assert!(hit.get()); 329 } 330 331 #[test] 332 fn backtrace_traces_to_host() { 333 #[inline(never)] // try to get this to show up in backtraces 334 fn look_for_me() { 335 run_test(); 336 } 337 fn assert_contains_host() { 338 let trace = backtrace::Backtrace::new(); 339 println!("{trace:?}"); 340 assert!( 341 trace 342 .frames() 343 .iter() 344 .flat_map(|f| f.symbols()) 345 .filter_map(|s| Some(s.name()?.to_string())) 346 .any(|s| s.contains("look_for_me")) 347 // TODO: apparently windows unwind routines don't unwind through fibers, so this will always fail. Is there a way we can fix that? 348 || cfg!(windows) 349 // TODO: the system libunwind is broken (#2808) 350 || cfg!(all(target_os = "macos", target_arch = "aarch64")) 351 // TODO: see comments in `arm.rs` about how this seems to work 352 // in gdb but not at runtime, unsure why at this time. 353 || cfg!(target_arch = "arm") 354 // asan does weird things 355 || cfg!(asan) 356 ); 357 } 358 359 fn run_test() { 360 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), s| { 361 assert_contains_host(); 362 s.suspend(()); 363 assert_contains_host(); 364 s.suspend(()); 365 assert_contains_host(); 366 }) 367 .unwrap(); 368 assert!(fiber.resume(()).is_err()); 369 assert!(fiber.resume(()).is_err()); 370 assert!(fiber.resume(()).is_ok()); 371 } 372 373 look_for_me(); 374 } 375 376 #[test] 377 #[cfg(feature = "std")] 378 fn panics_propagated() { 379 use std::panic::{self, AssertUnwindSafe}; 380 381 let a = Rc::new(Cell::new(false)); 382 let b = SetOnDrop(a.clone()); 383 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), _s| { 384 let _ = &b; 385 panic!(); 386 }) 387 .unwrap(); 388 assert!(panic::catch_unwind(AssertUnwindSafe(|| fiber.resume(()))).is_err()); 389 assert!(a.get()); 390 391 struct SetOnDrop(Rc<Cell<bool>>); 392 393 impl Drop for SetOnDrop { 394 fn drop(&mut self) { 395 self.0.set(true); 396 } 397 } 398 } 399 400 #[test] 401 fn suspend_and_resume_values() { 402 let fiber = Fiber::new(fiber_stack(1024 * 1024), move |first, s| { 403 assert_eq!(first, 2.0); 404 assert_eq!(s.suspend(4), 3.0); 405 "hello".to_string() 406 }) 407 .unwrap(); 408 assert_eq!(fiber.resume(2.0), Err(4)); 409 assert_eq!(fiber.resume(3.0), Ok("hello".to_string())); 410 } 411 } 412