Skip to content

Commit a5c2bce

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 <joel.dice@fermyon.com> fix clippy warnings and bench/fuzzing errors Signed-off-by: Joel Dice <joel.dice@fermyon.com> revert atomic.wit whitespace change Signed-off-by: Joel Dice <joel.dice@fermyon.com> fix build when component-model disabled Signed-off-by: Joel Dice <joel.dice@fermyon.com> bless component-macro expected output Signed-off-by: Joel Dice <joel.dice@fermyon.com> fix no-std build error Signed-off-by: Joel Dice <joel.dice@fermyon.com> fix build with --no-default-features --features runtime,component-model Signed-off-by: Joel Dice <joel.dice@fermyon.com> 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 <joel.dice@fermyon.com> maintain per-instance tables for futures, streams, and error-contexts Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent e32292c commit a5c2bce

File tree

161 files changed

+12002
-1887
lines changed

Some content is hidden

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

161 files changed

+12002
-1887
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)