Pauan bdcf27c7cb Major improvements to wasm-bindgen-futures (#1760)
This PR contains a few major improvements:

* Code duplication has been removed.

* Everything has been refactored so that the implementation is much easier to understand.

* `future_to_promise` is now implemented with `spawn_local` rather than the other way around (this means `spawn_local` is faster since it doesn't need to create an unneeded `Promise`).

* Both the single threaded and multi threaded executors have been rewritten from scratch:

   * They only create 1-2 allocations in Rust per Task, and all of the allocations happen when the Task is created.

   * The singlethreaded executor creates 1 Promise per tick, rather than 1 Promise per tick per Task.

   * Both executors do *not* create `Closure`s during polling, instead all needed `Closure`s are created ahead of time.

   * Both executors now have correct behavior with regard to spurious wakeups and waking up during the call to `poll`.

   * Both executors cache the `Waker` so it doesn't need to be recreated all the time.

I believe both executors are now optimal in terms of both Rust and JS performance.
2019-09-26 13:33:12 -05:00

183 lines
6.2 KiB
Rust

use std::cell::RefCell;
use std::future::Future;
use std::mem::ManuallyDrop;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
const SLEEPING: i32 = 0;
const AWAKE: i32 = 1;
struct AtomicWaker {
state: AtomicI32,
}
impl AtomicWaker {
fn new() -> Arc<Self> {
Arc::new(Self {
state: AtomicI32::new(AWAKE),
})
}
fn wake_by_ref(&self) {
// If we're already AWAKE then we previously notified and there's
// nothing to do...
match self.state.swap(AWAKE, SeqCst) {
AWAKE => return,
other => debug_assert_eq!(other, SLEEPING),
}
// ... otherwise we execute the native `notify` instruction to wake up
// the corresponding `waitAsync` that was waiting for the transition
// from SLEEPING to AWAKE.
unsafe {
core::arch::wasm32::atomic_notify(
&self.state as *const AtomicI32 as *mut i32,
1, // Number of threads to notify
);
}
}
/// Same as the singlethread module, this creates a standard library
/// `RawWaker`. We could use `futures_util::task::ArcWake` but it's small
/// enough that we just inline it for now.
unsafe fn into_raw_waker(this: Arc<Self>) -> RawWaker {
unsafe fn raw_clone(ptr: *const ()) -> RawWaker {
let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const AtomicWaker));
AtomicWaker::into_raw_waker((*ptr).clone())
}
unsafe fn raw_wake(ptr: *const ()) {
let ptr = Arc::from_raw(ptr as *const AtomicWaker);
AtomicWaker::wake_by_ref(&ptr);
}
unsafe fn raw_wake_by_ref(ptr: *const ()) {
let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const AtomicWaker));
AtomicWaker::wake_by_ref(&ptr);
}
unsafe fn raw_drop(ptr: *const ()) {
drop(Arc::from_raw(ptr as *const AtomicWaker));
}
const VTABLE: RawWakerVTable =
RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop);
RawWaker::new(Arc::into_raw(this) as *const (), &VTABLE)
}
}
struct Inner {
future: Pin<Box<dyn Future<Output = ()> + 'static>>,
closure: Closure<dyn FnMut(JsValue)>,
}
pub(crate) struct Task {
atomic: Arc<AtomicWaker>,
waker: Waker,
// See `singlethread.rs` for why this is an internal `Option`.
inner: RefCell<Option<Inner>>,
}
impl Task {
pub(crate) fn spawn(future: Pin<Box<dyn Future<Output = ()> + 'static>>) {
let atomic = AtomicWaker::new();
let waker = unsafe { Waker::from_raw(AtomicWaker::into_raw_waker(atomic.clone())) };
let this = Rc::new(Task {
atomic,
waker,
inner: RefCell::new(None),
});
let closure = {
let this = Rc::clone(&this);
Closure::wrap(Box::new(move |_| this.run()) as Box<dyn FnMut(JsValue)>)
};
*this.inner.borrow_mut() = Some(Inner { future, closure });
// Queue up the Future's work to happen on the next microtask tick.
crate::queue::QUEUE.with(move |queue| queue.push_task(this));
}
pub(crate) fn run(&self) {
let mut borrow = self.inner.borrow_mut();
// Same as `singlethread.rs`, handle spurious wakeups happening after we
// finished.
let inner = match borrow.as_mut() {
Some(inner) => inner,
None => return,
};
// Also the same as `singlethread.rs`, flag ourselves as ready to
// receive a notification.
let prev = self.atomic.state.swap(SLEEPING, SeqCst);
debug_assert_eq!(prev, AWAKE);
let poll = {
let mut cx = Context::from_waker(&self.waker);
inner.future.as_mut().poll(&mut cx)
};
match poll {
// Same as `singlethread.rs` (noticing a pattern?) clean up
// resources associated with the future ASAP.
Poll::Ready(()) => {
*borrow = None;
},
// Unlike `singlethread.rs` we are responsible for ensuring there's
// a closure to handle the notification that a Future is ready. In
// the single-threaded case the notification itself enqueues work,
// but in the multithreaded case we don't know what thread a
// notification comes from so we need to ensure the current running
// thread is the one that enqueues the work. To do that we execute
// `Atomics.waitAsync`, creating a local Promise on our own thread
// which will resolve once `Atomics.notify` is called.
//
// We could be in one of two states as we execute this:
//
// * `SLEEPING` - we'll get notified via `Atomics.notify`
// and then this Promise will resolve.
//
// * `AWAKE` - the Promise will immediately be resolved and
// we'll execute the work on the next microtask queue.
Poll::Pending => {
wait_async(&self.atomic.state, SLEEPING).then(&inner.closure);
}
}
}
}
fn wait_async(ptr: &AtomicI32, current_value: i32) -> js_sys::Promise {
// If `Atomics.waitAsync` isn't defined (as it isn't defined anywhere today)
// then we use our fallback, otherwise we use the native function.
return if Atomics::get_wait_async().is_undefined() {
crate::task::wait_async_polyfill::wait_async(ptr, current_value)
} else {
let mem = wasm_bindgen::memory().unchecked_into::<js_sys::WebAssembly::Memory>();
Atomics::wait_async(
&mem.buffer(),
ptr as *const AtomicI32 as i32 / 4,
current_value,
)
};
#[wasm_bindgen]
extern "C" {
type Atomics;
#[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync)]
fn wait_async(buf: &JsValue, index: i32, value: i32) -> js_sys::Promise;
#[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync, getter)]
fn get_wait_async() -> JsValue;
}
}