1 //! no_std implementation of fibers.
2 //!
3 //! This is a very stripped-down version of the Unix platform support,
4 //! but without mmap or guard pages, because on no_std systems we do
5 //! not assume that virtual memory exists.
6 //!
7 //! The stack layout is nevertheless the same (modulo the guard page)
8 //! as on Unix because we share its low-level implementations:
9 //!
10 //! ```text
11 //! 0xB000 +-----------------------+ <- top of stack
12 //! | &Cell<RunResult> | <- where to store results
13 //! 0xAff8 +-----------------------+
14 //! | *const u8 | <- last sp to resume from
15 //! 0xAff0 +-----------------------+ <- 16-byte aligned
16 //! | |
17 //! ~ ... ~ <- actual native stack space to use
18 //! | |
19 //! 0x0000 +-----------------------+
20 //! ```
21 //!
22 //! Here `0xAff8` is filled in temporarily while `resume` is running. The fiber
23 //! started with 0xB000 as a parameter so it knows how to find this.
24 //! Additionally `resumes` stores state at 0xAff0 to restart execution, and
25 //! `suspend`, which has 0xB000 so it can find this, will read that and write
26 //! its own resumption information into this slot as well.
27
28 use crate::stackswitch::*;
29 use crate::{Result, RunResult, RuntimeFiberStack};
30 use alloc::boxed::Box;
31 use core::cell::Cell;
32 use core::ops::Range;
33 use wasmtime_environ::prelude::*;
34
35 // The no_std implementation is infallible in practice, but we use
36 // `wasmtime_environ::error::Error` here absent any better alternative.
37 pub use wasmtime_environ::error::Error;
38
39 pub struct FiberStack {
40 base: BasePtr,
41 len: usize,
42 /// Backing storage, if owned. Allocated once at startup and then
43 /// not reallocated afterward.
44 storage: TryVec<u8>,
45 }
46
47 struct BasePtr(*mut u8);
48
49 unsafe impl Send for BasePtr {}
50 unsafe impl Sync for BasePtr {}
51
52 const STACK_ALIGN: usize = 16;
53
54 /// Align a pointer by incrementing it up to `align - 1`
55 /// bytes. `align` must be a power of two. Also updates the length as
56 /// appropriate so that `ptr + len` points to the same endpoint.
align_ptr(ptr: *mut u8, len: usize, align: usize) -> (*mut u8, usize)57 fn align_ptr(ptr: *mut u8, len: usize, align: usize) -> (*mut u8, usize) {
58 let ptr = ptr as usize;
59 let aligned = (ptr + align - 1) & !(align - 1);
60 let new_len = len - (aligned - ptr);
61 (aligned as *mut u8, new_len)
62 }
63
64 impl FiberStack {
new(size: usize, zeroed: bool) -> Result<Self>65 pub fn new(size: usize, zeroed: bool) -> Result<Self> {
66 // Round up the size to at least one page.
67 let size = core::cmp::max(4096, size);
68 let mut storage = TryVec::new();
69 storage.reserve_exact(size)?;
70 if zeroed {
71 storage.resize(size, 0)?;
72 }
73 let (base, len) = align_ptr(storage.as_mut_ptr(), size, STACK_ALIGN);
74 Ok(FiberStack {
75 storage,
76 base: BasePtr(base),
77 len,
78 })
79 }
80
from_raw_parts(base: *mut u8, guard_size: usize, len: usize) -> Result<Self>81 pub unsafe fn from_raw_parts(base: *mut u8, guard_size: usize, len: usize) -> Result<Self> {
82 Ok(FiberStack {
83 storage: TryVec::default(),
84 base: BasePtr(unsafe { base.offset(isize::try_from(guard_size).unwrap()) }),
85 len,
86 })
87 }
88
is_from_raw_parts(&self) -> bool89 pub fn is_from_raw_parts(&self) -> bool {
90 self.storage.is_empty()
91 }
92
from_custom(_custom: Box<dyn RuntimeFiberStack>) -> Result<Self>93 pub fn from_custom(_custom: Box<dyn RuntimeFiberStack>) -> Result<Self> {
94 unimplemented!("Custom fiber stacks not supported in no_std fiber library")
95 }
96
top(&self) -> Option<*mut u8>97 pub fn top(&self) -> Option<*mut u8> {
98 Some(self.base.0.wrapping_byte_add(self.len))
99 }
100
range(&self) -> Option<Range<usize>>101 pub fn range(&self) -> Option<Range<usize>> {
102 let base = self.base.0 as usize;
103 Some(base..base + self.len)
104 }
105
guard_range(&self) -> Option<Range<*mut u8>>106 pub fn guard_range(&self) -> Option<Range<*mut u8>> {
107 None
108 }
109 }
110
111 pub struct Fiber;
112
113 pub struct Suspend {
114 top_of_stack: *mut u8,
115 }
116
fiber_start<F, A, B, C>(arg0: *mut u8, top_of_stack: *mut u8) -> *mut u8 where F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,117 extern "C" fn fiber_start<F, A, B, C>(arg0: *mut u8, top_of_stack: *mut u8) -> *mut u8
118 where
119 F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
120 {
121 unsafe {
122 let inner = Suspend { top_of_stack };
123 let initial = inner.take_resume::<A, B, C>();
124 let inner =
125 super::Suspend::<A, B, C>::execute(inner, initial, Box::from_raw(arg0.cast::<F>()));
126 inner.top_of_stack
127 }
128 }
129
130 impl Fiber {
new<F, A, B, C>(stack: &FiberStack, func: F) -> Result<Self> where F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,131 pub fn new<F, A, B, C>(stack: &FiberStack, func: F) -> Result<Self>
132 where
133 F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
134 {
135 // On unsupported platforms `wasmtime_fiber_init` is a panicking shim so
136 // return an error saying the host architecture isn't supported instead.
137 if !SUPPORTED_ARCH {
138 bail!("fibers unsupported on this host architecture");
139 }
140 unsafe {
141 let data = Box::into_raw(try_new::<Box<_>>(func)?).cast();
142 wasmtime_fiber_init(stack.top().unwrap(), fiber_start::<F, A, B, C>, data);
143 }
144
145 Ok(Self)
146 }
147
resume<A, B, C>(&self, stack: &FiberStack, result: &Cell<RunResult<A, B, C>>)148 pub(crate) fn resume<A, B, C>(&self, stack: &FiberStack, result: &Cell<RunResult<A, B, C>>) {
149 unsafe {
150 // Store where our result is going at the very tip-top of the
151 // stack, otherwise known as our reserved slot for this information.
152 //
153 // In the diagram above this is updating address 0xAff8
154 let addr = stack.top().unwrap().cast::<usize>().offset(-1);
155 addr.write(result as *const _ as usize);
156
157 assert!(SUPPORTED_ARCH);
158 wasmtime_fiber_switch(stack.top().unwrap());
159
160 // null this out to help catch use-after-free
161 addr.write(0);
162 }
163 }
164
drop<A, B, C>(&mut self)165 pub(crate) unsafe fn drop<A, B, C>(&mut self) {}
166 }
167
168 impl Suspend {
switch<A, B, C>(&mut self, result: RunResult<A, B, C>) -> A169 pub(crate) fn switch<A, B, C>(&mut self, result: RunResult<A, B, C>) -> A {
170 unsafe {
171 // Calculate 0xAff8 and then write to it
172 (*self.result_location::<A, B, C>()).set(result);
173
174 wasmtime_fiber_switch(self.top_of_stack);
175
176 self.take_resume::<A, B, C>()
177 }
178 }
179
start_exit<A, B, C>(&mut self, result: RunResult<A, B, C>)180 pub(crate) fn start_exit<A, B, C>(&mut self, result: RunResult<A, B, C>) {
181 unsafe {
182 (*self.result_location::<A, B, C>()).set(result);
183 }
184 }
185
take_resume<A, B, C>(&self) -> A186 unsafe fn take_resume<A, B, C>(&self) -> A {
187 unsafe {
188 match (*self.result_location::<A, B, C>()).replace(RunResult::Executing) {
189 RunResult::Resuming(val) => val,
190 _ => panic!("not in resuming state"),
191 }
192 }
193 }
194
result_location<A, B, C>(&self) -> *const Cell<RunResult<A, B, C>>195 unsafe fn result_location<A, B, C>(&self) -> *const Cell<RunResult<A, B, C>> {
196 unsafe {
197 let ret = self.top_of_stack.cast::<*const u8>().offset(-1).read();
198 assert!(!ret.is_null());
199 ret.cast()
200 }
201 }
202 }
203