xref: /wasmtime-44.0.1/crates/fiber/src/miri.rs (revision f3156fe0)
1 //! A dummy implementation of fibers when running with MIRI to use a separate
2 //! thread as the implementation of a fiber.
3 //!
4 //! Note that this technically isn't correct because it means that the code
5 //! running in the fiber won't share TLS variables with the code managing the
6 //! fiber, but it's enough for now.
7 //!
8 //! The general idea is that a thread is held in a suspended state to hold the
9 //! state of the stack on that thread. When a fiber is resumed that thread
10 //! starts executing and the caller stops. When a fiber suspends then that
11 //! thread stops and the original caller returns. There's still possible minor
12 //! amounts of parallelism but in general they should be quite scoped and not
13 //! visible from the caller/callee really.
14 //!
15 //! An issue was opened at rust-lang/miri#4392 for a possible extension to miri
16 //! to support stack-switching in a first-class manner.
17 
18 use crate::{Result, RunResult, RuntimeFiberStack};
19 use std::boxed::Box;
20 use std::cell::Cell;
21 use std::io;
22 use std::mem;
23 use std::ops::Range;
24 use std::sync::{Arc, Condvar, Mutex};
25 use std::thread::{self, JoinHandle};
26 
27 pub type Error = io::Error;
28 
29 pub struct FiberStack(usize);
30 
31 impl FiberStack {
new(size: usize, _zeroed: bool) -> Result<Self>32     pub fn new(size: usize, _zeroed: bool) -> Result<Self> {
33         Ok(FiberStack(size))
34     }
35 
from_raw_parts(_base: *mut u8, _guard_size: usize, _len: usize) -> Result<Self>36     pub unsafe fn from_raw_parts(_base: *mut u8, _guard_size: usize, _len: usize) -> Result<Self> {
37         Err(io::ErrorKind::Unsupported.into())
38     }
39 
is_from_raw_parts(&self) -> bool40     pub fn is_from_raw_parts(&self) -> bool {
41         false
42     }
43 
from_custom(_custom: Box<dyn RuntimeFiberStack>) -> Result<Self>44     pub fn from_custom(_custom: Box<dyn RuntimeFiberStack>) -> Result<Self> {
45         Err(io::ErrorKind::Unsupported.into())
46     }
47 
top(&self) -> Option<*mut u8>48     pub fn top(&self) -> Option<*mut u8> {
49         None
50     }
51 
range(&self) -> Option<Range<usize>>52     pub fn range(&self) -> Option<Range<usize>> {
53         None
54     }
55 
guard_range(&self) -> Option<Range<*mut u8>>56     pub fn guard_range(&self) -> Option<Range<*mut u8>> {
57         None
58     }
59 }
60 
61 pub struct Fiber {
62     state: *const u8,
63     thread: Option<JoinHandle<()>>,
64 }
65 
66 pub struct Suspend {
67     state: *const u8,
68 }
69 
70 /// Shared state, inside an `Arc`, between `Fiber` and `Suspend`.
71 struct SharedFiberState<A, B, C> {
72     cond: Condvar,
73     state: Mutex<State<A, B, C>>,
74 }
75 
76 enum State<A, B, C> {
77     /// No current state, or otherwise something is waiting for something else
78     /// to happen.
79     None,
80 
81     /// The fiber is being resumed with this result.
82     ResumeWith(RunResult<A, B, C>),
83 
84     /// The fiber is being suspended with this result
85     SuspendWith(RunResult<A, B, C>),
86 
87     /// The fiber needs to exit (part of drop).
88     Exiting,
89 }
90 
91 unsafe impl<A, B, C> Send for State<A, B, C> {}
92 unsafe impl<A, B, C> Sync for State<A, B, C> {}
93 
94 struct IgnoreSendSync<T>(T);
95 
96 unsafe impl<T> Send for IgnoreSendSync<T> {}
97 unsafe impl<T> Sync for IgnoreSendSync<T> {}
98 
run<F, A, B, C>(state: Arc<SharedFiberState<A, B, C>>, func: IgnoreSendSync<F>) where F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,99 fn run<F, A, B, C>(state: Arc<SharedFiberState<A, B, C>>, func: IgnoreSendSync<F>)
100 where
101     F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
102 {
103     // Wait for the initial message of what to initially invoke `func` with.
104     let init = {
105         let mut lock = state.state.lock().unwrap();
106         lock = state
107             .cond
108             .wait_while(lock, |msg| !matches!(msg, State::ResumeWith(_)))
109             .unwrap();
110         match mem::replace(&mut *lock, State::None) {
111             State::ResumeWith(RunResult::Resuming(init)) => init,
112             _ => unreachable!(),
113         }
114     };
115 
116     // Execute this fiber through `Suspend::execute` and once that's done
117     // deallocate the `state` that we have.
118     let state = Arc::into_raw(state);
119     let mut suspend = super::Suspend::<A, B, C>::execute(
120         Suspend {
121             state: state.cast(),
122         },
123         init,
124         func.0,
125     );
126     match suspend.block_until_notified::<A, B, C>() {
127         State::Exiting => {}
128         _ => unreachable!(),
129     }
130     unsafe {
131         drop(Arc::from_raw(state));
132     }
133 }
134 
135 impl Fiber {
new<F, A, B, C>(stack: &FiberStack, func: F) -> Result<Self> where F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,136     pub fn new<F, A, B, C>(stack: &FiberStack, func: F) -> Result<Self>
137     where
138         F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
139     {
140         // Allocate shared state between the fiber and the suspension argument.
141         let state = Arc::new(SharedFiberState::<A, B, C> {
142             cond: Condvar::new(),
143             state: Mutex::new(State::None),
144         });
145 
146         // Note the use of `spawn_unchecked` to work around `Send`. Technically
147         // a lie as we are sure enough sending values across threads. We don't
148         // have many other tools in MIRI though to allocate separate call stacks
149         // so we're doing the best we can.
150         let thread = unsafe {
151             thread::Builder::new()
152                 .stack_size(stack.0)
153                 .spawn_unchecked({
154                     let state = state.clone();
155                     let func = IgnoreSendSync(func);
156                     move || run(state, func)
157                 })?
158         };
159 
160         // Cast the fiber back into a raw pointer to lose the type parameters
161         // which our storage container does not have access to. Additionally
162         // save off the thread so the dtor here can join the thread.
163         Ok(Fiber {
164             state: Arc::into_raw(state).cast(),
165             thread: Some(thread),
166         })
167     }
168 
resume<A, B, C>(&self, _stack: &FiberStack, result: &Cell<RunResult<A, B, C>>)169     pub(crate) fn resume<A, B, C>(&self, _stack: &FiberStack, result: &Cell<RunResult<A, B, C>>) {
170         let my_state = unsafe { self.state() };
171         let mut lock = my_state.state.lock().unwrap();
172 
173         // Swap `result` into our `lock`, then wake up the actual fiber.
174         *lock = State::ResumeWith(result.replace(RunResult::Executing));
175         my_state.cond.notify_one();
176 
177         // Wait for the fiber to finish
178         lock = my_state
179             .cond
180             .wait_while(lock, |l| !matches!(l, State::SuspendWith(_)))
181             .unwrap();
182 
183         // Swap the state in our `lock` back into `result`.
184         let message = match mem::replace(&mut *lock, State::None) {
185             State::SuspendWith(msg) => msg,
186             _ => unreachable!(),
187         };
188         result.set(message);
189     }
190 
state<A, B, C>(&self) -> &SharedFiberState<A, B, C>191     unsafe fn state<A, B, C>(&self) -> &SharedFiberState<A, B, C> {
192         unsafe { &*(self.state as *const SharedFiberState<A, B, C>) }
193     }
194 
drop<A, B, C>(&mut self)195     pub(crate) unsafe fn drop<A, B, C>(&mut self) {
196         let state = unsafe { self.state::<A, B, C>() };
197 
198         // Store an indication that we expect the fiber to exit, then wake it up
199         // if it's waiting.
200         *state.state.lock().unwrap() = State::Exiting;
201         state.cond.notify_one();
202 
203         // Wait for the child thread to complete.
204         self.thread.take().unwrap().join().unwrap();
205 
206         // Clean up our state using the type parameters we know of here.
207         unsafe {
208             drop(Arc::from_raw(
209                 self.state.cast::<SharedFiberState<A, B, C>>(),
210             ));
211         }
212     }
213 }
214 
215 impl Suspend {
set_result<A, B, C>(&mut self, result: RunResult<A, B, C>)216     fn set_result<A, B, C>(&mut self, result: RunResult<A, B, C>) {
217         let state = unsafe { self.state() };
218         let mut lock = state.state.lock().unwrap();
219 
220         // Our fiber state should be empty, and after verifying that store what
221         // we are suspending with.
222         assert!(matches!(*lock, State::None));
223         *lock = State::SuspendWith(result);
224         state.cond.notify_one();
225     }
226 
block_until_notified<A, B, C>(&mut self) -> State<A, B, C>227     fn block_until_notified<A, B, C>(&mut self) -> State<A, B, C> {
228         let state = unsafe { self.state() };
229         let mut lock = state.state.lock().unwrap();
230         lock = state
231             .cond
232             .wait_while(lock, |s| {
233                 !matches!(s, State::ResumeWith(_) | State::Exiting)
234             })
235             .unwrap();
236         mem::replace(&mut *lock, State::None)
237     }
238 
switch<A, B, C>(&mut self, result: RunResult<A, B, C>) -> A239     pub(crate) fn switch<A, B, C>(&mut self, result: RunResult<A, B, C>) -> A {
240         self.set_result(result);
241 
242         // Wait for the resumption to come back, which is returned from this
243         // method.
244         match self.block_until_notified::<A, B, C>() {
245             State::ResumeWith(RunResult::Resuming(a)) => a,
246             _ => unreachable!(),
247         }
248     }
249 
start_exit<A, B, C>(&mut self, result: RunResult<A, B, C>)250     pub(crate) fn start_exit<A, B, C>(&mut self, result: RunResult<A, B, C>) {
251         self.set_result(result);
252     }
253 
state<A, B, C>(&self) -> &SharedFiberState<A, B, C>254     unsafe fn state<A, B, C>(&self) -> &SharedFiberState<A, B, C> {
255         unsafe { &*(self.state as *const SharedFiberState<A, B, C>) }
256     }
257 }
258