Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fiber/src/miri.rs
1692 views
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 {
32
pub fn new(size: usize, _zeroed: bool) -> Result<Self> {
33
Ok(FiberStack(size))
34
}
35
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
40
pub fn is_from_raw_parts(&self) -> bool {
41
false
42
}
43
44
pub fn from_custom(_custom: Box<dyn RuntimeFiberStack>) -> Result<Self> {
45
Err(io::ErrorKind::Unsupported.into())
46
}
47
48
pub fn top(&self) -> Option<*mut u8> {
49
None
50
}
51
52
pub fn range(&self) -> Option<Range<usize>> {
53
None
54
}
55
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
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
super::Suspend::<A, B, C>::execute(
120
Suspend {
121
state: state.cast(),
122
},
123
init,
124
func.0,
125
);
126
unsafe {
127
drop(Arc::from_raw(state));
128
}
129
}
130
131
impl Fiber {
132
pub fn new<F, A, B, C>(stack: &FiberStack, func: F) -> Result<Self>
133
where
134
F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
135
{
136
// Allocate shared state between the fiber and the suspension argument.
137
let state = Arc::new(SharedFiberState::<A, B, C> {
138
cond: Condvar::new(),
139
state: Mutex::new(State::None),
140
});
141
142
// Note the use of `spawn_unchecked` to work around `Send`. Technically
143
// a lie as we are sure enough sending values across threads. We don't
144
// have many other tools in MIRI though to allocate separate call stacks
145
// so we're doing the best we can.
146
let thread = unsafe {
147
thread::Builder::new()
148
.stack_size(stack.0)
149
.spawn_unchecked({
150
let state = state.clone();
151
let func = IgnoreSendSync(func);
152
move || run(state, func)
153
})
154
.unwrap()
155
};
156
157
// Cast the fiber back into a raw pointer to lose the type parameters
158
// which our storage container does not have access to. Additionally
159
// save off the thread so the dtor here can join the thread.
160
Ok(Fiber {
161
state: Arc::into_raw(state).cast(),
162
thread: Some(thread),
163
})
164
}
165
166
pub(crate) fn resume<A, B, C>(&self, _stack: &FiberStack, result: &Cell<RunResult<A, B, C>>) {
167
let my_state = unsafe { self.state() };
168
let mut lock = my_state.state.lock().unwrap();
169
170
// Swap `result` into our `lock`, then wake up the actual fiber.
171
*lock = State::ResumeWith(result.replace(RunResult::Executing));
172
my_state.cond.notify_one();
173
174
// Wait for the fiber to finish
175
lock = my_state
176
.cond
177
.wait_while(lock, |l| !matches!(l, State::SuspendWith(_)))
178
.unwrap();
179
180
// Swap the state in our `lock` back into `result`.
181
let message = match mem::replace(&mut *lock, State::None) {
182
State::SuspendWith(msg) => msg,
183
_ => unreachable!(),
184
};
185
result.set(message);
186
}
187
188
unsafe fn state<A, B, C>(&self) -> &SharedFiberState<A, B, C> {
189
unsafe { &*(self.state as *const SharedFiberState<A, B, C>) }
190
}
191
192
pub(crate) unsafe fn drop<A, B, C>(&mut self) {
193
let state = unsafe { self.state::<A, B, C>() };
194
195
// Store an indication that we expect the fiber to exit, then wake it up
196
// if it's waiting.
197
*state.state.lock().unwrap() = State::Exiting;
198
state.cond.notify_one();
199
200
// Wait for the child thread to complete.
201
self.thread.take().unwrap().join().unwrap();
202
203
// Clean up our state using the type parameters we know of here.
204
unsafe {
205
drop(Arc::from_raw(
206
self.state.cast::<SharedFiberState<A, B, C>>(),
207
));
208
}
209
}
210
}
211
212
impl Suspend {
213
fn suspend<A, B, C>(&mut self, result: RunResult<A, B, C>) -> State<A, B, C> {
214
let state = unsafe { self.state() };
215
let mut lock = state.state.lock().unwrap();
216
217
// Our fiber state should be empty, and after verifying that store what
218
// we are suspending with.
219
assert!(matches!(*lock, State::None));
220
*lock = State::SuspendWith(result);
221
state.cond.notify_one();
222
223
// Wait for the resumption to come back, which is returned from this
224
// method.
225
lock = state
226
.cond
227
.wait_while(lock, |s| {
228
!matches!(s, State::ResumeWith(_) | State::Exiting)
229
})
230
.unwrap();
231
mem::replace(&mut *lock, State::None)
232
}
233
234
pub(crate) fn switch<A, B, C>(&mut self, result: RunResult<A, B, C>) -> A {
235
match self.suspend(result) {
236
State::ResumeWith(RunResult::Resuming(a)) => a,
237
_ => unreachable!(),
238
}
239
}
240
241
pub(crate) fn exit<A, B, C>(&mut self, result: RunResult<A, B, C>) {
242
match self.suspend(result) {
243
State::Exiting => {}
244
_ => unreachable!(),
245
}
246
}
247
248
unsafe fn state<A, B, C>(&self) -> &SharedFiberState<A, B, C> {
249
unsafe { &*(self.state as *const SharedFiberState<A, B, C>) }
250
}
251
}
252
253