1#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code, unused_imports))]
36
37use std::cell::{Cell, RefCell, UnsafeCell};
38use std::rc::Rc;
39use std::time::Duration;
40
41pub const DEFAULT_STACK: usize = 256 * 1024;
43const ASYNC_SIZE: usize = 256 * 1024;
44const MAIN_ASYNC_SIZE: usize = 1024 * 1024;
45const SCHED_STACK: usize = 64 * 1024;
46pub const FOREVER: f64 = -1.0;
48
49#[cfg(target_arch = "wasm32")]
50mod asm {
51 #[link(wasm_import_module = "asyncify")]
52 unsafe extern "C" {
53 pub fn start_unwind(data: *mut u8);
54 pub fn stop_unwind();
55 pub fn start_rewind(data: *mut u8);
56 pub fn stop_rewind();
57 }
58 unsafe extern "C" {
60 pub fn gasm__get_sp() -> usize;
61 pub fn gasm__set_sp(sp: usize);
62 }
63}
64
65#[derive(Clone, Copy, PartialEq, Eq, Debug)]
66enum State {
67 Ready,
68 Blocked,
69 WaitFrame,
70 Done,
71}
72
73#[repr(C)]
74struct AsyncData {
75 cur: *mut u8,
76 end: *mut u8,
77}
78
79struct Thread {
80 id: u64,
81 state: State,
82 body: Option<Box<dyn FnOnce()>>,
83 started: bool,
84 detached: bool,
85 timed_out: bool,
86 sp: usize,
88 on: usize,
90 deadline: f64,
92 stack: Vec<u128>,
94 adata: AsyncData,
95 abuf: Vec<u8>,
96}
97
98impl Thread {
99 fn new(id: u64, body: Option<Box<dyn FnOnce()>>, stack: usize, abuf: usize) -> Box<Thread> {
100 Box::new(Thread {
101 id,
102 state: State::Ready,
103 body,
104 started: false,
105 detached: false,
106 timed_out: false,
107 sp: 0,
108 on: 0,
109 deadline: FOREVER,
110 stack: vec![0u128; stack.div_ceil(16)],
111 adata: AsyncData { cur: std::ptr::null_mut(), end: std::ptr::null_mut() },
112 abuf: vec![0u8; abuf],
113 })
114 }
115 fn top(&self) -> usize {
116 self.stack.as_ptr_range().end as usize
117 }
118}
119
120struct Sched {
121 #[allow(clippy::vec_box)]
124 threads: Vec<Box<Thread>>,
125 current: usize,
126 cursor: usize,
127 next_id: u64,
128 active: bool,
129 started: bool,
130 finished: bool,
131 main: Option<fn() -> i32>,
132 main_result: i32,
133 main_base: usize,
134 frames: u32,
135}
136
137static mut REWINDING: bool = false;
140static mut UNWINDING: bool = false;
141
142struct Global(UnsafeCell<Option<Sched>>);
143unsafe impl Sync for Global {}
145static SCHED: Global = Global(UnsafeCell::new(None));
146
147#[repr(align(16))]
148struct SchedStack(UnsafeCell<[u8; SCHED_STACK]>);
149unsafe impl Sync for SchedStack {}
150static SCHED_STACK_BUF: SchedStack = SchedStack(UnsafeCell::new([0; SCHED_STACK]));
151
152fn sched() -> &'static mut Sched {
153 unsafe {
155 (*SCHED.0.get()).get_or_insert_with(|| Sched {
156 threads: Vec::new(),
157 current: 0,
158 cursor: 0,
159 next_id: 1,
160 active: false,
161 started: false,
162 finished: false,
163 main: None,
164 main_result: 0,
165 main_base: 0,
166 frames: 0,
167 })
168 }
169}
170
171fn sched_top() -> usize {
172 SCHED_STACK_BUF.0.get() as usize + SCHED_STACK
173}
174
175fn switch_impl() {
179 #[cfg(target_arch = "wasm32")]
180 unsafe {
181 if REWINDING {
182 asm::stop_rewind();
183 REWINDING = false;
184 return;
185 }
186 let s = sched();
187 let t = &mut s.threads[s.current];
188 t.adata.cur = t.abuf.as_mut_ptr();
189 t.adata.end = t.adata.cur.add(t.abuf.len());
190 UNWINDING = true;
191 asm::start_unwind((&raw mut t.adata).cast::<u8>());
192 }
193}
194static SWITCH: Global2 = Global2(switch_impl);
195struct Global2(fn());
196
197fn suspend(state: State) {
198 let s = sched();
199 let cur = s.current;
200 s.threads[cur].state = state;
201 let f = unsafe { std::ptr::read_volatile(&SWITCH.0) };
202 f()
203}
204
205pub fn available() -> bool {
207 sched().active
208}
209
210pub fn block(on: usize, deadline_ms: f64) -> bool {
215 let s = sched();
216 if !s.active {
217 if deadline_ms >= 0.0 {
218 return true;
219 }
220 crate::log("gasm::thread: deadlock (the only thread waits for something no other thread can do)");
221 std::process::abort();
222 }
223 let cur = s.current;
224 let t = &mut s.threads[cur];
225 t.on = on;
226 t.deadline = deadline_ms;
227 t.timed_out = false;
228 suspend(State::Blocked);
229 sched().threads[sched().current].timed_out
230}
231
232pub fn wake(on: usize, one: bool) {
234 if on == 0 {
235 return;
236 }
237 for t in sched().threads.iter_mut() {
238 if t.state == State::Blocked && t.on == on {
239 t.state = State::Ready;
240 if one {
241 return;
242 }
243 }
244 }
245}
246
247pub fn yield_now() {
249 if sched().active {
250 suspend(State::Ready);
251 }
252}
253
254pub fn sleep(d: Duration) {
256 let ms = d.as_secs_f64() * 1000.0;
257 if ms <= 0.0 {
258 yield_now();
259 } else {
260 block(0, crate::time_ms() + ms);
261 }
262}
263
264pub fn wait_frame() {
266 suspend(State::WaitFrame);
267}
268
269#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
271pub struct ThreadId(pub u64);
272
273pub fn current() -> ThreadId {
275 let s = sched();
276 ThreadId(s.threads.get(s.current).map_or(1, |t| t.id))
277}
278
279pub struct JoinHandle<T> {
282 id: u64,
283 result: Rc<RefCell<Option<T>>>,
284 joined: Cell<bool>,
285}
286
287impl<T> JoinHandle<T> {
288 pub fn thread_id(&self) -> ThreadId {
289 ThreadId(self.id)
290 }
291 pub fn is_finished(&self) -> bool {
293 self.result.borrow().is_some()
294 }
295 pub fn join(self) -> T {
297 while self.result.borrow().is_none() {
298 block(thread_addr(self.id), FOREVER);
299 }
300 self.joined.set(true);
301 let r = self.result.borrow_mut().take().expect("result");
302 if let Some(t) = sched().threads.iter_mut().find(|t| t.id == self.id) {
304 t.detached = true;
305 }
306 free_done();
307 r
308 }
309}
310
311impl<T> Drop for JoinHandle<T> {
312 fn drop(&mut self) {
313 if !self.joined.get() {
314 if let Some(t) = sched().threads.iter_mut().find(|t| t.id == self.id) {
315 t.detached = true;
316 }
317 free_done();
318 }
319 }
320}
321
322fn thread_addr(id: u64) -> usize {
324 usize::MAX - id as usize
325}
326
327fn free_done() {
329 let s = sched();
330 let cur_id = s.threads.get(s.current).map(|t| t.id);
331 let cursor_id = s.threads.get(s.cursor).map(|t| t.id);
332 s.threads.retain(|t| t.id == 1 || !(t.state == State::Done && t.detached) || Some(t.id) == cur_id);
333 s.current = cur_id.and_then(|id| s.threads.iter().position(|t| t.id == id)).unwrap_or(0);
334 s.cursor = cursor_id.and_then(|id| s.threads.iter().position(|t| t.id == id)).unwrap_or(0);
335}
336
337#[derive(Default)]
339pub struct Builder {
340 stack: Option<usize>,
341}
342
343impl Builder {
344 pub fn new() -> Builder {
345 Builder::default()
346 }
347 pub fn stack_size(mut self, bytes: usize) -> Builder {
349 self.stack = Some(bytes);
350 self
351 }
352 pub fn spawn<F, T>(self, f: F) -> JoinHandle<T>
355 where
356 F: FnOnce() -> T + 'static,
357 T: 'static,
358 {
359 let s = sched();
360 if !s.active {
361 panic!("gasm::thread::spawn: threads need gasm::threaded_main_loop! (and wasm-opt --asyncify)");
362 }
363 let result = Rc::new(RefCell::new(None));
364 let out = result.clone();
365 let id = s.next_id;
366 s.next_id += 1;
367 let body: Box<dyn FnOnce()> = Box::new(move || {
368 let v = f();
369 *out.borrow_mut() = Some(v);
370 });
371 s.threads.push(Thread::new(id, Some(body), self.stack.unwrap_or(DEFAULT_STACK), ASYNC_SIZE));
372 JoinHandle { id, result, joined: Cell::new(false) }
373 }
374}
375
376pub fn spawn<F, T>(f: F) -> JoinHandle<T>
378where
379 F: FnOnce() -> T + 'static,
380 T: 'static,
381{
382 Builder::new().spawn(f)
383}
384
385pub fn frames() -> u32 {
387 sched().frames
388}
389
390#[doc(hidden)]
391pub fn __set_main(f: fn() -> i32) {
392 sched().main = Some(f);
393}
394
395#[inline(never)]
398fn run_thread(id: u64) {
399 if id == 1 {
400 let main = sched().main;
401 let rc = main.map_or(0, |f| f());
402 if unsafe { UNWINDING } {
403 return;
404 }
405 sched().main_result = rc;
406 } else {
407 let body = sched().threads.iter_mut().find(|t| t.id == id).and_then(|t| t.body.take());
408 if let Some(b) = body {
409 b();
410 }
411 if unsafe { UNWINDING } {
412 return;
413 }
414 }
415 if let Some(t) = sched().threads.iter_mut().find(|t| t.id == id) {
416 t.state = State::Done;
417 }
418}
419
420fn pick(s: &Sched) -> Option<usize> {
422 let n = s.threads.len();
423 (1..=n).map(|k| (s.cursor + k) % n).find(|&i| s.threads[i].state == State::Ready)
424}
425
426#[doc(hidden)]
430#[inline(always)]
431pub fn __threaded_loop_frame() {
432 #[cfg(target_arch = "wasm32")]
433 unsafe {
434 let s = sched();
435 if s.finished {
436 return;
437 }
438 let entry_sp = asm::gasm__get_sp();
439 if !s.started {
440 s.started = true;
441 s.active = true;
442 s.main_base = entry_sp;
443 s.threads.push(Thread::new(s.next_id, None, 0, MAIN_ASYNC_SIZE));
444 s.next_id += 1;
445 } else {
446 s.frames += 1;
447 }
448 asm::gasm__set_sp(sched_top());
449 let now = crate::time_ms();
450 for t in sched().threads.iter_mut() {
451 if t.state == State::WaitFrame {
452 t.state = State::Ready;
453 } else if t.state == State::Blocked && t.deadline >= 0.0 && now >= t.deadline {
454 t.state = State::Ready;
455 t.timed_out = true;
456 }
457 }
458 while let Some(i) = pick(sched()) {
459 let s = sched();
460 s.cursor = i;
461 s.current = i;
462 let t = &mut s.threads[i];
463 let sp = if t.started { t.sp } else if i == 0 { s.main_base } else { t.top() };
465 let resume = t.started;
466 t.started = true;
467 let id = t.id;
468 asm::gasm__set_sp(sp);
469 if resume {
470 REWINDING = true;
471 asm::start_rewind((&raw mut s.threads[i].adata).cast::<u8>());
472 }
473 run_thread(id);
474 if UNWINDING {
475 asm::stop_unwind();
476 UNWINDING = false;
477 let unwound_sp = asm::gasm__get_sp();
478 let s = sched();
479 s.threads[s.current].sp = unwound_sp;
480 }
481 asm::gasm__set_sp(sched_top());
482 let s = sched();
483 let t = &s.threads[s.current];
484 if t.state == State::Done {
485 if t.id == 1 {
486 s.finished = true;
487 let code = s.main_result;
488 asm::gasm__set_sp(entry_sp);
489 crate::exit(code); }
491 let id = t.id;
492 wake(thread_addr(id), false); free_done();
494 }
495 }
496 let s = sched();
497 s.current = 0;
498 let alive = s.threads.iter().any(|t| t.state == State::WaitFrame || (t.state == State::Blocked && t.deadline >= 0.0));
500 if !alive {
501 crate::log("gasm::thread: deadlock: every thread is blocked");
502 for t in &s.threads {
503 if t.state == State::Blocked {
504 crate::log(&format!(" thread {} waits on {:#x}", t.id, t.on));
505 }
506 }
507 std::process::abort();
508 }
509 asm::gasm__set_sp(entry_sp);
510 }
511 #[cfg(not(target_arch = "wasm32"))]
512 {
513 let s = sched();
515 if s.finished {
516 return;
517 }
518 s.finished = true;
519 let rc = s.main.map_or(0, |f| f());
520 crate::exit(rc);
521 }
522}