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