-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathlib.rs
159 lines (145 loc) · 6.27 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Implement [`wasi-threads`].
//!
//! [`wasi-threads`]: https://github.com/WebAssembly/wasi-threads
use anyhow::{anyhow, bail, Result};
use rand::Rng;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
use std::thread;
use wasmtime::{Caller, Linker, Module, SharedMemory, Store, ValType};
use wasmtime_wasi::maybe_exit_on_error;
// This name is a function export designated by the wasi-threads specification:
// https://github.com/WebAssembly/wasi-threads/#detailed-design-discussion
const WASI_ENTRY_POINT: &str = "wasi_thread_start";
pub struct WasiThreadsCtx<T> {
module: Module,
linker: Arc<Linker<T>>,
}
impl<T: Clone + Send + 'static> WasiThreadsCtx<T> {
pub fn new(module: Module, linker: Arc<Linker<T>>) -> Result<Self> {
if !has_wasi_entry_point(&module) {
bail!(
"failed to find wasi-threads entry point function: {}",
WASI_ENTRY_POINT
);
}
Ok(Self { module, linker })
}
pub fn spawn(&self, host: T, thread_start_arg: i32) -> Result<i32> {
let module = self.module.clone();
let linker = self.linker.clone();
// Start a Rust thread running a new instance of the current module.
let wasi_thread_id = random_thread_id();
let builder = thread::Builder::new().name(format!("wasi-thread-{}", wasi_thread_id));
builder.spawn(move || {
// Catch any panic failures in host code; e.g., if a WASI module
// were to crash, we want all threads to exit, not just this one.
let result = catch_unwind(AssertUnwindSafe(|| {
// Each new instance is created in its own store.
let mut store = Store::new(&module.engine(), host);
// Ideally, we would have already checked much earlier (e.g.,
// `new`) whether the module can be instantiated. Because
// `Linker::instantiate_pre` requires a `Store` and that is only
// available now. TODO:
// https://github.com/bytecodealliance/wasmtime/issues/5675.
let instance = linker.instantiate(&mut store, &module).expect(&format!(
"wasi-thread-{} exited unsuccessfully: failed to instantiate",
wasi_thread_id
));
let thread_entry_point = instance
.get_typed_func::<(i32, i32), ()>(&mut store, WASI_ENTRY_POINT)
.unwrap();
// Start the thread's entry point. Any traps or calls to
// `proc_exit`, by specification, should end execution for all
// threads. This code uses `process::exit` to do so, which is what
// the user expects from the CLI but probably not in a Wasmtime
// embedding.
log::trace!(
"spawned thread id = {}; calling start function `{}` with: {}",
wasi_thread_id,
WASI_ENTRY_POINT,
thread_start_arg
);
match thread_entry_point.call(&mut store, (wasi_thread_id, thread_start_arg)) {
Ok(_) => log::trace!("exiting thread id = {} normally", wasi_thread_id),
Err(e) => {
log::trace!("exiting thread id = {} due to error", wasi_thread_id);
let e = maybe_exit_on_error(e);
eprintln!("Error: {:?}", e);
std::process::exit(1);
}
}
}));
if let Err(e) = result {
eprintln!("wasi-thread-{} panicked: {:?}", wasi_thread_id, e);
std::process::exit(1);
}
})?;
Ok(wasi_thread_id)
}
}
/// Helper for generating valid WASI thread IDs (TID).
///
/// Callers of `wasi_thread_spawn` expect a TID >=0 to indicate a successful
/// spawning of the thread whereas a negative return value indicates an
/// failure to spawn.
fn random_thread_id() -> i32 {
let tid: u32 = rand::thread_rng().gen();
(tid >> 1) as i32
}
/// Manually add the WASI `thread_spawn` function to the linker.
///
/// It is unclear what namespace the `wasi-threads` proposal should live under:
/// it is not clear if it should be included in any of the `preview*` releases
/// so for the time being its module namespace is simply `"wasi"` (TODO).
pub fn add_to_linker<T: Clone + Send + 'static>(
linker: &mut wasmtime::Linker<T>,
store: &wasmtime::Store<T>,
module: &Module,
get_cx: impl Fn(&mut T) -> &WasiThreadsCtx<T> + Send + Sync + Copy + 'static,
) -> anyhow::Result<SharedMemory> {
linker.func_wrap(
"wasi",
"thread-spawn",
move |mut caller: Caller<'_, T>, start_arg: i32| -> i32 {
log::trace!("new thread requested via `wasi::thread_spawn` call");
let host = caller.data().clone();
let ctx = get_cx(caller.data_mut());
match ctx.spawn(host, start_arg) {
Ok(thread_id) => {
assert!(thread_id >= 0, "thread_id = {}", thread_id);
thread_id
}
Err(e) => {
log::error!("failed to spawn thread: {}", e);
-1
}
}
},
)?;
// Find the shared memory import and satisfy it with a newly-created shared
// memory import. This currently does not handle multiple memories (TODO).
for import in module.imports() {
if let Some(m) = import.ty().memory() {
if m.is_shared() {
let mem = SharedMemory::new(module.engine(), m.clone())?;
linker.define(store, import.module(), import.name(), mem.clone())?;
return Ok(mem);
}
}
}
Err(anyhow!(
"unable to link a shared memory import to the module; a `wasi-threads` \
module should import a single shared memory as \"memory\""
))
}
fn has_wasi_entry_point(module: &Module) -> bool {
module
.get_export(WASI_ENTRY_POINT)
.and_then(|t| t.func().cloned())
.and_then(|t| {
let params: Vec<ValType> = t.params().collect();
Some(params == [ValType::I32, ValType::I32] && t.results().len() == 0)
})
.unwrap_or(false)
}