Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fiber/src/lib.rs
1692 views
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
436