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