From 8ebfc1ecc93540c43c5e9473320ba4fc220e2d64 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 22 Mar 2024 17:33:31 -0600 Subject: [PATCH] Add support for async/streams/futures to Rust generator This adds support for generating bindings 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`](https://github.com/WebAssembly/component-model/pull/405) types. By default, normal synchronous bindings are generated, but the user may opt-in to async bindings for all or some of the imported and/or exported functions in the target world and interfaces -- provided the default-enabled `async` feature is enabled. In addition, we generate `StreamPayload` and/or `FuturePayload` trait implementations for any types appearing as the `T` in `stream` or `future` in the WIT files, respectively. That enables user code to call `new_stream` or `new_future` to create `stream`s or `future`s with those payload types, then write to them, read from them, and/or pass the readable end as a parameter to a component import or return value of a component export. Note that I've added new `core::abi::Instruction` enum variants to handle async lifting and lowering, but they're currently tailored to the Rust generator and will probably change somewhat as we add support for other languages. This does not include any new tests; I'll add those in a follow-up commit. Signed-off-by: Joel Dice --- Cargo.lock | 144 +++- Cargo.toml | 12 +- crates/c/src/lib.rs | 42 +- crates/core/Cargo.toml | 4 + crates/core/src/abi.rs | 424 +++++++++--- crates/core/src/lib.rs | 33 +- crates/core/src/types.rs | 8 +- crates/csharp/src/lib.rs | 21 +- crates/go/src/bindgen.rs | 2 + crates/go/src/interface.rs | 5 +- crates/guest-rust/Cargo.toml | 3 +- crates/guest-rust/macro/Cargo.toml | 3 + crates/guest-rust/macro/src/lib.rs | 87 ++- crates/guest-rust/rt/Cargo.toml | 6 + crates/guest-rust/rt/src/async_support.rs | 510 +++++++++++++++ crates/guest-rust/rt/src/lib.rs | 10 +- crates/markdown/src/lib.rs | 30 +- crates/moonbit/src/lib.rs | 19 +- crates/rust/Cargo.toml | 4 + crates/rust/src/bindgen.rs | 167 ++++- crates/rust/src/interface.rs | 649 +++++++++++++++++-- crates/rust/src/lib.rs | 110 +++- crates/rust/src/stream_and_future_support.rs | 513 +++++++++++++++ crates/teavm-java/src/lib.rs | 19 +- crates/test-helpers/src/lib.rs | 3 +- tests/runtime/flavorful/wasm.rs | 2 - tests/runtime/main.rs | 15 +- 27 files changed, 2556 insertions(+), 289 deletions(-) create mode 100644 crates/guest-rust/rt/src/async_support.rs create mode 100644 crates/rust/src/stream_and_future_support.rs diff --git a/Cargo.lock b/Cargo.lock index 6ce2eb66e..30cd3a2b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,12 @@ dependencies = [ "syn", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "backtrace" version = "0.3.74" @@ -677,6 +683,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -699,12 +706,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -723,11 +752,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1356,6 +1390,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slice-group-by" version = "0.3.1" @@ -1459,10 +1502,10 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.219.0", + "wasm-encoder 0.219.1", "wit-bindgen-core", "wit-component", - "wit-parser 0.219.0", + "wit-parser 0.219.1", ] [[package]] @@ -1630,6 +1673,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1745,11 +1794,19 @@ dependencies = [ "wasmparser 0.219.0", ] +[[package]] +name = "wasm-encoder" +version = "0.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" +dependencies = [ + "leb128", + "wasmparser 0.219.1", +] + [[package]] name = "wasm-metadata" -version = "0.219.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96132fe00dd17d092d2be289eeed5a0a68ad3cf30b68e8875bc953b96f55f0be" +version = "0.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" dependencies = [ "anyhow", "indexmap", @@ -1757,8 +1814,8 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.219.0", - "wasmparser 0.219.0", + "wasm-encoder 0.219.1", + "wasmparser 0.219.1", ] [[package]] @@ -1780,6 +1837,15 @@ name = "wasmparser" version = "0.219.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "324b4e56d24439495b88cd81439dad5e97f3c7b1eedc3c7e10455ed1e045e9a2" +dependencies = [ + "bitflags", + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" dependencies = [ "ahash", "bitflags", @@ -1852,7 +1918,7 @@ dependencies = [ "wasmtime-slab", "wasmtime-versioned-export-macros", "wasmtime-winch", - "wat", + "wat 1.219.0", "windows-sys 0.52.0", ] @@ -2106,10 +2172,22 @@ dependencies = [ "bumpalo", "leb128", "memchr", - "unicode-width", + "unicode-width 0.1.14", "wasm-encoder 0.219.0", ] +[[package]] +name = "wast" +version = "219.0.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width 0.2.0", + "wasm-encoder 0.219.1", +] + [[package]] name = "wat" version = "1.219.0" @@ -2119,6 +2197,14 @@ dependencies = [ "wast 219.0.0", ] +[[package]] +name = "wat" +version = "1.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" +dependencies = [ + "wast 219.0.1", +] + [[package]] name = "wiggle" version = "25.0.1" @@ -2335,11 +2421,11 @@ dependencies = [ "clap", "heck 0.5.0", "test-helpers", - "wasm-encoder 0.219.0", + "wasm-encoder 0.219.1", "wasm-metadata", "wit-bindgen-core", "wit-component", - "wit-parser 0.219.0", + "wit-parser 0.219.1", ] [[package]] @@ -2350,8 +2436,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-artifacts", - "wasm-encoder 0.219.0", - "wasmparser 0.219.0", + "wasm-encoder 0.219.1", + "wasmparser 0.219.1", "wasmtime", "wasmtime-wasi", "wit-bindgen-c", @@ -2363,7 +2449,7 @@ dependencies = [ "wit-bindgen-rust", "wit-bindgen-teavm-java", "wit-component", - "wit-parser 0.219.0", + "wit-parser 0.219.1", ] [[package]] @@ -2372,7 +2458,7 @@ version = "0.34.0" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.219.0", + "wit-parser 0.219.1", ] [[package]] @@ -2384,12 +2470,12 @@ dependencies = [ "heck 0.5.0", "indexmap", "test-helpers", - "wasm-encoder 0.219.0", + "wasm-encoder 0.219.1", "wasm-metadata", - "wasmparser 0.219.0", + "wasmparser 0.219.1", "wit-bindgen-core", "wit-component", - "wit-parser 0.219.0", + "wit-parser 0.219.1", ] [[package]] @@ -2434,6 +2520,8 @@ name = "wit-bindgen-rt" version = "0.34.0" dependencies = [ "bitflags", + "futures", + "once_cell", ] [[package]] @@ -2483,9 +2571,8 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.219.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a76111c20444a814019de20499d30940ecd219b9512ee296f034a5edb18a2d" +version = "0.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" dependencies = [ "anyhow", "bitflags", @@ -2494,11 +2581,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.219.0", + "wasm-encoder 0.219.1", "wasm-metadata", - "wasmparser 0.219.0", - "wat", - "wit-parser 0.219.0", + "wasmparser 0.219.1", + "wat 1.219.1", + "wit-parser 0.219.1", ] [[package]] @@ -2521,9 +2608,8 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.219.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23102e180c0c464f36e293d31a27b524e3ece930d7b5527d2f33f9d2c963de64" +version = "0.219.1" +source = "git+https://github.com/dicej/wasm-tools?branch=async#940de62bd278831d1134bde84caedc1adcce6a00" dependencies = [ "anyhow", "id-arena", @@ -2534,7 +2620,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.219.0", + "wasmparser 0.219.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 75b172c66..90bd4080a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,11 +32,11 @@ indexmap = "2.0.0" prettyplease = "0.2.20" syn = { version = "2.0", features = ["printing"] } -wasmparser = "0.219.0" -wasm-encoder = "0.219.0" -wasm-metadata = "0.219.0" -wit-parser = "0.219.0" -wit-component = "0.219.0" +wasmparser = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wasm-encoder = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wasm-metadata = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "async" } wit-bindgen-core = { path = 'crates/core', version = '0.34.0' } wit-bindgen-c = { path = 'crates/c', version = '0.34.0' } @@ -74,6 +74,7 @@ default = [ 'go', 'csharp', 'moonbit', + 'async', ] c = ['dep:wit-bindgen-c'] rust = ['dep:wit-bindgen-rust'] @@ -83,6 +84,7 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] +async = ["wit-bindgen-rust/async"] [dev-dependencies] heck = { workspace = true } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 99eb5c689..26d5ee13f 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -718,6 +718,7 @@ fn is_prim_type_id(resolve: &Resolve, id: TypeId) -> bool { | TypeDefKind::Result(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) + | TypeDefKind::ErrorContext | TypeDefKind::Unknown => false, } } @@ -779,8 +780,9 @@ pub fn push_ty_name(resolve: &Resolve, ty: &Type, src: &mut String) { src.push_str("list_"); push_ty_name(resolve, ty, src); } - TypeDefKind::Future(_) => unimplemented!(), - TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Future(_) => todo!(), + TypeDefKind::Stream(_) => todo!(), + TypeDefKind::ErrorContext => todo!(), TypeDefKind::Handle(Handle::Own(resource)) => { src.push_str("own_"); push_ty_name(resolve, &Type::Id(*resource), src); @@ -992,6 +994,7 @@ impl Return { TypeDefKind::Future(_) => todo!("return_single for future"), TypeDefKind::Stream(_) => todo!("return_single for stream"), + TypeDefKind::ErrorContext => todo!("return_single for error-context"), TypeDefKind::Resource => todo!("return_single for resource"), TypeDefKind::Unknown => unreachable!(), } @@ -1427,12 +1430,16 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> todo!("print_anonymous_type for future"); } - fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Stream, _docs: &Docs) { + fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { todo!("print_anonymous_type for stream"); } - fn anonymous_typ_type(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { - todo!("print_anonymous_type for typ"); + fn anonymous_type_error_context(&mut self) { + todo!("print_anonymous_type for error-context"); + } + + fn anonymous_type_type(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { + todo!("print_anonymous_type for type"); } } @@ -1605,6 +1612,7 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Future(_) => todo!("print_dtor for future"), TypeDefKind::Stream(_) => todo!("print_dtor for stream"), + TypeDefKind::ErrorContext => todo!("print_dtor for error-context"), TypeDefKind::Resource => {} TypeDefKind::Handle(Handle::Borrow(id) | Handle::Own(id)) => { self.free(&Type::Id(*id), "*ptr"); @@ -1750,6 +1758,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut f, + false, ); let FunctionBindgen { @@ -1822,6 +1831,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut f, + false, ); let FunctionBindgen { src, .. } = f; self.src.c_adapters(&src); @@ -1852,7 +1862,7 @@ impl InterfaceGenerator<'_> { let mut f = FunctionBindgen::new(self, c_sig, &import_name); f.params = params; - abi::post_return(f.gen.resolve, func, &mut f); + abi::post_return(f.gen.resolve, func, &mut f, false); let FunctionBindgen { src, .. } = f; self.src.c_fns(&src); self.src.c_fns("}\n"); @@ -2075,17 +2085,8 @@ impl InterfaceGenerator<'_> { TypeDefKind::List(ty) => self.contains_droppable_borrow(ty), - TypeDefKind::Future(r) => r - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)), - - TypeDefKind::Stream(s) => { - s.element - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)) - || s.end - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)) + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext => { + false } TypeDefKind::Type(ty) => self.contains_droppable_borrow(ty), @@ -2753,7 +2754,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src.push_str(");\n"); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let mut args = String::new(); for (i, (op, (byref, _))) in operands.iter().zip(&self.sig.params).enumerate() { if i > 0 { @@ -3037,6 +3038,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "}}"); } + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + i => unimplemented!("{:?}", i), } } @@ -3145,6 +3150,7 @@ pub fn is_arg_by_pointer(resolve: &Resolve, ty: &Type) -> bool { TypeDefKind::Tuple(_) | TypeDefKind::Record(_) | TypeDefKind::List(_) => true, TypeDefKind::Future(_) => todo!("is_arg_by_pointer for future"), TypeDefKind::Stream(_) => todo!("is_arg_by_pointer for stream"), + TypeDefKind::ErrorContext => todo!("is_arg_by_pointer for error-context"), TypeDefKind::Resource => todo!("is_arg_by_pointer for resource"), TypeDefKind::Unknown => unreachable!(), }, diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 60935ef8d..bc476ea80 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -18,3 +18,7 @@ doctest = false wit-parser = { workspace = true } anyhow = { workspace = true } heck = { workspace = true } + +[features] +default = ["async"] +async = [] diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index f0175afaa..54f6a069e 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1,7 +1,7 @@ pub use wit_parser::abi::{AbiVariant, WasmSignature, WasmType}; use wit_parser::{ - Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, Results, SizeAlign, - Tuple, Type, TypeDefKind, TypeId, Variant, + ElementInfo, Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, Results, + SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, }; // Helper macro for defining instructions without having to have tons of @@ -350,6 +350,40 @@ def_instruction! { ty: TypeId, } : [1] => [1], + /// Create an `i32` from a future. + FutureLower { + payload: &'a Option, + ty: TypeId, + } : [1] => [1], + + /// Create a future from an `i32`. + FutureLift { + payload: &'a Option, + ty: TypeId, + } : [1] => [1], + + /// Create an `i32` from a stream. + StreamLower { + payload: &'a Type, + ty: TypeId, + } : [1] => [1], + + /// Create a stream from an `i32`. + StreamLift { + payload: &'a Type, + ty: TypeId, + } : [1] => [1], + + /// Create an `i32` from an error-context. + ErrorContextLower { + ty: TypeId, + } : [1] => [1], + + /// Create a error-context from an `i32`. + ErrorContextLift { + ty: TypeId, + } : [1] => [1], + /// Pops a tuple value off the stack, decomposes the tuple to all of /// its fields, and then pushes the fields onto the stack. TupleLower { @@ -470,7 +504,8 @@ def_instruction! { /// Note that this will be used for async functions. CallInterface { func: &'a Function, - } : [func.params.len()] => [func.results.len()], + async_: bool, + } : [func.params.len()] => [if *async_ { 1 } else { func.results.len() }], /// Returns `amt` values on the stack. This is always the last /// instruction. @@ -519,6 +554,39 @@ def_instruction! { GuestDeallocateVariant { blocks: usize, } : [1] => [0], + + /// Allocate the parameter and/or return areas to use for an + /// async-lowered import call. + /// + /// This cannot be allocated on the (shadow-)stack since it needs to + /// remain valid until the callee has finished using the buffers, which + /// may be after we pop the current stack frame. + AsyncMalloc { size: usize, align: usize } : [0] => [1], + + /// Call an async-lowered import. + /// + /// `size` and `align` are used to deallocate the parameter area + /// allocated using `AsyncMalloc` after the callee task returns a value. + AsyncCallWasm { name: &'a str, size: usize, align: usize } : [2] => [0], + + /// Generate code to run after `CallInterface` for an async-lifted export. + /// + /// For example, this might include task management for the + /// future/promise/task returned by the call made for `CallInterface`. + AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len() + 1], + + /// Call `task.return` for an async-lifted export once the task returned + /// by `CallInterface` and managed by `AsyncPostCallInterface` + /// yields a value. + AsyncCallReturn { name: &'a str, params: &'a [WasmType] } : [params.len()] => [0], + + /// Force the evaluation of the specified number of expressions and push + /// the results to the stack. + /// + /// This is useful prior to disposing of temporary variables and/or + /// allocations which are referenced by one or more not-yet-evaluated + /// expressions. + Flush { amt: usize } : [*amt] => [*amt], } } @@ -683,8 +751,50 @@ pub fn call( lift_lower: LiftLower, func: &Function, bindgen: &mut impl Bindgen, + async_: bool, +) { + if async_ && !cfg!(feature = "async") { + panic!("must enable `async` feature to lift or lower using the async ABI"); + } + + Generator::new(resolve, variant, lift_lower, bindgen, async_).call(func); +} + +pub fn lower_to_memory( + resolve: &Resolve, + bindgen: &mut B, + address: B::Operand, + value: B::Operand, + ty: &Type, ) { - Generator::new(resolve, variant, lift_lower, bindgen).call(func); + // TODO: refactor so we don't need to pass in a bunch of unused dummy parameters: + let mut generator = Generator::new( + resolve, + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + bindgen, + true, + ); + generator.stack.push(value); + generator.write_to_memory(ty, address, 0); +} + +pub fn lift_from_memory( + resolve: &Resolve, + bindgen: &mut B, + address: B::Operand, + ty: &Type, +) -> B::Operand { + // TODO: refactor so we don't need to pass in a bunch of unused dummy parameters: + let mut generator = Generator::new( + resolve, + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + bindgen, + true, + ); + generator.read_from_memory(ty, address, 0); + generator.stack.pop().unwrap() } /// Used in a similar manner as the `Interface::call` function except is @@ -693,12 +803,13 @@ pub fn call( /// This is only intended to be used in guest generators for exported /// functions and will primarily generate `GuestDeallocate*` instructions, /// plus others used as input to those instructions. -pub fn post_return(resolve: &Resolve, func: &Function, bindgen: &mut impl Bindgen) { +pub fn post_return(resolve: &Resolve, func: &Function, bindgen: &mut impl Bindgen, async_: bool) { Generator::new( resolve, AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, bindgen, + async_, ) .post_return(func); } @@ -734,7 +845,9 @@ fn needs_post_return(resolve: &Resolve, ty: &Type) -> bool { .filter_map(|t| t.as_ref()) .any(|t| needs_post_return(resolve, t)), TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => false, - TypeDefKind::Future(_) | TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext => { + unimplemented!() + } TypeDefKind::Unknown => unreachable!(), }, @@ -757,6 +870,7 @@ struct Generator<'a, B: Bindgen> { variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, + async_: bool, resolve: &'a Resolve, operands: Vec, results: Vec, @@ -770,12 +884,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, + async_: bool, ) -> Generator<'a, B> { Generator { resolve, variant, lift_lower, bindgen, + async_, operands: Vec::new(), results: Vec::new(), stack: Vec::new(), @@ -784,74 +900,122 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn call(&mut self, func: &Function) { + const MAX_FLAT_PARAMS: usize = 16; + let sig = self.resolve.wasm_signature(self.variant, func); match self.lift_lower { LiftLower::LowerArgsLiftResults => { - if !sig.indirect_params { - // If the parameters for this function aren't indirect - // (there aren't too many) then we simply do a normal lower - // operation for them all. + if let (AbiVariant::GuestExport, true) = (self.variant, self.async_) { + unimplemented!("host-side code generation for async lift/lower not supported"); + } + + let lower_to_memory = |self_: &mut Self, ptr: B::Operand| { + let mut offset = 0usize; for (nth, (_, ty)) in func.params.iter().enumerate() { - self.emit(&Instruction::GetArg { nth }); - self.lower(ty); + self_.emit(&Instruction::GetArg { nth }); + offset = align_to(offset, self_.bindgen.sizes().align(ty).align_wasm32()); + self_.write_to_memory(ty, ptr.clone(), offset as i32); + offset += self_.bindgen.sizes().size(ty).size_wasm32(); } - } else { - // ... otherwise if parameters are indirect space is - // allocated from them and each argument is lowered - // individually into memory. - let info = self + + self_.stack.push(ptr); + }; + + let params_size_align = if self.async_ { + let ElementInfo { size, align } = self .bindgen .sizes() - .record(func.params.iter().map(|t| &t.1)); - let ptr = match self.variant { - // When a wasm module calls an import it will provide - // space that isn't explicitly deallocated. - AbiVariant::GuestImport => self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()), - // When calling a wasm module from the outside, though, - // malloc needs to be called. - AbiVariant::GuestExport => { - self.emit(&Instruction::Malloc { - realloc: "cabi_realloc", - size: info.size.size_wasm32(), - align: info.align.align_wasm32(), - }); - self.stack.pop().unwrap() + .record(func.params.iter().map(|(_, ty)| ty)); + self.emit(&Instruction::AsyncMalloc { + size: size.size_wasm32(), + align: align.align_wasm32(), + }); + let ptr = self.stack.pop().unwrap(); + lower_to_memory(self, ptr); + Some((size, align)) + } else { + if !sig.indirect_params { + // If the parameters for this function aren't indirect + // (there aren't too many) then we simply do a normal lower + // operation for them all. + for (nth, (_, ty)) in func.params.iter().enumerate() { + self.emit(&Instruction::GetArg { nth }); + self.lower(ty); } - }; - let mut offset = 0usize; - for (nth, (_, ty)) in func.params.iter().enumerate() { - self.emit(&Instruction::GetArg { nth }); - offset = align_to(offset, self.bindgen.sizes().align(ty).align_wasm32()); - self.write_to_memory(ty, ptr.clone(), offset as i32); - offset += self.bindgen.sizes().size(ty).size_wasm32(); + } else { + // ... otherwise if parameters are indirect space is + // allocated from them and each argument is lowered + // individually into memory. + let info = self + .bindgen + .sizes() + .record(func.params.iter().map(|t| &t.1)); + let ptr = match self.variant { + // When a wasm module calls an import it will provide + // space that isn't explicitly deallocated. + AbiVariant::GuestImport => self + .bindgen + .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()), + // When calling a wasm module from the outside, though, + // malloc needs to be called. + AbiVariant::GuestExport => { + self.emit(&Instruction::Malloc { + realloc: "cabi_realloc", + size: info.size.size_wasm32(), + align: info.align.align_wasm32(), + }); + self.stack.pop().unwrap() + } + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } + }; + lower_to_memory(self, ptr); } - - self.stack.push(ptr); - } + None + }; // If necessary we may need to prepare a return pointer for // this ABI. - if self.variant == AbiVariant::GuestImport && sig.retptr { - let info = self.bindgen.sizes().params(func.results.iter_types()); - let ptr = self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); - self.return_pointer = Some(ptr.clone()); - self.stack.push(ptr); - } + let dealloc_size_align = + if let Some((params_size, params_align)) = params_size_align { + let ElementInfo { size, align } = + self.bindgen.sizes().record(func.results.iter_types()); + self.emit(&Instruction::AsyncMalloc { + size: size.size_wasm32(), + align: align.align_wasm32(), + }); + let ptr = self.stack.pop().unwrap(); + self.return_pointer = Some(ptr.clone()); + self.stack.push(ptr); + + assert_eq!(self.stack.len(), 2); + self.emit(&Instruction::AsyncCallWasm { + name: &format!("[async]{}", func.name), + size: params_size.size_wasm32(), + align: params_align.align_wasm32(), + }); + Some((size, align)) + } else { + if self.variant == AbiVariant::GuestImport && sig.retptr { + let info = self.bindgen.sizes().params(func.results.iter_types()); + let ptr = self + .bindgen + .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); + self.return_pointer = Some(ptr.clone()); + self.stack.push(ptr); + } - // Now that all the wasm args are prepared we can call the - // actual wasm function. - assert_eq!(self.stack.len(), sig.params.len()); - self.emit(&Instruction::CallWasm { - name: &func.name, - sig: &sig, - }); + assert_eq!(self.stack.len(), sig.params.len()); + self.emit(&Instruction::CallWasm { + name: &func.name, + sig: &sig, + }); + None + }; - if !sig.retptr { + if !(sig.retptr || self.async_) { // With no return pointer in use we can simply lift the // result(s) of the function from the result of the core // wasm function. @@ -862,7 +1026,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let ptr = match self.variant { // imports into guests means it's a wasm module // calling an imported function. We supplied the - // return poitner as the last argument (saved in + // return pointer as the last argument (saved in // `self.return_pointer`) so we use that to read // the result of the function from memory. AbiVariant::GuestImport => { @@ -874,9 +1038,24 @@ impl<'a, B: Bindgen> Generator<'a, B> { // calling wasm so wasm returned a pointer to where // the result is stored AbiVariant::GuestExport => self.stack.pop().unwrap(), + + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } }; - self.read_results_from_memory(&func.results, ptr, 0); + self.read_results_from_memory(&func.results, ptr.clone(), 0); + self.emit(&Instruction::Flush { + amt: func.results.len(), + }); + + if let Some((size, align)) = dealloc_size_align { + self.stack.push(ptr); + self.emit(&Instruction::GuestDeallocate { + size: size.size_wasm32(), + align: align.align_wasm32(), + }); + } } self.emit(&Instruction::Return { @@ -885,6 +1064,20 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); } LiftLower::LiftArgsLowerResults => { + if let (AbiVariant::GuestImport, true) = (self.variant, self.async_) { + todo!("implement host-side support for async lift/lower"); + } + + let read_from_memory = |self_: &mut Self| { + let mut offset = 0usize; + let ptr = self_.stack.pop().unwrap(); + for (_, ty) in func.params.iter() { + offset = align_to(offset, self_.bindgen.sizes().align(ty).align_wasm32()); + self_.read_from_memory(ty, ptr.clone(), offset as i32); + offset += self_.bindgen.sizes().size(ty).size_wasm32(); + } + }; + if !sig.indirect_params { // If parameters are not passed indirectly then we lift each // argument in succession from the component wasm types that @@ -904,23 +1097,33 @@ impl<'a, B: Bindgen> Generator<'a, B> { // ... otherwise argument is read in succession from memory // where the pointer to the arguments is the first argument // to the function. - let mut offset = 0usize; self.emit(&Instruction::GetArg { nth: 0 }); - let ptr = self.stack.pop().unwrap(); - for (_, ty) in func.params.iter() { - offset = align_to(offset, self.bindgen.sizes().align(ty).align_wasm32()); - self.read_from_memory(ty, ptr.clone(), offset as i32); - offset += self.bindgen.sizes().size(ty).size_wasm32(); - } + read_from_memory(self); } // ... and that allows us to call the interface types function - self.emit(&Instruction::CallInterface { func }); + self.emit(&Instruction::CallInterface { + func, + async_: self.async_, + }); - // This was dynamically allocated by the caller so after - // it's been read by the guest we need to deallocate it. + let (lower_to_memory, async_results) = if self.async_ { + self.emit(&Instruction::AsyncPostCallInterface { func }); + + let mut results = Vec::new(); + for ty in func.results.iter_types() { + self.resolve.push_flat(ty, &mut results); + } + (results.len() > MAX_FLAT_PARAMS, Some(results)) + } else { + (sig.retptr, None) + }; + + // This was dynamically allocated by the caller (or async start + // function) so after it's been read by the guest we need to + // deallocate it. if let AbiVariant::GuestExport = self.variant { - if sig.indirect_params { + if sig.indirect_params && !self.async_ { let info = self .bindgen .sizes() @@ -933,7 +1136,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - if !sig.retptr { + if !lower_to_memory { // With no return pointer in use we simply lower the // result(s) and return that directly from the function. let results = self @@ -973,13 +1176,31 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.write_params_to_memory(func.results.iter_types(), ptr.clone(), 0); self.stack.push(ptr); } + + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } } } - self.emit(&Instruction::Return { - func, - amt: sig.results.len(), - }); + if let Some(results) = async_results { + let name = &format!("[task-return]{}", func.name); + + self.emit(&Instruction::AsyncCallReturn { + name, + params: &if results.len() > MAX_FLAT_PARAMS { + vec![WasmType::Pointer] + } else { + results + }, + }); + self.emit(&Instruction::Return { func, amt: 1 }); + } else { + self.emit(&Instruction::Return { + func, + amt: sig.results.len(), + }); + } } } @@ -1177,8 +1398,21 @@ impl<'a, B: Bindgen> Generator<'a, B> { results: &results, }); } - TypeDefKind::Future(_) => todo!("lower future"), - TypeDefKind::Stream(_) => todo!("lower stream"), + TypeDefKind::Future(ty) => { + self.emit(&FutureLower { + payload: ty, + ty: id, + }); + } + TypeDefKind::Stream(ty) => { + self.emit(&StreamLower { + payload: ty, + ty: id, + }); + } + TypeDefKind::ErrorContext => { + self.emit(&ErrorContextLower { ty: id }); + } TypeDefKind::Unknown => unreachable!(), }, } @@ -1362,8 +1596,21 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&ResultLift { result: r, ty: id }); } - TypeDefKind::Future(_) => todo!("lift future"), - TypeDefKind::Stream(_) => todo!("lift stream"), + TypeDefKind::Future(ty) => { + self.emit(&FutureLift { + payload: ty, + ty: id, + }); + } + TypeDefKind::Stream(ty) => { + self.emit(&StreamLift { + payload: ty, + ty: id, + }); + } + TypeDefKind::ErrorContext => { + self.emit(&ErrorContextLift { ty: id }); + } TypeDefKind::Unknown => unreachable!(), }, } @@ -1431,7 +1678,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), - TypeDefKind::Handle(_) => self.lower_and_emit(ty, addr, &I32Store { offset }), + TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::ErrorContext + | TypeDefKind::Handle(_) => self.lower_and_emit(ty, addr, &I32Store { offset }), // Decompose the record into its components and then write all // the components into memory one-by-one. @@ -1521,8 +1771,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.store_intrepr(offset, e.tag()); } - TypeDefKind::Future(_) => todo!("write future to memory"), - TypeDefKind::Stream(_) => todo!("write stream to memory"), TypeDefKind::Unknown => unreachable!(), }, } @@ -1625,7 +1873,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), - TypeDefKind::Handle(_) => self.emit_and_lift(ty, addr, &I32Load { offset }), + TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::ErrorContext + | TypeDefKind::Handle(_) => self.emit_and_lift(ty, addr, &I32Load { offset }), TypeDefKind::Resource => { todo!(); @@ -1709,8 +1960,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lift(ty); } - TypeDefKind::Future(_) => todo!("read future from memory"), - TypeDefKind::Stream(_) => todo!("read stream from memory"), TypeDefKind::Unknown => unreachable!(), }, } @@ -1887,6 +2136,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::ErrorContext => todo!("read error-context from memory"), TypeDefKind::Unknown => unreachable!(), }, } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index fae3f3b90..b0cc21a47 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -22,6 +22,22 @@ pub enum Direction { pub trait WorldGenerator { fn generate(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { + // TODO: Should we refine this test to inspect only types reachable from + // the specified world? + if !cfg!(feature = "async") + && resolve.types.iter().any(|(_, ty)| { + matches!( + ty.kind, + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext + ) + }) + { + anyhow::bail!( + "must enable `async` feature when using WIT files \ + containing future, stream, or error types" + ); + } + let world = &resolve.worlds[id]; self.preprocess(resolve, id); @@ -174,9 +190,12 @@ pub trait InterfaceGenerator<'a> { TypeDefKind::Result(r) => self.type_result(id, name, r, &ty.docs), TypeDefKind::List(t) => self.type_list(id, name, t, &ty.docs), TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), - TypeDefKind::Future(_) => todo!("generate for future"), - TypeDefKind::Stream(_) => todo!("generate for stream"), - TypeDefKind::Handle(_) => todo!("generate for handle"), + TypeDefKind::Future(_) => panic!("future types do not require definition"), + TypeDefKind::Stream(_) => panic!("stream types do not require definition"), + TypeDefKind::Handle(_) => panic!("handle types do not require definition"), + TypeDefKind::ErrorContext => { + panic!("the error-context type does not require definition") + } TypeDefKind::Unknown => unreachable!(), } } @@ -191,8 +210,9 @@ pub trait AnonymousTypeGenerator<'a> { fn anonymous_type_result(&mut self, id: TypeId, ty: &Result_, docs: &Docs); fn anonymous_type_list(&mut self, id: TypeId, ty: &Type, docs: &Docs); fn anonymous_type_future(&mut self, id: TypeId, ty: &Option, docs: &Docs); - fn anonymous_type_stream(&mut self, id: TypeId, ty: &Stream, docs: &Docs); - fn anonymous_typ_type(&mut self, id: TypeId, ty: &Type, docs: &Docs); + fn anonymous_type_stream(&mut self, id: TypeId, ty: &Type, docs: &Docs); + fn anonymous_type_type(&mut self, id: TypeId, ty: &Type, docs: &Docs); + fn anonymous_type_error_context(&mut self); fn define_anonymous_type(&mut self, id: TypeId) { let ty = &self.resolve().types[id]; @@ -204,13 +224,14 @@ pub trait AnonymousTypeGenerator<'a> { | TypeDefKind::Variant(_) => { unreachable!() } - TypeDefKind::Type(t) => self.anonymous_typ_type(id, t, &ty.docs), + TypeDefKind::Type(t) => self.anonymous_type_type(id, t, &ty.docs), TypeDefKind::Tuple(tuple) => self.anonymous_type_tuple(id, tuple, &ty.docs), TypeDefKind::Option(t) => self.anonymous_type_option(id, t, &ty.docs), TypeDefKind::Result(r) => self.anonymous_type_result(id, r, &ty.docs), TypeDefKind::List(t) => self.anonymous_type_list(id, t, &ty.docs), TypeDefKind::Future(f) => self.anonymous_type_future(id, f, &ty.docs), TypeDefKind::Stream(s) => self.anonymous_type_stream(id, s, &ty.docs), + TypeDefKind::ErrorContext => self.anonymous_type_error_context(), TypeDefKind::Handle(handle) => self.anonymous_type_handle(id, handle, &ty.docs), TypeDefKind::Unknown => unreachable!(), } diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index a0862f5e8..45030c0ea 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -193,13 +193,7 @@ impl Types { info = self.optional_type_info(resolve, r.ok.as_ref()); info |= self.optional_type_info(resolve, r.err.as_ref()); } - TypeDefKind::Future(ty) => { - info = self.optional_type_info(resolve, ty.as_ref()); - } - TypeDefKind::Stream(stream) => { - info = self.optional_type_info(resolve, stream.element.as_ref()); - info |= self.optional_type_info(resolve, stream.end.as_ref()); - } + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext => {} TypeDefKind::Unknown => unreachable!(), } let prev = self.type_info.insert(ty, info); diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index 6f11ef47b..ff415d33b 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -1059,6 +1059,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -1178,6 +1179,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -2589,7 +2591,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { operands[0], operands[1] )), - Instruction::ListLower { element, realloc } => { + Instruction::ListLower { element, realloc: _ } => { let Block { body, results: block_results, @@ -2694,7 +2696,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let module = self.gen.name; let func_name = self.func_name.to_upper_camel_case(); let interface_name = CSharp::get_class_name_from_qualified_name(module).1; @@ -3005,6 +3007,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { } results.push(resource); } + + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + + Instruction::AsyncMalloc { .. } + | Instruction::AsyncPostCallInterface { .. } + | Instruction::AsyncCallReturn { .. } + | Instruction::FutureLower { .. } + | Instruction::FutureLift { .. } + | Instruction::StreamLower { .. } + | Instruction::StreamLift { .. } + | Instruction::ErrorContextLower { .. } + | Instruction::ErrorContextLift { .. } + | Instruction::AsyncCallWasm { .. } => todo!(), } } diff --git a/crates/go/src/bindgen.rs b/crates/go/src/bindgen.rs index f045bbd00..853f97673 100644 --- a/crates/go/src/bindgen.rs +++ b/crates/go/src/bindgen.rs @@ -319,6 +319,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } TypeDefKind::Future(_) => todo!("impl future"), TypeDefKind::Stream(_) => todo!("impl stream"), + TypeDefKind::ErrorContext => todo!("impl error-context"), TypeDefKind::Resource => todo!("impl resource"), TypeDefKind::Handle(h) => { match self.interface.direction { @@ -609,6 +610,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } TypeDefKind::Future(_) => todo!("impl future"), TypeDefKind::Stream(_) => todo!("impl stream"), + TypeDefKind::ErrorContext => todo!("impl error-context"), TypeDefKind::Resource => todo!("impl resource"), TypeDefKind::Handle(h) => { match self.interface.direction { diff --git a/crates/go/src/interface.rs b/crates/go/src/interface.rs index ba7e150cb..e47438a02 100644 --- a/crates/go/src/interface.rs +++ b/crates/go/src/interface.rs @@ -322,11 +322,11 @@ impl InterfaceGenerator<'_> { TypeDefKind::Stream(t) => { let mut src = String::new(); src.push_str("Stream"); - src.push_str(&self.optional_ty_name(t.element.as_ref())); - src.push_str(&self.optional_ty_name(t.end.as_ref())); + src.push_str(&self.ty_name(t)); src.push('T'); src } + TypeDefKind::ErrorContext => "ErrorContext".to_owned(), TypeDefKind::Handle(Handle::Own(ty)) => { // Currently there is no different between Own and Borrow // in the Go code. They are just represented as @@ -678,6 +678,7 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Future(_) => todo!("anonymous_type for future"), TypeDefKind::Stream(_) => todo!("anonymous_type for stream"), + TypeDefKind::ErrorContext => todo!("anonymous_type for error-context"), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 1918921db..71305e02a 100644 --- a/crates/guest-rust/Cargo.toml +++ b/crates/guest-rust/Cargo.toml @@ -16,6 +16,7 @@ wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.34.0" wit-bindgen-rt = { path = "./rt", version = "0.34.0", features = ["bitflags"] } [features] -default = ["macros", "realloc"] +default = ["macros", "realloc", "async"] macros = ["dep:wit-bindgen-rust-macro"] realloc = [] +async = ["macros", "wit-bindgen-rt/async", "wit-bindgen-rust-macro/async"] diff --git a/crates/guest-rust/macro/Cargo.toml b/crates/guest-rust/macro/Cargo.toml index 984a1bffc..f30dd8780 100644 --- a/crates/guest-rust/macro/Cargo.toml +++ b/crates/guest-rust/macro/Cargo.toml @@ -24,3 +24,6 @@ anyhow = { workspace = true } syn = { workspace = true } prettyplease = { workspace = true } +[features] +default = ["async"] +async = ["wit-bindgen-rust/async"] diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index c6cb439be..264bb766a 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -8,7 +8,7 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{braced, token, LitStr, Token}; use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; -use wit_bindgen_rust::{Opts, Ownership, WithOption}; +use wit_bindgen_rust::{AsyncConfig, Opts, Ownership, WithOption}; #[proc_macro] pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -46,6 +46,7 @@ struct Config { resolve: Resolve, world: WorldId, files: Vec, + debug: bool, } /// The source of the wit package definition @@ -63,6 +64,8 @@ impl Parse for Config { let mut world = None; let mut source = None; let mut features = Vec::new(); + let mut async_configured = false; + let mut debug = false; if input.peek(token::Brace) { let content; @@ -140,6 +143,22 @@ impl Parse for Config { Opt::DisableCustomSectionLinkHelpers(disable) => { opts.disable_custom_section_link_helpers = disable.value(); } + Opt::Debug(enable) => { + debug = enable.value(); + } + Opt::Async(val, span) => { + if async_configured { + return Err(Error::new(span, "cannot specify second async config")); + } + async_configured = true; + if !matches!(val, AsyncConfig::None) && !cfg!(feature = "async") { + return Err(Error::new( + span, + "must enable `async` feature to enable async imports and/or exports", + )); + } + opts.async_ = val; + } } } } else { @@ -159,6 +178,7 @@ impl Parse for Config { resolve, world, files, + debug, }) } } @@ -222,7 +242,7 @@ fn parse_source( }; let (pkg, sources) = resolve.push_path(normalized_path)?; pkgs.push(pkg); - files.extend(sources); + files.extend(sources.package_paths(pkg).unwrap().map(|v| v.to_owned())); } Ok(()) }; @@ -254,7 +274,7 @@ impl Config { // place a formatted version of the expanded code into a file. This file // will then show up in rustc error messages for any codegen issues and can // be inspected manually. - if std::env::var("WIT_BINDGEN_DEBUG").is_ok() { + if std::env::var("WIT_BINDGEN_DEBUG").is_ok() || self.debug { static INVOCATION: AtomicUsize = AtomicUsize::new(0); let root = Path::new(env!("DEBUG_OUTPUT_DIR")); let world_name = &self.resolve.worlds[self.world].name; @@ -313,6 +333,8 @@ mod kw { syn::custom_keyword!(generate_unused_types); syn::custom_keyword!(features); syn::custom_keyword!(disable_custom_section_link_helpers); + syn::custom_keyword!(imports); + syn::custom_keyword!(debug); } #[derive(Clone)] @@ -342,6 +364,11 @@ impl From for wit_bindgen_rust::ExportKey { } } +enum AsyncConfigSomeKind { + Imports, + Exports, +} + enum Opt { World(syn::LitStr), Path(Span, Vec), @@ -366,6 +393,8 @@ enum Opt { GenerateUnusedTypes(syn::LitBool), Features(Vec), DisableCustomSectionLinkHelpers(syn::LitBool), + Async(AsyncConfig, Span), + Debug(syn::LitBool), } impl Parse for Opt { @@ -513,6 +542,34 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::DisableCustomSectionLinkHelpers(input.parse()?)) + } else if l.peek(kw::debug) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Debug(input.parse()?)) + } else if l.peek(Token![async]) { + let span = input.parse::()?.span; + input.parse::()?; + if input.peek(syn::LitBool) { + if input.parse::()?.value { + Ok(Opt::Async(AsyncConfig::All, span)) + } else { + Ok(Opt::Async(AsyncConfig::None, span)) + } + } else { + let mut imports = Vec::new(); + let mut exports = Vec::new(); + let contents; + syn::braced!(contents in input); + for (kind, values) in + contents.parse_terminated(parse_async_some_field, Token![,])? + { + match kind { + AsyncConfigSomeKind::Imports => imports = values, + AsyncConfigSomeKind::Exports => exports = values, + } + } + Ok(Opt::Async(AsyncConfig::Some { imports, exports }, span)) + } } else { Err(l.error()) } @@ -571,3 +628,27 @@ fn fmt(input: &str) -> Result { let syntax_tree = syn::parse_file(&input)?; Ok(prettyplease::unparse(&syntax_tree)) } + +fn parse_async_some_field(input: ParseStream<'_>) -> Result<(AsyncConfigSomeKind, Vec)> { + let lookahead = input.lookahead1(); + let kind = if lookahead.peek(kw::imports) { + input.parse::()?; + input.parse::()?; + AsyncConfigSomeKind::Imports + } else if lookahead.peek(kw::exports) { + input.parse::()?; + input.parse::()?; + AsyncConfigSomeKind::Exports + } else { + return Err(lookahead.error()); + }; + + let list; + syn::bracketed!(list in input); + let fields = list.parse_terminated(Parse::parse, Token![,])?; + + Ok(( + kind, + fields.iter().map(|s: &syn::LitStr| s.value()).collect(), + )) +} diff --git a/crates/guest-rust/rt/Cargo.toml b/crates/guest-rust/rt/Cargo.toml index d038f3fcc..e84f04d18 100644 --- a/crates/guest-rust/rt/Cargo.toml +++ b/crates/guest-rust/rt/Cargo.toml @@ -12,3 +12,9 @@ Runtime support for the `wit-bindgen` crate [dependencies] # Optionally re-export the version of bitflags used by wit-bindgen. bitflags = { workspace = true, optional = true } +futures = { version = "0.3.30", optional = true } +once_cell = { version = "1.19.0", optional = true } + +[features] +default = ["async"] +async = ["dep:futures", "dep:once_cell"] diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs new file mode 100644 index 000000000..fba6ce2d6 --- /dev/null +++ b/crates/guest-rust/rt/src/async_support.rs @@ -0,0 +1,510 @@ +#![deny(missing_docs)] + +use { + futures::{ + channel::oneshot, + future::FutureExt, + stream::{FuturesUnordered, StreamExt}, + }, + once_cell::sync::Lazy, + std::{ + alloc::{self, Layout}, + any::Any, + collections::hash_map, + collections::HashMap, + fmt::{self, Debug, Display}, + future::Future, + pin::Pin, + ptr, + sync::Arc, + task::{Context, Poll, Wake, Waker}, + }, +}; + +type BoxFuture = Pin + 'static>>; + +/// Represents a task created by either a call to an async-lifted export or a +/// future run using `block_on` or `poll_future`. +struct FutureState { + /// Number of in-progress async-lowered import calls and/or stream/future reads/writes. + todo: usize, + /// Remaining work to do (if any) before this task can be considered "done". + /// + /// Note that we won't tell the host the task is done until this is drained + /// and `todo` is zero. + tasks: Option>, +} + +/// Represents the state of a stream or future. +#[doc(hidden)] +pub enum Handle { + LocalOpen, + LocalReady(Box, Waker), + LocalWaiting(oneshot::Sender>), + LocalClosed, + Read, + Write, +} + +/// The current task being polled (or null if none). +static mut CURRENT: *mut FutureState = ptr::null_mut(); + +/// Map of any in-progress calls to async-lowered imports, keyed by the +/// identifiers issued by the host. +static mut CALLS: Lazy>> = Lazy::new(HashMap::new); + +/// Any newly-deferred work queued by calls to the `spawn` function while +/// polling the current task. +static mut SPAWNED: Vec = Vec::new(); + +/// The states of all currently-open streams and futures. +static mut HANDLES: Lazy> = Lazy::new(HashMap::new); + +#[doc(hidden)] +pub fn with_entry(handle: u32, fun: impl FnOnce(hash_map::Entry<'_, u32, Handle>) -> T) -> T { + fun(unsafe { HANDLES.entry(handle) }) +} + +fn dummy_waker() -> Waker { + struct DummyWaker; + + impl Wake for DummyWaker { + fn wake(self: Arc) {} + } + + static WAKER: Lazy> = Lazy::new(|| Arc::new(DummyWaker)); + + WAKER.clone().into() +} + +/// Poll the specified task until it either completes or can't make immediate +/// progress. +unsafe fn poll(state: *mut FutureState) -> Poll<()> { + loop { + if let Some(futures) = (*state).tasks.as_mut() { + CURRENT = state; + let poll = futures.poll_next_unpin(&mut Context::from_waker(&dummy_waker())); + CURRENT = ptr::null_mut(); + + if SPAWNED.is_empty() { + match poll { + Poll::Ready(Some(())) => (), + Poll::Ready(None) => { + (*state).tasks = None; + break Poll::Ready(()); + } + Poll::Pending => break Poll::Pending, + } + } else { + futures.extend(SPAWNED.drain(..)); + } + } else { + break Poll::Ready(()); + } + } +} + +/// Poll the future generated by a call to an async-lifted export once, calling +/// the specified closure (presumably backed by a call to `task.return`) when it +/// generates a value. +/// +/// This will return a non-null pointer representing the task if it hasn't +/// completed immediately; otherwise it returns null. +#[doc(hidden)] +pub fn first_poll( + future: impl Future + 'static, + fun: impl FnOnce(T) + 'static, +) -> *mut u8 { + let state = Box::into_raw(Box::new(FutureState { + todo: 0, + tasks: Some( + [Box::pin(future.map(fun)) as BoxFuture] + .into_iter() + .collect(), + ), + })); + match unsafe { poll(state) } { + Poll::Ready(()) => ptr::null_mut(), + Poll::Pending => state as _, + } +} + +/// Await the completion of a call to an async-lowered import. +#[doc(hidden)] +pub async unsafe fn await_result( + import: unsafe extern "C" fn(*mut u8, *mut u8) -> i32, + params_layout: Layout, + params: *mut u8, + results: *mut u8, +) { + const STATUS_STARTING: u32 = 0; + const STATUS_STARTED: u32 = 1; + const STATUS_RETURNED: u32 = 2; + const STATUS_DONE: u32 = 3; + + let result = import(params, results) as u32; + let status = result >> 30; + let call = (result & !(0b11 << 30)) as i32; + + if status != STATUS_DONE { + assert!(!CURRENT.is_null()); + (*CURRENT).todo += 1; + } + + match status { + STATUS_STARTING => { + let (tx, rx) = oneshot::channel(); + CALLS.insert(call, tx); + rx.await.unwrap(); + alloc::dealloc(params, params_layout); + } + STATUS_STARTED => { + alloc::dealloc(params, params_layout); + let (tx, rx) = oneshot::channel(); + CALLS.insert(call, tx); + rx.await.unwrap(); + } + STATUS_RETURNED | STATUS_DONE => { + alloc::dealloc(params, params_layout); + } + _ => unreachable!(), + } +} + +/// stream/future read/write results defined by the Component Model ABI. +mod results { + pub const BLOCKED: u32 = 0xffff_ffff; + pub const CLOSED: u32 = 0x8000_0000; + pub const CANCELED: u32 = 0; +} + +/// Await the completion of a future read or write. +#[doc(hidden)] +pub async unsafe fn await_future_result( + import: unsafe extern "C" fn(u32, *mut u8) -> u32, + future: u32, + address: *mut u8, +) -> bool { + let result = import(future, address); + match result { + results::BLOCKED => { + assert!(!CURRENT.is_null()); + (*CURRENT).todo += 1; + let (tx, rx) = oneshot::channel(); + CALLS.insert(future as _, tx); + let v = rx.await.unwrap(); + v == 1 + } + results::CLOSED | results::CANCELED => false, + 1 => true, + _ => unreachable!(), + } +} + +/// Await the completion of a stream read or write. +#[doc(hidden)] +pub async unsafe fn await_stream_result( + import: unsafe extern "C" fn(u32, *mut u8, u32) -> u32, + stream: u32, + address: *mut u8, + count: u32, +) -> Option { + let result = import(stream, address, count); + match result { + results::BLOCKED => { + assert!(!CURRENT.is_null()); + (*CURRENT).todo += 1; + let (tx, rx) = oneshot::channel(); + CALLS.insert(stream as _, tx); + let v = rx.await.unwrap(); + if let results::CLOSED | results::CANCELED = v { + None + } else { + Some(usize::try_from(v).unwrap()) + } + } + results::CLOSED | results::CANCELED => None, + v => Some(usize::try_from(v).unwrap()), + } +} + +/// Call the `subtask.drop` canonical built-in function. +fn subtask_drop(subtask: u32) { + #[cfg(not(target_arch = "wasm32"))] + { + _ = subtask; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[subtask-drop]"] + fn subtask_drop(_: u32); + } + unsafe { + subtask_drop(subtask); + } + } +} + +/// Handle a progress notification from the host regarding either a call to an +/// async-lowered import or a stream/future read/write operation. +#[doc(hidden)] +pub unsafe fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 { + const _EVENT_CALL_STARTING: i32 = 0; + const EVENT_CALL_STARTED: i32 = 1; + const EVENT_CALL_RETURNED: i32 = 2; + const EVENT_CALL_DONE: i32 = 3; + const _EVENT_YIELDED: i32 = 4; + const EVENT_STREAM_READ: i32 = 5; + const EVENT_STREAM_WRITE: i32 = 6; + const EVENT_FUTURE_READ: i32 = 7; + const EVENT_FUTURE_WRITE: i32 = 8; + + match event0 { + EVENT_CALL_STARTED => 0, + EVENT_CALL_RETURNED | EVENT_CALL_DONE | EVENT_STREAM_READ | EVENT_STREAM_WRITE + | EVENT_FUTURE_READ | EVENT_FUTURE_WRITE => { + if let Some(call) = CALLS.remove(&event1) { + _ = call.send(event2 as _); + } + + let state = ctx as *mut FutureState; + let done = poll(state).is_ready(); + + if event0 == EVENT_CALL_DONE { + subtask_drop(event1 as u32); + } + + if matches!( + event0, + EVENT_CALL_DONE + | EVENT_STREAM_READ + | EVENT_STREAM_WRITE + | EVENT_FUTURE_READ + | EVENT_FUTURE_WRITE + ) { + (*state).todo -= 1; + } + + if done && (*state).todo == 0 { + drop(Box::from_raw(state)); + 1 + } else { + 0 + } + } + _ => unreachable!(), + } +} + +/// Represents the Component Model `error-context` type. +pub struct ErrorContext { + handle: u32, +} + +impl ErrorContext { + #[doc(hidden)] + pub fn from_handle(handle: u32) -> Self { + Self { handle } + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + self.handle + } +} + +impl Debug for ErrorContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ErrorContext").finish() + } +} + +impl Display for ErrorContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error") + } +} + +impl std::error::Error for ErrorContext {} + +impl Drop for ErrorContext { + fn drop(&mut self) { + #[cfg(not(target_arch = "wasm32"))] + { + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[error-context-drop]"] + fn error_drop(_: u32); + } + if self.handle != 0 { + unsafe { error_drop(self.handle) } + } + } + } +} + +/// Defer the specified future to be run after the current async-lifted export +/// task has returned a value. +/// +/// The task will remain in a running state until all spawned futures have +/// completed. +pub fn spawn(future: impl Future + 'static) { + unsafe { SPAWNED.push(Box::pin(future)) } +} + +fn task_wait(state: &mut FutureState) { + #[cfg(not(target_arch = "wasm32"))] + { + _ = state; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[task-wait]"] + fn wait(_: *mut i32) -> i32; + } + let mut payload = [0i32; 2]; + unsafe { + let event0 = wait(payload.as_mut_ptr()); + callback(state as *mut _ as _, event0, payload[0], payload[1]); + } + } +} + +/// Run the specified future to completion, returning the result. +/// +/// This uses `task.wait` to poll for progress on any in-progress calls to +/// async-lowered imports as necessary. +// TODO: refactor so `'static` bounds aren't necessary +pub fn block_on(future: impl Future + 'static) -> T { + let (tx, mut rx) = oneshot::channel(); + let state = &mut FutureState { + todo: 0, + tasks: Some( + [Box::pin(future.map(move |v| drop(tx.send(v)))) as BoxFuture] + .into_iter() + .collect(), + ), + }; + loop { + match unsafe { poll(state) } { + Poll::Ready(()) => break rx.try_recv().unwrap().unwrap(), + Poll::Pending => task_wait(state), + } + } +} + +fn task_poll(state: &mut FutureState) -> bool { + #[cfg(not(target_arch = "wasm32"))] + { + _ = state; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[task-poll]"] + fn poll(_: *mut i32) -> i32; + } + let mut payload = [0i32; 3]; + unsafe { + let got_event = poll(payload.as_mut_ptr()) != 0; + if got_event { + callback(state as *mut _ as _, payload[0], payload[1], payload[2]); + } + got_event + } + } +} + +/// Attempt to run the specified future to completion without blocking, +/// returning the result if it completes. +/// +/// This is similar to `block_on` except that it uses `task.poll` instead of +/// `task.wait` to check for progress on any in-progress calls to async-lowered +/// imports, returning `None` if one or more of those calls remain pending. +// TODO: refactor so `'static` bounds aren't necessary +pub fn poll_future(future: impl Future + 'static) -> Option { + let (tx, mut rx) = oneshot::channel(); + let state = &mut FutureState { + todo: 0, + tasks: Some( + [Box::pin(future.map(move |v| drop(tx.send(v)))) as BoxFuture] + .into_iter() + .collect(), + ), + }; + loop { + match unsafe { poll(state) } { + Poll::Ready(()) => break Some(rx.try_recv().unwrap().unwrap()), + Poll::Pending => { + if !task_poll(state) { + break None; + } + } + } + } +} + +/// Call the `task.yield` canonical built-in function. +/// +/// This yields control to the host temporarily, allowing other tasks to make +/// progress. It's a good idea to call this inside a busy loop which does not +/// otherwise ever yield control the the host. +pub fn task_yield() { + #[cfg(not(target_arch = "wasm32"))] + { + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[task-yield]"] + fn yield_(); + } + unsafe { + yield_(); + } + } +} + +/// Call the `task.backpressure` canonical built-in function. +/// +/// When `enabled` is `true`, this tells the host to defer any new calls to this +/// component instance until further notice (i.e. until `task.backpressure` is +/// called again with `enabled` set to `false`). +pub fn task_backpressure(enabled: bool) { + #[cfg(not(target_arch = "wasm32"))] + { + _ = enabled; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[task-backpressure]"] + fn backpressure(_: i32); + } + unsafe { + backpressure(if enabled { 1 } else { 0 }); + } + } +} diff --git a/crates/guest-rust/rt/src/lib.rs b/crates/guest-rust/rt/src/lib.rs index 406ed61dc..c9a63cf71 100644 --- a/crates/guest-rust/rt/src/lib.rs +++ b/crates/guest-rust/rt/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(feature = "async"), no_std)] extern crate alloc; @@ -112,3 +112,11 @@ pub fn run_ctors_once() { } } } + +/// Support for using the Component Model Async ABI +#[cfg(not(feature = "async"))] +pub mod async_support {} + +/// Support for using the Component Model Async ABI +#[cfg(feature = "async")] +pub mod async_support; diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index ae6380003..2ecd5421f 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -413,28 +413,14 @@ impl InterfaceGenerator<'_> { self.push_str("future"); } }, - TypeDefKind::Stream(s) => match (s.element, s.end) { - (Some(element), Some(end)) => { - self.push_str("stream<"); - self.print_ty(&element); - self.push_str(", "); - self.print_ty(&end); - self.push_str(">"); - } - (None, Some(end)) => { - self.push_str("stream<_, "); - self.print_ty(&end); - self.push_str(">"); - } - (Some(element), None) => { - self.push_str("stream<"); - self.print_ty(&element); - self.push_str(">"); - } - (None, None) => { - self.push_str("stream"); - } - }, + TypeDefKind::Stream(t) => { + self.push_str("stream<"); + self.print_ty(t); + self.push_str(">"); + } + TypeDefKind::ErrorContext => { + self.push_str("error-context"); + } TypeDefKind::Handle(Handle::Own(ty)) => { self.push_str("own<"); self.print_ty(&Type::Id(*ty)); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 0fb989372..6ddd7a288 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -779,6 +779,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -868,6 +869,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -927,7 +929,7 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); - abi::post_return(bindgen.gen.resolve, func, &mut bindgen); + abi::post_return(bindgen.gen.resolve, func, &mut bindgen, false); let src = bindgen.src; @@ -2579,6 +2581,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "{ffi_qualifier}free({address})"); } + + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + + Instruction::AsyncMalloc { .. } + | Instruction::AsyncPostCallInterface { .. } + | Instruction::AsyncCallReturn { .. } + | Instruction::FutureLower { .. } + | Instruction::FutureLift { .. } + | Instruction::StreamLower { .. } + | Instruction::StreamLift { .. } + | Instruction::ErrorContextLower { .. } + | Instruction::ErrorContextLift { .. } + | Instruction::AsyncCallWasm { .. } => todo!(), } } diff --git a/crates/rust/Cargo.toml b/crates/rust/Cargo.toml index 1fe1d3bc2..09a70bed8 100644 --- a/crates/rust/Cargo.toml +++ b/crates/rust/Cargo.toml @@ -32,3 +32,7 @@ test-helpers = { path = '../test-helpers' } # For use with the custom attributes test serde = { version = "1.0", features = ["derive"] } serde_json = "1" + +[features] +default = ["async"] +async = ["wit-bindgen-core/async"] diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 1e9dc9ff1..f8e77a5d8 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -8,6 +8,8 @@ use wit_bindgen_core::{dealias, uwrite, uwriteln, wit_parser::*, Source}; pub(super) struct FunctionBindgen<'a, 'b> { pub gen: &'b mut InterfaceGenerator<'a>, params: Vec, + async_: bool, + wasm_import_module: &'b str, pub src: Source, blocks: Vec, block_storage: Vec<(Source, Vec<(String, String)>)>, @@ -23,10 +25,14 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { pub(super) fn new( gen: &'b mut InterfaceGenerator<'a>, params: Vec, + async_: bool, + wasm_import_module: &'b str, ) -> FunctionBindgen<'a, 'b> { FunctionBindgen { gen, params, + async_, + wasm_import_module, src: Default::default(), blocks: Vec::new(), block_storage: Vec::new(), @@ -58,14 +64,9 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } } - fn declare_import( - &mut self, - module_name: &str, - name: &str, - params: &[WasmType], - results: &[WasmType], - ) -> String { + fn declare_import(&mut self, name: &str, params: &[WasmType], results: &[WasmType]) -> String { // Define the actual function we're calling inline + let tmp = self.tmp(); let mut sig = "(".to_owned(); for param in params.iter() { sig.push_str("_: "); @@ -78,6 +79,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { sig.push_str(" -> "); sig.push_str(wasm_type(*result)); } + let module_name = self.wasm_import_module; uwrite!( self.src, " @@ -85,14 +87,14 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { #[link(wasm_import_module = \"{module_name}\")] extern \"C\" {{ #[link_name = \"{name}\"] - fn wit_import{sig}; + fn wit_import{tmp}{sig}; }} #[cfg(not(target_arch = \"wasm32\"))] - fn wit_import{sig} {{ unreachable!() }} + extern \"C\" fn wit_import{tmp}{sig} {{ unreachable!() }} " ); - "wit_import".to_string() + format!("wit_import{tmp}") } fn let_results(&mut self, amt: usize, results: &mut Vec) { @@ -456,6 +458,45 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(result); } + Instruction::FutureLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).into_handle() as i32")) + } + + Instruction::FutureLift { .. } => { + let stream_and_future_support = self.gen.path_to_stream_and_future_support(); + let op = &operands[0]; + results.push(format!( + "{stream_and_future_support}::FutureReader::from_handle({op} as u32)" + )) + } + + Instruction::StreamLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).into_handle() as i32")) + } + + Instruction::StreamLift { .. } => { + let stream_and_future_support = self.gen.path_to_stream_and_future_support(); + let op = &operands[0]; + results.push(format!( + "{stream_and_future_support}::StreamReader::from_handle({op} as u32)" + )) + } + + Instruction::ErrorContextLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).handle() as i32")) + } + + Instruction::ErrorContextLift { .. } => { + let async_support = self.gen.path_to_async_support(); + let op = &operands[0]; + results.push(format!( + "{async_support}::ErrorContext::from_handle({op} as u32)" + )) + } + Instruction::RecordLower { ty, record, .. } => { self.record_lower(*ty, record, &operands[0], results); } @@ -779,12 +820,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::IterBasePointer => results.push("base".to_string()), Instruction::CallWasm { name, sig, .. } => { - let func = self.declare_import( - self.gen.wasm_import_module.unwrap(), - name, - &sig.params, - &sig.results, - ); + let func = self.declare_import(name, &sig.params, &sig.results); // ... then call the function with all our operands if !sig.results.is_empty() { @@ -797,8 +833,32 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(");\n"); } + Instruction::AsyncCallWasm { name, size, align } => { + let func = self.declare_import(name, &[WasmType::Pointer; 2], &[WasmType::I32]); + + let async_support = self.gen.path_to_async_support(); + let tmp = self.tmp(); + let layout = format!("layout{tmp}"); + let alloc = self.gen.path_to_std_alloc_module(); + self.push_str(&format!( + "let {layout} = {alloc}::Layout::from_size_align_unchecked({size}, {align});\n", + )); + let operands = operands.join(", "); + uwriteln!( + self.src, + "{async_support}::await_result({func}, {layout}, {operands}).await;" + ); + } + Instruction::CallInterface { func, .. } => { - self.let_results(func.results.len(), results); + if self.async_ { + let tmp = self.tmp(); + let result = format!("result{tmp}"); + self.push_str(&format!("let {result} = ")); + results.push(result); + } else { + self.let_results(func.results.len(), results); + }; match &func.kind { FunctionKind::Freestanding => { self.push_str(&format!("T::{}", to_rust_ident(&func.name))); @@ -839,6 +899,77 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(";\n"); } + Instruction::AsyncMalloc { size, align } => { + let alloc = self.gen.path_to_std_alloc_module(); + let tmp = self.tmp(); + let ptr = format!("ptr{tmp}"); + let layout = format!("layout{tmp}"); + uwriteln!( + self.src, + "let {layout} = {alloc}::Layout::from_size_align_unchecked({size}, {align}); + let {ptr} = {alloc}::alloc({layout});" + ); + results.push(ptr); + } + + Instruction::AsyncPostCallInterface { func } => { + let result = &operands[0]; + results.push("result".into()); + let params = (0..func.results.len()) + .map(|_| { + let tmp = self.tmp(); + let param = format!("result{}", tmp); + results.push(param.clone()); + param + }) + .collect::>() + .join(", "); + let params = if func.results.len() != 1 { + format!("({params})") + } else { + params + }; + let async_support = self.gen.path_to_async_support(); + // TODO: This relies on `abi::Generator` emitting + // `AsyncCallReturn` immediately after this instruction to + // complete the incomplete expression we generate here. We + // should refactor this so it's less fragile (e.g. have + // `abi::Generator` emit a `AsyncCallReturn` first, which would + // push a closure expression we can consume here). + // + // The async-specific `Instruction`s will probably need to be + // refactored anyway once we start implementing support for + // other languages besides Rust. + uwriteln!( + self.src, + "\ + let result = {async_support}::first_poll({result}, |{params}| {{ + " + ); + } + + Instruction::AsyncCallReturn { name, params } => { + let func = self.declare_import(name, params, &[]); + + uwriteln!( + self.src, + "\ + {func}({}); + }}); + ", + operands.join(", ") + ); + } + + Instruction::Flush { amt } => { + for i in 0..*amt { + let tmp = self.tmp(); + let result = format!("result{}", tmp); + uwriteln!(self.src, "let {result} = {};", operands[i]); + results.push(result); + } + } + Instruction::Return { amt, .. } => { self.emit_cleanup(); match amt { @@ -868,7 +999,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let tmp = self.tmp(); uwriteln!( self.src, - "let l{tmp} = i32::from(*{}.add({offset}).cast::());", + "let l{tmp} = i32::from(*{0}.add({offset}).cast::());", operands[0] ); results.push(format!("l{tmp}")); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 8a7468960..a96795e6f 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1,7 +1,7 @@ use crate::bindgen::FunctionBindgen; use crate::{ - int_repr, to_rust_ident, to_upper_camel_case, wasm_type, FnSig, Identifier, InterfaceName, - Ownership, RuntimeItem, RustFlagsRepr, RustWasm, + int_repr, to_rust_ident, to_upper_camel_case, wasm_type, AsyncConfig, FnSig, Identifier, + InterfaceName, Ownership, RuntimeItem, RustFlagsRepr, RustWasm, }; use anyhow::Result; use heck::*; @@ -19,7 +19,7 @@ pub struct InterfaceGenerator<'a> { pub in_import: bool, pub sizes: SizeAlign, pub(super) gen: &'a mut RustWasm, - pub wasm_import_module: Option<&'a str>, + pub wasm_import_module: &'a str, pub resolve: &'a Resolve, pub return_pointer_area_size: usize, pub return_pointer_area_align: usize, @@ -156,6 +156,17 @@ impl InterfaceGenerator<'_> { continue; } + let async_ = match &self.gen.opts.async_ { + AsyncConfig::None => false, + AsyncConfig::All => true, + AsyncConfig::Some { exports, .. } => { + exports.contains(&if let Some((_, key)) = interface { + format!("{}#{}", self.resolve.name_world_key(key), func.name) + } else { + func.name.clone() + }) + } + }; let resource = match func.kind { FunctionKind::Freestanding => None, FunctionKind::Method(id) @@ -163,12 +174,13 @@ impl InterfaceGenerator<'_> { | FunctionKind::Static(id) => Some(id), }; - funcs_to_export.push((func, resource)); + funcs_to_export.push((func, resource, async_)); let (trait_name, methods) = traits.get_mut(&resource).unwrap(); - self.generate_guest_export(func, &trait_name); + self.generate_guest_export(func, interface.map(|(_, k)| k), &trait_name, async_); let prev = mem::take(&mut self.src); let mut sig = FnSig { + async_, use_item_name: true, private: true, ..Default::default() @@ -177,7 +189,7 @@ impl InterfaceGenerator<'_> { sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig); + self.print_signature(func, true, &sig, false); self.src.push_str(";\n"); let trait_method = mem::replace(&mut self.src, prev); methods.push(trait_method); @@ -188,9 +200,9 @@ impl InterfaceGenerator<'_> { self.generate_interface_trait( &name, &methods, - traits.iter().map(|(resource, (trait_name, _methods))| { - (resource.unwrap(), trait_name.as_str()) - }), + traits + .iter() + .map(|(resource, (trait_name, ..))| (resource.unwrap(), trait_name.as_str())), ) } @@ -259,7 +271,7 @@ fn _resource_rep(handle: u32) -> *mut u8 None => { let world = match self.identifier { Identifier::World(w) => w, - Identifier::Interface(..) => unreachable!(), + Identifier::None | Identifier::Interface(..) => unreachable!(), }; let world = self.resolve.worlds[world].name.to_snake_case(); format!("__export_world_{world}_cabi") @@ -292,7 +304,7 @@ macro_rules! {macro_name} {{ " ); - for (func, resource) in funcs_to_export { + for (func, resource, async_) in funcs_to_export { let ty = match resource { None => "$ty".to_string(), Some(id) => { @@ -304,13 +316,13 @@ macro_rules! {macro_name} {{ format!("<$ty as $($path_to_types)*::Guest>::{name}") } }; - self.generate_raw_cabi_export(func, &ty, "$($path_to_types)*"); + self.generate_raw_cabi_export(func, &ty, "$($path_to_types)*", async_); } let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); for name in resources_to_drop { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), - Identifier::World(_) => unreachable!(), + Identifier::None | Identifier::World(_) => unreachable!(), }; let camel = name.to_upper_camel_case(); uwriteln!( @@ -357,9 +369,13 @@ macro_rules! {macro_name} {{ uwriteln!(self.src, "}}"); } - pub fn generate_imports<'a>(&mut self, funcs: impl Iterator) { + pub fn generate_imports<'a>( + &mut self, + funcs: impl Iterator, + interface: Option<&WorldKey>, + ) { for func in funcs { - self.generate_guest_import(func); + self.generate_guest_import(func, interface); } } @@ -454,12 +470,387 @@ macro_rules! {macro_name} {{ map.push((module, module_path)) } - fn generate_guest_import(&mut self, func: &Function) { + fn generate_payloads(&mut self, prefix: &str, func: &Function, interface: Option<&WorldKey>) { + for (index, ty) in func + .find_futures_and_streams(self.resolve) + .into_iter() + .enumerate() + { + let module = format!( + "{prefix}{}", + interface + .map(|name| self.resolve.name_world_key(name)) + .unwrap_or_else(|| "$root".into()) + ); + let func_name = &func.name; + let type_mode = TypeMode { + lifetime: None, + lists_borrowed: false, + style: TypeOwnershipStyle::Owned, + }; + let stream_and_future_support = self.path_to_stream_and_future_support(); + let async_support = self.path_to_async_support(); + + match &self.resolve.types[ty].kind { + TypeDefKind::Future(payload_type) => { + let (name, full_name) = if let Some(payload_type) = payload_type { + ( + { + let old = mem::take(&mut self.src); + self.print_ty(&payload_type, type_mode); + String::from(mem::replace(&mut self.src, old)) + }, + { + let old = mem::take(&mut self.src); + let old_identifier = + mem::replace(&mut self.identifier, Identifier::None); + self.print_ty(&payload_type, type_mode); + self.identifier = old_identifier; + String::from(mem::replace(&mut self.src, old)) + }, + ) + } else { + ("()".into(), "()".into()) + }; + + if self.gen.future_payloads_emitted.insert(full_name) { + let (size, align) = if let Some(payload_type) = payload_type { + ( + self.sizes.size(payload_type), + self.sizes.align(payload_type), + ) + } else { + ( + ArchitectureSize { + bytes: 0, + pointers: 0, + }, + Alignment::default(), + ) + }; + let size = size.size_wasm32(); + let align = align.align_wasm32(); + let (lower, lift) = if let Some(payload_type) = payload_type { + let lower = + self.lower_to_memory("address", "value", &payload_type, &module); + let lift = + self.lift_from_memory("address", "value", &payload_type, &module); + (lower, lift) + } else { + (String::new(), "let value = ();\n".into()) + }; + + uwriteln!( + self.src, + r#" +impl {stream_and_future_support}::FuturePayload for {name} {{ + fn new() -> u32 {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-new-{index}]{func_name}"] + fn new() -> u32; + }} + unsafe {{ new() }} + }} + }} + + async fn write(future: u32, value: Self) -> bool {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[repr(align({align}))] + struct Buffer([::core::mem::MaybeUninit::; {size}]); + let mut buffer = Buffer([::core::mem::MaybeUninit::uninit(); {size}]); + let address = buffer.0.as_mut_ptr() as *mut u8; + {lower} + + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[async][future-write-{index}]{func_name}"] + fn wit_import(_: u32, _: *mut u8) -> u32; + }} + + unsafe {{ {async_support}::await_future_result(wit_import, future, address).await }} + }} + }} + + async fn read(future: u32) -> Option {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + struct Buffer([::core::mem::MaybeUninit::; {size}]); + let mut buffer = Buffer([::core::mem::MaybeUninit::uninit(); {size}]); + let address = buffer.0.as_mut_ptr() as *mut u8; + + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[async][future-read-{index}]{func_name}"] + fn wit_import(_: u32, _: *mut u8) -> u32; + }} + + if unsafe {{ {async_support}::await_future_result(wit_import, future, address).await }} {{ + {lift} + Some(value) + }} else {{ + None + }} + }} + }} + + fn close_writable(writer: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-close-writable-{index}]{func_name}"] + fn drop(_: u32, _: u32); + }} + unsafe {{ drop(writer, 0) }} + }} + }} + + fn close_readable(reader: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-close-readable-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(reader) }} + }} + }} +}} + "#, + ); + } + } + TypeDefKind::Stream(payload_type) => { + let name = { + let old = mem::take(&mut self.src); + self.print_ty(&payload_type, type_mode); + String::from(mem::replace(&mut self.src, old)) + }; + + let full_name = { + let old = mem::take(&mut self.src); + let old_identifier = mem::replace(&mut self.identifier, Identifier::None); + self.print_ty(&payload_type, type_mode); + self.identifier = old_identifier; + String::from(mem::replace(&mut self.src, old)) + }; + + if self.gen.stream_payloads_emitted.insert(full_name) { + let size = self.sizes.size(payload_type).size_wasm32(); + let align = self.sizes.align(payload_type).align_wasm32(); + let alloc = self.path_to_std_alloc_module(); + let (lower_address, lower, lift_address, lift) = + if stream_direct(payload_type) { + let lower_address = "let address = values.as_ptr() as _;".into(); + let lift_address = "let address = values.as_mut_ptr() as _;".into(); + ( + lower_address, + String::new(), + lift_address, + "let value = ();\n".into(), + ) + } else { + let address = format!( + "let address = {alloc}::alloc\ + ({alloc}::Layout::from_size_align_unchecked\ + ({size} * values.len(), {align}));" + ); + let lower = self.lower_to_memory( + "address", + "value", + &payload_type, + &module, + ); + let lower = format!( + r#" +for (index, value) in values.iter().enumerate() {{ + let address = address + (index * size); + {lower} +}} + "# + ); + let lift = self.lift_from_memory( + "address", + "value", + &payload_type, + &module, + ); + let lift = format!( + r#" +for (index, dst) in values.iter_mut().enumerate() {{ + let address = address + (index * size); + {lift} + *dst = value; +}} + "# + ); + (address.clone(), lower, address, lift) + }; + + uwriteln!( + self.src, + r#" +impl {stream_and_future_support}::StreamPayload for {name} {{ + fn new() -> u32 {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-new-{index}]{func_name}"] + fn new() -> u32; + }} + unsafe {{ new() }} + }} + }} + + async fn write(stream: u32, values: &[Self]) -> Option {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + {lower_address} + {lower} + + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[async][stream-write-{index}]{func_name}"] + fn wit_import(_: u32, _: *mut u8, _: u32) -> u32; + }} + + unsafe {{ + {async_support}::await_stream_result(wit_import, stream, address, u32::try_from(values.len()).unwrap()).await + }} + }} + }} + + async fn read(stream: u32, values: &mut [::core::mem::MaybeUninit::]) -> Option {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + {lift_address} + + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[async][stream-read-{index}]{func_name}"] + fn wit_import(_: u32, _: *mut u8, _: u32) -> u32; + }} + + let count = unsafe {{ + {async_support}::await_stream_result(wit_import, stream, address, u32::try_from(values.len()).unwrap()).await + }}; + #[allow(unused)] + if let Some(count) = count {{ + {lift} + }} + count + }} + }} + + fn close_writable(writer: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-close-writable-{index}]{func_name}"] + fn drop(_: u32, _: u32); + }} + unsafe {{ drop(writer, 0) }} + }} + }} + + fn close_readable(reader: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-close-readable-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(reader) }} + }} + }} +}} + "# + ); + } + } + _ => unreachable!(), + } + } + } + + fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { if self.gen.skip.contains(&func.name) { return; } - let mut sig = FnSig::default(); + self.generate_payloads("[import-payload]", func, interface); + + let async_ = match &self.gen.opts.async_ { + AsyncConfig::None => false, + AsyncConfig::All => true, + AsyncConfig::Some { imports, .. } => imports.contains(&if let Some(key) = interface { + format!("{}#{}", self.resolve.name_world_key(key), func.name) + } else { + func.name.clone() + }), + }; + let mut sig = FnSig { + async_, + ..Default::default() + }; match func.kind { FunctionKind::Freestanding => {} FunctionKind::Method(id) | FunctionKind::Static(id) | FunctionKind::Constructor(id) => { @@ -474,17 +865,53 @@ macro_rules! {macro_name} {{ } } self.src.push_str("#[allow(unused_unsafe, clippy::all)]\n"); - let params = self.print_signature(func, false, &sig); + let params = self.print_signature(func, false, &sig, true); self.src.push_str("{\n"); self.src.push_str("unsafe {\n"); - let mut f = FunctionBindgen::new(self, params); + self.generate_guest_import_body(&self.wasm_import_module, func, params, async_); + + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + match func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Method(_) | FunctionKind::Static(_) | FunctionKind::Constructor(_) => { + self.src.push_str("}\n"); + } + } + } + + fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { + let mut f = FunctionBindgen::new(self, Vec::new(), true, module); + abi::lower_to_memory(f.gen.resolve, &mut f, address.into(), value.into(), ty); + format!("unsafe {{ {} }}", String::from(f.src)) + } + + fn lift_from_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { + let mut f = FunctionBindgen::new(self, Vec::new(), true, module); + let result = abi::lift_from_memory(f.gen.resolve, &mut f, address.into(), ty); + format!( + "let {value} = unsafe {{ {}\n{result} }};", + String::from(f.src) + ) + } + + fn generate_guest_import_body( + &mut self, + module: &str, + func: &Function, + params: Vec, + async_: bool, + ) { + let mut f = FunctionBindgen::new(self, params, async_, module); abi::call( f.gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, &mut f, + async_, ); let FunctionBindgen { needs_cleanup_list, @@ -511,29 +938,28 @@ macro_rules! {macro_name} {{ ); } self.src.push_str(&String::from(src)); - - self.src.push_str("}\n"); - self.src.push_str("}\n"); - - match func.kind { - FunctionKind::Freestanding => {} - FunctionKind::Method(_) | FunctionKind::Static(_) | FunctionKind::Constructor(_) => { - self.src.push_str("}\n"); - } - } } - fn generate_guest_export(&mut self, func: &Function, trait_name: &str) { + fn generate_guest_export( + &mut self, + func: &Function, + interface: Option<&WorldKey>, + trait_name: &str, + async_: bool, + ) { let name_snake = func.name.to_snake_case().replace('.', "_"); + + self.generate_payloads("[export-payload]", func, interface); + uwrite!( self.src, "\ #[doc(hidden)] #[allow(non_snake_case)] pub unsafe fn _export_{name_snake}_cabi\ -", + ", ); - let params = self.print_export_sig(func); + let params = self.print_export_sig(func, async_); self.push_str(" {"); if !self.gen.opts.disable_run_ctors_once_workaround { @@ -552,13 +978,14 @@ macro_rules! {macro_name} {{ ); } - let mut f = FunctionBindgen::new(self, params); + let mut f = FunctionBindgen::new(self, params, async_, self.wasm_import_module); abi::call( f.gen.resolve, AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, func, &mut f, + async_, ); let FunctionBindgen { needs_cleanup_list, @@ -574,20 +1001,32 @@ macro_rules! {macro_name} {{ self.src.push_str(&String::from(src)); self.src.push_str("}\n"); - if abi::guest_export_needs_post_return(self.resolve, func) { + if async_ { + let async_support = self.path_to_async_support(); + uwrite!( + self.src, + "\ + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __callback_{name_snake}(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 {{ + {async_support}::callback(ctx, event0, event1, event2) + }} + " + ); + } else if abi::guest_export_needs_post_return(self.resolve, func) { uwrite!( self.src, "\ #[doc(hidden)] #[allow(non_snake_case)] pub unsafe fn __post_return_{name_snake}\ -" + " ); let params = self.print_post_return_sig(func); self.src.push_str("{\n"); - let mut f = FunctionBindgen::new(self, params); - abi::post_return(f.gen.resolve, func, &mut f); + let mut f = FunctionBindgen::new(self, params, async_, self.wasm_import_module); + abi::post_return(f.gen.resolve, func, &mut f, async_); let FunctionBindgen { needs_cleanup_list, src, @@ -601,14 +1040,26 @@ macro_rules! {macro_name} {{ } } - fn generate_raw_cabi_export(&mut self, func: &Function, ty: &str, path_to_self: &str) { + fn generate_raw_cabi_export( + &mut self, + func: &Function, + ty: &str, + path_to_self: &str, + async_: bool, + ) { let name_snake = func.name.to_snake_case().replace('.', "_"); let wasm_module_export_name = match self.identifier { Identifier::Interface(_, key) => Some(self.resolve.name_world_key(key)), Identifier::World(_) => None, + Identifier::None => unreachable!(), }; let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); let export_name = func.legacy_core_export_name(wasm_module_export_name.as_deref()); + let export_name = if async_ { + format!("[async]{export_name}") + } else { + export_name.to_string() + }; uwrite!( self.src, "\ @@ -617,7 +1068,7 @@ macro_rules! {macro_name} {{ ", ); - let params = self.print_export_sig(func); + let params = self.print_export_sig(func, async_); self.push_str(" {\n"); uwriteln!( self.src, @@ -626,8 +1077,18 @@ macro_rules! {macro_name} {{ ); self.push_str("}\n"); - if abi::guest_export_needs_post_return(self.resolve, func) { - let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + if async_ { + uwrite!( + self.src, + "\ + #[export_name = \"{export_prefix}[callback]{export_name}\"] + unsafe extern \"C\" fn _callback_{name_snake}(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 {{ + {path_to_self}::__callback_{name_snake}(ctx, event0, event1, event2) + }} + " + ); + } else if abi::guest_export_needs_post_return(self.resolve, func) { uwrite!( self.src, "\ @@ -646,7 +1107,7 @@ macro_rules! {macro_name} {{ } } - fn print_export_sig(&mut self, func: &Function) -> Vec { + fn print_export_sig(&mut self, func: &Function, async_: bool) -> Vec { self.src.push_str("("); let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func); let mut params = Vec::new(); @@ -657,13 +1118,18 @@ macro_rules! {macro_name} {{ } self.src.push_str(")"); - match sig.results.len() { - 0 => {} - 1 => { - uwrite!(self.src, " -> {}", wasm_type(sig.results[0])); + if async_ { + self.push_str(" -> *mut u8"); + } else { + match sig.results.len() { + 0 => {} + 1 => { + uwrite!(self.src, " -> {}", wasm_type(sig.results[0])); + } + _ => unimplemented!(), } - _ => unimplemented!(), } + params } @@ -740,7 +1206,7 @@ macro_rules! {macro_name} {{ sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig); + self.print_signature(func, true, &sig, true); self.src.push_str("{ unreachable!() }\n"); } @@ -798,12 +1264,18 @@ macro_rules! {macro_name} {{ // } } - fn print_signature(&mut self, func: &Function, params_owned: bool, sig: &FnSig) -> Vec { - let params = self.print_docs_and_params(func, params_owned, sig); + fn print_signature( + &mut self, + func: &Function, + params_owned: bool, + sig: &FnSig, + use_async_sugar: bool, + ) -> Vec { + let params = self.print_docs_and_params(func, params_owned, sig, use_async_sugar); if let FunctionKind::Constructor(_) = &func.kind { self.push_str(" -> Self") } else { - self.print_results(&func.results); + self.print_results(&func.results, sig.async_ && !use_async_sugar); } params } @@ -813,6 +1285,7 @@ macro_rules! {macro_name} {{ func: &Function, params_owned: bool, sig: &FnSig, + use_async_sugar: bool, ) -> Vec { self.rustdoc(&func.docs); self.rustdoc_params(&func.params, "Parameters"); @@ -825,7 +1298,7 @@ macro_rules! {macro_name} {{ if sig.unsafe_ { self.push_str("unsafe "); } - if sig.async_ { + if sig.async_ && use_async_sugar { self.push_str("async "); } self.push_str("fn "); @@ -915,18 +1388,24 @@ macro_rules! {macro_name} {{ params } - fn print_results(&mut self, results: &Results) { + fn print_results(&mut self, results: &Results, async_: bool) { + self.push_str(" -> "); + if async_ { + self.push_str("impl ::core::future::Future {} + 0 => { + self.push_str("()"); + } 1 => { - self.push_str(" -> "); let ty = results.iter_types().next().unwrap(); let mode = self.type_mode_for(ty, TypeOwnershipStyle::Owned, "'INVALID"); assert!(mode.lifetime.is_none()); self.print_ty(ty, mode); } _ => { - self.push_str(" -> ("); + self.push_str("("); for ty in results.iter_types() { let mode = self.type_mode_for(ty, TypeOwnershipStyle::Owned, "'INVALID"); assert!(mode.lifetime.is_none()); @@ -936,6 +1415,10 @@ macro_rules! {macro_name} {{ self.push_str(")") } } + + if async_ { + self.push_str("> + 'static"); + } } /// Calculates the `TypeMode` to be used for the `ty` specified. @@ -1863,6 +2346,17 @@ macro_rules! {macro_name} {{ self.path_from_runtime_module(RuntimeItem::StdAllocModule, "alloc") } + pub fn path_to_stream_and_future_support(&mut self) -> String { + self.path_from_runtime_module( + RuntimeItem::StreamAndFutureSupport, + "stream_and_future_support", + ) + } + + pub fn path_to_async_support(&mut self) -> String { + "::wit_bindgen_rt::async_support".into() + } + fn path_from_runtime_module( &mut self, item: RuntimeItem, @@ -1920,11 +2414,12 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { }} "# ); - self.wasm_import_module.unwrap().to_string() + self.wasm_import_module.to_string() } else { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), Identifier::World(_) => unimplemented!("resource exports from worlds"), + Identifier::None => unreachable!(), }; let box_path = self.path_to_box(); uwriteln!( @@ -2197,7 +2692,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< self.resolve } - fn anonymous_typ_type(&mut self, _id: TypeId, ty: &Type, _docs: &Docs) { + fn anonymous_type_type(&mut self, _id: TypeId, ty: &Type, _docs: &Docs) { self.interface.print_ty(ty, self.mode); } @@ -2265,18 +2760,42 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_future(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { - self.interface.push_str("Future<"); + let stream_and_future_support = self.interface.path_to_stream_and_future_support(); + self.interface + .push_str(&format!("{stream_and_future_support}::FutureReader<")); self.interface.print_optional_ty(ty.as_ref(), self.mode); self.interface.push_str(">"); } - fn anonymous_type_stream(&mut self, _id: TypeId, stream: &Stream, _docs: &Docs) { - self.interface.push_str("Stream<"); + fn anonymous_type_stream(&mut self, _id: TypeId, ty: &Type, _docs: &Docs) { + let stream_and_future_support = self.interface.path_to_stream_and_future_support(); self.interface - .print_optional_ty(stream.element.as_ref(), self.mode); - self.interface.push_str(","); - self.interface - .print_optional_ty(stream.end.as_ref(), self.mode); + .push_str(&format!("{stream_and_future_support}::StreamReader<")); + self.interface.print_ty(ty, self.mode); self.interface.push_str(">"); } + + fn anonymous_type_error_context(&mut self) { + let async_support = self.interface.path_to_async_support(); + self.interface + .push_str(&format!("{async_support}::ErrorContext")); + } +} + +fn stream_direct(ty: &Type) -> bool { + // TODO: might be able to return `true` for other types if the generated Rust versions of those types are + // guaranteed to be safely transmutable to and from their lowered form. + matches!( + ty, + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::F32 + | Type::F64 + ) } diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 6edb6be27..745e49361 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -44,8 +44,12 @@ struct RustWasm { rt_module: IndexSet, export_macros: Vec<(String, String)>, + /// Interface names to how they should be generated with: GenerationConfiguration, + + future_payloads_emitted: HashSet, + stream_payloads_emitted: HashSet, } #[derive(Default)] @@ -97,6 +101,7 @@ enum RuntimeItem { AsF64, ResourceType, BoxType, + StreamAndFutureSupport, } #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -117,6 +122,52 @@ fn parse_with(s: &str) -> Result<(String, WithOption), String> { Ok((k.to_string(), v)) } +#[derive(Default, Debug, Clone)] +pub enum AsyncConfig { + #[default] + None, + Some { + imports: Vec, + exports: Vec, + }, + All, +} + +#[cfg(feature = "clap")] +fn parse_async(s: &str) -> Result { + Ok(match s { + "none" => AsyncConfig::None, + "all" => AsyncConfig::All, + _ => { + if let Some(values) = s.strip_prefix("some=") { + let mut imports = Vec::new(); + let mut exports = Vec::new(); + for value in values.split(',') { + let error = || { + Err(format!( + "expected string of form `import:` or `export:`; got `{value}`" + )) + }; + if let Some((k, v)) = value.split_once(":") { + match k { + "import" => imports.push(v.into()), + "export" => exports.push(v.into()), + _ => return error(), + } + } else { + return error(); + } + } + AsyncConfig::Some { imports, exports } + } else { + return Err(format!( + "expected string of form `none`, `all`, or `some=[,...]`; got `{s}`" + )); + } + } + }) +} + #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Args))] pub struct Opts { @@ -234,6 +285,18 @@ pub struct Opts { /// library-based usage of `generate!` prone to breakage. #[cfg_attr(feature = "clap", arg(long))] pub disable_custom_section_link_helpers: bool, + + /// Determines which functions to lift or lower `async`, if any. + /// + /// Accepted values are: + /// - none + /// - all + /// - some=[,...], where each is of the form: + /// - import: or + /// - export: + #[cfg_attr(all(feature = "clap", feature = "async"), arg(long = "async", value_parser = parse_async))] + #[cfg_attr(all(feature = "clap", not(feature = "async")), skip)] + pub async_: AsyncConfig, } impl Opts { @@ -253,7 +316,7 @@ impl RustWasm { fn interface<'a>( &'a mut self, identifier: Identifier<'a>, - wasm_import_module: Option<&'a str>, + wasm_import_module: &'a str, resolve: &'a Resolve, in_import: bool, ) -> InterfaceGenerator<'a> { @@ -297,7 +360,7 @@ impl RustWasm { // Ignore dead-code warnings. If the bindings are only used // within a crate, and not exported to a different crate, some // parts may be unused, and that's ok. - uwriteln!(me, "#[allow(dead_code)]"); + uwriteln!(me, "#[allow(dead_code, clippy::all)]"); uwriteln!(me, "pub mod {name} {{"); emit(me, submodule); @@ -366,6 +429,7 @@ impl RustWasm { return; } self.src.push_str("mod _rt {\n"); + self.src.push_str("#![allow(dead_code, clippy::all)]\n"); let mut emitted = IndexSet::new(); while !self.rt_module.is_empty() { for item in mem::take(&mut self.rt_module) { @@ -375,6 +439,10 @@ impl RustWasm { } } self.src.push_str("}\n"); + if emitted.contains(&RuntimeItem::StreamAndFutureSupport) { + self.src + .push_str("pub use _rt::stream_and_future_support;\n"); + } } fn emit_runtime_item(&mut self, item: RuntimeItem) { @@ -607,6 +675,13 @@ impl Drop for Resource { "#, ); } + + RuntimeItem::StreamAndFutureSupport => { + self.src.push_str("pub mod stream_and_future_support {"); + self.src + .push_str(include_str!("stream_and_future_support.rs")); + self.src.push_str("}"); + } } } @@ -780,6 +855,7 @@ macro_rules! __export_{world_name}_impl {{ .unwrap(); self.src.push_str("#[doc(hidden)]\n"); + self.src.push_str("#[allow(clippy::octal_escapes)]\n"); self.src.push_str(&format!( "pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; {}] = *b\"\\\n", component_type.len() @@ -909,7 +985,7 @@ impl WorldGenerator for RustWasm { let wasm_import_module = resolve.name_world_key(name); let mut gen = self.interface( Identifier::Interface(id, name), - Some(&wasm_import_module), + &wasm_import_module, resolve, true, ); @@ -919,7 +995,7 @@ impl WorldGenerator for RustWasm { } gen.types(id); - gen.generate_imports(resolve.interfaces[id].functions.values()); + gen.generate_imports(resolve.interfaces[id].functions.values(), Some(name)); gen.finish_append_submodule(&snake, module_path); @@ -935,9 +1011,9 @@ impl WorldGenerator for RustWasm { ) { self.import_funcs_called = true; - let mut gen = self.interface(Identifier::World(world), Some("$root"), resolve, true); + let mut gen = self.interface(Identifier::World(world), "$root", resolve, true); - gen.generate_imports(funcs.iter().map(|(_, func)| *func)); + gen.generate_imports(funcs.iter().map(|(_, func)| *func), None); let src = gen.finish(); self.src.push_str(&src); @@ -951,7 +1027,13 @@ impl WorldGenerator for RustWasm { _files: &mut Files, ) -> Result<()> { self.interface_last_seen_as_import.insert(id, false); - let mut gen = self.interface(Identifier::Interface(id, name), None, resolve, false); + let wasm_import_module = format!("[export]{}", resolve.name_world_key(name)); + let mut gen = self.interface( + Identifier::Interface(id, name), + &wasm_import_module, + resolve, + false, + ); let (snake, module_path) = gen.start_append_submodule(name); if gen.gen.name_interface(resolve, id, name, true)? { return Ok(()); @@ -965,7 +1047,12 @@ impl WorldGenerator for RustWasm { if self.opts.stubs { let world_id = self.world.unwrap(); - let mut gen = self.interface(Identifier::World(world_id), None, resolve, false); + let mut gen = self.interface( + Identifier::World(world_id), + &wasm_import_module, + resolve, + false, + ); gen.generate_stub(Some((id, name)), resolve.interfaces[id].functions.values()); let stub = gen.finish(); self.src.push_str(&stub); @@ -980,14 +1067,14 @@ impl WorldGenerator for RustWasm { funcs: &[(&str, &Function)], _files: &mut Files, ) -> Result<()> { - let mut gen = self.interface(Identifier::World(world), None, resolve, false); + let mut gen = self.interface(Identifier::World(world), "[export]$root", resolve, false); let macro_name = gen.generate_exports(None, funcs.iter().map(|f| f.1))?; let src = gen.finish(); self.src.push_str(&src); self.export_macros.push((macro_name, String::new())); if self.opts.stubs { - let mut gen = self.interface(Identifier::World(world), None, resolve, false); + let mut gen = self.interface(Identifier::World(world), "[export]$root", resolve, false); gen.generate_stub(None, funcs.iter().map(|f| f.1)); let stub = gen.finish(); self.src.push_str(&stub); @@ -1002,7 +1089,7 @@ impl WorldGenerator for RustWasm { types: &[(&str, TypeId)], _files: &mut Files, ) { - let mut gen = self.interface(Identifier::World(world), Some("$root"), resolve, true); + let mut gen = self.interface(Identifier::World(world), "$root", resolve, true); for (name, ty) in types { gen.define_type(name, *ty); } @@ -1147,6 +1234,7 @@ fn compute_module_path(name: &WorldKey, resolve: &Resolve, is_export: bool) -> V } enum Identifier<'a> { + None, World(WorldId), Interface(InterfaceId, &'a WorldKey), } diff --git a/crates/rust/src/stream_and_future_support.rs b/crates/rust/src/stream_and_future_support.rs new file mode 100644 index 000000000..7a61bb5e1 --- /dev/null +++ b/crates/rust/src/stream_and_future_support.rs @@ -0,0 +1,513 @@ +use { + futures::{ + channel::oneshot, + future::{self, FutureExt}, + sink::Sink, + stream::Stream, + }, + std::{ + collections::hash_map::Entry, + convert::Infallible, + future::{Future, IntoFuture}, + iter, + marker::PhantomData, + mem::{self, ManuallyDrop, MaybeUninit}, + pin::Pin, + task::{Context, Poll}, + }, + wit_bindgen_rt::async_support::{self, Handle}, +}; + +#[doc(hidden)] +pub trait FuturePayload: Sized + 'static { + fn new() -> u32; + async fn write(future: u32, value: Self) -> bool; + async fn read(future: u32) -> Option; + fn close_writable(future: u32); + fn close_readable(future: u32); +} + +/// Represents the writable end of a Component Model `future`. +pub struct FutureWriter { + handle: u32, + _phantom: PhantomData, +} + +impl FutureWriter { + /// Write the specified value to this `future`. + pub async fn write(self, v: T) { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + let mut v = Some(v); + Box::pin(future::poll_fn(move |cx| { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + entry.insert(Handle::LocalReady( + Box::new(v.take().unwrap()), + cx.waker().clone(), + )); + Poll::Pending + } + Handle::LocalReady(..) => Poll::Pending, + Handle::LocalClosed => Poll::Ready(()), + Handle::LocalWaiting(_) | Handle::Read | Handle::Write => { + unreachable!() + } + }, + }) + })) as Pin>> + } + Handle::LocalWaiting(_) => { + let Handle::LocalWaiting(tx) = entry.insert(Handle::LocalClosed) else { + unreachable!() + }; + _ = tx.send(Box::new(v)); + Box::pin(future::ready(())) + } + Handle::LocalClosed => Box::pin(future::ready(())), + Handle::Read | Handle::LocalReady(..) => unreachable!(), + Handle::Write => Box::pin(T::write(self.handle, v).map(drop)), + }, + }) + .await; + } +} + +impl Drop for FutureWriter { + fn drop(&mut self) { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get_mut() { + Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { + entry.insert(Handle::LocalClosed); + } + Handle::Read => unreachable!(), + Handle::Write | Handle::LocalClosed => { + entry.remove(); + T::close_writable(self.handle); + } + }, + }); + } +} + +/// Represents the readable end of a Component Model `future`. +pub struct FutureReader { + handle: u32, + _phantom: PhantomData, +} + +impl FutureReader { + #[doc(hidden)] + pub fn from_handle(handle: u32) -> Self { + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(entry) => { + entry.insert(Handle::Read); + } + Entry::Occupied(mut entry) => match entry.get() { + Handle::Write => { + entry.insert(Handle::LocalOpen); + } + Handle::Read + | Handle::LocalOpen + | Handle::LocalReady(..) + | Handle::LocalWaiting(_) + | Handle::LocalClosed => { + unreachable!() + } + }, + }); + + Self { + handle, + _phantom: PhantomData, + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + entry.insert(Handle::Write); + } + Handle::Read | Handle::LocalClosed => { + entry.remove(); + } + Handle::LocalReady(..) | Handle::LocalWaiting(_) | Handle::Write => unreachable!(), + }, + }); + + ManuallyDrop::new(self).handle + } +} + +impl IntoFuture for FutureReader { + type Output = Option; + type IntoFuture = Pin + 'static>>; + + /// Convert this object into a `Future` which will resolve when a value is + /// written to the writable end of this `future` (yielding a `Some` result) + /// or when the writable end is dropped (yielding a `None` result). + fn into_future(self) -> Self::IntoFuture { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::Write | Handle::LocalWaiting(_) => unreachable!(), + Handle::Read => Box::pin(async move { T::read(self.handle).await }) + as Pin>>, + Handle::LocalOpen => { + let (tx, rx) = oneshot::channel(); + entry.insert(Handle::LocalWaiting(tx)); + Box::pin(async move { rx.await.ok().map(|v| *v.downcast().unwrap()) }) + } + Handle::LocalClosed => Box::pin(future::ready(None)), + Handle::LocalReady(..) => { + let Handle::LocalReady(v, waker) = entry.insert(Handle::LocalClosed) else { + unreachable!() + }; + waker.wake(); + Box::pin(future::ready(Some(*v.downcast().unwrap()))) + } + }, + }) + } +} + +impl Drop for FutureReader { + fn drop(&mut self) { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get_mut() { + Handle::LocalReady(..) => { + let Handle::LocalReady(_, waker) = entry.insert(Handle::LocalClosed) else { + unreachable!() + }; + waker.wake(); + } + Handle::LocalOpen | Handle::LocalWaiting(_) => { + entry.insert(Handle::LocalClosed); + } + Handle::Read | Handle::LocalClosed => { + entry.remove(); + T::close_readable(self.handle); + } + Handle::Write => unreachable!(), + }, + }); + } +} + +#[doc(hidden)] +pub trait StreamPayload: Unpin + Sized + 'static { + fn new() -> u32; + async fn write(stream: u32, values: &[Self]) -> Option; + async fn read(stream: u32, values: &mut [MaybeUninit]) -> Option; + fn close_writable(future: u32); + fn close_readable(future: u32); +} + +/// Represents the writable end of a Component Model `stream`. +pub struct StreamWriter { + handle: u32, + future: Option + 'static>>>, + _phantom: PhantomData, +} + +impl Sink> for StreamWriter { + type Error = Infallible; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + + if let Some(future) = &mut me.future { + match future.as_mut().poll(cx) { + Poll::Ready(_) => { + me.future = None; + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + } else { + Poll::Ready(Ok(())) + } + } + + fn start_send(self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { + assert!(self.future.is_none()); + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + let handle = self.handle; + let mut item = Some(item); + self.get_mut().future = Some(Box::pin(future::poll_fn(move |cx| { + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + if let Some(item) = item.take() { + entry.insert(Handle::LocalReady( + Box::new(item), + cx.waker().clone(), + )); + Poll::Pending + } else { + Poll::Ready(()) + } + } + Handle::LocalReady(..) => Poll::Pending, + Handle::LocalClosed => Poll::Ready(()), + Handle::LocalWaiting(_) | Handle::Read | Handle::Write => { + unreachable!() + } + }, + }) + }))); + } + Handle::LocalWaiting(_) => { + let Handle::LocalWaiting(tx) = entry.insert(Handle::LocalOpen) else { + unreachable!() + }; + _ = tx.send(Box::new(item)); + } + Handle::LocalClosed => (), + Handle::Read | Handle::LocalReady(..) => unreachable!(), + Handle::Write => { + let handle = self.handle; + self.get_mut().future = Some(Box::pin(async move { + let mut offset = 0; + while offset < item.len() { + if let Some(count) = T::write(handle, &item[offset..]).await { + offset += count; + } else { + break; + } + } + })); + } + }, + }); + Ok(()) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_ready(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_ready(cx) + } +} + +impl Drop for StreamWriter { + fn drop(&mut self) { + if self.future.is_some() { + todo!("gracefully handle `StreamWriter::drop` when a write is in progress by calling `stream.cancel-write`"); + } + + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get_mut() { + Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { + entry.insert(Handle::LocalClosed); + } + Handle::Read => unreachable!(), + Handle::Write | Handle::LocalClosed => { + entry.remove(); + T::close_writable(self.handle); + } + }, + }); + } +} + +/// Represents the readable end of a Component Model `stream`. +pub struct StreamReader { + handle: u32, + future: Option>> + 'static>>>, + _phantom: PhantomData, +} + +impl StreamReader { + #[doc(hidden)] + pub fn from_handle(handle: u32) -> Self { + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(entry) => { + entry.insert(Handle::Read); + } + Entry::Occupied(mut entry) => match entry.get() { + Handle::Write => { + entry.insert(Handle::LocalOpen); + } + Handle::Read + | Handle::LocalOpen + | Handle::LocalReady(..) + | Handle::LocalWaiting(_) + | Handle::LocalClosed => { + unreachable!() + } + }, + }); + + Self { + handle, + future: None, + _phantom: PhantomData, + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen => { + entry.insert(Handle::Write); + } + Handle::Read | Handle::LocalClosed => { + entry.remove(); + } + Handle::LocalReady(..) | Handle::LocalWaiting(_) | Handle::Write => unreachable!(), + }, + }); + + ManuallyDrop::new(self).handle + } +} + +impl Stream for StreamReader { + type Item = Vec; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + + if me.future.is_none() { + me.future = Some(async_support::with_entry(me.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::Write | Handle::LocalWaiting(_) => unreachable!(), + Handle::Read => { + let handle = me.handle; + Box::pin(async move { + let mut buffer = iter::repeat_with(MaybeUninit::uninit) + .take(ceiling(64 * 1024, mem::size_of::())) + .collect::>(); + + if let Some(count) = T::read(handle, &mut buffer).await { + buffer.truncate(count); + Some(unsafe { + mem::transmute::>, Vec>(buffer) + }) + } else { + None + } + }) as Pin>> + } + Handle::LocalOpen => { + let (tx, rx) = oneshot::channel(); + entry.insert(Handle::LocalWaiting(tx)); + Box::pin(rx.map(|v| v.ok().map(|v| *v.downcast().unwrap()))) + } + Handle::LocalClosed => Box::pin(future::ready(None)), + Handle::LocalReady(..) => { + let Handle::LocalReady(v, waker) = entry.insert(Handle::LocalOpen) else { + unreachable!() + }; + waker.wake(); + Box::pin(future::ready(Some(*v.downcast().unwrap()))) + } + }, + })); + } + + match me.future.as_mut().unwrap().as_mut().poll(cx) { + Poll::Ready(v) => { + me.future = None; + Poll::Ready(v) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl Drop for StreamReader { + fn drop(&mut self) { + if self.future.is_some() { + todo!("gracefully handle `StreamReader::drop` when a read is in progress by calling `stream.cancel-read`"); + } + + async_support::with_entry(self.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get_mut() { + Handle::LocalReady(..) => { + let Handle::LocalReady(_, waker) = entry.insert(Handle::LocalClosed) else { + unreachable!() + }; + waker.wake(); + } + Handle::LocalOpen | Handle::LocalWaiting(_) => { + entry.insert(Handle::LocalClosed); + } + Handle::Read | Handle::LocalClosed => { + entry.remove(); + T::close_readable(self.handle); + } + Handle::Write => unreachable!(), + }, + }); + } +} + +/// Creates a new Component Model `future` with the specified payload type. +pub fn new_future() -> (FutureWriter, FutureReader) { + let handle = T::new(); + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(entry) => { + entry.insert(Handle::LocalOpen); + } + Entry::Occupied(_) => unreachable!(), + }); + ( + FutureWriter { + handle, + _phantom: PhantomData, + }, + FutureReader { + handle, + _phantom: PhantomData, + }, + ) +} + +/// Creates a new Component Model `stream` with the specified payload type. +pub fn new_stream() -> (StreamWriter, StreamReader) { + let handle = T::new(); + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(entry) => { + entry.insert(Handle::LocalOpen); + } + Entry::Occupied(_) => unreachable!(), + }); + ( + StreamWriter { + handle, + future: None, + _phantom: PhantomData, + }, + StreamReader { + handle, + future: None, + _phantom: PhantomData, + }, + ) +} + +fn ceiling(x: usize, y: usize) -> usize { + (x / y) + if x % y == 0 { 0 } else { 1 } +} diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 1d0c90593..0a9689e90 100644 --- a/crates/teavm-java/src/lib.rs +++ b/crates/teavm-java/src/lib.rs @@ -501,6 +501,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -570,6 +571,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -623,7 +625,7 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); - abi::post_return(bindgen.gen.resolve, func, &mut bindgen); + abi::post_return(bindgen.gen.resolve, func, &mut bindgen, false); let src = bindgen.src; @@ -2028,6 +2030,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { "Memory.free(org.teavm.interop.Address.fromInt({address}), ({length}) * {size}, {align});" ); } + + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + + Instruction::AsyncMalloc { .. } + | Instruction::AsyncPostCallInterface { .. } + | Instruction::AsyncCallReturn { .. } + | Instruction::FutureLower { .. } + | Instruction::FutureLift { .. } + | Instruction::StreamLower { .. } + | Instruction::StreamLift { .. } + | Instruction::ErrorContextLower { .. } + | Instruction::ErrorContextLift { .. } + | Instruction::AsyncCallWasm { .. } => todo!(), } } diff --git a/crates/test-helpers/src/lib.rs b/crates/test-helpers/src/lib.rs index 1129238bb..1af51bcc9 100644 --- a/crates/test-helpers/src/lib.rs +++ b/crates/test-helpers/src/lib.rs @@ -32,9 +32,10 @@ pub fn test_directory(suite_name: &str, gen_name: &str, wit_name: &str) -> PathB /// Helper function to execute a process during tests and print informative /// information if it fails. pub fn run_command(cmd: &mut Command) { + let command = format!("{cmd:?}"); let output = cmd .output() - .expect("failed to run executable; is it installed"); + .unwrap_or_else(|e| panic!("failed to run executable: {e}; command was `{command}`")); if output.status.success() { return; diff --git a/tests/runtime/flavorful/wasm.rs b/tests/runtime/flavorful/wasm.rs index 058eb2f74..82454cda9 100644 --- a/tests/runtime/flavorful/wasm.rs +++ b/tests/runtime/flavorful/wasm.rs @@ -44,7 +44,6 @@ impl Guest for Component { assert!(errno_result().is_err()); MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); fn assert_error() {} assert_error::(); @@ -107,7 +106,6 @@ impl exports::test::flavorful::test::Guest for Component { fn errno_result() -> Result<(), MyErrno> { MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); fn assert_error() {} assert_error::(); Err(MyErrno::B) diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 0b3ae64f3..53bd38d7c 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -229,10 +229,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { if compiler.ends_with("++") { cmd.arg("-Wno-deprecated"); } - println!("{:?}", cmd); + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -301,10 +301,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { cmd.arg(&out_wasm); cmd.arg(format!("{snake}.go")); cmd.current_dir(&out_dir); - + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -571,10 +571,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { .arg("--self-contained") .arg("-o") .arg(&out_wasm); - + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -735,9 +735,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { .arg("/p:UseAppHost=false") .arg("-o") .arg(&out_wasm); + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() {