From a3100f7c960dae5c2d90b5b4adb02a472fc3a01d Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 22 Mar 2024 17:33:31 -0600 Subject: [PATCH 1/5] 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 add `async: true` case to Rust `codegen_tests` This ensures that all the codegen test WIT files produce compile-able bindings with `async: true` (i.e. all imports lowered and all exports lifted using the async ABI). That revealed some issues involving resource methods and constructors, as well as missing stub support, which I've resolved. Signed-off-by: Joel Dice add codegen tests for futures, streams, and error-contexts Signed-off-by: Joel Dice remove async_support::poll_future It was both unsafe to use and intended only for testing (and not even good for that, it turns out). Signed-off-by: Joel Dice add stream/future read/write cancellation support Also, fix some issues with stream/future payload lifting/lowering which I _thought_ I had already tested but actually hadn't. Signed-off-by: Joel Dice support callback-less (AKA stackful) async lifts Signed-off-by: Joel Dice revert incorrect test change in flavorful/wasm.rs I had thoughtlessly removed test code based on a clippy warning, not realizing it was testing (at compile time) that the generated types implemented `Debug`. Signed-off-by: Joel Dice test `async: true` option in Rust codegen tests I had meant to do this originally, but apparently forgot to actually use the option. Signed-off-by: Joel Dice add docs for new `debug` and `async` Rust macro options Signed-off-by: Joel Dice address `cargo check` lifetime warning Signed-off-by: Joel Dice minimize use of features based on PR feedback Signed-off-by: Joel Dice --- Cargo.lock | 134 +++- Cargo.toml | 13 +- crates/c/src/lib.rs | 57 +- crates/c/tests/codegen.rs | 8 + crates/core/src/abi.rs | 426 ++++++++-- crates/core/src/lib.rs | 18 +- crates/core/src/types.rs | 8 +- crates/csharp/src/function.rs | 17 +- crates/csharp/src/interface.rs | 17 + crates/csharp/tests/codegen.rs | 8 + crates/go/src/bindgen.rs | 2 + crates/go/src/interface.rs | 20 +- crates/go/tests/codegen.rs | 9 + crates/guest-rust/Cargo.toml | 3 +- crates/guest-rust/macro/Cargo.toml | 2 + crates/guest-rust/macro/src/lib.rs | 79 +- crates/guest-rust/rt/Cargo.toml | 5 + crates/guest-rust/rt/src/async_support.rs | 463 +++++++++++ crates/guest-rust/rt/src/lib.rs | 4 + crates/guest-rust/src/lib.rs | 26 + crates/markdown/src/lib.rs | 47 +- crates/moonbit/src/lib.rs | 34 +- crates/moonbit/tests/codegen.rs | 8 + crates/rust/Cargo.toml | 2 + crates/rust/src/bindgen.rs | 202 ++++- crates/rust/src/interface.rs | 802 +++++++++++++++++-- crates/rust/src/lib.rs | 107 ++- crates/rust/src/stream_and_future_support.rs | 765 ++++++++++++++++++ crates/rust/tests/codegen.rs | 13 + crates/rust/tests/codegen_no_std.rs | 10 + crates/teavm-java/src/lib.rs | 34 +- crates/teavm-java/tests/codegen.rs | 8 + crates/test-helpers/src/lib.rs | 3 +- tests/codegen/error-context.wit | 12 + tests/codegen/futures.wit | 87 ++ tests/codegen/resources-with-futures.wit | 17 + tests/codegen/resources-with-streams.wit | 17 + tests/codegen/streams.wit | 85 ++ tests/runtime/flavorful/wasm.rs | 4 +- tests/runtime/main.rs | 15 +- 40 files changed, 3274 insertions(+), 317 deletions(-) create mode 100644 crates/guest-rust/rt/src/async_support.rs create mode 100644 crates/rust/src/stream_and_future_support.rs create mode 100644 tests/codegen/error-context.wit create mode 100644 tests/codegen/futures.wit create mode 100644 tests/codegen/resources-with-futures.wit create mode 100644 tests/codegen/resources-with-streams.wit create mode 100644 tests/codegen/streams.wit diff --git a/Cargo.lock b/Cargo.lock index a68ba8ff5..5efb90ec9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -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]] @@ -802,11 +836,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -868,12 +903,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] @@ -1056,7 +1091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "indexmap", "memchr", ] @@ -1356,6 +1391,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 +1503,10 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.220.0", + "wasm-encoder 0.222.0", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -1737,19 +1781,19 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.220.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf48234b389415b226a4daef6562933d38c7b28a8b8f64c5c4130dad1561ab7" +checksum = "3432682105d7e994565ef928ccf5856cf6af4ba3dddebedb737f61caed70f956" dependencies = [ "leb128", - "wasmparser 0.220.0", + "wasmparser 0.222.0", ] [[package]] name = "wasm-metadata" -version = "0.220.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3e5f5920c5abfc45573c89b07b38efdaae1515ef86f83dad12d60e50ecd62b" +checksum = "5eb09677e9412cb0dca2fec2cfdb49e5dfe8d82bf869c6d031bdf2ab73324f9d" dependencies = [ "anyhow", "indexmap", @@ -1757,8 +1801,9 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.220.0", - "wasmparser 0.220.0", + "url", + "wasm-encoder 0.222.0", + "wasmparser 0.222.0", ] [[package]] @@ -1777,13 +1822,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.220.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" +checksum = "4adf50fde1b1a49c1add6a80d47aea500c88db70551805853aa8b88f3ea27ab5" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "semver", "serde", @@ -2099,24 +2143,24 @@ dependencies = [ [[package]] name = "wast" -version = "220.0.0" +version = "222.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e708c8de08751fd66e70961a32bae9d71901f14a70871e181cb8461a3bb3165" +checksum = "5ce7191f4b7da0dd300cc32476abae6457154e4625d9b1bc26890828a9a26f6e" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.220.0", + "wasm-encoder 0.222.0", ] [[package]] name = "wat" -version = "1.220.0" +version = "1.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4f1d7d59614ba690541360102b995c4eb1b9ed373701d5102cc1a968b1c5a3" +checksum = "8fde61b4b52f9a84ae31b5e8902a2cd3162ea45d8bf564c729c3288fe52f4334" dependencies = [ - "wast 220.0.0", + "wast 222.0.0", ] [[package]] @@ -2335,11 +2379,11 @@ dependencies = [ "clap", "heck 0.5.0", "test-helpers", - "wasm-encoder 0.220.0", + "wasm-encoder 0.222.0", "wasm-metadata", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -2350,8 +2394,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-artifacts", - "wasm-encoder 0.220.0", - "wasmparser 0.220.0", + "wasm-encoder 0.222.0", + "wasmparser 0.222.0", "wasmtime", "wasmtime-wasi", "wit-bindgen-c", @@ -2363,7 +2407,7 @@ dependencies = [ "wit-bindgen-rust", "wit-bindgen-teavm-java", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -2372,7 +2416,7 @@ version = "0.36.0" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -2384,12 +2428,12 @@ dependencies = [ "heck 0.5.0", "indexmap", "test-helpers", - "wasm-encoder 0.220.0", + "wasm-encoder 0.222.0", "wasm-metadata", - "wasmparser 0.220.0", + "wasmparser 0.222.0", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -2431,6 +2475,8 @@ name = "wit-bindgen-rt" version = "0.36.0" dependencies = [ "bitflags", + "futures", + "once_cell", ] [[package]] @@ -2439,6 +2485,7 @@ version = "0.36.0" dependencies = [ "anyhow", "clap", + "futures", "heck 0.5.0", "indexmap", "prettyplease", @@ -2449,6 +2496,7 @@ dependencies = [ "wasm-metadata", "wit-bindgen", "wit-bindgen-core", + "wit-bindgen-rt", "wit-component", ] @@ -2480,9 +2528,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.220.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ccedf54cc65f287da268d64d2bf4f7530d2cfb2296ffbe3ad5f65567e4cf53" +checksum = "a7ce0f2820a7fdca09e9a0cbbd39513abf2d722479b28c73d81bccd61bb21499" dependencies = [ "anyhow", "bitflags", @@ -2491,11 +2539,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.220.0", + "wasm-encoder 0.222.0", "wasm-metadata", - "wasmparser 0.220.0", + "wasmparser 0.222.0", "wat", - "wit-parser 0.220.0", + "wit-parser 0.222.0", ] [[package]] @@ -2518,9 +2566,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.220.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7117ce3adc0b4354b46dc1cf3190b00b333e65243d244c613ffcc58bdec84d" +checksum = "533abd14901514db88e4107655345fdd8ab8a72fb61e85d871bd361509744c35" dependencies = [ "anyhow", "id-arena", @@ -2531,7 +2579,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.220.0", + "wasmparser 0.222.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8232acf60..435c11242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,13 @@ clap = { version = "4.3.19", features = ["derive"] } indexmap = "2.0.0" prettyplease = "0.2.20" syn = { version = "2.0.89", features = ["printing"] } +futures = "0.3.31" -wasmparser = "0.220.0" -wasm-encoder = "0.220.0" -wasm-metadata = "0.220.0" -wit-parser = "0.220.0" -wit-component = "0.220.0" +wasmparser = "0.222.0" +wasm-encoder = "0.222.0" +wasm-metadata = "0.222.0" +wit-parser = "0.222.0" +wit-component = "0.222.0" wit-bindgen-core = { path = 'crates/core', version = '0.36.0' } wit-bindgen-c = { path = 'crates/c', version = '0.36.0' } @@ -74,6 +75,7 @@ default = [ 'go', 'csharp', 'moonbit', + 'async', ] c = ['dep:wit-bindgen-c'] rust = ['dep:wit-bindgen-rust'] @@ -83,6 +85,7 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] +async = [] [dev-dependencies] heck = { workspace = true } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index c9e996137..fdcf018c8 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); @@ -997,6 +999,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!(), } @@ -1344,6 +1347,21 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{ self.finish_typedef_struct(id); } + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } + fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { let _ = (id, name, ty, docs); } @@ -1432,12 +1450,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"); } } @@ -1610,6 +1632,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"); @@ -1755,6 +1778,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut f, + false, ); let FunctionBindgen { @@ -1827,6 +1851,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut f, + false, ); let FunctionBindgen { src, .. } = f; self.src.c_adapters(&src); @@ -1857,7 +1882,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"); @@ -2080,17 +2105,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), @@ -2758,7 +2774,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 { @@ -3042,6 +3058,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "}}"); } + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + i => unimplemented!("{:?}", i), } } @@ -3150,6 +3170,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/c/tests/codegen.rs b/crates/c/tests/codegen.rs index 7546cd846..359217cb1 100644 --- a/crates/c/tests/codegen.rs +++ b/crates/c/tests/codegen.rs @@ -6,6 +6,14 @@ use std::process::Command; use wit_parser::{Resolve, UnresolvedPackageGroup}; macro_rules! codegen_test { + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { #[test] fn $id() { diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index f0175afaa..e331188fd 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,46 @@ pub fn call( lift_lower: LiftLower, func: &Function, bindgen: &mut impl Bindgen, + async_: bool, +) { + 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 +799,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 +841,7 @@ 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 => false, TypeDefKind::Unknown => unreachable!(), }, @@ -757,6 +864,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 +878,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 +894,124 @@ 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 + | AbiVariant::GuestExportAsyncStackful => { + 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,11 +1022,11 @@ 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 => { - assert!(sig.results.is_empty()); + assert!(sig.results.is_empty() || self.async_); self.return_pointer.take().unwrap() } @@ -874,9 +1034,26 @@ 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 + | AbiVariant::GuestExportAsyncStackful => { + 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 +1062,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 +1095,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 +1134,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 +1174,33 @@ 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 + | AbiVariant::GuestExportAsyncStackful => { + 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..b193df61a 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -154,6 +154,9 @@ pub trait InterfaceGenerator<'a> { fn type_alias(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs); fn types(&mut self, iface: InterfaceId) { let iface = &self.resolve().interfaces[iface]; for (name, id) in iface.types.iter() { @@ -174,9 +177,10 @@ 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(t) => self.type_future(id, name, t, &ty.docs), + TypeDefKind::Stream(t) => self.type_stream(id, name, t, &ty.docs), + TypeDefKind::Handle(_) => panic!("handle types do not require definition"), + TypeDefKind::ErrorContext => self.type_error_context(id, name, &ty.docs), TypeDefKind::Unknown => unreachable!(), } } @@ -191,8 +195,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 +209,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..42f4f60e6 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -193,12 +193,8 @@ 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 => { + info.has_resource = true; } TypeDefKind::Unknown => unreachable!(), } diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 5f1d1ec20..faa6aad35 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -779,7 +779,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let module = self.interface_gen.name; let func_name = self.func_name.to_upper_camel_case(); let interface_name = CSharp::get_class_name_from_qualified_name(module).1; @@ -1097,6 +1097,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/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 04226c6c0..1cb5dd24b 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -270,6 +270,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -395,6 +396,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -1158,6 +1160,21 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { }) .direction = self.direction; } + + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } } fn payload_and_results(resolve: &Resolve, ty: Type) -> (Option, Vec) { diff --git a/crates/csharp/tests/codegen.rs b/crates/csharp/tests/codegen.rs index 6d10104cd..9501e6df0 100644 --- a/crates/csharp/tests/codegen.rs +++ b/crates/csharp/tests/codegen.rs @@ -8,6 +8,14 @@ use std::{ use wit_component::StringEncoding; macro_rules! codegen_test { + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { #[test] fn $id() { 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..39c54aef1 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!(), } } @@ -1265,6 +1266,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { // no impl since these types are generated as anonymous types } + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } + fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { todo!("type_builtin") } diff --git a/crates/go/tests/codegen.rs b/crates/go/tests/codegen.rs index 9a7d8b837..daa437658 100644 --- a/crates/go/tests/codegen.rs +++ b/crates/go/tests/codegen.rs @@ -8,6 +8,15 @@ use heck::*; macro_rules! codegen_test { (issue668 $name:tt $test:tt) => {}; (multiversion $name:tt $test:tt) => {}; + + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { #[test] fn $id() { diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 713256ed8..4eb6ee28b 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.36.0" wit-bindgen-rt = { path = "./rt", version = "0.36.0", features = ["bitflags"] } [features] -default = ["macros", "realloc"] +default = ["macros", "realloc", "async"] macros = ["dep:wit-bindgen-rust-macro"] realloc = [] +async = ["macros", "wit-bindgen-rt/async"] diff --git a/crates/guest-rust/macro/Cargo.toml b/crates/guest-rust/macro/Cargo.toml index 984a1bffc..5fb9c1ea6 100644 --- a/crates/guest-rust/macro/Cargo.toml +++ b/crates/guest-rust/macro/Cargo.toml @@ -24,3 +24,5 @@ anyhow = { workspace = true } syn = { workspace = true } prettyplease = { workspace = true } +[features] +async = [] diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 5648eff71..69f71fd06 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,16 @@ 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; + opts.async_ = val; + } } } } else { @@ -159,6 +172,7 @@ impl Parse for Config { resolve, world, files, + debug, }) } } @@ -254,7 +268,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 +327,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 +358,11 @@ impl From for wit_bindgen_rust::ExportKey { } } +enum AsyncConfigSomeKind { + Imports, + Exports, +} + enum Opt { World(syn::LitStr), Path(Span, Vec), @@ -366,6 +387,8 @@ enum Opt { GenerateUnusedTypes(syn::LitBool), Features(Vec), DisableCustomSectionLinkHelpers(syn::LitBool), + Async(AsyncConfig, Span), + Debug(syn::LitBool), } impl Parse for Opt { @@ -513,6 +536,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 +622,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..d0a43412e 100644 --- a/crates/guest-rust/rt/Cargo.toml +++ b/crates/guest-rust/rt/Cargo.toml @@ -12,3 +12,8 @@ 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] +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..338c77e43 --- /dev/null +++ b/crates/guest-rust/rt/src/async_support.rs @@ -0,0 +1,463 @@ +#![deny(missing_docs)] +#![allow(static_mut_refs)] + +extern crate std; + +use { + futures::{ + channel::oneshot, + future::FutureExt, + stream::{FuturesUnordered, StreamExt}, + }, + once_cell::sync::Lazy, + std::{ + alloc::{self, Layout}, + any::Any, + boxed::Box, + collections::hash_map, + collections::HashMap, + fmt::{self, Debug, Display}, + future::Future, + pin::Pin, + ptr, + sync::Arc, + task::{Context, Poll, Wake, Waker}, + vec::Vec, + }, +}; + +pub use futures; + +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), + } + } +} + +/// 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..0f2858362 100644 --- a/crates/guest-rust/rt/src/lib.rs +++ b/crates/guest-rust/rt/src/lib.rs @@ -112,3 +112,7 @@ pub fn run_ctors_once() { } } } + +/// Support for using the Component Model Async ABI +#[cfg(feature = "async")] +pub mod async_support; diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index a804cfa58..37b28385e 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -818,6 +818,32 @@ /// // used in library-like situations. This is `false` by default with /// // `#[used]` statics being emitted. /// disable_custom_section_link_helpers: false, +/// +/// // Write generated code to a .rs file, which allows the compiler to +/// // emit more useful diagnostics for errors in the generated code. This +/// // is primarily useful for `wit-bindgen` developers. +/// // +/// // This does the same thing as setting `WIT_BINDGEN_DEBUG=1`, except +/// // that it can be used on a more fine-grained basis (i.e. it only affects +/// // the specific `generate!` call where it is used. +/// debug: true, +/// +/// // Generate async import and/or export bindings. +/// // +/// // The resulting bindings will use the component model +/// // [async ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md). +/// // This may be specified either as a boolean (e.g. `async: true`, meaning +/// // all imports and exports should use the async ABI) or as lists of +/// // specific imports and/or exports as shown here: +/// async: { +/// imports: [ +/// "wasi:http/types@0.3.0-draft#[static]body.finish", +/// "wasi:http/handler@0.3.0-draft#handle", +/// ], +/// exports: [ +/// "wasi:http/handler@0.3.0-draft#handle", +/// ] +/// } /// }); /// ``` /// diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index ae6380003..a890e8200 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -235,7 +235,7 @@ impl WorldGenerator for Markdown { } impl Markdown { - fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> InterfaceGenerator<'_> { + fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> InterfaceGenerator<'a> { InterfaceGenerator { gen: self, resolve, @@ -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)); @@ -670,6 +656,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { self.type_alias(id, name, &Type::Id(id), docs); } + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } + fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { self.type_alias(id, name, ty, docs) } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 0fb989372..72be26664 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; @@ -1497,6 +1499,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { // Not needed } + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } + fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { unimplemented!(); } @@ -2579,6 +2596,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/moonbit/tests/codegen.rs b/crates/moonbit/tests/codegen.rs index a015cc880..c0857dded 100644 --- a/crates/moonbit/tests/codegen.rs +++ b/crates/moonbit/tests/codegen.rs @@ -2,6 +2,14 @@ use std::path::Path; use std::process::Command; macro_rules! codegen_test { + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { #[test] fn $id() { diff --git a/crates/rust/Cargo.toml b/crates/rust/Cargo.toml index 1fe1d3bc2..d2e0b69be 100644 --- a/crates/rust/Cargo.toml +++ b/crates/rust/Cargo.toml @@ -27,7 +27,9 @@ syn = { workspace = true } prettyplease = { workspace = true } [dev-dependencies] +futures = { workspace = true } wit-bindgen = { path = '../guest-rust' } +wit-bindgen-rt = { path = '../guest-rust/rt' } test-helpers = { path = '../test-helpers' } # For use with the custom attributes test serde = { version = "1.0", features = ["derive"] } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 1e9dc9ff1..ecbc87a18 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,26 +833,57 @@ 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); - match &func.kind { + 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); + }; + let constructor_type = match &func.kind { FunctionKind::Freestanding => { self.push_str(&format!("T::{}", to_rust_ident(&func.name))); + None } FunctionKind::Method(_) | FunctionKind::Static(_) => { self.push_str(&format!("T::{}", to_rust_ident(func.item_name()))); + None } FunctionKind::Constructor(ty) => { - self.push_str(&format!( - "{}::new(T::new", - resolve.types[*ty] - .name - .as_deref() - .unwrap() - .to_upper_camel_case() - )); + let ty = resolve.types[*ty] + .name + .as_deref() + .unwrap() + .to_upper_camel_case(); + let call = if self.async_ { + let async_support = self.gen.path_to_async_support(); + format!("{async_support}::futures::FutureExt::map(T::new") + } else { + format!("{ty}::new(T::new",) + }; + self.push_str(&call); + Some(ty) } - } + }; self.push_str("("); for (i, operand) in operands.iter().enumerate() { if i > 0 { @@ -833,12 +900,87 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } self.push_str(")"); - if let FunctionKind::Constructor(_) = &func.kind { - self.push_str(")"); + if let Some(ty) = constructor_type { + self.push_str(&if self.async_ { + format!(", {ty}::new)") + } else { + ")".into() + }); } 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 +1010,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 5c44bcefa..a918830b5 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); } } @@ -460,12 +476,455 @@ 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 cancel_write(writer: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-cancel-write-{index}]{func_name}"] + fn cancel(_: u32) -> u32; + }} + unsafe {{ cancel(writer) }}; + }} + }} + + fn cancel_read(reader: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-cancel-read-{index}]{func_name}"] + fn cancel(_: u32) -> u32; + }} + unsafe {{ cancel(reader) }}; + }} + }} + + 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 = unsafe {{ {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 = unsafe {{ address.add(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().take(count).enumerate() {{ + let address = unsafe {{ address.add(index * {size}) }}; + {lift} + dst.write(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 cancel_write(writer: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-cancel-write-{index}]{func_name}"] + fn cancel(_: u32) -> u32; + }} + unsafe {{ cancel(writer) }}; + }} + }} + + fn cancel_read(reader: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-cancel-read-{index}]{func_name}"] + fn cancel(_: u32) -> u32; + }} + unsafe {{ cancel(reader) }}; + }} + }} + + 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) => { @@ -480,17 +939,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, @@ -517,29 +1012,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 { @@ -558,13 +1052,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, @@ -580,20 +1075,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, @@ -607,14 +1114,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, "\ @@ -623,7 +1142,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, @@ -632,8 +1151,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, "\ @@ -652,7 +1181,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(); @@ -663,13 +1192,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 } @@ -709,7 +1243,7 @@ macro_rules! {macro_name} {{ let resource_methods = funcs.remove(&Some(*id)).unwrap_or(Vec::new()); let trait_name = format!("{path}::Guest{camel}"); - self.generate_stub_impl(&trait_name, "", &resource_methods); + self.generate_stub_impl(&trait_name, "", &resource_methods, interface); } format!("{path}::Guest") } @@ -720,7 +1254,7 @@ macro_rules! {macro_name} {{ }; if !root_methods.is_empty() || !extra_trait_items.is_empty() { - self.generate_stub_impl(&guest_trait, &extra_trait_items, &root_methods); + self.generate_stub_impl(&guest_trait, &extra_trait_items, &root_methods, interface); } } @@ -729,6 +1263,7 @@ macro_rules! {macro_name} {{ trait_name: &str, extra_trait_items: &str, funcs: &[&Function], + interface: Option<(InterfaceId, &WorldKey)>, ) { uwriteln!(self.src, "impl {trait_name} for Stub {{"); self.src.push_str(extra_trait_items); @@ -737,7 +1272,19 @@ macro_rules! {macro_name} {{ if self.gen.skip.contains(&func.name) { 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 mut sig = FnSig { + async_, use_item_name: true, private: true, ..Default::default() @@ -746,8 +1293,14 @@ macro_rules! {macro_name} {{ sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig); - self.src.push_str("{ unreachable!() }\n"); + self.print_signature(func, true, &sig, false); + let call = if async_ { + let async_support = self.path_to_async_support(); + format!("{{ #[allow(unreachable_code)]{async_support}::futures::future::ready(unreachable!()) }}\n") + } else { + "{ unreachable!() }\n".into() + }; + self.src.push_str(&call); } self.src.push_str("}\n"); @@ -804,12 +1357,22 @@ 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") + self.push_str(if sig.async_ && !use_async_sugar { + " -> impl ::core::future::Future" + } else { + " -> Self" + }) } else { - self.print_results(&func.results); + self.print_results(&func.results, sig.async_ && !use_async_sugar); } params } @@ -819,6 +1382,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"); @@ -831,7 +1395,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 "); @@ -921,18 +1485,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()); @@ -942,6 +1512,10 @@ macro_rules! {macro_name} {{ self.push_str(")") } } + + if async_ { + self.push_str("> + 'static"); + } } /// Calculates the `TypeMode` to be used for the `ty` specified. @@ -1893,6 +2467,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, @@ -1950,11 +2535,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!( @@ -2206,6 +2792,48 @@ impl<'a> {camel}Borrow<'a>{{ } } + fn type_future(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { + let stream_and_future_support = self.path_to_stream_and_future_support(); + let mode = TypeMode { + style: TypeOwnershipStyle::Owned, + lists_borrowed: false, + lifetime: None, + }; + self.rustdoc(docs); + self.push_str(&format!("pub type {}", name.to_upper_camel_case())); + self.print_generics(mode.lifetime); + self.push_str(" = "); + self.push_str(&format!("{stream_and_future_support}::FutureReader<")); + self.print_optional_ty(ty.as_ref(), mode); + self.push_str(">"); + self.push_str(";\n"); + } + + fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let stream_and_future_support = self.path_to_stream_and_future_support(); + let mode = TypeMode { + style: TypeOwnershipStyle::Owned, + lists_borrowed: false, + lifetime: None, + }; + self.rustdoc(docs); + self.push_str(&format!("pub type {}", name.to_upper_camel_case())); + self.print_generics(mode.lifetime); + self.push_str(" = "); + self.push_str(&format!("{stream_and_future_support}::StreamReader<")); + self.print_ty(ty, mode); + self.push_str(">"); + self.push_str(";\n"); + } + + fn type_error_context(&mut self, _id: TypeId, name: &str, docs: &Docs) { + let async_support = self.path_to_async_support(); + self.rustdoc(docs); + self.push_str(&format!("pub type {} = ", name.to_upper_camel_case())); + self.push_str(&format!("{async_support}::ErrorContext")); + self.push_str(";\n"); + } + fn type_builtin(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { self.rustdoc(docs); self.src @@ -2227,7 +2855,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); } @@ -2295,18 +2923,52 @@ 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<"); - self.interface.print_optional_ty(ty.as_ref(), self.mode); + let stream_and_future_support = self.interface.path_to_stream_and_future_support(); + let mode = TypeMode { + style: TypeOwnershipStyle::Owned, + lists_borrowed: false, + lifetime: None, + }; + self.interface + .push_str(&format!("{stream_and_future_support}::FutureReader<")); + self.interface.print_optional_ty(ty.as_ref(), mode); self.interface.push_str(">"); } - fn anonymous_type_stream(&mut self, _id: TypeId, stream: &Stream, _docs: &Docs) { - self.interface.push_str("Stream<"); - self.interface - .print_optional_ty(stream.element.as_ref(), self.mode); - self.interface.push_str(","); + 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(); + let mode = TypeMode { + style: TypeOwnershipStyle::Owned, + lists_borrowed: false, + lifetime: None, + }; self.interface - .print_optional_ty(stream.end.as_ref(), self.mode); + .push_str(&format!("{stream_and_future_support}::StreamReader<")); + self.interface.print_ty(ty, 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 50ba0b3d7..754955c47 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -45,8 +45,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)] @@ -98,6 +102,7 @@ enum RuntimeItem { AsF64, ResourceType, BoxType, + StreamAndFutureSupport, } #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -118,6 +123,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 { @@ -235,6 +286,17 @@ 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(feature = "clap", arg(long = "async", value_parser = parse_async))] + pub async_: AsyncConfig, } impl Opts { @@ -254,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> { @@ -383,6 +445,7 @@ impl RustWasm { } 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) { @@ -392,6 +455,10 @@ impl RustWasm { } } self.src.push_str("}\n"); + if emitted.contains(&RuntimeItem::StreamAndFutureSupport) { + self.src + .push_str("#[allow(unused_imports)]\npub use _rt::stream_and_future_support;\n"); + } } fn emit_runtime_item(&mut self, item: RuntimeItem) { @@ -624,6 +691,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("}"); + } } } @@ -797,6 +871,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() @@ -972,7 +1047,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, ); @@ -982,7 +1057,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)); let docs = &resolve.interfaces[id].docs; @@ -1000,9 +1075,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); @@ -1016,7 +1091,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(()); @@ -1033,7 +1114,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); @@ -1048,14 +1134,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); @@ -1070,7 +1156,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); } @@ -1220,6 +1306,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..2540b22b5 --- /dev/null +++ b/crates/rust/src/stream_and_future_support.rs @@ -0,0 +1,765 @@ +use { + futures::{ + channel::oneshot, + future::{self, FutureExt}, + sink::Sink, + stream::Stream, + }, + std::{ + collections::hash_map::Entry, + convert::Infallible, + fmt, + 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: Unpin + Sized + 'static { + fn new() -> u32; + async fn write(future: u32, value: Self) -> bool; + async fn read(future: u32) -> Option; + fn cancel_write(future: u32); + fn cancel_read(future: u32); + 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 fmt::Debug for FutureWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureWriter") + .field("handle", &self.handle) + .finish() + } +} + +/// Represents a write operation which may be canceled prior to completion. +pub struct CancelableWrite { + writer: Option>, + future: Pin>>, +} + +impl Future for CancelableWrite { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + let me = self.get_mut(); + match me.future.poll_unpin(cx) { + Poll::Ready(()) => { + me.writer = None; + Poll::Ready(()) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl CancelableWrite { + /// Cancel this write if it hasn't already completed, returning the original `FutureWriter`. + /// + /// This method will panic if the write has already completed. + pub fn cancel(mut self) -> FutureWriter { + self.cancel_mut() + } + + fn cancel_mut(&mut self) -> FutureWriter { + let writer = self.writer.take().unwrap(); + async_support::with_entry(writer.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalWaiting(_) + | Handle::Read + | Handle::LocalClosed => unreachable!(), + Handle::LocalReady(..) => { + entry.insert(Handle::LocalOpen); + } + Handle::Write => T::cancel_write(writer.handle), + }, + }); + writer + } +} + +impl Drop for CancelableWrite { + fn drop(&mut self) { + if self.writer.is_some() { + self.cancel_mut(); + } + } +} + +impl FutureWriter { + /// Write the specified value to this `future`. + pub fn write(self, v: T) -> CancelableWrite { + let handle = self.handle; + CancelableWrite { + writer: Some(self), + future: async_support::with_entry(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(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(handle, v).map(drop)), + }, + }), + } + } +} + +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 a read operation which may be canceled prior to completion. +pub struct CancelableRead { + reader: Option>, + future: Pin>>>, +} + +impl Future for CancelableRead { + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + match me.future.poll_unpin(cx) { + Poll::Ready(v) => { + me.reader = None; + Poll::Ready(v) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl CancelableRead { + /// Cancel this read if it hasn't already completed, returning the original `FutureReader`. + /// + /// This method will panic if the read has already completed. + pub fn cancel(mut self) -> FutureReader { + self.cancel_mut() + } + + fn cancel_mut(&mut self) -> FutureReader { + let reader = self.reader.take().unwrap(); + async_support::with_entry(reader.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalReady(..) + | Handle::Write + | Handle::LocalClosed => unreachable!(), + Handle::LocalWaiting(_) => { + entry.insert(Handle::LocalOpen); + } + Handle::Read => T::cancel_read(reader.handle), + }, + }); + reader + } +} + +impl Drop for CancelableRead { + fn drop(&mut self) { + if self.reader.is_some() { + self.cancel_mut(); + } + } +} + +/// Represents the readable end of a Component Model `future`. +pub struct FutureReader { + handle: u32, + _phantom: PhantomData, +} + +impl fmt::Debug for FutureReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureReader") + .field("handle", &self.handle) + .finish() + } +} + +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 = CancelableRead; + + /// 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 { + let handle = self.handle; + CancelableRead { + reader: Some(self), + future: async_support::with_entry(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(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 cancel_write(stream: u32); + fn cancel_read(stream: u32); + fn close_writable(stream: u32); + fn close_readable(stream: u32); +} + +struct CancelWriteOnDrop { + handle: Option, + _phantom: PhantomData, +} + +impl Drop for CancelWriteOnDrop { + fn drop(&mut self) { + if let Some(handle) = self.handle.take() { + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalWaiting(_) + | Handle::Read + | Handle::LocalClosed => unreachable!(), + Handle::LocalReady(..) => { + entry.insert(Handle::LocalOpen); + } + Handle::Write => T::cancel_write(handle), + }, + }); + } + } +} + +/// Represents the writable end of a Component Model `stream`. +pub struct StreamWriter { + handle: u32, + future: Option + 'static>>>, + _phantom: PhantomData, +} + +impl StreamWriter { + /// Cancel the current pending write operation. + /// + /// This will panic if no such operation is pending. + pub fn cancel(&mut self) { + assert!(self.future.is_some()); + self.future = None; + } +} + +impl fmt::Debug for StreamWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StreamWriter") + .field("handle", &self.handle) + .finish() + } +} + +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); + let mut cancel_on_drop = Some(CancelWriteOnDrop:: { + handle: Some(handle), + _phantom: PhantomData, + }); + 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 { + cancel_on_drop.take().unwrap().handle = None; + Poll::Ready(()) + } + } + Handle::LocalReady(..) => Poll::Pending, + Handle::LocalClosed => { + cancel_on_drop.take().unwrap().handle = None; + 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; + let mut cancel_on_drop = CancelWriteOnDrop:: { + handle: Some(handle), + _phantom: PhantomData, + }; + 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; + } + } + cancel_on_drop.handle = None; + drop(cancel_on_drop); + })); + } + }, + }); + 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) { + self.future = None; + + 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); + } + }, + }); + } +} + +struct CancelReadOnDrop { + handle: Option, + _phantom: PhantomData, +} + +impl Drop for CancelReadOnDrop { + fn drop(&mut self) { + if let Some(handle) = self.handle.take() { + async_support::with_entry(handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalReady(..) + | Handle::Write + | Handle::LocalClosed => unreachable!(), + Handle::LocalWaiting(_) => { + entry.insert(Handle::LocalOpen); + } + Handle::Read => T::cancel_read(handle), + }, + }); + } + } +} + +/// Represents the readable end of a Component Model `stream`. +pub struct StreamReader { + handle: u32, + future: Option>> + 'static>>>, + _phantom: PhantomData, +} + +impl StreamReader { + /// Cancel the current pending read operation. + /// + /// This will panic if no such operation is pending. + pub fn cancel(&mut self) { + assert!(self.future.is_some()); + self.future = None; + } +} + +impl fmt::Debug for StreamReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StreamReader") + .field("handle", &self.handle) + .finish() + } +} + +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; + let mut cancel_on_drop = CancelReadOnDrop:: { + handle: Some(handle), + _phantom: PhantomData, + }; + Box::pin(async move { + let mut buffer = iter::repeat_with(MaybeUninit::uninit) + .take(ceiling(64 * 1024, mem::size_of::())) + .collect::>(); + + let result = if let Some(count) = T::read(handle, &mut buffer).await { + buffer.truncate(count); + Some(unsafe { + mem::transmute::>, Vec>(buffer) + }) + } else { + None + }; + cancel_on_drop.handle = None; + drop(cancel_on_drop); + result + }) as Pin>> + } + Handle::LocalOpen => { + let (tx, rx) = oneshot::channel(); + entry.insert(Handle::LocalWaiting(tx)); + let mut cancel_on_drop = CancelReadOnDrop:: { + handle: Some(me.handle), + _phantom: PhantomData, + }; + Box::pin(async move { + let result = rx.map(|v| v.ok().map(|v| *v.downcast().unwrap())).await; + cancel_on_drop.handle = None; + drop(cancel_on_drop); + result + }) + } + 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) { + self.future = None; + + 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/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index ad2310e3c..4d79b48c8 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -51,6 +51,19 @@ mod codegen_tests { #[test] fn works() {} } + + mod async_ { + wit_bindgen::generate!({ + path: $test, + stubs, + export_prefix: "[async-prefix]", + generate_all, + async: true, + }); + + #[test] + fn works() {} + } } }; diff --git a/crates/rust/tests/codegen_no_std.rs b/crates/rust/tests/codegen_no_std.rs index 6c2db154d..362931c92 100644 --- a/crates/rust/tests/codegen_no_std.rs +++ b/crates/rust/tests/codegen_no_std.rs @@ -12,6 +12,16 @@ mod codegen_tests { macro_rules! codegen_test { (wasi_cli $name:tt $test:tt) => {}; (wasi_http $name:tt $test:tt) => {}; + + // TODO: We should be able to support streams, futures, and + // error-contexts in no_std mode if desired, but haven't done the work + // yet. + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { mod $id { wit_bindgen::generate!({ diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 1d0c90593..014bd6ace 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; @@ -1068,6 +1070,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { self.type_name(&Type::Id(id)); } + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs) { + _ = (id, name, docs); + todo!() + } + fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { unimplemented!(); } @@ -2028,6 +2045,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/teavm-java/tests/codegen.rs b/crates/teavm-java/tests/codegen.rs index 16d3f107b..ab637cf04 100644 --- a/crates/teavm-java/tests/codegen.rs +++ b/crates/teavm-java/tests/codegen.rs @@ -30,6 +30,14 @@ macro_rules! codegen_test { (issue929_no_export $name:tt $test:tt) => {}; (issue929_only_methods $name:tt $test:tt) => {}; + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + ($id:ident $name:tt $test:tt) => { #[test] fn $id() { 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/codegen/error-context.wit b/tests/codegen/error-context.wit new file mode 100644 index 000000000..d76f89685 --- /dev/null +++ b/tests/codegen/error-context.wit @@ -0,0 +1,12 @@ +package foo:foo; + +interface error-contexts { + type foo = error-context; + + bar: func(x: foo, y: error-context, z: future) -> result, error-context>; +} + +world foo { + import error-contexts; + export error-contexts; +} diff --git a/tests/codegen/futures.wit b/tests/codegen/futures.wit new file mode 100644 index 000000000..2d634a400 --- /dev/null +++ b/tests/codegen/futures.wit @@ -0,0 +1,87 @@ +package foo:foo; + +interface futures { + future-param: func(x: future); + future-u8-param: func(x: future); + future-u16-param: func(x: future); + future-u32-param: func(x: future); + future-u64-param: func(x: future); + future-s8-param: func(x: future); + future-s16-param: func(x: future); + future-s32-param: func(x: future); + future-s64-param: func(x: future); + future-f32-param: func(x: future); + future-f64-param: func(x: future); + + future-ret: func(x: future); + future-u8-ret: func() -> future; + future-u16-ret: func() -> future; + future-u32-ret: func() -> future; + future-u64-ret: func() -> future; + future-s8-ret: func() -> future; + future-s16-ret: func() -> future; + future-s32-ret: func() -> future; + future-s64-ret: func() -> future; + future-f32-ret: func() -> future; + future-f64-ret: func() -> future; + + tuple-future: func(x: future>) -> future>; + string-future-arg: func(a: future); + string-future-ret: func() -> future; + tuple-string-future: func(x: future>) -> future>; + string-future: func(x: future) -> future; + + record some-record { + x: string, + y: other-record, + z: future, + c1: u32, + c2: u64, + c3: s32, + c4: s64, + } + record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: future, + } + record-future: func(x: future) -> future; + record-future-reverse: func(x: future) -> future; + + variant some-variant { + a(string), + b, + c(u32), + d(future), + } + variant other-variant { + a, + b(u32), + c(string), + } + variant-future: func(x: future) -> future; + + type load-store-all-sizes = future>; + load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes; +} + +world the-futures { + import futures; + export futures; +} diff --git a/tests/codegen/resources-with-futures.wit b/tests/codegen/resources-with-futures.wit new file mode 100644 index 000000000..33a2b2aeb --- /dev/null +++ b/tests/codegen/resources-with-futures.wit @@ -0,0 +1,17 @@ +package my:resources; + +interface with-futures { + resource x { + constructor(l: future); + get: func() -> future; + set: func(l: future); + etc: static func(l: future) -> future; + } + + foo: func(x: future) -> future; +} + +world resources { + import with-futures; + export with-futures; +} diff --git a/tests/codegen/resources-with-streams.wit b/tests/codegen/resources-with-streams.wit new file mode 100644 index 000000000..d9e3620fc --- /dev/null +++ b/tests/codegen/resources-with-streams.wit @@ -0,0 +1,17 @@ +package my:resources; + +interface with-streams { + resource x { + constructor(l: stream); + get: func() -> stream; + set: func(l: stream); + etc: static func(l: stream) -> stream; + } + + foo: func(x: stream) -> stream; +} + +world resources { + import with-streams; + export with-streams; +} diff --git a/tests/codegen/streams.wit b/tests/codegen/streams.wit new file mode 100644 index 000000000..fd00239b7 --- /dev/null +++ b/tests/codegen/streams.wit @@ -0,0 +1,85 @@ +package foo:foo; + +interface streams { + stream-u8-param: func(x: stream); + stream-u16-param: func(x: stream); + stream-u32-param: func(x: stream); + stream-u64-param: func(x: stream); + stream-s8-param: func(x: stream); + stream-s16-param: func(x: stream); + stream-s32-param: func(x: stream); + stream-s64-param: func(x: stream); + stream-f32-param: func(x: stream); + stream-f64-param: func(x: stream); + + stream-u8-ret: func() -> stream; + stream-u16-ret: func() -> stream; + stream-u32-ret: func() -> stream; + stream-u64-ret: func() -> stream; + stream-s8-ret: func() -> stream; + stream-s16-ret: func() -> stream; + stream-s32-ret: func() -> stream; + stream-s64-ret: func() -> stream; + stream-f32-ret: func() -> stream; + stream-f64-ret: func() -> stream; + + tuple-stream: func(x: stream>) -> stream>; + string-stream-arg: func(a: stream); + string-stream-ret: func() -> stream; + tuple-string-stream: func(x: stream>) -> stream>; + string-stream: func(x: stream) -> stream; + + record some-record { + x: string, + y: other-record, + z: stream, + c1: u32, + c2: u64, + c3: s32, + c4: s64, + } + record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: stream, + } + record-stream: func(x: stream) -> stream; + record-stream-reverse: func(x: stream) -> stream; + + variant some-variant { + a(string), + b, + c(u32), + d(stream), + } + variant other-variant { + a, + b(u32), + c(string), + } + variant-stream: func(x: stream) -> stream; + + type load-store-all-sizes = stream>; + load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes; +} + +world the-streams { + import streams; + export streams; +} diff --git a/tests/runtime/flavorful/wasm.rs b/tests/runtime/flavorful/wasm.rs index 058eb2f74..96836a51b 100644 --- a/tests/runtime/flavorful/wasm.rs +++ b/tests/runtime/flavorful/wasm.rs @@ -44,7 +44,7 @@ impl Guest for Component { assert!(errno_result().is_err()); MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); + _ = format!("{:?}", MyErrno::A); fn assert_error() {} assert_error::(); @@ -107,7 +107,7 @@ impl exports::test::flavorful::test::Guest for Component { fn errno_result() -> Result<(), MyErrno> { MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); + _ = 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() { From 32892f4ce0f5aefa59994736fe96479d32ef05b5 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 7 Jan 2025 13:30:56 -0700 Subject: [PATCH 2/5] add comment explaining lack of recursion in `type_id_info` Signed-off-by: Joel Dice --- crates/core/src/types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 42f4f60e6..52e6b1c3d 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -194,6 +194,8 @@ impl Types { info |= self.optional_type_info(resolve, r.err.as_ref()); } TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext => { + // These are all u32 handles regardless of payload type, so no + // need to recurse. info.has_resource = true; } TypeDefKind::Unknown => unreachable!(), From d43a95924e8eb825efe1e9dec2960026c61ef39c Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 8 Jan 2025 12:24:03 -0700 Subject: [PATCH 3/5] refactor stream and future support for Rust generator Per a discussion with Alex, this moves most of the stream and future code into the `wit-bindgen-rt` crate. That's where I had intended to put it to begin with, but ran into orphan rule issues given the trait-based approach I was using. The new approach uses dynamic dispatch via a vtable type. Thus we've traded a small (theoretical) amount of performance for much better compatibility in cases of separately-generated bindings (e.g. passing `FutureWriter` between crates should work fine now), easier debugging, etc. Signed-off-by: Joel Dice --- crates/guest-rust/rt/src/async_support.rs | 6 + .../src/async_support/streams_and_futures.rs} | 298 ++++++++-------- crates/rust/src/bindgen.rs | 30 +- crates/rust/src/interface.rs | 336 ++++++++++-------- crates/rust/src/lib.rs | 88 ++++- 5 files changed, 434 insertions(+), 324 deletions(-) rename crates/{rust/src/stream_and_future_support.rs => guest-rust/rt/src/async_support/streams_and_futures.rs} (76%) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 338c77e43..ac42f3223 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -26,6 +26,12 @@ use { }, }; +mod streams_and_futures; + +pub use streams_and_futures::{ + FutureReader, FutureVtable, FutureWriter, StreamReader, StreamVtable, StreamWriter, +}; + pub use futures; type BoxFuture = Pin + 'static>>; diff --git a/crates/rust/src/stream_and_future_support.rs b/crates/guest-rust/rt/src/async_support/streams_and_futures.rs similarity index 76% rename from crates/rust/src/stream_and_future_support.rs rename to crates/guest-rust/rt/src/async_support/streams_and_futures.rs index 2540b22b5..03bb90a11 100644 --- a/crates/rust/src/stream_and_future_support.rs +++ b/crates/guest-rust/rt/src/async_support/streams_and_futures.rs @@ -1,4 +1,7 @@ +extern crate std; + use { + super::Handle, futures::{ channel::oneshot, future::{self, FutureExt}, @@ -6,37 +9,40 @@ use { stream::Stream, }, std::{ + boxed::Box, collections::hash_map::Entry, convert::Infallible, fmt, future::{Future, IntoFuture}, iter, - marker::PhantomData, mem::{self, ManuallyDrop, MaybeUninit}, pin::Pin, task::{Context, Poll}, + vec::Vec, }, - wit_bindgen_rt::async_support::{self, Handle}, }; +fn ceiling(x: usize, y: usize) -> usize { + (x / y) + if x % y == 0 { 0 } else { 1 } +} + #[doc(hidden)] -pub trait FuturePayload: Unpin + Sized + 'static { - fn new() -> u32; - async fn write(future: u32, value: Self) -> bool; - async fn read(future: u32) -> Option; - fn cancel_write(future: u32); - fn cancel_read(future: u32); - fn close_writable(future: u32); - fn close_readable(future: u32); +pub struct FutureVtable { + pub write: fn(future: u32, value: T) -> Pin>>, + pub read: fn(future: u32) -> Pin>>>, + pub cancel_write: fn(future: u32), + pub cancel_read: fn(future: u32), + pub close_writable: fn(future: u32), + pub close_readable: fn(future: u32), } /// Represents the writable end of a Component Model `future`. -pub struct FutureWriter { +pub struct FutureWriter { handle: u32, - _phantom: PhantomData, + vtable: &'static FutureVtable, } -impl fmt::Debug for FutureWriter { +impl fmt::Debug for FutureWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FutureWriter") .field("handle", &self.handle) @@ -45,12 +51,12 @@ impl fmt::Debug for FutureWriter { } /// Represents a write operation which may be canceled prior to completion. -pub struct CancelableWrite { +pub struct CancelableWrite { writer: Option>, future: Pin>>, } -impl Future for CancelableWrite { +impl Future for CancelableWrite { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { @@ -65,7 +71,7 @@ impl Future for CancelableWrite { } } -impl CancelableWrite { +impl CancelableWrite { /// Cancel this write if it hasn't already completed, returning the original `FutureWriter`. /// /// This method will panic if the write has already completed. @@ -75,7 +81,7 @@ impl CancelableWrite { fn cancel_mut(&mut self) -> FutureWriter { let writer = self.writer.take().unwrap(); - async_support::with_entry(writer.handle, |entry| match entry { + super::with_entry(writer.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen @@ -85,14 +91,14 @@ impl CancelableWrite { Handle::LocalReady(..) => { entry.insert(Handle::LocalOpen); } - Handle::Write => T::cancel_write(writer.handle), + Handle::Write => (writer.vtable.cancel_write)(writer.handle), }, }); writer } } -impl Drop for CancelableWrite { +impl Drop for CancelableWrite { fn drop(&mut self) { if self.writer.is_some() { self.cancel_mut(); @@ -100,19 +106,25 @@ impl Drop for CancelableWrite { } } -impl FutureWriter { +impl FutureWriter { + #[doc(hidden)] + pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + /// Write the specified value to this `future`. pub fn write(self, v: T) -> CancelableWrite { let handle = self.handle; + let vtable = self.vtable; CancelableWrite { writer: Some(self), - future: async_support::with_entry(handle, |entry| match entry { + future: super::with_entry(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(handle, |entry| match entry { + super::with_entry(handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen => { @@ -140,16 +152,16 @@ impl FutureWriter { } Handle::LocalClosed => Box::pin(future::ready(())), Handle::Read | Handle::LocalReady(..) => unreachable!(), - Handle::Write => Box::pin(T::write(handle, v).map(drop)), + Handle::Write => Box::pin((vtable.write)(handle, v).map(drop)), }, }), } } } -impl Drop for FutureWriter { +impl Drop for FutureWriter { fn drop(&mut self) { - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get_mut() { Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { @@ -158,7 +170,7 @@ impl Drop for FutureWriter { Handle::Read => unreachable!(), Handle::Write | Handle::LocalClosed => { entry.remove(); - T::close_writable(self.handle); + (self.vtable.close_writable)(self.handle); } }, }); @@ -166,12 +178,12 @@ impl Drop for FutureWriter { } /// Represents a read operation which may be canceled prior to completion. -pub struct CancelableRead { +pub struct CancelableRead { reader: Option>, future: Pin>>>, } -impl Future for CancelableRead { +impl Future for CancelableRead { type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -186,7 +198,7 @@ impl Future for CancelableRead { } } -impl CancelableRead { +impl CancelableRead { /// Cancel this read if it hasn't already completed, returning the original `FutureReader`. /// /// This method will panic if the read has already completed. @@ -196,7 +208,7 @@ impl CancelableRead { fn cancel_mut(&mut self) -> FutureReader { let reader = self.reader.take().unwrap(); - async_support::with_entry(reader.handle, |entry| match entry { + super::with_entry(reader.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen @@ -206,14 +218,14 @@ impl CancelableRead { Handle::LocalWaiting(_) => { entry.insert(Handle::LocalOpen); } - Handle::Read => T::cancel_read(reader.handle), + Handle::Read => (reader.vtable.cancel_read)(reader.handle), }, }); reader } } -impl Drop for CancelableRead { +impl Drop for CancelableRead { fn drop(&mut self) { if self.reader.is_some() { self.cancel_mut(); @@ -222,12 +234,12 @@ impl Drop for CancelableRead { } /// Represents the readable end of a Component Model `future`. -pub struct FutureReader { +pub struct FutureReader { handle: u32, - _phantom: PhantomData, + vtable: &'static FutureVtable, } -impl fmt::Debug for FutureReader { +impl fmt::Debug for FutureReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FutureReader") .field("handle", &self.handle) @@ -235,10 +247,15 @@ impl fmt::Debug for FutureReader { } } -impl FutureReader { +impl FutureReader { + #[doc(hidden)] + pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + #[doc(hidden)] - pub fn from_handle(handle: u32) -> Self { - async_support::with_entry(handle, |entry| match entry { + pub fn from_handle_and_vtable(handle: u32, vtable: &'static FutureVtable) -> Self { + super::with_entry(handle, |entry| match entry { Entry::Vacant(entry) => { entry.insert(Handle::Read); } @@ -256,15 +273,12 @@ impl FutureReader { }, }); - Self { - handle, - _phantom: PhantomData, - } + Self { handle, vtable } } #[doc(hidden)] pub fn into_handle(self) -> u32 { - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen => { @@ -281,7 +295,7 @@ impl FutureReader { } } -impl IntoFuture for FutureReader { +impl IntoFuture for FutureReader { type Output = Option; type IntoFuture = CancelableRead; @@ -290,13 +304,14 @@ impl IntoFuture for FutureReader { /// or when the writable end is dropped (yielding a `None` result). fn into_future(self) -> Self::IntoFuture { let handle = self.handle; + let vtable = self.vtable; CancelableRead { reader: Some(self), - future: async_support::with_entry(handle, |entry| match entry { + future: super::with_entry(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(handle).await }) + Handle::Read => Box::pin(async move { (vtable.read)(handle).await }) as Pin>>, Handle::LocalOpen => { let (tx, rx) = oneshot::channel(); @@ -317,9 +332,9 @@ impl IntoFuture for FutureReader { } } -impl Drop for FutureReader { +impl Drop for FutureReader { fn drop(&mut self) { - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get_mut() { Handle::LocalReady(..) => { @@ -333,7 +348,7 @@ impl Drop for FutureReader { } Handle::Read | Handle::LocalClosed => { entry.remove(); - T::close_readable(self.handle); + (self.vtable.close_readable)(self.handle); } Handle::Write => unreachable!(), }, @@ -342,25 +357,27 @@ impl Drop for FutureReader { } #[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 cancel_write(stream: u32); - fn cancel_read(stream: u32); - fn close_writable(stream: u32); - fn close_readable(stream: u32); -} - -struct CancelWriteOnDrop { +pub struct StreamVtable { + pub write: fn(future: u32, values: &[T]) -> Pin>>>, + pub read: fn( + future: u32, + values: &mut [MaybeUninit], + ) -> Pin>>>, + pub cancel_write: fn(future: u32), + pub cancel_read: fn(future: u32), + pub close_writable: fn(future: u32), + pub close_readable: fn(future: u32), +} + +struct CancelWriteOnDrop { handle: Option, - _phantom: PhantomData, + vtable: &'static StreamVtable, } -impl Drop for CancelWriteOnDrop { +impl Drop for CancelWriteOnDrop { fn drop(&mut self) { if let Some(handle) = self.handle.take() { - async_support::with_entry(handle, |entry| match entry { + super::with_entry(handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen @@ -370,7 +387,7 @@ impl Drop for CancelWriteOnDrop { Handle::LocalReady(..) => { entry.insert(Handle::LocalOpen); } - Handle::Write => T::cancel_write(handle), + Handle::Write => (self.vtable.cancel_write)(handle), }, }); } @@ -378,13 +395,22 @@ impl Drop for CancelWriteOnDrop { } /// Represents the writable end of a Component Model `stream`. -pub struct StreamWriter { +pub struct StreamWriter { handle: u32, future: Option + 'static>>>, - _phantom: PhantomData, + vtable: &'static StreamVtable, } -impl StreamWriter { +impl StreamWriter { + #[doc(hidden)] + pub fn new(handle: u32, vtable: &'static StreamVtable) -> Self { + Self { + handle, + future: None, + vtable, + } + } + /// Cancel the current pending write operation. /// /// This will panic if no such operation is pending. @@ -394,7 +420,7 @@ impl StreamWriter { } } -impl fmt::Debug for StreamWriter { +impl fmt::Debug for StreamWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StreamWriter") .field("handle", &self.handle) @@ -402,7 +428,7 @@ impl fmt::Debug for StreamWriter { } } -impl Sink> for StreamWriter { +impl Sink> for StreamWriter { type Error = Infallible; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -423,7 +449,7 @@ impl Sink> for StreamWriter { 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 { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen => { @@ -431,10 +457,10 @@ impl Sink> for StreamWriter { let mut item = Some(item); let mut cancel_on_drop = Some(CancelWriteOnDrop:: { handle: Some(handle), - _phantom: PhantomData, + vtable: self.vtable, }); self.get_mut().future = Some(Box::pin(future::poll_fn(move |cx| { - async_support::with_entry(handle, |entry| match entry { + super::with_entry(handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen => { @@ -471,14 +497,15 @@ impl Sink> for StreamWriter { Handle::Read | Handle::LocalReady(..) => unreachable!(), Handle::Write => { let handle = self.handle; + let vtable = self.vtable; let mut cancel_on_drop = CancelWriteOnDrop:: { handle: Some(handle), - _phantom: PhantomData, + vtable, }; 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 { + if let Some(count) = (vtable.write)(handle, &item[offset..]).await { offset += count; } else { break; @@ -502,11 +529,11 @@ impl Sink> for StreamWriter { } } -impl Drop for StreamWriter { +impl Drop for StreamWriter { fn drop(&mut self) { self.future = None; - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get_mut() { Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { @@ -515,22 +542,22 @@ impl Drop for StreamWriter { Handle::Read => unreachable!(), Handle::Write | Handle::LocalClosed => { entry.remove(); - T::close_writable(self.handle); + (self.vtable.close_writable)(self.handle); } }, }); } } -struct CancelReadOnDrop { +struct CancelReadOnDrop { handle: Option, - _phantom: PhantomData, + vtable: &'static StreamVtable, } -impl Drop for CancelReadOnDrop { +impl Drop for CancelReadOnDrop { fn drop(&mut self) { if let Some(handle) = self.handle.take() { - async_support::with_entry(handle, |entry| match entry { + super::with_entry(handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen @@ -540,7 +567,7 @@ impl Drop for CancelReadOnDrop { Handle::LocalWaiting(_) => { entry.insert(Handle::LocalOpen); } - Handle::Read => T::cancel_read(handle), + Handle::Read => (self.vtable.cancel_read)(handle), }, }); } @@ -548,13 +575,13 @@ impl Drop for CancelReadOnDrop { } /// Represents the readable end of a Component Model `stream`. -pub struct StreamReader { +pub struct StreamReader { handle: u32, future: Option>> + 'static>>>, - _phantom: PhantomData, + vtable: &'static StreamVtable, } -impl StreamReader { +impl StreamReader { /// Cancel the current pending read operation. /// /// This will panic if no such operation is pending. @@ -564,7 +591,7 @@ impl StreamReader { } } -impl fmt::Debug for StreamReader { +impl fmt::Debug for StreamReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StreamReader") .field("handle", &self.handle) @@ -572,10 +599,19 @@ impl fmt::Debug for StreamReader { } } -impl StreamReader { +impl StreamReader { #[doc(hidden)] - pub fn from_handle(handle: u32) -> Self { - async_support::with_entry(handle, |entry| match entry { + pub fn new(handle: u32, vtable: &'static StreamVtable) -> Self { + Self { + handle, + future: None, + vtable, + } + } + + #[doc(hidden)] + pub fn from_handle_and_vtable(handle: u32, vtable: &'static StreamVtable) -> Self { + super::with_entry(handle, |entry| match entry { Entry::Vacant(entry) => { entry.insert(Handle::Read); } @@ -596,13 +632,13 @@ impl StreamReader { Self { handle, future: None, - _phantom: PhantomData, + vtable, } } #[doc(hidden)] pub fn into_handle(self) -> u32 { - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get() { Handle::LocalOpen => { @@ -619,36 +655,38 @@ impl StreamReader { } } -impl Stream for StreamReader { +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 { + me.future = Some(super::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; + let vtable = me.vtable; let mut cancel_on_drop = CancelReadOnDrop:: { handle: Some(handle), - _phantom: PhantomData, + vtable, }; Box::pin(async move { let mut buffer = iter::repeat_with(MaybeUninit::uninit) .take(ceiling(64 * 1024, mem::size_of::())) .collect::>(); - let result = if let Some(count) = T::read(handle, &mut buffer).await { - buffer.truncate(count); - Some(unsafe { - mem::transmute::>, Vec>(buffer) - }) - } else { - None - }; + let result = + if let Some(count) = (vtable.read)(handle, &mut buffer).await { + buffer.truncate(count); + Some(unsafe { + mem::transmute::>, Vec>(buffer) + }) + } else { + None + }; cancel_on_drop.handle = None; drop(cancel_on_drop); result @@ -659,7 +697,7 @@ impl Stream for StreamReader { entry.insert(Handle::LocalWaiting(tx)); let mut cancel_on_drop = CancelReadOnDrop:: { handle: Some(me.handle), - _phantom: PhantomData, + vtable: me.vtable, }; Box::pin(async move { let result = rx.map(|v| v.ok().map(|v| *v.downcast().unwrap())).await; @@ -690,11 +728,11 @@ impl Stream for StreamReader { } } -impl Drop for StreamReader { +impl Drop for StreamReader { fn drop(&mut self) { self.future = None; - async_support::with_entry(self.handle, |entry| match entry { + super::with_entry(self.handle, |entry| match entry { Entry::Vacant(_) => unreachable!(), Entry::Occupied(mut entry) => match entry.get_mut() { Handle::LocalReady(..) => { @@ -708,58 +746,10 @@ impl Drop for StreamReader { } Handle::Read | Handle::LocalClosed => { entry.remove(); - T::close_readable(self.handle); + (self.vtable.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/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index ecbc87a18..26db6f381 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -1,4 +1,4 @@ -use crate::{int_repr, to_rust_ident, wasm_type, InterfaceGenerator, RustFlagsRepr}; +use crate::{int_repr, to_rust_ident, wasm_type, Identifier, InterfaceGenerator, RustFlagsRepr}; use heck::*; use std::fmt::Write as _; use std::mem; @@ -463,11 +463,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("({op}).into_handle() as i32")) } - Instruction::FutureLift { .. } => { - let stream_and_future_support = self.gen.path_to_stream_and_future_support(); + Instruction::FutureLift { payload, .. } => { + let async_support = self.gen.path_to_async_support(); let op = &operands[0]; + let name = payload + .as_ref() + .map(|ty| { + self.gen + .full_type_name_owned(ty, Identifier::StreamOrFuturePayload) + }) + .unwrap_or_else(|| "()".into()); + let ordinal = self.gen.gen.future_payloads.get_index_of(&name).unwrap(); + let path = self.gen.path_to_root(); results.push(format!( - "{stream_and_future_support}::FutureReader::from_handle({op} as u32)" + "{async_support}::FutureReader::from_handle_and_vtable\ + ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" )) } @@ -476,11 +486,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("({op}).into_handle() as i32")) } - Instruction::StreamLift { .. } => { - let stream_and_future_support = self.gen.path_to_stream_and_future_support(); + Instruction::StreamLift { payload, .. } => { + let async_support = self.gen.path_to_async_support(); let op = &operands[0]; + let name = self + .gen + .full_type_name_owned(payload, Identifier::StreamOrFuturePayload); + let ordinal = self.gen.gen.stream_payloads.get_index_of(&name).unwrap(); + let path = self.gen.path_to_root(); results.push(format!( - "{stream_and_future_support}::StreamReader::from_handle({op} as u32)" + "{async_support}::StreamReader::from_handle_and_vtable\ + ({op} as u32, &{path}wit_stream::vtable{ordinal}::VTABLE)" )) } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index a918830b5..192dd80ac 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -127,7 +127,7 @@ impl TypeOwnershipStyle { } } -impl InterfaceGenerator<'_> { +impl<'i> InterfaceGenerator<'i> { pub(super) fn generate_exports<'a>( &mut self, interface: Option<(InterfaceId, &WorldKey)>, @@ -271,7 +271,7 @@ fn _resource_rep(handle: u32) -> *mut u8 None => { let world = match self.identifier { Identifier::World(w) => w, - Identifier::None | Identifier::Interface(..) => unreachable!(), + Identifier::Interface(..) | Identifier::StreamOrFuturePayload => unreachable!(), }; let world = self.resolve.worlds[world].name.to_snake_case(); format!("__export_world_{world}_cabi") @@ -322,7 +322,9 @@ macro_rules! {macro_name} {{ for name in resources_to_drop { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), - Identifier::None | Identifier::World(_) => unreachable!(), + Identifier::World(_) | Identifier::StreamOrFuturePayload => { + unreachable!() + } }; let camel = name.to_upper_camel_case(); uwriteln!( @@ -404,25 +406,29 @@ macro_rules! {macro_name} {{ src } - fn path_to_root(&self) -> String { + pub(crate) fn path_to_root(&self) -> String { let mut path_to_root = String::new(); - if let Identifier::Interface(_, key) = self.identifier { - // Escape the submodule for this interface - path_to_root.push_str("super::"); - - // Escape the `exports` top-level submodule - if !self.in_import { + match self.identifier { + Identifier::Interface(_, key) => { + // Escape the submodule for this interface path_to_root.push_str("super::"); - } - // Escape the namespace/package submodules for interface-based ids - match key { - WorldKey::Name(_) => {} - WorldKey::Interface(_) => { - path_to_root.push_str("super::super::"); + // Escape the `exports` top-level submodule + if !self.in_import { + path_to_root.push_str("super::"); + } + + // Escape the namespace/package submodules for interface-based ids + match key { + WorldKey::Name(_) => {} + WorldKey::Interface(_) => { + path_to_root.push_str("super::super::"); + } } } + Identifier::StreamOrFuturePayload => path_to_root.push_str("super::super::"), + _ => {} } path_to_root } @@ -461,7 +467,7 @@ macro_rules! {macro_name} {{ let module = format!( "\ {docs} - #[allow(dead_code, clippy::all)] + #[allow(dead_code, unused_imports, clippy::all)] pub mod {snake} {{ {used_static} {module} @@ -489,37 +495,18 @@ macro_rules! {macro_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)) - }, - ) + let name = if let Some(payload_type) = payload_type { + self.full_type_name_owned(payload_type, Identifier::StreamOrFuturePayload) } else { - ("()".into(), "()".into()) + "()".into() }; - if self.gen.future_payloads_emitted.insert(full_name) { + if !self.gen.future_payloads.contains_key(&name) { + let ordinal = self.gen.future_payloads.len(); let (size, align) = if let Some(payload_type) = payload_type { ( self.sizes.size(payload_type), @@ -546,28 +533,12 @@ macro_rules! {macro_name} {{ (String::new(), "let value = ();\n".into()) }; - uwriteln!( - self.src, + let box_ = format!("super::super::{}", self.path_to_box()); + let code = format!( 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 {{ +#[doc(hidden)] +pub mod vtable{ordinal} {{ + fn write(future: u32, value: {name}) -> ::core::pin::Pin<{box_}>> {{ #[cfg(not(target_arch = "wasm32"))] {{ unreachable!(); @@ -587,11 +558,13 @@ impl {stream_and_future_support}::FuturePayload for {name} {{ fn wit_import(_: u32, _: *mut u8) -> u32; }} - unsafe {{ {async_support}::await_future_result(wit_import, future, address).await }} + {box_}::pin(async move {{ + unsafe {{ {async_support}::await_future_result(wit_import, future, address).await }} + }}) }} }} - async fn read(future: u32) -> Option {{ + fn read(future: u32) -> ::core::pin::Pin<{box_}>>> {{ #[cfg(not(target_arch = "wasm32"))] {{ unreachable!(); @@ -609,12 +582,14 @@ impl {stream_and_future_support}::FuturePayload for {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 - }} + {box_}::pin(async move {{ + if unsafe {{ {async_support}::await_future_result(wit_import, future, address).await }} {{ + {lift} + Some(value) + }} else {{ + None + }} + }}) }} }} @@ -685,27 +660,42 @@ impl {stream_and_future_support}::FuturePayload for {name} {{ unsafe {{ drop(reader) }} }} }} + + pub static VTABLE: {async_support}::FutureVtable<{name}> = {async_support}::FutureVtable::<{name}> {{ + write, read, cancel_write, cancel_read, close_writable, close_readable + }}; + + impl super::FuturePayload for {name} {{ + fn new() -> (u32, &'static {async_support}::FutureVtable) {{ + #[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() }}, &VTABLE) + }} + }} + }} }} "#, ); + + self.gen.future_payloads.insert(name, code); } } 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 name = + self.full_type_name_owned(payload_type, Identifier::StreamOrFuturePayload); - 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) { + if !self.gen.stream_payloads.contains_key(&name) { + let ordinal = self.gen.stream_payloads.len(); 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(); @@ -757,28 +747,12 @@ for (index, dst) in values.iter_mut().take(count).enumerate() {{ (address.clone(), lower, address, lift) }; - uwriteln!( - self.src, + let box_ = format!("super::super::{}", self.path_to_box()); + let code = format!( 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 {{ +#[doc(hidden)] +pub mod vtable{ordinal} {{ + fn write(stream: u32, values: &[{name}]) -> ::core::pin::Pin<{box_}>>> {{ #[cfg(not(target_arch = "wasm32"))] {{ unreachable!(); @@ -795,13 +769,20 @@ impl {stream_and_future_support}::StreamPayload for {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 - }} + {box_}::pin(async move {{ + 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 {{ + fn read(stream: u32, values: &mut [::core::mem::MaybeUninit::<{name}>]) -> ::core::pin::Pin<{box_}>>> {{ #[cfg(not(target_arch = "wasm32"))] {{ unreachable!(); @@ -817,14 +798,21 @@ impl {stream_and_future_support}::StreamPayload for {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 + {box_}::pin(async move {{ + 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 + }}) }} }} @@ -895,9 +883,34 @@ impl {stream_and_future_support}::StreamPayload for {name} {{ unsafe {{ drop(reader) }} }} }} + + pub static VTABLE: {async_support}::StreamVtable<{name}> = {async_support}::StreamVtable::<{name}> {{ + write, read, cancel_write, cancel_read, close_writable, close_readable + }}; + + impl super::StreamPayload for {name} {{ + fn new() -> (u32, &'static {async_support}::StreamVtable) {{ + #[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() }}, &VTABLE) + }} + }} + }} }} - "# + "#, ); + + self.gen.stream_payloads.insert(name, code); } } _ => unreachable!(), @@ -1125,7 +1138,7 @@ impl {stream_and_future_support}::StreamPayload for {name} {{ let wasm_module_export_name = match self.identifier { Identifier::Interface(_, key) => Some(self.resolve.name_world_key(key)), Identifier::World(_) => None, - Identifier::None => unreachable!(), + Identifier::StreamOrFuturePayload => 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()); @@ -1686,6 +1699,31 @@ impl {stream_and_future_support}::StreamPayload for {name} {{ } } + pub(crate) fn full_type_name_owned(&mut self, ty: &Type, id: Identifier<'i>) -> String { + self.full_type_name( + ty, + TypeMode { + lifetime: None, + lists_borrowed: false, + style: TypeOwnershipStyle::Owned, + }, + id, + ) + } + + fn full_type_name(&mut self, ty: &Type, mode: TypeMode, id: Identifier<'i>) -> String { + let old_identifier = mem::replace(&mut self.identifier, id); + let name = self.type_name(ty, mode); + self.identifier = old_identifier; + name + } + + fn type_name(&mut self, ty: &Type, mode: TypeMode) -> String { + let old = mem::take(&mut self.src); + self.print_ty(ty, mode); + String::from(mem::replace(&mut self.src, old)) + } + fn print_ty(&mut self, ty: &Type, mode: TypeMode) { // If we have a typedef of a string or a list, the typedef is an alias // for `String` or `Vec`. If this is a borrow, instead of borrowing @@ -2327,21 +2365,27 @@ impl {stream_and_future_support}::StreamPayload for {name} {{ return Some(path_to_root); } else { let mut full_path = String::new(); - if let Identifier::Interface(cur, name) = self.identifier { - if cur == interface { - return None; - } - if !self.in_import { - full_path.push_str("super::"); - } - match name { - WorldKey::Name(_) => { + match self.identifier { + Identifier::Interface(cur, name) => { + if cur == interface { + return None; + } + if !self.in_import { full_path.push_str("super::"); } - WorldKey::Interface(_) => { - full_path.push_str("super::super::super::"); + match name { + WorldKey::Name(_) => { + full_path.push_str("super::"); + } + WorldKey::Interface(_) => { + full_path.push_str("super::super::super::"); + } } } + Identifier::StreamOrFuturePayload => { + full_path.push_str("super::super::"); + } + _ => {} } full_path.push_str(&path); Some(full_path) @@ -2467,13 +2511,6 @@ impl {stream_and_future_support}::StreamPayload for {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() } @@ -2485,7 +2522,12 @@ impl {stream_and_future_support}::StreamPayload for {name} {{ ) -> String { self.needs_runtime_module = true; self.gen.rt_module.insert(item); - format!("_rt::{name_in_runtime_module}") + let prefix = if let Identifier::StreamOrFuturePayload = &self.identifier { + "super::super::" + } else { + "" + }; + format!("{prefix}_rt::{name_in_runtime_module}") } } @@ -2540,7 +2582,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), Identifier::World(_) => unimplemented!("resource exports from worlds"), - Identifier::None => unreachable!(), + Identifier::StreamOrFuturePayload => unreachable!(), }; let box_path = self.path_to_box(); uwriteln!( @@ -2793,7 +2835,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_future(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { - let stream_and_future_support = self.path_to_stream_and_future_support(); + let async_support = self.path_to_async_support(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2803,14 +2845,14 @@ impl<'a> {camel}Borrow<'a>{{ self.push_str(&format!("pub type {}", name.to_upper_camel_case())); self.print_generics(mode.lifetime); self.push_str(" = "); - self.push_str(&format!("{stream_and_future_support}::FutureReader<")); + self.push_str(&format!("{async_support}::FutureReader<")); self.print_optional_ty(ty.as_ref(), mode); self.push_str(">"); self.push_str(";\n"); } fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - let stream_and_future_support = self.path_to_stream_and_future_support(); + let async_support = self.path_to_async_support(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2820,7 +2862,7 @@ impl<'a> {camel}Borrow<'a>{{ self.push_str(&format!("pub type {}", name.to_upper_camel_case())); self.print_generics(mode.lifetime); self.push_str(" = "); - self.push_str(&format!("{stream_and_future_support}::StreamReader<")); + self.push_str(&format!("{async_support}::StreamReader<")); self.print_ty(ty, mode); self.push_str(">"); self.push_str(";\n"); @@ -2923,27 +2965,27 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_future(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { - let stream_and_future_support = self.interface.path_to_stream_and_future_support(); + let async_support = self.interface.path_to_async_support(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, lifetime: None, }; self.interface - .push_str(&format!("{stream_and_future_support}::FutureReader<")); + .push_str(&format!("{async_support}::FutureReader<")); self.interface.print_optional_ty(ty.as_ref(), mode); self.interface.push_str(">"); } 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(); + let async_support = self.interface.path_to_async_support(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, lifetime: None, }; self.interface - .push_str(&format!("{stream_and_future_support}::StreamReader<")); + .push_str(&format!("{async_support}::StreamReader<")); self.interface.print_ty(ty, mode); self.interface.push_str(">"); } diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 754955c47..3b136034c 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -1,7 +1,7 @@ use crate::interface::InterfaceGenerator; use anyhow::{bail, Result}; use heck::*; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::{self, Write as _}; use std::mem; @@ -49,8 +49,8 @@ struct RustWasm { /// Interface names to how they should be generated with: GenerationConfiguration, - future_payloads_emitted: HashSet, - stream_payloads_emitted: HashSet, + future_payloads: IndexMap, + stream_payloads: IndexMap, } #[derive(Default)] @@ -102,7 +102,6 @@ enum RuntimeItem { AsF64, ResourceType, BoxType, - StreamAndFutureSupport, } #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -455,9 +454,74 @@ impl RustWasm { } } self.src.push_str("}\n"); - if emitted.contains(&RuntimeItem::StreamAndFutureSupport) { - self.src - .push_str("#[allow(unused_imports)]\npub use _rt::stream_and_future_support;\n"); + + if !self.future_payloads.is_empty() { + self.src.push_str( + "\ +pub mod wit_future { + #![allow(dead_code, unused_variables, clippy::all)] + + #[doc(hidden)] + pub trait FuturePayload: Unpin + Sized + 'static { + fn new() -> (u32, &'static ::wit_bindgen_rt::async_support::FutureVtable); + }", + ); + for code in self.future_payloads.values() { + self.src.push_str(code); + } + self.src.push_str( + "\ + /// Creates a new Component Model `future` with the specified payload type. + pub fn new() -> (::wit_bindgen_rt::async_support::FutureWriter, ::wit_bindgen_rt::async_support::FutureReader) { + let (handle, vtable) = T::new(); + ::wit_bindgen_rt::async_support::with_entry(handle, |entry| match entry { + ::std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(::wit_bindgen_rt::async_support::Handle::LocalOpen); + } + ::std::collections::hash_map::Entry::Occupied(_) => unreachable!(), + }); + ( + ::wit_bindgen_rt::async_support::FutureWriter::new(handle, vtable), + ::wit_bindgen_rt::async_support::FutureReader::new(handle, vtable), + ) + } +} + ", + ); + } + + if !self.stream_payloads.is_empty() { + self.src.push_str( + "\ +pub mod wit_stream { + #![allow(dead_code, unused_variables, clippy::all)] + + pub trait StreamPayload: Unpin + Sized + 'static { + fn new() -> (u32, &'static ::wit_bindgen_rt::async_support::StreamVtable); + }", + ); + for code in self.stream_payloads.values() { + self.src.push_str(code); + } + self.src.push_str( + "\ + /// Creates a new Component Model `stream` with the specified payload type. + pub fn new() -> (::wit_bindgen_rt::async_support::StreamWriter, ::wit_bindgen_rt::async_support::StreamReader) { + let (handle, vtable) = T::new(); + ::wit_bindgen_rt::async_support::with_entry(handle, |entry| match entry { + ::std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(::wit_bindgen_rt::async_support::Handle::LocalOpen); + } + ::std::collections::hash_map::Entry::Occupied(_) => unreachable!(), + }); + ( + ::wit_bindgen_rt::async_support::StreamWriter::new(handle, vtable), + ::wit_bindgen_rt::async_support::StreamReader::new(handle, vtable), + ) + } +} + ", + ); } } @@ -482,7 +546,6 @@ impl RustWasm { self.rt_module.insert(RuntimeItem::AllocCrate); uwriteln!(self.src, "pub use alloc_crate::vec::Vec;"); } - RuntimeItem::CabiDealloc => { self.rt_module.insert(RuntimeItem::StdAllocModule); self.src.push_str( @@ -691,13 +754,6 @@ 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("}"); - } } } @@ -1306,9 +1362,9 @@ fn compute_module_path(name: &WorldKey, resolve: &Resolve, is_export: bool) -> V } enum Identifier<'a> { - None, World(WorldId), Interface(InterfaceId, &'a WorldKey), + StreamOrFuturePayload, } fn group_by_resource<'a>( From 0d9bffceee8a80c4a4448d5ec96b1ec147286f9a Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 8 Jan 2025 13:30:03 -0700 Subject: [PATCH 4/5] break `streams_and_futures` into two modules Signed-off-by: Joel Dice --- crates/guest-rust/rt/src/async_support.rs | 8 +- .../rt/src/async_support/future_support.rs | 348 ++++++++++++++++++ ...reams_and_futures.rs => stream_support.rs} | 332 +---------------- 3 files changed, 354 insertions(+), 334 deletions(-) create mode 100644 crates/guest-rust/rt/src/async_support/future_support.rs rename crates/guest-rust/rt/src/async_support/{streams_and_futures.rs => stream_support.rs} (55%) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index ac42f3223..1afec3549 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -26,10 +26,12 @@ use { }, }; -mod streams_and_futures; +mod future_support; +mod stream_support; -pub use streams_and_futures::{ - FutureReader, FutureVtable, FutureWriter, StreamReader, StreamVtable, StreamWriter, +pub use { + future_support::{FutureReader, FutureVtable, FutureWriter}, + stream_support::{StreamReader, StreamVtable, StreamWriter}, }; pub use futures; diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs new file mode 100644 index 000000000..93cf71872 --- /dev/null +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -0,0 +1,348 @@ +extern crate std; + +use { + super::Handle, + futures::{ + channel::oneshot, + future::{self, FutureExt}, + }, + std::{ + boxed::Box, + collections::hash_map::Entry, + fmt, + future::{Future, IntoFuture}, + mem::ManuallyDrop, + pin::Pin, + task::{Context, Poll}, + }, +}; + +#[doc(hidden)] +pub struct FutureVtable { + pub write: fn(future: u32, value: T) -> Pin>>, + pub read: fn(future: u32) -> Pin>>>, + pub cancel_write: fn(future: u32), + pub cancel_read: fn(future: u32), + pub close_writable: fn(future: u32), + pub close_readable: fn(future: u32), +} + +/// Represents the writable end of a Component Model `future`. +pub struct FutureWriter { + handle: u32, + vtable: &'static FutureVtable, +} + +impl fmt::Debug for FutureWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureWriter") + .field("handle", &self.handle) + .finish() + } +} + +/// Represents a write operation which may be canceled prior to completion. +pub struct CancelableWrite { + writer: Option>, + future: Pin>>, +} + +impl Future for CancelableWrite { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + let me = self.get_mut(); + match me.future.poll_unpin(cx) { + Poll::Ready(()) => { + me.writer = None; + Poll::Ready(()) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl CancelableWrite { + /// Cancel this write if it hasn't already completed, returning the original `FutureWriter`. + /// + /// This method will panic if the write has already completed. + pub fn cancel(mut self) -> FutureWriter { + self.cancel_mut() + } + + fn cancel_mut(&mut self) -> FutureWriter { + let writer = self.writer.take().unwrap(); + super::with_entry(writer.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalWaiting(_) + | Handle::Read + | Handle::LocalClosed => unreachable!(), + Handle::LocalReady(..) => { + entry.insert(Handle::LocalOpen); + } + Handle::Write => (writer.vtable.cancel_write)(writer.handle), + }, + }); + writer + } +} + +impl Drop for CancelableWrite { + fn drop(&mut self) { + if self.writer.is_some() { + self.cancel_mut(); + } + } +} + +impl FutureWriter { + #[doc(hidden)] + pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + + /// Write the specified value to this `future`. + pub fn write(self, v: T) -> CancelableWrite { + let handle = self.handle; + let vtable = self.vtable; + CancelableWrite { + writer: Some(self), + future: super::with_entry(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| { + super::with_entry(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((vtable.write)(handle, v).map(drop)), + }, + }), + } + } +} + +impl Drop for FutureWriter { + fn drop(&mut self) { + super::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(); + (self.vtable.close_writable)(self.handle); + } + }, + }); + } +} + +/// Represents a read operation which may be canceled prior to completion. +pub struct CancelableRead { + reader: Option>, + future: Pin>>>, +} + +impl Future for CancelableRead { + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + match me.future.poll_unpin(cx) { + Poll::Ready(v) => { + me.reader = None; + Poll::Ready(v) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl CancelableRead { + /// Cancel this read if it hasn't already completed, returning the original `FutureReader`. + /// + /// This method will panic if the read has already completed. + pub fn cancel(mut self) -> FutureReader { + self.cancel_mut() + } + + fn cancel_mut(&mut self) -> FutureReader { + let reader = self.reader.take().unwrap(); + super::with_entry(reader.handle, |entry| match entry { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut entry) => match entry.get() { + Handle::LocalOpen + | Handle::LocalReady(..) + | Handle::Write + | Handle::LocalClosed => unreachable!(), + Handle::LocalWaiting(_) => { + entry.insert(Handle::LocalOpen); + } + Handle::Read => (reader.vtable.cancel_read)(reader.handle), + }, + }); + reader + } +} + +impl Drop for CancelableRead { + fn drop(&mut self) { + if self.reader.is_some() { + self.cancel_mut(); + } + } +} + +/// Represents the readable end of a Component Model `future`. +pub struct FutureReader { + handle: u32, + vtable: &'static FutureVtable, +} + +impl fmt::Debug for FutureReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureReader") + .field("handle", &self.handle) + .finish() + } +} + +impl FutureReader { + #[doc(hidden)] + pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + + #[doc(hidden)] + pub fn from_handle_and_vtable(handle: u32, vtable: &'static FutureVtable) -> Self { + super::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, vtable } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + super::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 = CancelableRead; + + /// 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 { + let handle = self.handle; + let vtable = self.vtable; + CancelableRead { + reader: Some(self), + future: super::with_entry(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 { (vtable.read)(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) { + super::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(); + (self.vtable.close_readable)(self.handle); + } + Handle::Write => unreachable!(), + }, + }); + } +} diff --git a/crates/guest-rust/rt/src/async_support/streams_and_futures.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs similarity index 55% rename from crates/guest-rust/rt/src/async_support/streams_and_futures.rs rename to crates/guest-rust/rt/src/async_support/stream_support.rs index 03bb90a11..ec2ed596f 100644 --- a/crates/guest-rust/rt/src/async_support/streams_and_futures.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -13,7 +13,7 @@ use { collections::hash_map::Entry, convert::Infallible, fmt, - future::{Future, IntoFuture}, + future::Future, iter, mem::{self, ManuallyDrop, MaybeUninit}, pin::Pin, @@ -26,336 +26,6 @@ fn ceiling(x: usize, y: usize) -> usize { (x / y) + if x % y == 0 { 0 } else { 1 } } -#[doc(hidden)] -pub struct FutureVtable { - pub write: fn(future: u32, value: T) -> Pin>>, - pub read: fn(future: u32) -> Pin>>>, - pub cancel_write: fn(future: u32), - pub cancel_read: fn(future: u32), - pub close_writable: fn(future: u32), - pub close_readable: fn(future: u32), -} - -/// Represents the writable end of a Component Model `future`. -pub struct FutureWriter { - handle: u32, - vtable: &'static FutureVtable, -} - -impl fmt::Debug for FutureWriter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FutureWriter") - .field("handle", &self.handle) - .finish() - } -} - -/// Represents a write operation which may be canceled prior to completion. -pub struct CancelableWrite { - writer: Option>, - future: Pin>>, -} - -impl Future for CancelableWrite { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { - let me = self.get_mut(); - match me.future.poll_unpin(cx) { - Poll::Ready(()) => { - me.writer = None; - Poll::Ready(()) - } - Poll::Pending => Poll::Pending, - } - } -} - -impl CancelableWrite { - /// Cancel this write if it hasn't already completed, returning the original `FutureWriter`. - /// - /// This method will panic if the write has already completed. - pub fn cancel(mut self) -> FutureWriter { - self.cancel_mut() - } - - fn cancel_mut(&mut self) -> FutureWriter { - let writer = self.writer.take().unwrap(); - super::with_entry(writer.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalWaiting(_) - | Handle::Read - | Handle::LocalClosed => unreachable!(), - Handle::LocalReady(..) => { - entry.insert(Handle::LocalOpen); - } - Handle::Write => (writer.vtable.cancel_write)(writer.handle), - }, - }); - writer - } -} - -impl Drop for CancelableWrite { - fn drop(&mut self) { - if self.writer.is_some() { - self.cancel_mut(); - } - } -} - -impl FutureWriter { - #[doc(hidden)] - pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { - Self { handle, vtable } - } - - /// Write the specified value to this `future`. - pub fn write(self, v: T) -> CancelableWrite { - let handle = self.handle; - let vtable = self.vtable; - CancelableWrite { - writer: Some(self), - future: super::with_entry(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| { - super::with_entry(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((vtable.write)(handle, v).map(drop)), - }, - }), - } - } -} - -impl Drop for FutureWriter { - fn drop(&mut self) { - super::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(); - (self.vtable.close_writable)(self.handle); - } - }, - }); - } -} - -/// Represents a read operation which may be canceled prior to completion. -pub struct CancelableRead { - reader: Option>, - future: Pin>>>, -} - -impl Future for CancelableRead { - type Output = Option; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let me = self.get_mut(); - match me.future.poll_unpin(cx) { - Poll::Ready(v) => { - me.reader = None; - Poll::Ready(v) - } - Poll::Pending => Poll::Pending, - } - } -} - -impl CancelableRead { - /// Cancel this read if it hasn't already completed, returning the original `FutureReader`. - /// - /// This method will panic if the read has already completed. - pub fn cancel(mut self) -> FutureReader { - self.cancel_mut() - } - - fn cancel_mut(&mut self) -> FutureReader { - let reader = self.reader.take().unwrap(); - super::with_entry(reader.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalReady(..) - | Handle::Write - | Handle::LocalClosed => unreachable!(), - Handle::LocalWaiting(_) => { - entry.insert(Handle::LocalOpen); - } - Handle::Read => (reader.vtable.cancel_read)(reader.handle), - }, - }); - reader - } -} - -impl Drop for CancelableRead { - fn drop(&mut self) { - if self.reader.is_some() { - self.cancel_mut(); - } - } -} - -/// Represents the readable end of a Component Model `future`. -pub struct FutureReader { - handle: u32, - vtable: &'static FutureVtable, -} - -impl fmt::Debug for FutureReader { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FutureReader") - .field("handle", &self.handle) - .finish() - } -} - -impl FutureReader { - #[doc(hidden)] - pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { - Self { handle, vtable } - } - - #[doc(hidden)] - pub fn from_handle_and_vtable(handle: u32, vtable: &'static FutureVtable) -> Self { - super::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, vtable } - } - - #[doc(hidden)] - pub fn into_handle(self) -> u32 { - super::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 = CancelableRead; - - /// 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 { - let handle = self.handle; - let vtable = self.vtable; - CancelableRead { - reader: Some(self), - future: super::with_entry(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 { (vtable.read)(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) { - super::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(); - (self.vtable.close_readable)(self.handle); - } - Handle::Write => unreachable!(), - }, - }); - } -} - #[doc(hidden)] pub struct StreamVtable { pub write: fn(future: u32, values: &[T]) -> Pin>>>, From 5b7e6b9c00eaf7342e5a5831eed9436619c86e8c Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 8 Jan 2025 14:04:06 -0700 Subject: [PATCH 5/5] restore previously-removed feature check in Rust macro Signed-off-by: Joel Dice --- crates/guest-rust/Cargo.toml | 2 +- crates/guest-rust/macro/src/lib.rs | 6 ++++++ crates/rust/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 4eb6ee28b..3fb139adf 100644 --- a/crates/guest-rust/Cargo.toml +++ b/crates/guest-rust/Cargo.toml @@ -19,4 +19,4 @@ wit-bindgen-rt = { path = "./rt", version = "0.36.0", features = ["bitflags"] } default = ["macros", "realloc", "async"] macros = ["dep:wit-bindgen-rust-macro"] realloc = [] -async = ["macros", "wit-bindgen-rt/async"] +async = ["macros", "wit-bindgen-rt/async", "wit-bindgen-rust-macro/async"] diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 69f71fd06..24a04f291 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -151,6 +151,12 @@ impl Parse for Config { 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; } } diff --git a/crates/rust/Cargo.toml b/crates/rust/Cargo.toml index d2e0b69be..3a6ea5952 100644 --- a/crates/rust/Cargo.toml +++ b/crates/rust/Cargo.toml @@ -28,7 +28,7 @@ prettyplease = { workspace = true } [dev-dependencies] futures = { workspace = true } -wit-bindgen = { path = '../guest-rust' } +wit-bindgen = { path = '../guest-rust', features = ['async'] } wit-bindgen-rt = { path = '../guest-rust/rt' } test-helpers = { path = '../test-helpers' } # For use with the custom attributes test