Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fiber/src/lib.rs
3052 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 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
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
/// 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`.
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.
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.
223
pub fn done(&self) -> bool {
224
self.done.get()
225
}
226
227
/// Gets the stack associated with this fiber.
228
pub fn stack(&self) -> &FiberStack {
229
self.stack.as_ref().unwrap()
230
}
231
232
/// When this fiber has finished executing, reclaim its stack.
233
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.
249
pub fn suspend(&mut self, value: Yield) -> Resume {
250
self.inner
251
.switch::<Resume, Yield, Return>(RunResult::Yield(value))
252
}
253
254
fn execute(
255
inner: imp::Suspend,
256
initial: Resume,
257
func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return,
258
) {
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.exit::<Resume, Yield, Return>(result);
282
}
283
}
284
285
impl<A, B, C> Drop for Fiber<'_, A, B, C> {
286
fn drop(&mut self) {
287
debug_assert!(self.done.get(), "fiber dropped without finishing");
288
unsafe {
289
self.inner.drop::<A, B, C>();
290
}
291
}
292
}
293
294
#[cfg(all(test))]
295
mod tests {
296
use super::{Fiber, FiberStack};
297
use alloc::string::ToString;
298
use std::cell::Cell;
299
use std::rc::Rc;
300
301
fn fiber_stack(size: usize) -> FiberStack {
302
FiberStack::new(size, false).unwrap()
303
}
304
305
#[test]
306
fn small_stacks() {
307
Fiber::<(), (), ()>::new(fiber_stack(0), |_, _| {})
308
.unwrap()
309
.resume(())
310
.unwrap();
311
Fiber::<(), (), ()>::new(fiber_stack(1), |_, _| {})
312
.unwrap()
313
.resume(())
314
.unwrap();
315
}
316
317
#[test]
318
fn smoke() {
319
let hit = Rc::new(Cell::new(false));
320
let hit2 = hit.clone();
321
let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, _| {
322
hit2.set(true);
323
})
324
.unwrap();
325
assert!(!hit.get());
326
fiber.resume(()).unwrap();
327
assert!(hit.get());
328
}
329
330
#[test]
331
fn suspend_and_resume() {
332
let hit = Rc::new(Cell::new(false));
333
let hit2 = hit.clone();
334
let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |_, s| {
335
s.suspend(());
336
hit2.set(true);
337
s.suspend(());
338
})
339
.unwrap();
340
assert!(!hit.get());
341
assert!(fiber.resume(()).is_err());
342
assert!(!hit.get());
343
assert!(fiber.resume(()).is_err());
344
assert!(hit.get());
345
assert!(fiber.resume(()).is_ok());
346
assert!(hit.get());
347
}
348
349
#[test]
350
fn backtrace_traces_to_host() {
351
#[inline(never)] // try to get this to show up in backtraces
352
fn look_for_me() {
353
run_test();
354
}
355
fn assert_contains_host() {
356
let trace = backtrace::Backtrace::new();
357
println!("{trace:?}");
358
assert!(
359
trace
360
.frames()
361
.iter()
362
.flat_map(|f| f.symbols())
363
.filter_map(|s| Some(s.name()?.to_string()))
364
.any(|s| s.contains("look_for_me"))
365
// TODO: apparently windows unwind routines don't unwind through fibers, so this will always fail. Is there a way we can fix that?
366
|| cfg!(windows)
367
// TODO: the system libunwind is broken (#2808)
368
|| cfg!(all(target_os = "macos", target_arch = "aarch64"))
369
// TODO: see comments in `arm.rs` about how this seems to work
370
// in gdb but not at runtime, unsure why at this time.
371
|| cfg!(target_arch = "arm")
372
// asan does weird things
373
|| cfg!(asan)
374
// miri is a bit of a stretch to get working here
375
|| cfg!(miri)
376
);
377
}
378
379
fn run_test() {
380
let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), s| {
381
assert_contains_host();
382
s.suspend(());
383
assert_contains_host();
384
s.suspend(());
385
assert_contains_host();
386
})
387
.unwrap();
388
assert!(fiber.resume(()).is_err());
389
assert!(fiber.resume(()).is_err());
390
assert!(fiber.resume(()).is_ok());
391
}
392
393
look_for_me();
394
}
395
396
#[test]
397
#[cfg(feature = "std")]
398
fn panics_propagated() {
399
use std::panic::{self, AssertUnwindSafe};
400
401
let a = Rc::new(Cell::new(false));
402
let b = SetOnDrop(a.clone());
403
let fiber = Fiber::<(), (), ()>::new(fiber_stack(1024 * 1024), move |(), _s| {
404
let _ = &b;
405
panic!();
406
})
407
.unwrap();
408
assert!(panic::catch_unwind(AssertUnwindSafe(|| fiber.resume(()))).is_err());
409
assert!(a.get());
410
411
struct SetOnDrop(Rc<Cell<bool>>);
412
413
impl Drop for SetOnDrop {
414
fn drop(&mut self) {
415
self.0.set(true);
416
}
417
}
418
}
419
420
#[test]
421
fn suspend_and_resume_values() {
422
let fiber = Fiber::new(fiber_stack(1024 * 1024), move |first, s| {
423
assert_eq!(first, 2.0);
424
assert_eq!(s.suspend(4), 3.0);
425
"hello".to_string()
426
})
427
.unwrap();
428
assert_eq!(fiber.resume(2.0), Err(4));
429
assert_eq!(fiber.resume(3.0), Ok("hello".to_string()));
430
}
431
}
432
433