Last active
February 4, 2025 13:16
-
-
Save dicej/21519f9cf2e4d57a3316ea0b2167d281 to your computer and use it in GitHub Desktop.
Sketch of alternative `wasmtime-wit-bindgen` `concurrent-imports` approach based on thread-local storage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Somewhere in Wasmtime: | |
thread_local! { | |
static LOCAL_STORE: Cell<*mut dyn VMStore> = Cell::new(ptr::null_mut()); | |
} | |
pub struct LocalStore<'a, T> { | |
// We only use `T` in the `with` method below. | |
_phatom_type: PhantomData<T>, | |
// 'a is a dummy lifetime to discourage application code from storing an | |
// `LocalStore` instance and using it outside of the `async fn` it was | |
// passed to. | |
_phantom_lifetime: PhantomData<&'a ()>, | |
} | |
impl<'a, T> LocalStore<'a, T> { | |
/// Run the specified function using the current store and return the | |
/// result. | |
/// | |
/// This will only succeed if called from a `Future` being polled by the | |
/// Wasmtime runtime as part of an async host function which was registered | |
/// using `LinkerInstance::func_wrap_concurrent`; otherwise it will panic. | |
fn with<R>(&self, fun: impl FnOnce(StoreContextMut<'_, T>) -> R) -> R { | |
LOCAL_STORE.with(|store| { | |
assert!(!store.get().is_null()); | |
fun(unsafe { StoreContextMut::<T>(&mut *store.get().cast()) }) | |
}) | |
} | |
} | |
// Elsewhere in wasmtime: | |
// the function where we poll host function futures for progress | |
fn poll_loop<T>(store: StoreContextMut<T> /* ...*/) -> Result<()> { | |
// ... | |
let ready = LOCAL_STORE.with(|s| { | |
// set the thread local to point to a valid store | |
s.set(store.0.traitobj().as_ptr()); | |
// poll the host function futures | |
let cx = AsyncCx::new(&mut store); | |
let mut future = pin!(store.concurrent_state().futures.next()); | |
let ready = unsafe { cx.poll(future.as_mut()) }; | |
// Reset the thread local to null. | |
// In reality, we'd use RAII to ensure this line is always run: | |
s.set(ptr::null_mut()); | |
ready | |
}); | |
// ... | |
} | |
// In `bindgen!`-generated code: | |
trait Host { | |
// ... | |
async fn foo(local_store: LocalStore<'_, Self>, param: String) -> Result<String>; | |
// ... | |
} | |
// In application code: | |
struct MyHost {/*...*/} | |
impl Host for MyHost { | |
// ... | |
async fn foo(local_store: LocalStore<'_, Self>, param: String) -> Result<String> { | |
do_something(param).await?; | |
// Note that the API of `LocalStore::with` prevents returning anything | |
// from the closure that uses the `StoreContextMut`'s lifetime, thus | |
// preventing `Store`-based references from being held across `await` | |
// points: | |
let bar = local_store.with(|store| get_something_from_the_store(store))?; | |
let baz = do_something_else(bar).await?; | |
let result = local_store.with(|store| put_something_into_the_store(store, baz))?; | |
Ok(result) | |
} | |
// ... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment