Skip to content

Commit 71897f2

Browse files
committed
Add support for async/streams/futures
This adds support for loading, compiling, linking, and running components which use the [Async ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) along with the [`stream`, `future`, and `error-context`](WebAssembly/component-model#405) types. It also adds support for generating host bindings such that multiple host functions can be run concurrently with guest tasks -- without monopolizing the `Store`. See the [implementation RFC](bytecodealliance/rfcs#38) for details, as well as [this repo](https://github.com/dicej/component-async-demo) containing end-to-end smoke tests. This is very much a work-in progress, with a number of tasks remaining: - [ ] Avoid exposing global task IDs to guests and use per-instance IDs instead - [ ] Track `task.return` type during compilation and assert the actual and expected types match at runtime - [ ] Ensure all guest pointers are bounds-checked when lifting, lowering, or copying values - [ ] Reduce code duplication in `wasmtime_cranelift::compiler::component` - [ ] Reduce code duplication between `StoreContextMut::on_fiber` and `concurrent::on_fiber` - [ ] Minimize and/or document the use of unsafe code - [ ] Add support for `(Typed)Func::call_concurrent` per the RFC - [ ] Add support for multiplexing stream/future reads/writes and concurrent calls to guest exports per the RFC - [ ] Refactor, clean up, and unify handling of backpressure, yields, and even polling - [ ] Guard against reentrance where required (e.g. in certain fused adapter calls) - [ ] Add integration test cases covering new functionality to tests/all/component_model (starting by porting over the tests in https://github.com/dicej/component-async-demo) - [ ] Add binding generation test cases to crates/component-macro/tests - [ ] Add WAST tests to tests/misc_testsuite/component-model - [ ] Add support and test coverage for callback-less async functions (e.g. goroutines) - [ ] Switch to back to upstream `wasm-tools` once bytecodealliance/wasm-tools#1895 has been merged and released Signed-off-by: Joel Dice <[email protected]> fix clippy warnings and bench/fuzzing errors Signed-off-by: Joel Dice <[email protected]> revert atomic.wit whitespace change Signed-off-by: Joel Dice <[email protected]> fix build when component-model disabled Signed-off-by: Joel Dice <[email protected]> bless component-macro expected output Signed-off-by: Joel Dice <[email protected]> fix no-std build error Signed-off-by: Joel Dice <[email protected]> fix build with --no-default-features --features runtime,component-model Signed-off-by: Joel Dice <[email protected]> partly fix no-std build It's still broken due to the use of `std::collections::HashMap` in crates/wasmtime/src/runtime/vm/component.rs. I'll address that as part of the work to avoid exposing global task/future/stream/error-context handles to guests. Signed-off-by: Joel Dice <[email protected]> maintain per-instance tables for futures, streams, and error-contexts Signed-off-by: Joel Dice <[email protected]> refactor task/stream/future handle lifting/lowering This addresses a couple of issues: - Previously, we were passing task/stream/future/error-context reps directly to instances while keeping track of which instance had access to which rep. That worked fine in that there was no way to forge access to inaccessible reps, but it leaked information about what other instances were doing. Now we maintain per-instance waitable and error-context tables which map the reps to and from the handles which the instance sees. - The `no_std` build was broken due to use of `HashMap` in `runtime::vm::component`, which is now fixed. Note that we use one single table per instance for all tasks, streams, and futures. This is partly necessary because, when async events are delivered to the guest, it wouldn't have enough context to know which stream or future we're talking about if each unique stream and future type had its own table. So at minimum, we need to use the same table for all streams (regardless of payload type), and likewise for futures. Also, per WebAssembly/component-model#395 (comment), the plan is to move towards a shared table for all resource types as well, so this moves us in that direction. Signed-off-by: Joel Dice <[email protected]> fix wave breakage due to new stream/future/error-context types Signed-off-by: Joel Dice <[email protected]>
1 parent 8fd6208 commit 71897f2

File tree

162 files changed

+12235
-1892
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

162 files changed

+12235
-1892
lines changed

Cargo.lock

Lines changed: 128 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -276,16 +276,16 @@ wit-bindgen = { version = "0.35.0", default-features = false }
276276
wit-bindgen-rust-macro = { version = "0.35.0", default-features = false }
277277

278278
# wasm-tools family:
279-
wasmparser = { version = "0.220.0", default-features = false }
280-
wat = "1.220.0"
281-
wast = "220.0.0"
282-
wasmprinter = "0.220.0"
283-
wasm-encoder = "0.220.0"
284-
wasm-smith = "0.220.0"
285-
wasm-mutate = "0.220.0"
286-
wit-parser = "0.220.0"
287-
wit-component = "0.220.0"
288-
wasm-wave = "0.220.0"
279+
wasmparser = { git = "https://github.com/dicej/wasm-tools", branch = "async", default-features = false }
280+
wat = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
281+
wast = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
282+
wasmprinter = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
283+
wasm-encoder = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
284+
wasm-smith = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
285+
wasm-mutate = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
286+
wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
287+
wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
288+
wasm-wave = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
289289

290290
# Non-Bytecode Alliance maintained dependencies:
291291
# --------------------------

benches/call.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,8 @@ mod component {
628628
+ PartialEq
629629
+ Debug
630630
+ Send
631-
+ Sync,
631+
+ Sync
632+
+ 'static,
632633
{
633634
// Benchmark the "typed" version.
634635
c.bench_function(&format!("component - host-to-wasm - typed - {name}"), |b| {

crates/component-macro/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ similar = { workspace = true }
4141
[features]
4242
async = []
4343
std = ['wasmtime-wit-bindgen/std']
44+
component-model-async = ['std', 'async', 'wasmtime-wit-bindgen/component-model-async']

crates/component-macro/src/bindgen.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use proc_macro2::{Span, TokenStream};
22
use quote::ToTokens;
3-
use std::collections::HashMap;
4-
use std::collections::HashSet;
3+
use std::collections::{HashMap, HashSet};
54
use std::env;
65
use std::path::{Path, PathBuf};
76
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
87
use syn::parse::{Error, Parse, ParseStream, Result};
98
use syn::punctuated::Punctuated;
109
use syn::{braced, token, Token};
11-
use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError, TrappableImports};
10+
use wasmtime_wit_bindgen::{
11+
AsyncConfig, CallStyle, Opts, Ownership, TrappableError, TrappableImports,
12+
};
1213
use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
1314

1415
pub struct Config {
@@ -20,13 +21,22 @@ pub struct Config {
2021
}
2122

2223
pub fn expand(input: &Config) -> Result<TokenStream> {
23-
if !cfg!(feature = "async") && input.opts.async_.maybe_async() {
24+
if let (CallStyle::Async | CallStyle::Concurrent, false) =
25+
(input.opts.call_style(), cfg!(feature = "async"))
26+
{
2427
return Err(Error::new(
2528
Span::call_site(),
2629
"cannot enable async bindings unless `async` crate feature is active",
2730
));
2831
}
2932

33+
if input.opts.concurrent_imports && !cfg!(feature = "component-model-async") {
34+
return Err(Error::new(
35+
Span::call_site(),
36+
"cannot enable `concurrent_imports` option unless `component-model-async` crate feature is active",
37+
));
38+
}
39+
3040
let mut src = match input.opts.generate(&input.resolve, input.world) {
3141
Ok(s) => s,
3242
Err(e) => return Err(Error::new(Span::call_site(), e.to_string())),
@@ -40,7 +50,10 @@ pub fn expand(input: &Config) -> Result<TokenStream> {
4050
// place a formatted version of the expanded code into a file. This file
4151
// will then show up in rustc error messages for any codegen issues and can
4252
// be inspected manually.
43-
if input.include_generated_code_from_file || std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok() {
53+
if input.include_generated_code_from_file
54+
|| input.opts.debug
55+
|| std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok()
56+
{
4457
static INVOCATION: AtomicUsize = AtomicUsize::new(0);
4558
let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
4659
let world_name = &input.resolve.worlds[input.world].name;
@@ -107,13 +120,15 @@ impl Parse for Config {
107120
}
108121
Opt::Tracing(val) => opts.tracing = val,
109122
Opt::VerboseTracing(val) => opts.verbose_tracing = val,
123+
Opt::Debug(val) => opts.debug = val,
110124
Opt::Async(val, span) => {
111125
if async_configured {
112126
return Err(Error::new(span, "cannot specify second async config"));
113127
}
114128
async_configured = true;
115129
opts.async_ = val;
116130
}
131+
Opt::ConcurrentImports(val) => opts.concurrent_imports = val,
117132
Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
118133
Opt::TrappableImports(val) => opts.trappable_imports = val,
119134
Opt::Ownership(val) => opts.ownership = val,
@@ -138,7 +153,7 @@ impl Parse for Config {
138153
"cannot specify a world with `interfaces`",
139154
));
140155
}
141-
world = Some("interfaces".to_string());
156+
world = Some("wasmtime:component-macro-synthesized/interfaces".to_string());
142157

143158
opts.only_interfaces = true;
144159
}
@@ -281,6 +296,8 @@ mod kw {
281296
syn::custom_keyword!(require_store_data_send);
282297
syn::custom_keyword!(wasmtime_crate);
283298
syn::custom_keyword!(include_generated_code_from_file);
299+
syn::custom_keyword!(concurrent_imports);
300+
syn::custom_keyword!(debug);
284301
}
285302

286303
enum Opt {
@@ -301,12 +318,18 @@ enum Opt {
301318
RequireStoreDataSend(bool),
302319
WasmtimeCrate(syn::Path),
303320
IncludeGeneratedCodeFromFile(bool),
321+
ConcurrentImports(bool),
322+
Debug(bool),
304323
}
305324

306325
impl Parse for Opt {
307326
fn parse(input: ParseStream<'_>) -> Result<Self> {
308327
let l = input.lookahead1();
309-
if l.peek(kw::path) {
328+
if l.peek(kw::debug) {
329+
input.parse::<kw::debug>()?;
330+
input.parse::<Token![:]>()?;
331+
Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))
332+
} else if l.peek(kw::path) {
310333
input.parse::<kw::path>()?;
311334
input.parse::<Token![:]>()?;
312335

@@ -380,6 +403,10 @@ impl Parse for Opt {
380403
span,
381404
))
382405
}
406+
} else if l.peek(kw::concurrent_imports) {
407+
input.parse::<kw::concurrent_imports>()?;
408+
input.parse::<Token![:]>()?;
409+
Ok(Opt::ConcurrentImports(input.parse::<syn::LitBool>()?.value))
383410
} else if l.peek(kw::ownership) {
384411
input.parse::<kw::ownership>()?;
385412
input.parse::<Token![:]>()?;

crates/component-macro/tests/expanded/char.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl<T> Clone for TheWorldPre<T> {
1818
}
1919
}
2020
}
21-
impl<_T> TheWorldPre<_T> {
21+
impl<_T: 'static> TheWorldPre<_T> {
2222
/// Creates a new copy of `TheWorldPre` bindings which can then
2323
/// be used to instantiate into a particular store.
2424
///
@@ -152,7 +152,10 @@ const _: () = {
152152
mut store: impl wasmtime::AsContextMut<Data = _T>,
153153
component: &wasmtime::component::Component,
154154
linker: &wasmtime::component::Linker<_T>,
155-
) -> wasmtime::Result<TheWorld> {
155+
) -> wasmtime::Result<TheWorld>
156+
where
157+
_T: 'static,
158+
{
156159
let pre = linker.instantiate_pre(component)?;
157160
TheWorldPre::new(pre)?.instantiate(store)
158161
}
@@ -194,19 +197,23 @@ pub mod foo {
194197
}
195198
pub trait GetHost<
196199
T,
197-
>: Fn(T) -> <Self as GetHost<T>>::Host + Send + Sync + Copy + 'static {
200+
D,
201+
>: Fn(T) -> <Self as GetHost<T, D>>::Host + Send + Sync + Copy + 'static {
198202
type Host: Host;
199203
}
200-
impl<F, T, O> GetHost<T> for F
204+
impl<F, T, D, O> GetHost<T, D> for F
201205
where
202206
F: Fn(T) -> O + Send + Sync + Copy + 'static,
203207
O: Host,
204208
{
205209
type Host = O;
206210
}
207-
pub fn add_to_linker_get_host<T>(
211+
pub fn add_to_linker_get_host<
212+
T,
213+
G: for<'a> GetHost<&'a mut T, T, Host: Host>,
214+
>(
208215
linker: &mut wasmtime::component::Linker<T>,
209-
host_getter: impl for<'a> GetHost<&'a mut T>,
216+
host_getter: G,
210217
) -> wasmtime::Result<()> {
211218
let mut inst = linker.instance("foo:foo/chars")?;
212219
inst.func_wrap(
@@ -354,7 +361,10 @@ pub mod exports {
354361
&self,
355362
mut store: S,
356363
arg0: char,
357-
) -> wasmtime::Result<()> {
364+
) -> wasmtime::Result<()>
365+
where
366+
<S as wasmtime::AsContext>::Data: Send + 'static,
367+
{
358368
let callee = unsafe {
359369
wasmtime::component::TypedFunc::<
360370
(char,),
@@ -369,7 +379,10 @@ pub mod exports {
369379
pub fn call_return_char<S: wasmtime::AsContextMut>(
370380
&self,
371381
mut store: S,
372-
) -> wasmtime::Result<char> {
382+
) -> wasmtime::Result<char>
383+
where
384+
<S as wasmtime::AsContext>::Data: Send + 'static,
385+
{
373386
let callee = unsafe {
374387
wasmtime::component::TypedFunc::<
375388
(),

crates/component-macro/tests/expanded/char_async.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl<T> Clone for TheWorldPre<T> {
1818
}
1919
}
2020
}
21-
impl<_T> TheWorldPre<_T> {
21+
impl<_T: Send + 'static> TheWorldPre<_T> {
2222
/// Creates a new copy of `TheWorldPre` bindings which can then
2323
/// be used to instantiate into a particular store.
2424
///
@@ -46,10 +46,7 @@ impl<_T> TheWorldPre<_T> {
4646
pub async fn instantiate_async(
4747
&self,
4848
mut store: impl wasmtime::AsContextMut<Data = _T>,
49-
) -> wasmtime::Result<TheWorld>
50-
where
51-
_T: Send,
52-
{
49+
) -> wasmtime::Result<TheWorld> {
5350
let mut store = store.as_context_mut();
5451
let instance = self.instance_pre.instantiate_async(&mut store).await?;
5552
self.indices.load(&mut store, &instance)
@@ -157,7 +154,7 @@ const _: () = {
157154
linker: &wasmtime::component::Linker<_T>,
158155
) -> wasmtime::Result<TheWorld>
159156
where
160-
_T: Send,
157+
_T: Send + 'static,
161158
{
162159
let pre = linker.instantiate_pre(component)?;
163160
TheWorldPre::new(pre)?.instantiate_async(store).await
@@ -202,19 +199,23 @@ pub mod foo {
202199
}
203200
pub trait GetHost<
204201
T,
205-
>: Fn(T) -> <Self as GetHost<T>>::Host + Send + Sync + Copy + 'static {
202+
D,
203+
>: Fn(T) -> <Self as GetHost<T, D>>::Host + Send + Sync + Copy + 'static {
206204
type Host: Host + Send;
207205
}
208-
impl<F, T, O> GetHost<T> for F
206+
impl<F, T, D, O> GetHost<T, D> for F
209207
where
210208
F: Fn(T) -> O + Send + Sync + Copy + 'static,
211209
O: Host + Send,
212210
{
213211
type Host = O;
214212
}
215-
pub fn add_to_linker_get_host<T>(
213+
pub fn add_to_linker_get_host<
214+
T,
215+
G: for<'a> GetHost<&'a mut T, T, Host: Host + Send>,
216+
>(
216217
linker: &mut wasmtime::component::Linker<T>,
217-
host_getter: impl for<'a> GetHost<&'a mut T>,
218+
host_getter: G,
218219
) -> wasmtime::Result<()>
219220
where
220221
T: Send,
@@ -373,7 +374,7 @@ pub mod exports {
373374
arg0: char,
374375
) -> wasmtime::Result<()>
375376
where
376-
<S as wasmtime::AsContext>::Data: Send,
377+
<S as wasmtime::AsContext>::Data: Send + 'static,
377378
{
378379
let callee = unsafe {
379380
wasmtime::component::TypedFunc::<
@@ -393,7 +394,7 @@ pub mod exports {
393394
mut store: S,
394395
) -> wasmtime::Result<char>
395396
where
396-
<S as wasmtime::AsContext>::Data: Send,
397+
<S as wasmtime::AsContext>::Data: Send + 'static,
397398
{
398399
let callee = unsafe {
399400
wasmtime::component::TypedFunc::<

crates/component-macro/tests/expanded/char_tracing_async.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl<T> Clone for TheWorldPre<T> {
1818
}
1919
}
2020
}
21-
impl<_T> TheWorldPre<_T> {
21+
impl<_T: Send + 'static> TheWorldPre<_T> {
2222
/// Creates a new copy of `TheWorldPre` bindings which can then
2323
/// be used to instantiate into a particular store.
2424
///
@@ -46,10 +46,7 @@ impl<_T> TheWorldPre<_T> {
4646
pub async fn instantiate_async(
4747
&self,
4848
mut store: impl wasmtime::AsContextMut<Data = _T>,
49-
) -> wasmtime::Result<TheWorld>
50-
where
51-
_T: Send,
52-
{
49+
) -> wasmtime::Result<TheWorld> {
5350
let mut store = store.as_context_mut();
5451
let instance = self.instance_pre.instantiate_async(&mut store).await?;
5552
self.indices.load(&mut store, &instance)
@@ -157,7 +154,7 @@ const _: () = {
157154
linker: &wasmtime::component::Linker<_T>,
158155
) -> wasmtime::Result<TheWorld>
159156
where
160-
_T: Send,
157+
_T: Send + 'static,
161158
{
162159
let pre = linker.instantiate_pre(component)?;
163160
TheWorldPre::new(pre)?.instantiate_async(store).await
@@ -202,19 +199,23 @@ pub mod foo {
202199
}
203200
pub trait GetHost<
204201
T,
205-
>: Fn(T) -> <Self as GetHost<T>>::Host + Send + Sync + Copy + 'static {
202+
D,
203+
>: Fn(T) -> <Self as GetHost<T, D>>::Host + Send + Sync + Copy + 'static {
206204
type Host: Host + Send;
207205
}
208-
impl<F, T, O> GetHost<T> for F
206+
impl<F, T, D, O> GetHost<T, D> for F
209207
where
210208
F: Fn(T) -> O + Send + Sync + Copy + 'static,
211209
O: Host + Send,
212210
{
213211
type Host = O;
214212
}
215-
pub fn add_to_linker_get_host<T>(
213+
pub fn add_to_linker_get_host<
214+
T,
215+
G: for<'a> GetHost<&'a mut T, T, Host: Host + Send>,
216+
>(
216217
linker: &mut wasmtime::component::Linker<T>,
217-
host_getter: impl for<'a> GetHost<&'a mut T>,
218+
host_getter: G,
218219
) -> wasmtime::Result<()>
219220
where
220221
T: Send,
@@ -402,7 +403,7 @@ pub mod exports {
402403
arg0: char,
403404
) -> wasmtime::Result<()>
404405
where
405-
<S as wasmtime::AsContext>::Data: Send,
406+
<S as wasmtime::AsContext>::Data: Send + 'static,
406407
{
407408
use tracing::Instrument;
408409
let span = tracing::span!(
@@ -431,7 +432,7 @@ pub mod exports {
431432
mut store: S,
432433
) -> wasmtime::Result<char>
433434
where
434-
<S as wasmtime::AsContext>::Data: Send,
435+
<S as wasmtime::AsContext>::Data: Send + 'static,
435436
{
436437
use tracing::Instrument;
437438
let span = tracing::span!(

0 commit comments

Comments
 (0)