1use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
17use std::sync::{Arc, Mutex};
18
19use wasmtime::{Caller, Engine, Linker, Module, SharedMemory};
20
21use crate::host::{GuestMemory, Host};
22
23#[derive(Clone, Debug)]
25pub enum ThreadEnd {
26 Exit(i32),
27 Trap(String),
28}
29
30pub struct Threads {
32 engine: Engine,
33 module: Module,
34 linker: Mutex<Option<Linker<Host>>>,
36 memory: SharedMemory,
37 limit: usize,
39 live: AtomicUsize,
40 next_tid: AtomicI32,
41 end: Mutex<Option<ThreadEnd>>,
42 acknowledged: std::sync::atomic::AtomicBool,
45 start: std::time::Instant,
47}
48
49impl Threads {
50 pub fn new(engine: &Engine, module: &Module, memory: SharedMemory, limit: usize, start: std::time::Instant) -> Arc<Threads> {
51 Arc::new(Threads {
52 engine: engine.clone(),
53 module: module.clone(),
54 linker: Mutex::new(None),
55 memory,
56 limit,
57 live: AtomicUsize::new(0),
58 next_tid: AtomicI32::new(1),
59 end: Mutex::new(None),
60 acknowledged: std::sync::atomic::AtomicBool::new(false),
61 start,
62 })
63 }
64
65 pub fn set_linker(&self, linker: Linker<Host>) {
67 *self.linker.lock().unwrap() = Some(linker);
68 }
69
70 pub fn memory(&self) -> &SharedMemory {
71 &self.memory
72 }
73
74 pub fn ended(&self) -> Option<ThreadEnd> {
76 let e = self.end.lock().unwrap().clone();
77 if e.is_some() {
78 self.acknowledged.store(true, Ordering::Release);
79 }
80 e
81 }
82
83 pub fn limit(&self) -> usize {
85 self.limit
86 }
87
88 pub fn live(&self) -> usize {
89 self.live.load(Ordering::Acquire)
90 }
91
92 fn spawn(self: &Arc<Self>, start_arg: i32, assets: crate::assets::Assets) -> i32 {
95 if self.live.fetch_add(1, Ordering::AcqRel) >= self.limit {
96 self.live.fetch_sub(1, Ordering::AcqRel);
97 return -1;
98 }
99 let tid = self.next_tid.fetch_add(1, Ordering::AcqRel);
100 if tid >= 0x1fff_ffff {
101 self.live.fetch_sub(1, Ordering::AcqRel);
103 return -1;
104 }
105 let me = self.clone();
106 let started = std::thread::Builder::new().name(format!("gasm-thread-{tid}")).spawn(move || {
107 let r = me.run(tid, start_arg, assets);
108 if let Err(end) = r {
109 let first = {
110 let mut e = me.end.lock().unwrap();
111 let first = e.is_none();
112 if first {
113 *e = Some(end.clone());
114 }
115 first
116 };
117 if first {
121 let me = me.clone();
122 std::thread::spawn(move || {
123 std::thread::sleep(std::time::Duration::from_secs(2));
124 if !me.acknowledged.load(Ordering::Acquire) {
125 match end {
126 ThreadEnd::Exit(c) => {
127 eprintln!("[gasm] guest exited with code {c} (on a worker thread)");
128 std::process::exit(c);
129 }
130 ThreadEnd::Trap(t) => {
131 eprintln!("error: {t}");
132 std::process::exit(1);
133 }
134 }
135 }
136 });
137 }
138 }
139 me.live.fetch_sub(1, Ordering::AcqRel);
140 });
141 match started {
142 Ok(_) => tid,
143 Err(e) => {
144 eprintln!("[gasm] threads: can't start a thread: {e}");
145 self.live.fetch_sub(1, Ordering::AcqRel);
146 -1
147 }
148 }
149 }
150
151 fn run(self: &Arc<Self>, tid: i32, start_arg: i32, assets: crate::assets::Assets) -> Result<(), ThreadEnd> {
152 let linker = self.linker.lock().unwrap().clone().ok_or_else(|| ThreadEnd::Trap("threads: spawned before the game was linked".into()))?;
153 let mut host = Host::worker(self.clone(), self.start, assets);
154 host.set_memory(GuestMemory::Shared(self.memory.clone()));
155 let mut store = wasmtime::Store::new(&self.engine, host);
156 store.epoch_deadline_trap();
158 store.set_epoch_deadline(u64::MAX / 2);
159 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| ThreadEnd::Trap(format!("thread {tid}: {e:#}")))?;
160 let start = instance
161 .get_typed_func::<(i32, i32), ()>(&mut store, "wasi_thread_start")
162 .map_err(|_| ThreadEnd::Trap("the module imports wasi.thread-spawn but doesn't export wasi_thread_start".into()))?;
163 match start.call(&mut store, (tid, start_arg)) {
164 Ok(()) => Ok(()),
165 Err(e) => match e.downcast_ref::<crate::wasi::Exit>() {
166 Some(x) => Err(ThreadEnd::Exit(x.0)),
167 None => Err(ThreadEnd::Trap(format!("thread {tid}: {e:#}"))),
168 },
169 }
170 }
171}
172
173pub fn add_to_linker(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
175 linker.func_wrap("wasi", "thread-spawn", |caller: Caller<'_, Host>, start_arg: i32| -> i32 {
176 match caller.data().threads() {
177 Some(t) => t.clone().spawn(start_arg, caller.data().assets.for_thread()),
178 None => -1,
179 }
180 })?;
181 Ok(())
182}
183
184pub fn shared_memory_import(module: &Module) -> Option<wasmtime::MemoryType> {
186 module.imports().find_map(|i| match i.ty() {
187 wasmtime::ExternType::Memory(m) if m.is_shared() => Some(m),
188 _ => None,
189 })
190}