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 core::cell::Cell;
17 use core::marker::PhantomData;
18 use core::ops::Range;
19 use wasmtime_environ::error::Error;
20
21 cfg_if::cfg_if! {
22 if #[cfg(not(feature = "std"))] {
23 mod nostd;
24 use nostd as imp;
25 mod stackswitch;
26 } else if #[cfg(miri)] {
27 mod miri;
28 use miri as imp;
29 } else if #[cfg(windows)] {
30 mod windows;
31 use windows as imp;
32 } else if #[cfg(unix)] {
33 mod unix;
34 use unix as imp;
35 mod stackswitch;
36 } else {
37 compile_error!("fibers are not supported on this platform");
38 }
39 }
40
41 /// Represents an execution stack to use for a fiber.
42 pub struct FiberStack(imp::FiberStack);
43
_assert_send_sync()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.
new(size: usize, zeroed: bool) -> Result<Self>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.
from_custom(custom: Box<dyn RuntimeFiberStack>) -> Result<Self>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.
from_raw_parts(bottom: *mut u8, guard_size: usize, len: usize) -> Result<Self>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.
top(&self) -> Option<*mut u8>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.
range(&self) -> Option<Range<usize>>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.
is_from_raw_parts(&self) -> bool100 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.
guard_range(&self) -> Option<Range<*mut u8>>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.
new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn RuntimeFiberStack>, Error>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.
top(&self) -> *mut u8123 fn top(&self) -> *mut u8;
124 /// The valid range of the stack without guard pages.
range(&self) -> Range<usize>125 fn range(&self) -> Range<usize>;
126 /// The range of the guard page(s)
guard_range(&self) -> Range<*mut u8>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 /// A structure that is stored on a stack frame of a call to `Fiber::resume`.
143 ///
144 /// This is used to both transmit data to a fiber (the resume step) as well as
145 /// acquire data from a fiber (the suspension step).
146 enum RunResult<Resume, Yield, Return> {
147 /// The fiber is currently executing meaning it picked up whatever it was
148 /// resuming with and hasn't yet completed.
149 Executing,
150
151 /// Resume with this value. Called for each invocation of
152 /// `Fiber::resume`.
153 Resuming(Resume),
154
155 /// The fiber hasn't finished but has provided the following value
156 /// during its suspension.
157 Yield(Yield),
158
159 /// The fiber has completed with the provided value and can no
160 /// longer be resumed.
161 Returned(Return),
162
163 /// The fiber execution panicked.
164 #[cfg(feature = "std")]
165 Panicked(Box<dyn core::any::Any + Send>),
166 }
167
168 impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
169 /// Creates a new fiber which will execute `func` on the given stack.
170 ///
171 /// This function returns a `Fiber` which, when resumed, will execute `func`
172 /// to completion. When desired the `func` can suspend itself via
173 /// `Fiber::suspend`.
new( stack: FiberStack, func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return + 'a, ) -> Result<Self>174 pub fn new(
175 stack: FiberStack,
176 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return + 'a,
177 ) -> Result<Self> {
178 let inner = imp::Fiber::new(&stack.0, func)?;
179
180 Ok(Self {
181 stack: Some(stack),
182 inner,
183 done: Cell::new(false),
184 _phantom: PhantomData,
185 })
186 }
187
188 /// Resumes execution of this fiber.
189 ///
190 /// This function will transfer execution to the fiber and resume from where
191 /// it last left off.
192 ///
193 /// Returns `true` if the fiber finished or `false` if the fiber was
194 /// suspended in the middle of execution.
195 ///
196 /// # Panics
197 ///
198 /// Panics if the current thread is already executing a fiber or if this
199 /// fiber has already finished.
200 ///
201 /// Note that if the fiber itself panics during execution then the panic
202 /// will be propagated to this caller.
resume(&self, val: Resume) -> Result<Return, Yield>203 pub fn resume(&self, val: Resume) -> Result<Return, Yield> {
204 assert!(!self.done.replace(true), "cannot resume a finished fiber");
205 let result = Cell::new(RunResult::Resuming(val));
206 self.inner.resume(&self.stack().0, &result);
207 match result.into_inner() {
208 RunResult::Resuming(_) | RunResult::Executing => unreachable!(),
209 RunResult::Yield(y) => {
210 self.done.set(false);
211 Err(y)
212 }
213 RunResult::Returned(r) => Ok(r),
214 #[cfg(feature = "std")]
215 RunResult::Panicked(_payload) => {
216 use std::panic;
217 panic::resume_unwind(_payload);
218 }
219 }
220 }
221
222 /// Returns whether this fiber has finished executing.
done(&self) -> bool223 pub fn done(&self) -> bool {
224 self.done.get()
225 }
226
227 /// Gets the stack associated with this fiber.
stack(&self) -> &FiberStack228 pub fn stack(&self) -> &FiberStack {
229 self.stack.as_ref().unwrap()
230 }
231
232 /// When this fiber has finished executing, reclaim its stack.
into_stack(mut self) -> FiberStack233 pub fn into_stack(mut self) -> FiberStack {
234 assert!(self.done());
235 self.stack.take().unwrap()
236 }
237 }
238
239 impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> {
240 /// Suspend execution of a currently running fiber.
241 ///
242 /// This function will switch control back to the original caller of
243 /// `Fiber::resume`. This function will then return once the `Fiber::resume`
244 /// function is called again.
245 ///
246 /// # Panics
247 ///
248 /// Panics if the current thread is not executing a fiber from this library.
suspend(&mut self, value: Yield) -> Resume249 pub fn suspend(&mut self, value: Yield) -> Resume {
250 self.inner
251 .switch::<Resume, Yield, Return>(RunResult::Yield(value))
252 }
253
execute( inner: imp::Suspend, initial: Resume, func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return, ) -> imp::Suspend254 fn execute(
255 inner: imp::Suspend,
256 initial: Resume,
257 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return,
258 ) -> imp::Suspend {
259 let mut suspend = Suspend {
260 inner,
261 _phantom: PhantomData,
262 };
263
264 #[cfg(feature = "std")]
265 let result = {
266 use std::panic::{self, AssertUnwindSafe};
267 let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend)));
268 match result {
269 Ok(result) => RunResult::Returned(result),
270 Err(panic) => RunResult::Panicked(panic),
271 }
272 };
273
274 // Note that it is sound to omit the `catch_unwind` here: it
275 // will not result in unwinding going off the top of the fiber
276 // stack, because the code on the fiber stack is invoked via
277 // an extern "C" boundary which will panic on unwinds.
278 #[cfg(not(feature = "std"))]
279 let result = RunResult::Returned((func)(initial, &mut suspend));
280
281 suspend.inner.start_exit::<Resume, Yield, Return>(result);
282 suspend.inner
283 }
284 }
285
286 impl<A, B, C> Drop for Fiber<'_, A, B, C> {
drop(&mut self)287 fn drop(&mut self) {
288 debug_assert!(self.done.get(), "fiber dropped without finishing");
289 unsafe {
290 self.inner.drop::<A, B, C>();
291 }
292 }
293 }
294
295 #[cfg(all(test))]
296 mod tests {
297 use super::{Fiber, FiberStack};
298 use alloc::string::ToString;
299 use std::cell::Cell;
300 use std::rc::Rc;
301
fiber_stack(size: usize) -> FiberStack302 fn fiber_stack(size: usize) -> FiberStack {
303 FiberStack::new(size, false).unwrap()
304 }
305
306 #[test]
small_stacks()307 fn small_stacks() {
308 Fiber::<(), (), ()>::new(fiber_stack(0), |_, _| {})
309 .unwrap()
310 .resume(())
311 .unwrap();
312 Fiber::<(), (), ()>::new(fiber_stack(1), |_, _| {})
313 .unwrap()
314 .resume(())
315 .unwrap();
316 }
317
318 #[test]
smoke()319 fn smoke() {
320 let hit = Rc::new(Cell::new(false));
321 let hit2 = hit.clone();
322 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, _| {
323 hit2.set(true);
324 })
325 .unwrap();
326 assert!(!hit.get());
327 fiber.resume(()).unwrap();
328 assert!(hit.get());
329 }
330
331 #[test]
suspend_and_resume()332 fn suspend_and_resume() {
333 let hit = Rc::new(Cell::new(false));
334 let hit2 = hit.clone();
335 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, s| {
336 s.suspend(());
337 hit2.set(true);
338 s.suspend(());
339 })
340 .unwrap();
341 assert!(!hit.get());
342 assert!(fiber.resume(()).is_err());
343 assert!(!hit.get());
344 assert!(fiber.resume(()).is_err());
345 assert!(hit.get());
346 assert!(fiber.resume(()).is_ok());
347 assert!(hit.get());
348 }
349
350 #[test]
backtrace_traces_to_host()351 fn backtrace_traces_to_host() {
352 #[inline(never)] // try to get this to show up in backtraces
353 fn look_for_me() {
354 run_test();
355 }
356 fn assert_contains_host() {
357 let trace = backtrace::Backtrace::new();
358 println!("{trace:?}");
359 assert!(
360 trace
361 .frames()
362 .iter()
363 .flat_map(|f| f.symbols())
364 .filter_map(|s| Some(s.name()?.to_string()))
365 .any(|s| s.contains("look_for_me"))
366 // TODO: apparently windows unwind routines don't unwind through fibers, so this will always fail. Is there a way we can fix that?
367 || cfg!(windows)
368 // TODO: the system libunwind is broken (#2808)
369 || cfg!(all(target_os = "macos", target_arch = "aarch64"))
370 // TODO: see comments in `arm.rs` about how this seems to work
371 // in gdb but not at runtime, unsure why at this time.
372 || cfg!(target_arch = "arm")
373 // asan does weird things
374 || cfg!(asan)
375 // miri is a bit of a stretch to get working here
376 || cfg!(miri)
377 );
378 }
379
380 fn run_test() {
381 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), s| {
382 assert_contains_host();
383 s.suspend(());
384 assert_contains_host();
385 s.suspend(());
386 assert_contains_host();
387 })
388 .unwrap();
389 assert!(fiber.resume(()).is_err());
390 assert!(fiber.resume(()).is_err());
391 assert!(fiber.resume(()).is_ok());
392 }
393
394 look_for_me();
395 }
396
397 #[test]
398 #[cfg(feature = "std")]
panics_propagated()399 fn panics_propagated() {
400 use std::panic::{self, AssertUnwindSafe};
401
402 let a = Rc::new(Cell::new(false));
403 let b = SetOnDrop(a.clone());
404 let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), _s| {
405 let _ = &b;
406 panic!();
407 })
408 .unwrap();
409 assert!(panic::catch_unwind(AssertUnwindSafe(|| fiber.resume(()))).is_err());
410 assert!(a.get());
411
412 struct SetOnDrop(Rc<Cell<bool>>);
413
414 impl Drop for SetOnDrop {
415 fn drop(&mut self) {
416 self.0.set(true);
417 }
418 }
419 }
420
421 #[test]
suspend_and_resume_values()422 fn suspend_and_resume_values() {
423 let fiber = Fiber::new(fiber_stack(1024 * 1024), move |first, s| {
424 assert_eq!(first, 2.0);
425 assert_eq!(s.suspend(4), 3.0);
426 "hello".to_string()
427 })
428 .unwrap();
429 assert_eq!(fiber.resume(2.0), Err(4));
430 assert_eq!(fiber.resume(3.0), Ok("hello".to_string()));
431 }
432
433 #[test]
fiber_stack_max_size()434 fn fiber_stack_max_size() {
435 if cfg!(windows) || cfg!(miri) {
436 return;
437 }
438 assert!(FiberStack::new(usize::MAX, true).is_err());
439 assert!(FiberStack::new(usize::MAX, false).is_err());
440 }
441 }
442