diff --git a/Cargo.lock b/Cargo.lock index a68ba8ff5..1793de3d1 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.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -873,7 +908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", "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.1", "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.221.2", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.221.2", ] [[package]] @@ -1745,11 +1789,19 @@ dependencies = [ "wasmparser 0.220.0", ] +[[package]] +name = "wasm-encoder" +version = "0.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" +dependencies = [ + "leb128", + "wasmparser 0.221.2", +] + [[package]] name = "wasm-metadata" -version = "0.220.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3e5f5920c5abfc45573c89b07b38efdaae1515ef86f83dad12d60e50ecd62b" +version = "0.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" dependencies = [ "anyhow", "indexmap", @@ -1757,8 +1809,9 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.220.0", - "wasmparser 0.220.0", + "url", + "wasm-encoder 0.221.2", + "wasmparser 0.221.2", ] [[package]] @@ -1781,9 +1834,17 @@ version = "0.220.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.14.5", + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" +dependencies = [ + "bitflags", + "hashbrown 0.15.1", "indexmap", "semver", "serde", @@ -1852,7 +1913,7 @@ dependencies = [ "wasmtime-slab", "wasmtime-versioned-export-macros", "wasmtime-winch", - "wat", + "wat 1.220.0", "windows-sys 0.52.0", ] @@ -2110,6 +2171,18 @@ dependencies = [ "wasm-encoder 0.220.0", ] +[[package]] +name = "wast" +version = "221.0.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.221.2", +] + [[package]] name = "wat" version = "1.220.0" @@ -2119,6 +2192,14 @@ dependencies = [ "wast 220.0.0", ] +[[package]] +name = "wat" +version = "1.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" +dependencies = [ + "wast 221.0.2", +] + [[package]] name = "wiggle" version = "25.0.1" @@ -2335,11 +2416,11 @@ dependencies = [ "clap", "heck 0.5.0", "test-helpers", - "wasm-encoder 0.220.0", + "wasm-encoder 0.221.2", "wasm-metadata", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.221.2", ] [[package]] @@ -2350,8 +2431,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-artifacts", - "wasm-encoder 0.220.0", - "wasmparser 0.220.0", + "wasm-encoder 0.221.2", + "wasmparser 0.221.2", "wasmtime", "wasmtime-wasi", "wit-bindgen-c", @@ -2363,7 +2444,7 @@ dependencies = [ "wit-bindgen-rust", "wit-bindgen-teavm-java", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.221.2", ] [[package]] @@ -2372,7 +2453,7 @@ version = "0.36.0" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.220.0", + "wit-parser 0.221.2", ] [[package]] @@ -2384,12 +2465,12 @@ dependencies = [ "heck 0.5.0", "indexmap", "test-helpers", - "wasm-encoder 0.220.0", + "wasm-encoder 0.221.2", "wasm-metadata", - "wasmparser 0.220.0", + "wasmparser 0.221.2", "wit-bindgen-core", "wit-component", - "wit-parser 0.220.0", + "wit-parser 0.221.2", ] [[package]] @@ -2431,6 +2512,8 @@ name = "wit-bindgen-rt" version = "0.36.0" dependencies = [ "bitflags", + "futures", + "once_cell", ] [[package]] @@ -2439,6 +2522,7 @@ version = "0.36.0" dependencies = [ "anyhow", "clap", + "futures", "heck 0.5.0", "indexmap", "prettyplease", @@ -2449,6 +2533,7 @@ dependencies = [ "wasm-metadata", "wit-bindgen", "wit-bindgen-core", + "wit-bindgen-rt", "wit-component", ] @@ -2480,9 +2565,8 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.220.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ccedf54cc65f287da268d64d2bf4f7530d2cfb2296ffbe3ad5f65567e4cf53" +version = "0.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" dependencies = [ "anyhow", "bitflags", @@ -2491,11 +2575,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.220.0", + "wasm-encoder 0.221.2", "wasm-metadata", - "wasmparser 0.220.0", - "wat", - "wit-parser 0.220.0", + "wasmparser 0.221.2", + "wat 1.221.2", + "wit-parser 0.221.2", ] [[package]] @@ -2518,9 +2602,8 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.220.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7117ce3adc0b4354b46dc1cf3190b00b333e65243d244c613ffcc58bdec84d" +version = "0.221.2" +source = "git+https://github.com/dicej/wasm-tools?branch=async#c8c3f47e46f8e1ef1cc95114cdf714281ca0a92e" dependencies = [ "anyhow", "id-arena", @@ -2531,7 +2614,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.220.0", + "wasmparser 0.221.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8232acf60..798f840df 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 = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wasm-encoder = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wasm-metadata = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "async" } +wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "async" } wit-bindgen-core = { path = 'crates/core', version = '0.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 = ["wit-bindgen-rust/async"] [dev-dependencies] heck = { workspace = true } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 99eb5c689..fd7a5cc81 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -718,6 +718,7 @@ fn is_prim_type_id(resolve: &Resolve, id: TypeId) -> bool { | TypeDefKind::Result(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) + | TypeDefKind::ErrorContext | TypeDefKind::Unknown => false, } } @@ -779,8 +780,9 @@ pub fn push_ty_name(resolve: &Resolve, ty: &Type, src: &mut String) { src.push_str("list_"); push_ty_name(resolve, ty, src); } - TypeDefKind::Future(_) => unimplemented!(), - TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Future(_) => todo!(), + TypeDefKind::Stream(_) => todo!(), + TypeDefKind::ErrorContext => todo!(), TypeDefKind::Handle(Handle::Own(resource)) => { src.push_str("own_"); push_ty_name(resolve, &Type::Id(*resource), src); @@ -992,6 +994,7 @@ impl Return { TypeDefKind::Future(_) => todo!("return_single for future"), TypeDefKind::Stream(_) => todo!("return_single for stream"), + TypeDefKind::ErrorContext => todo!("return_single for error-context"), TypeDefKind::Resource => todo!("return_single for resource"), TypeDefKind::Unknown => unreachable!(), } @@ -1339,6 +1342,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); } @@ -1427,12 +1445,16 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> todo!("print_anonymous_type for future"); } - fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Stream, _docs: &Docs) { + fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { todo!("print_anonymous_type for stream"); } - fn anonymous_typ_type(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { - todo!("print_anonymous_type for typ"); + fn anonymous_type_error_context(&mut self) { + todo!("print_anonymous_type for error-context"); + } + + fn anonymous_type_type(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { + todo!("print_anonymous_type for type"); } } @@ -1605,6 +1627,7 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Future(_) => todo!("print_dtor for future"), TypeDefKind::Stream(_) => todo!("print_dtor for stream"), + TypeDefKind::ErrorContext => todo!("print_dtor for error-context"), TypeDefKind::Resource => {} TypeDefKind::Handle(Handle::Borrow(id) | Handle::Own(id)) => { self.free(&Type::Id(*id), "*ptr"); @@ -1750,6 +1773,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut f, + false, ); let FunctionBindgen { @@ -1822,6 +1846,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut f, + false, ); let FunctionBindgen { src, .. } = f; self.src.c_adapters(&src); @@ -1852,7 +1877,7 @@ impl InterfaceGenerator<'_> { let mut f = FunctionBindgen::new(self, c_sig, &import_name); f.params = params; - abi::post_return(f.gen.resolve, func, &mut f); + abi::post_return(f.gen.resolve, func, &mut f, false); let FunctionBindgen { src, .. } = f; self.src.c_fns(&src); self.src.c_fns("}\n"); @@ -2075,17 +2100,8 @@ impl InterfaceGenerator<'_> { TypeDefKind::List(ty) => self.contains_droppable_borrow(ty), - TypeDefKind::Future(r) => r - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)), - - TypeDefKind::Stream(s) => { - s.element - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)) - || s.end - .as_ref() - .map_or(false, |ty| self.contains_droppable_borrow(ty)) + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext => { + false } TypeDefKind::Type(ty) => self.contains_droppable_borrow(ty), @@ -2753,7 +2769,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src.push_str(");\n"); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let mut args = String::new(); for (i, (op, (byref, _))) in operands.iter().zip(&self.sig.params).enumerate() { if i > 0 { @@ -3037,6 +3053,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "}}"); } + Instruction::Flush { amt } => { + results.extend(operands.iter().take(*amt).map(|v| v.clone())); + } + i => unimplemented!("{:?}", i), } } @@ -3145,6 +3165,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/Cargo.toml b/crates/core/Cargo.toml index 60935ef8d..bc476ea80 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -18,3 +18,7 @@ doctest = false wit-parser = { workspace = true } anyhow = { workspace = true } heck = { workspace = true } + +[features] +default = ["async"] +async = [] diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index f0175afaa..027e4a0d0 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1,7 +1,7 @@ pub use wit_parser::abi::{AbiVariant, WasmSignature, WasmType}; use wit_parser::{ - Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, Results, SizeAlign, - Tuple, Type, TypeDefKind, TypeId, Variant, + ElementInfo, Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, Results, + SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, }; // Helper macro for defining instructions without having to have tons of @@ -350,6 +350,40 @@ def_instruction! { ty: TypeId, } : [1] => [1], + /// Create an `i32` from a future. + FutureLower { + payload: &'a Option, + ty: TypeId, + } : [1] => [1], + + /// Create a future from an `i32`. + FutureLift { + payload: &'a Option, + ty: TypeId, + } : [1] => [1], + + /// Create an `i32` from a stream. + StreamLower { + payload: &'a Type, + ty: TypeId, + } : [1] => [1], + + /// Create a stream from an `i32`. + StreamLift { + payload: &'a Type, + ty: TypeId, + } : [1] => [1], + + /// Create an `i32` from an error-context. + ErrorContextLower { + ty: TypeId, + } : [1] => [1], + + /// Create a error-context from an `i32`. + ErrorContextLift { + ty: TypeId, + } : [1] => [1], + /// Pops a tuple value off the stack, decomposes the tuple to all of /// its fields, and then pushes the fields onto the stack. TupleLower { @@ -470,7 +504,8 @@ def_instruction! { /// Note that this will be used for async functions. CallInterface { func: &'a Function, - } : [func.params.len()] => [func.results.len()], + async_: bool, + } : [func.params.len()] => [if *async_ { 1 } else { func.results.len() }], /// Returns `amt` values on the stack. This is always the last /// instruction. @@ -519,6 +554,39 @@ def_instruction! { GuestDeallocateVariant { blocks: usize, } : [1] => [0], + + /// Allocate the parameter and/or return areas to use for an + /// async-lowered import call. + /// + /// This cannot be allocated on the (shadow-)stack since it needs to + /// remain valid until the callee has finished using the buffers, which + /// may be after we pop the current stack frame. + AsyncMalloc { size: usize, align: usize } : [0] => [1], + + /// Call an async-lowered import. + /// + /// `size` and `align` are used to deallocate the parameter area + /// allocated using `AsyncMalloc` after the callee task returns a value. + AsyncCallWasm { name: &'a str, size: usize, align: usize } : [2] => [0], + + /// Generate code to run after `CallInterface` for an async-lifted export. + /// + /// For example, this might include task management for the + /// future/promise/task returned by the call made for `CallInterface`. + AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len() + 1], + + /// Call `task.return` for an async-lifted export once the task returned + /// by `CallInterface` and managed by `AsyncPostCallInterface` + /// yields a value. + AsyncCallReturn { name: &'a str, params: &'a [WasmType] } : [params.len()] => [0], + + /// Force the evaluation of the specified number of expressions and push + /// the results to the stack. + /// + /// This is useful prior to disposing of temporary variables and/or + /// allocations which are referenced by one or more not-yet-evaluated + /// expressions. + Flush { amt: usize } : [*amt] => [*amt], } } @@ -683,8 +751,50 @@ pub fn call( lift_lower: LiftLower, func: &Function, bindgen: &mut impl Bindgen, + async_: bool, ) { - Generator::new(resolve, variant, lift_lower, bindgen).call(func); + if async_ && !cfg!(feature = "async") { + panic!("must enable `async` feature to lift or lower using the async ABI"); + } + + Generator::new(resolve, variant, lift_lower, bindgen, async_).call(func); +} + +pub fn lower_to_memory( + resolve: &Resolve, + bindgen: &mut B, + address: B::Operand, + value: B::Operand, + ty: &Type, +) { + // TODO: refactor so we don't need to pass in a bunch of unused dummy parameters: + let mut generator = Generator::new( + resolve, + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + bindgen, + true, + ); + generator.stack.push(value); + generator.write_to_memory(ty, address, 0); +} + +pub fn lift_from_memory( + resolve: &Resolve, + bindgen: &mut B, + address: B::Operand, + ty: &Type, +) -> B::Operand { + // TODO: refactor so we don't need to pass in a bunch of unused dummy parameters: + let mut generator = Generator::new( + resolve, + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + bindgen, + true, + ); + generator.read_from_memory(ty, address, 0); + generator.stack.pop().unwrap() } /// Used in a similar manner as the `Interface::call` function except is @@ -693,12 +803,13 @@ pub fn call( /// This is only intended to be used in guest generators for exported /// functions and will primarily generate `GuestDeallocate*` instructions, /// plus others used as input to those instructions. -pub fn post_return(resolve: &Resolve, func: &Function, bindgen: &mut impl Bindgen) { +pub fn post_return(resolve: &Resolve, func: &Function, bindgen: &mut impl Bindgen, async_: bool) { Generator::new( resolve, AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, bindgen, + async_, ) .post_return(func); } @@ -734,7 +845,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 +868,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 +882,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 +898,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 +1026,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 +1038,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 +1066,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 +1099,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 +1138,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 +1178,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 +1402,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 +1600,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 +1682,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 +1775,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 +1877,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 +1964,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 +2140,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..ccebe94d1 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -22,6 +22,22 @@ pub enum Direction { pub trait WorldGenerator { fn generate(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { + // TODO: Should we refine this test to inspect only types reachable from + // the specified world? + if !cfg!(feature = "async") + && resolve.types.iter().any(|(_, ty)| { + matches!( + ty.kind, + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::ErrorContext + ) + }) + { + anyhow::bail!( + "must enable `async` feature when using WIT files \ + containing future, stream, or error types" + ); + } + let world = &resolve.worlds[id]; self.preprocess(resolve, id); @@ -154,6 +170,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 +193,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 +211,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 +225,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/lib.rs b/crates/csharp/src/lib.rs index a771954f8..83ed7d6cd 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -1130,6 +1130,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -1253,6 +1254,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -1997,6 +1999,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!(); } @@ -2798,7 +2815,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let module = self.gen.name; let func_name = self.func_name.to_upper_camel_case(); let interface_name = CSharp::get_class_name_from_qualified_name(module).1; @@ -3109,6 +3126,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/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..3fb139adf 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", "wit-bindgen-rust-macro/async"] diff --git a/crates/guest-rust/macro/Cargo.toml b/crates/guest-rust/macro/Cargo.toml index 984a1bffc..f30dd8780 100644 --- a/crates/guest-rust/macro/Cargo.toml +++ b/crates/guest-rust/macro/Cargo.toml @@ -24,3 +24,6 @@ anyhow = { workspace = true } syn = { workspace = true } prettyplease = { workspace = true } +[features] +default = ["async"] +async = ["wit-bindgen-rust/async"] diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 5648eff71..24a04f291 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -8,7 +8,7 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{braced, token, LitStr, Token}; use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; -use wit_bindgen_rust::{Opts, Ownership, WithOption}; +use wit_bindgen_rust::{AsyncConfig, Opts, Ownership, WithOption}; #[proc_macro] pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -46,6 +46,7 @@ struct Config { resolve: Resolve, world: WorldId, files: Vec, + debug: bool, } /// The source of the wit package definition @@ -63,6 +64,8 @@ impl Parse for Config { let mut world = None; let mut source = None; let mut features = Vec::new(); + let mut async_configured = false; + let mut debug = false; if input.peek(token::Brace) { let content; @@ -140,6 +143,22 @@ impl Parse for Config { Opt::DisableCustomSectionLinkHelpers(disable) => { opts.disable_custom_section_link_helpers = disable.value(); } + Opt::Debug(enable) => { + debug = enable.value(); + } + Opt::Async(val, span) => { + if async_configured { + return Err(Error::new(span, "cannot specify second async config")); + } + async_configured = true; + if !matches!(val, AsyncConfig::None) && !cfg!(feature = "async") { + return Err(Error::new( + span, + "must enable `async` feature to enable async imports and/or exports", + )); + } + opts.async_ = val; + } } } } else { @@ -159,6 +178,7 @@ impl Parse for Config { resolve, world, files, + debug, }) } } @@ -254,7 +274,7 @@ impl Config { // place a formatted version of the expanded code into a file. This file // will then show up in rustc error messages for any codegen issues and can // be inspected manually. - if std::env::var("WIT_BINDGEN_DEBUG").is_ok() { + if std::env::var("WIT_BINDGEN_DEBUG").is_ok() || self.debug { static INVOCATION: AtomicUsize = AtomicUsize::new(0); let root = Path::new(env!("DEBUG_OUTPUT_DIR")); let world_name = &self.resolve.worlds[self.world].name; @@ -313,6 +333,8 @@ mod kw { syn::custom_keyword!(generate_unused_types); syn::custom_keyword!(features); syn::custom_keyword!(disable_custom_section_link_helpers); + syn::custom_keyword!(imports); + syn::custom_keyword!(debug); } #[derive(Clone)] @@ -342,6 +364,11 @@ impl From for wit_bindgen_rust::ExportKey { } } +enum AsyncConfigSomeKind { + Imports, + Exports, +} + enum Opt { World(syn::LitStr), Path(Span, Vec), @@ -366,6 +393,8 @@ enum Opt { GenerateUnusedTypes(syn::LitBool), Features(Vec), DisableCustomSectionLinkHelpers(syn::LitBool), + Async(AsyncConfig, Span), + Debug(syn::LitBool), } impl Parse for Opt { @@ -513,6 +542,34 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::DisableCustomSectionLinkHelpers(input.parse()?)) + } else if l.peek(kw::debug) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Debug(input.parse()?)) + } else if l.peek(Token![async]) { + let span = input.parse::()?.span; + input.parse::()?; + if input.peek(syn::LitBool) { + if input.parse::()?.value { + Ok(Opt::Async(AsyncConfig::All, span)) + } else { + Ok(Opt::Async(AsyncConfig::None, span)) + } + } else { + let mut imports = Vec::new(); + let mut exports = Vec::new(); + let contents; + syn::braced!(contents in input); + for (kind, values) in + contents.parse_terminated(parse_async_some_field, Token![,])? + { + match kind { + AsyncConfigSomeKind::Imports => imports = values, + AsyncConfigSomeKind::Exports => exports = values, + } + } + Ok(Opt::Async(AsyncConfig::Some { imports, exports }, span)) + } } else { Err(l.error()) } @@ -571,3 +628,27 @@ fn fmt(input: &str) -> Result { let syntax_tree = syn::parse_file(&input)?; Ok(prettyplease::unparse(&syntax_tree)) } + +fn parse_async_some_field(input: ParseStream<'_>) -> Result<(AsyncConfigSomeKind, Vec)> { + let lookahead = input.lookahead1(); + let kind = if lookahead.peek(kw::imports) { + input.parse::()?; + input.parse::()?; + AsyncConfigSomeKind::Imports + } else if lookahead.peek(kw::exports) { + input.parse::()?; + input.parse::()?; + AsyncConfigSomeKind::Exports + } else { + return Err(lookahead.error()); + }; + + let list; + syn::bracketed!(list in input); + let fields = list.parse_terminated(Parse::parse, Token![,])?; + + Ok(( + kind, + fields.iter().map(|s: &syn::LitStr| s.value()).collect(), + )) +} diff --git a/crates/guest-rust/rt/Cargo.toml b/crates/guest-rust/rt/Cargo.toml index d038f3fcc..e84f04d18 100644 --- a/crates/guest-rust/rt/Cargo.toml +++ b/crates/guest-rust/rt/Cargo.toml @@ -12,3 +12,9 @@ Runtime support for the `wit-bindgen` crate [dependencies] # Optionally re-export the version of bitflags used by wit-bindgen. bitflags = { workspace = true, optional = true } +futures = { version = "0.3.30", optional = true } +once_cell = { version = "1.19.0", optional = true } + +[features] +default = ["async"] +async = ["dep:futures", "dep:once_cell"] diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs new file mode 100644 index 000000000..a2a1a285e --- /dev/null +++ b/crates/guest-rust/rt/src/async_support.rs @@ -0,0 +1,459 @@ +#![deny(missing_docs)] +#![allow(static_mut_refs)] + +use { + futures::{ + channel::oneshot, + future::FutureExt, + stream::{FuturesUnordered, StreamExt}, + }, + once_cell::sync::Lazy, + std::{ + alloc::{self, Layout}, + any::Any, + collections::hash_map, + collections::HashMap, + fmt::{self, Debug, Display}, + future::Future, + pin::Pin, + ptr, + sync::Arc, + task::{Context, Poll, Wake, Waker}, + }, +}; + +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..c9a63cf71 100644 --- a/crates/guest-rust/rt/src/lib.rs +++ b/crates/guest-rust/rt/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(feature = "async"), no_std)] extern crate alloc; @@ -112,3 +112,11 @@ pub fn run_ctors_once() { } } } + +/// Support for using the Component Model Async ABI +#[cfg(not(feature = "async"))] +pub mod async_support {} + +/// Support for using the Component Model Async ABI +#[cfg(feature = "async")] +pub mod async_support; diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index ae6380003..e41cb7a3a 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -413,28 +413,14 @@ impl InterfaceGenerator<'_> { self.push_str("future"); } }, - TypeDefKind::Stream(s) => match (s.element, s.end) { - (Some(element), Some(end)) => { - self.push_str("stream<"); - self.print_ty(&element); - self.push_str(", "); - self.print_ty(&end); - self.push_str(">"); - } - (None, Some(end)) => { - self.push_str("stream<_, "); - self.print_ty(&end); - self.push_str(">"); - } - (Some(element), None) => { - self.push_str("stream<"); - self.print_ty(&element); - self.push_str(">"); - } - (None, None) => { - self.push_str("stream"); - } - }, + TypeDefKind::Stream(t) => { + self.push_str("stream<"); + self.print_ty(t); + self.push_str(">"); + } + TypeDefKind::ErrorContext => { + self.push_str("error-context"); + } TypeDefKind::Handle(Handle::Own(ty)) => { self.push_str("own<"); self.print_ty(&Type::Id(*ty)); @@ -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..ddaa117e2 100644 --- a/crates/rust/Cargo.toml +++ b/crates/rust/Cargo.toml @@ -27,8 +27,14 @@ 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"] } serde_json = "1" + +[features] +default = ["async"] +async = ["wit-bindgen-core/async"] diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 1e9dc9ff1..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..22efce8f8 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,18 @@ pub struct Opts { /// library-based usage of `generate!` prone to breakage. #[cfg_attr(feature = "clap", arg(long))] pub disable_custom_section_link_helpers: bool, + + /// Determines which functions to lift or lower `async`, if any. + /// + /// Accepted values are: + /// - none + /// - all + /// - some=[,...], where each is of the form: + /// - import: or + /// - export: + #[cfg_attr(all(feature = "clap", feature = "async"), arg(long = "async", value_parser = parse_async))] + #[cfg_attr(all(feature = "clap", not(feature = "async")), skip)] + pub async_: AsyncConfig, } impl Opts { @@ -254,7 +317,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 +446,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 +456,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 +692,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 +872,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 +1048,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 +1058,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 +1076,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 +1092,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 +1115,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 +1135,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 +1157,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 +1307,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..c8be4e3db 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -51,6 +51,19 @@ mod codegen_tests { #[test] fn works() {} } + + #[cfg(feature = "async")] + mod async_ { + wit_bindgen::generate!({ + path: $test, + stubs, + export_prefix: "[async-prefix]", + generate_all, + }); + + #[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..82454cda9 100644 --- a/tests/runtime/flavorful/wasm.rs +++ b/tests/runtime/flavorful/wasm.rs @@ -44,7 +44,6 @@ impl Guest for Component { assert!(errno_result().is_err()); MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); fn assert_error() {} assert_error::(); @@ -107,7 +106,6 @@ impl exports::test::flavorful::test::Guest for Component { fn errno_result() -> Result<(), MyErrno> { MyErrno::A.to_string(); - format!("{:?}", MyErrno::A); fn assert_error() {} assert_error::(); Err(MyErrno::B) diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 0b3ae64f3..53bd38d7c 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -229,10 +229,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { if compiler.ends_with("++") { cmd.arg("-Wno-deprecated"); } - println!("{:?}", cmd); + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -301,10 +301,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { cmd.arg(&out_wasm); cmd.arg(format!("{snake}.go")); cmd.current_dir(&out_dir); - + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -571,10 +571,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { .arg("--self-contained") .arg("-o") .arg(&out_wasm); - + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() { @@ -735,9 +735,10 @@ fn tests(name: &str, dir_name: &str) -> Result> { .arg("/p:UseAppHost=false") .arg("-o") .arg(&out_wasm); + let command = format!("{cmd:?}"); let output = match cmd.output() { Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), + Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), }; if !output.status.success() {