Skip to content

Commit 3bc4a74

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]>
1 parent a126152 commit 3bc4a74

Some content is hidden

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

55 files changed

+9554
-428
lines changed

Cargo.lock

+120-47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+9-9
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,15 @@ wit-bindgen = { version = "0.34.0", default-features = false }
274274
wit-bindgen-rust-macro = { version = "0.34.0", default-features = false }
275275

276276
# wasm-tools family:
277-
wasmparser = { version = "0.219.1", default-features = false }
278-
wat = "1.219.1"
279-
wast = "219.0.1"
280-
wasmprinter = "0.219.1"
281-
wasm-encoder = "0.219.1"
282-
wasm-smith = "0.219.1"
283-
wasm-mutate = "0.219.1"
284-
wit-parser = "0.219.1"
285-
wit-component = "0.219.1"
277+
wasmparser = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
278+
wat = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
279+
wast = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
280+
wasmprinter = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
281+
wasm-encoder = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
282+
wasm-smith = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
283+
wasm-mutate = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
284+
wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
285+
wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "async" }
286286

287287
# Non-Bytecode Alliance maintained dependencies:
288288
# --------------------------

crates/component-macro/Cargo.toml

+1
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

+35-8
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
}
@@ -205,7 +220,7 @@ fn parse_source(
205220
};
206221
let (pkg, sources) = resolve.push_path(normalized_path)?;
207222
pkgs.push(pkg);
208-
files.extend(sources);
223+
files.extend(sources.package_paths(pkg).unwrap().map(|v| v.to_owned()));
209224
}
210225
Ok(())
211226
};
@@ -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/cranelift/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ gc = ["wasmtime-environ/gc"]
4545
gc-drc = ["gc", "wasmtime-environ/gc-drc"]
4646
gc-null = ["gc", "wasmtime-environ/gc-null"]
4747
threads = ["wasmtime-environ/threads"]
48+

0 commit comments

Comments
 (0)