-
Notifications
You must be signed in to change notification settings - Fork 923
Async execution API + concurrent execution support #5906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* Add proper trap code for suspending in non-async context
…structors are not guaranteed to run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces async execution support to Wasmer through Function::call_async and Function::new_*_async APIs. The implementation follows the JSPI (JavaScript Promise Integration) proposal, allowing WebAssembly coroutines to suspend and resume during execution. Key architectural changes include:
- New Store locking mechanism: Store now uses an internal
Rc<LocalRwLock>to enable concurrent async contexts while maintaining single-threaded execution - Breaking API changes:
Storeno longer directly implementsAsStoreRef/AsStoreMut; instead, embedders must callStore::as_ref()orStore::as_mut() - Coroutine support: Futures spawned by
Function::call_asyncare!Sendand must run on single-threaded async runtimes
Reviewed changes
Copilot reviewed 94 out of 95 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
lib/api/src/entities/store/local_rwlock.rs |
New single-threaded async-aware RwLock implementation for store access |
lib/api/src/entities/store/context.rs |
Thread-local store context stack for managing active StoreMut instances |
lib/api/src/entities/store/asynk.rs |
AsyncStore trait and async lock implementations |
lib/api/src/entities/store/store_ref.rs |
Refactored AsStoreRef/AsStoreMut traits to work with new locking |
lib/api/src/entities/store/mod.rs |
Updated Store with internal RwLock and new as_ref()/as_mut() methods |
lib/api/src/entities/function/async_host.rs |
New AsyncHostFunction trait for async host functions |
lib/api/src/entities/function/inner.rs |
Added async function creation and calling logic |
lib/api/tests/jspi_async.rs |
Comprehensive tests for async execution including JSPI example |
lib/api/tests/simple-greenthread.rs |
Tests for coroutine switching with async contexts |
lib/wasix/src/syscalls/* |
Updated WASIX syscalls to use new Store API |
tests/lib/wast/src/*.rs |
Updated test infrastructure for new Store API |
tests/wasix/vfork/main.c |
Replaced invalid function pointer trap with __builtin_trap() |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rom the store context This can be useful if the embedder knows what they're doing, which is the case with our own c-api crate.
| /// ``` | ||
| /// # use wasmer::{Global, Mutability, Store, Value}; | ||
| /// # let mut store = Store::default(); | ||
| /// # let mut store = store.as_mut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no no , this should not be needed / required
| /// ```should_panic | ||
| /// # use wasmer::{Global, Store, Value}; | ||
| /// # let mut store = Store::default(); | ||
| /// # let mut store = store.as_mut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a non-desired change.
| /// ``` | ||
| /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; | ||
| /// # let mut store = Store::default(); | ||
| /// # let mut store = store.as_mut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is non desired change
| /// (import "host" "func2" (func)) | ||
| /// )"#; | ||
| /// let module = Module::new(&store, wat)?; | ||
| /// let module = Module::new(&store.engine(), wat)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a no-go
| // Tests that the table type of `table` is compatible with the export in the WAT | ||
| // This tests that `wasmer_types::types::is_table_compatible` works as expected. | ||
| let mut store = Store::default(); | ||
| let module = Module::new(&store, WAT).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UNDO
| #[cfg_attr(feature = "wasmi", ignore = "wasmi does not support funcrefs")] | ||
| fn table_new() -> Result<(), String> { | ||
| let mut store = Store::default(); | ||
| let mut store = store.as_mut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No-go
| let print_f32 = Function::new_typed(store, |val: f32| println!("{val}: f32")); | ||
| let print_f64 = Function::new_typed(store, |val: f64| println!("{val}: f64")); | ||
| let print_i32_f32 = Function::new_typed(store, |i: i32, f: f32| { | ||
| let mut store_mut = store.as_mut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name this file async_ or _async
| StoreObjects::same(&a.inner.objects, &b.inner.objects) | ||
| } | ||
| pub struct StoreMut { | ||
| pub(crate) inner: LocalWriteGuardRc<StoreInner>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This affects all the backend. It should not
| /// # Usage: | ||
| /// ```no_run | ||
| /// use wasmer::{Store, Exports, Module, Instance, imports, Imports, Function, FunctionEnvMut}; | ||
| /// # fn foo_test(mut store: &mut Store, module: Module) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UNDO
|
@Arshia001 I'll add my review after you addressed syrus comments |
|
Closing this in favor of #5920, which contains an improved design. |
Summary and explanation
This rather large PR add support for async execution (through
Function::call_asyncand theFunction::new_*_asyncfamily).The design is explained in WARP-43.
A call to
Function::call_asyncstarts a new async context and a coroutine. The async context allows imported functions to suspend; such functions may be created with a call toFunction::new_*_async, and passing in an async or otherwise future-returning host function.New coroutines may be started simultaneously by making further calls to
Function::call_async, either alongside the firstcall_asyncor within imported functions. The embedder shall be responsible for driving the resulting futures to completion.Note that this PR changes how
StoreandAsStoreMutwork fundamentally, including breaking changes to their API. In the new model,Stores use anRcinternally, which lets them stay alive as long as necessary. However, since many coroutines may be accessing the store simultaneously, theStorealso uses an internal single-threaded read-write lock. To access the store's data, this lock must be taken, which is whyStoreitself no longer implementsAsStoreRefandAsStoreMut. Embedders may gain access to anAsStoreMutby callingStore::as_mut, which acquires a write lock on the store and places it in the returnedAsStoreMutimplementation.Since async functions may suspend, the lifetime of the write lock must be handled with more care; this is why
Function::call_asyncreceives a&Storedirectly. However, as long as the store remains locked, the future cannot progress; For example, this code results in a deadlock:Note that, as of this version, the futures spawned by
Function::call_asyncare notSend. A single-thread async runtime may be used to drive multiple coroutines forward on the same thread.Original PR description
This PR adds two functions mimicking the JS-promise-integration API for WebAssembly (jspi):
https://github.com/WebAssembly/js-promise-integration
Following the new JS methods, this PR introduces:
Function::new_async(...),Function::new_with_env_async(...),Function::new_typed_async(...)andFunciton::new_typed_with_env_async(...)as to mimicnew WebAssembly.Suspending(...)function.call_async(...)instead of justfunction.call(...)to mimicWebAssembly.promisingAPITips for reviewers
This PR was designed in stages; as we discovered new potential for better usability and better APIs, we would go back to do more design work. As such, it is recommended to review the PR stage by stage. The stages are as follows:
Function::new_typed_*_asyncby @syrusakbary: a4f9896!Send: fb77c93wasmer-wasixandwasmer-clito get everything working again: b82c860..a973700