From d9f7c6f5339877c46206a13413f9991d0f04fdcc Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 22 Mar 2024 17:33:31 -0600 Subject: [PATCH 1/7] first draft of async lift/lower for Rust generator Signed-off-by: Joel Dice --- crates/c/src/lib.rs | 6 +- crates/core/src/abi.rs | 274 ++++++++++++++++++++++------- crates/csharp/src/lib.rs | 9 +- crates/guest-rust/macro/src/lib.rs | 65 ++++++- crates/rust/src/async_support.rs | 45 +++++ crates/rust/src/bindgen.rs | 92 +++++++++- crates/rust/src/interface.rs | 124 +++++++++---- crates/rust/src/lib.rs | 57 ++++-- crates/teavm-java/src/lib.rs | 9 +- 9 files changed, 564 insertions(+), 117 deletions(-) create mode 100644 crates/rust/src/async_support.rs diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index e61ae7468..4f402db80 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1653,6 +1653,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut f, + false, ); let FunctionBindgen { @@ -1725,6 +1726,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut f, + false, ); let FunctionBindgen { src, .. } = f; self.src.c_adapters(&src); @@ -1755,7 +1757,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"); @@ -2654,7 +2656,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 { diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index ef4e5e10e..5d30256c5 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -470,7 +470,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 +520,20 @@ def_instruction! { GuestDeallocateVariant { blocks: usize, } : [1] => [0], + + AsyncMalloc { size: usize, align: usize } : [0] => [1], + + AsyncCallWasm { name: &'a str, size: usize, align: usize } : [3] => [0], + + AsyncCallStart { + name: &'a str, + params: &'a [WasmType], + results: &'a [WasmType] + } : [params.len()] => [results.len()], + + AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len()], + + AsyncCallReturn { name: &'a str, params: &'a [WasmType] } : [params.len()] => [0], } } @@ -683,8 +698,9 @@ pub fn call( lift_lower: LiftLower, func: &Function, bindgen: &mut impl Bindgen, + async_: bool, ) { - Generator::new(resolve, variant, lift_lower, bindgen).call(func); + Generator::new(resolve, variant, lift_lower, bindgen, async_).call(func); } /// Used in a similar manner as the `Interface::call` function except is @@ -693,12 +709,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); } @@ -757,6 +774,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 +788,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,70 +804,112 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn call(&mut self, func: &Function) { + const MAX_FLAT_PARAMS: usize = 16; + const MAX_FLAT_RESULTS: usize = 1; + 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_) { + todo!("implement host-side support for async lift/lower"); + } + + 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)); + self_.write_to_memory(ty, ptr.clone(), offset as i32); + offset += self_.bindgen.sizes().size(ty); } - } else { - // ... otherwise if parameters are indirect space is - // allocated from them and each argument is lowered - // individually into memory. + + self_.stack.push(ptr); + }; + + let params_size_align = if self.async_ { let (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(size, align), - // When calling a wasm module from the outside, though, - // malloc needs to be called. - AbiVariant::GuestExport => { - self.emit(&Instruction::Malloc { - realloc: "cabi_realloc", - size, - align, - }); - self.stack.pop().unwrap() + .record(func.params.iter().map(|(_, ty)| ty)); + self.emit(&Instruction::AsyncMalloc { size, align }); + 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)); - self.write_to_memory(ty, ptr.clone(), offset as i32); - offset += self.bindgen.sizes().size(ty); + } else { + // ... otherwise if parameters are indirect space is + // allocated from them and each argument is lowered + // individually into memory. + let (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(size, align), + // When calling a wasm module from the outside, though, + // malloc needs to be called. + AbiVariant::GuestExport => { + self.emit(&Instruction::Malloc { + realloc: "cabi_realloc", + size, + align, + }); + self.stack.pop().unwrap() + } + }; + 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 (size, align) = self.bindgen.sizes().params(func.results.iter_types()); - let ptr = self.bindgen.return_pointer(size, align); + let dealloc_size_align = if let Some((params_size, params_align)) = + params_size_align + { + let (size, align) = self.bindgen.sizes().record(func.results.iter_types()); + self.emit(&Instruction::AsyncMalloc { size, align }); + let ptr = self.stack.pop().unwrap(); self.return_pointer = Some(ptr.clone()); self.stack.push(ptr); - } + // ... and another return pointer for the call handle + self.stack.push(self.bindgen.return_pointer(4, 4)); + + assert_eq!(self.stack.len(), 3); + self.emit(&Instruction::AsyncCallWasm { + name: &func.name, + size: params_size, + align: params_align, + }); + Some((size, align)) + } else { + if self.variant == AbiVariant::GuestImport && sig.retptr { + let (size, align) = self.bindgen.sizes().params(func.results.iter_types()); + let ptr = self.bindgen.return_pointer(size, align); + 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. @@ -872,7 +934,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { AbiVariant::GuestExport => self.stack.pop().unwrap(), }; - self.read_results_from_memory(&func.results, ptr, 0); + self.read_results_from_memory(&func.results, ptr.clone(), 0); + + if let Some((size, align)) = dealloc_size_align { + self.stack.push(ptr); + self.emit(&Instruction::GuestDeallocate { size, align }); + } } self.emit(&Instruction::Return { @@ -881,7 +948,53 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); } LiftLower::LiftArgsLowerResults => { - if !sig.indirect_params { + 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)); + self_.read_from_memory(ty, ptr.clone(), offset as i32); + offset += self_.bindgen.sizes().size(ty); + } + }; + + if self.async_ { + let mut params = Vec::new(); + for (_, ty) in func.params.iter() { + self.resolve.push_flat(ty, &mut params); + } + + let name = &format!("[start]{}", func.name); + + if params.len() > MAX_FLAT_RESULTS { + let (size, align) = self + .bindgen + .sizes() + .params(func.params.iter().map(|(_, ty)| ty)); + let ptr = self.bindgen.return_pointer(size, align); + self.stack.push(ptr.clone()); + self.emit(&Instruction::AsyncCallStart { + name, + params: &[WasmType::Pointer], + results: &[], + }); + self.stack.push(ptr); + read_from_memory(self); + } else { + self.emit(&Instruction::AsyncCallStart { + name, + params: &[], + results: ¶ms, + }); + for (_, ty) in func.params.iter() { + self.lift(ty); + } + } + } else if !sig.indirect_params { // If parameters are not passed indirectly then we lift each // argument in succession from the component wasm types that // make-up the type. @@ -900,23 +1013,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)); - self.read_from_memory(ty, ptr.clone(), offset as i32); - offset += self.bindgen.sizes().size(ty); - } + 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_, + }); + + 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 so after - // it's been read by the guest we need to deallocate it. + // 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 (size, align) = self .bindgen .sizes() @@ -926,7 +1049,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 @@ -968,10 +1091,27 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - self.emit(&Instruction::Return { - func, - amt: sig.results.len(), - }); + if let Some(results) = async_results { + let name = &format!("[return]{}", func.name); + + self.emit(&Instruction::AsyncCallReturn { + name, + params: &if results.len() > MAX_FLAT_PARAMS { + vec![WasmType::Pointer] + } else { + results + }, + }); + self.emit(&Instruction::ConstZero { + tys: &[WasmType::Pointer], + }); + self.emit(&Instruction::Return { func, amt: 1 }); + } else { + self.emit(&Instruction::Return { + func, + amt: sig.results.len(), + }); + } } } diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index b2dce297d..bfaf06476 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -968,6 +968,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -1024,6 +1025,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -2061,7 +2063,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::CallInterface { func } => { + Instruction::CallInterface { func, .. } => { let module = self.gen.name.to_string(); let func_name = self.func_name.to_upper_camel_case(); let interface_name = CSharp::get_class_name_from_qualified_name(module).1; @@ -2140,6 +2142,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { name: _, ty: _dir, } => todo!("HandleLeft"), + + Instruction::AsyncMalloc { .. } + | Instruction::AsyncCallStart { .. } + | Instruction::AsyncPostCallInterface { .. } + | Instruction::AsyncCallReturn { .. } => todo!(), } } diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index e7636a751..74b2c2ad6 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -7,7 +7,7 @@ use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::{braced, token, Token}; use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId}; -use wit_bindgen_rust::{Opts, Ownership}; +use wit_bindgen_rust::{AsyncConfig, Opts, Ownership}; #[proc_macro] pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -46,6 +46,7 @@ impl Parse for Config { let mut opts = Opts::default(); let mut world = None; let mut source = None; + let mut async_configured = false; if input.peek(token::Brace) { let content; @@ -113,6 +114,13 @@ impl Parse for Config { Opt::PubExportMacro(enable) => { opts.pub_export_macro = enable.value(); } + Opt::Async(val, span) => { + if async_configured { + return Err(Error::new(span, "cannot specify second async config")); + } + async_configured = true; + opts.async_ = val; + } } } } else { @@ -231,6 +239,7 @@ mod kw { syn::custom_keyword!(default_bindings_module); syn::custom_keyword!(export_macro_name); syn::custom_keyword!(pub_export_macro); + syn::custom_keyword!(imports); } #[derive(Clone)] @@ -260,6 +269,11 @@ impl From for wit_bindgen_rust::ExportKey { } } +enum AsyncConfigSomeKind { + Imports, + Exports, +} + enum Opt { World(syn::LitStr), Path(syn::LitStr), @@ -280,6 +294,7 @@ enum Opt { DefaultBindingsModule(syn::LitStr), ExportMacroName(syn::LitStr), PubExportMacro(syn::LitBool), + Async(AsyncConfig, Span), } impl Parse for Opt { @@ -398,6 +413,30 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::PubExportMacro(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()) } @@ -446,3 +485,27 @@ fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> { Ok((interface, buf)) } + +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/rust/src/async_support.rs b/crates/rust/src/async_support.rs new file mode 100644 index 000000000..191b21663 --- /dev/null +++ b/crates/rust/src/async_support.rs @@ -0,0 +1,45 @@ +use std::{ + alloc::Layout, + future::Future, + pin::pin, + sync::Arc, + task::{Context, Poll, Wake}, +}; + +pub fn first_poll(future: impl Future + 'static) -> Result { + struct DummyWaker; + + impl Wake for DummyWaker { + fn wake(self: Arc) {} + } + + let mut future = pin!(future); + + match future + .as_mut() + .poll(&mut Context::from_waker(&Arc::new(DummyWaker).into())) + { + Poll::Ready(result) => Ok(result), + Poll::Pending => todo!(), + } +} + +const STATUS_NOT_STARTED: i32 = 0; +const STATUS_PARAMS_READ: i32 = 1; +const STATUS_RESULTS_WRITTEN: i32 = 2; +const STATUS_DONE: i32 = 3; + +pub async unsafe fn await_result( + import: unsafe extern "C" fn(*mut u8, *mut u8, *mut u8) -> i32, + params_layout: Layout, + params: *mut u8, + results: *mut u8, + call: *mut u8, +) { + match import(params, results, call) { + STATUS_DONE => { + alloc::dealloc(params, params_layout); + } + _ => todo!(), + } +} diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 9a3ae3c7b..76c0b284d 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -8,6 +8,7 @@ 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, pub src: Source, blocks: Vec, block_storage: Vec<(Source, Vec<(String, String)>)>, @@ -23,10 +24,12 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { pub(super) fn new( gen: &'b mut InterfaceGenerator<'a>, params: Vec, + async_: bool, ) -> FunctionBindgen<'a, 'b> { FunctionBindgen { gen, params, + async_, src: Default::default(), blocks: Vec::new(), block_storage: Vec::new(), @@ -66,6 +69,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { 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("_: "); @@ -85,14 +89,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) { @@ -780,7 +784,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::CallWasm { name, sig, .. } => { let func = self.declare_import( - self.gen.wasm_import_module.unwrap(), + self.gen.wasm_import_module, name, &sig.params, &sig.results, @@ -797,8 +801,37 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(");\n"); } + Instruction::AsyncCallWasm { name, size, align } => { + let func = self.declare_import( + self.gen.wasm_import_module, + name, + &[WasmType::Pointer; 3], + &[WasmType::I32], + ); + + let await_result = self.gen.path_to_await_result(); + 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, + "{await_result}({func}, {layout}, {operands}).await;" + ); + } + Instruction::CallInterface { func, .. } => { - self.let_results(func.results.len(), results); + if self.async_ { + let tmp = self.tmp(); + let result = format!("result{tmp}"); + self.push_str(&format!("let {result} = ")); + results.push(result); + } else { + self.let_results(func.results.len(), results); + }; match &func.kind { FunctionKind::Freestanding => { self.push_str(&format!("T::{}", to_rust_ident(&func.name))); @@ -839,6 +872,55 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(";\n"); } + Instruction::AsyncMalloc { size, align } => { + let alloc = self.gen.path_to_std_alloc_module(); + let tmp = self.tmp(); + let ptr = format!("ptr{tmp}"); + let layout = format!("layout{tmp}"); + uwriteln!( + self.src, + "let {layout} = {alloc}::Layout::from_size_align_unchecked({size}, {align}); + let {ptr} = {alloc}::alloc({layout});" + ); + results.push(ptr); + } + + Instruction::AsyncPostCallInterface { func } => { + let result = &operands[0]; + self.let_results(func.results.len(), results); + let first_poll = self.gen.path_to_first_poll(); + uwriteln!( + self.src, + "\ + match {first_poll}({result}) {{ + Ok(results) => results, + Err(ctx) => return ctx, + }};\ + " + ); + } + + Instruction::AsyncCallStart { + name, + params, + results: call_results, + } => { + let func = + self.declare_import(self.gen.wasm_import_module, name, params, call_results); + + if !call_results.is_empty() { + self.push_str("let ret = "); + results.push("ret".to_string()); + } + uwriteln!(self.src, "{func}({});", operands.join(", ")); + } + + Instruction::AsyncCallReturn { name, params } => { + let func = self.declare_import(self.gen.wasm_import_module, name, params, &[]); + + uwriteln!(self.src, "{func}({});", operands.join(", ")); + } + Instruction::Return { amt, .. } => { self.emit_cleanup(); match amt { diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index f31d39235..6664a0643 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::*; @@ -17,7 +17,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, @@ -135,7 +135,7 @@ impl InterfaceGenerator<'_> { let mut funcs_to_export = Vec::new(); let mut resources_to_drop = Vec::new(); - traits.insert(None, ("Guest".to_string(), Vec::new())); + traits.insert(None, ("Guest".to_string(), Vec::new(), false)); if let Some((id, _)) = interface { for (name, id) in self.resolve.interfaces[id].types.iter() { @@ -145,7 +145,7 @@ impl InterfaceGenerator<'_> { } resources_to_drop.push(name); let camel = name.to_upper_camel_case(); - traits.insert(Some(*id), (format!("Guest{camel}"), Vec::new())); + traits.insert(Some(*id), (format!("Guest{camel}"), Vec::new(), false)); } } @@ -154,6 +154,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) @@ -161,12 +172,14 @@ impl InterfaceGenerator<'_> { | FunctionKind::Static(id) => Some(id), }; - funcs_to_export.push((func, resource)); - let (trait_name, methods) = traits.get_mut(&resource).unwrap(); - self.generate_guest_export(func, &trait_name); + funcs_to_export.push((func, resource, async_)); + let (trait_name, methods, async_methods) = traits.get_mut(&resource).unwrap(); + *async_methods |= async_; + self.generate_guest_export(func, &trait_name, async_); let prev = mem::take(&mut self.src); let mut sig = FnSig { + async_, use_item_name: true, private: true, ..Default::default() @@ -181,18 +194,22 @@ impl InterfaceGenerator<'_> { methods.push(trait_method); } - let (name, methods) = traits.remove(&None).unwrap(); + let (name, methods, async_methods) = traits.remove(&None).unwrap(); if !methods.is_empty() || !traits.is_empty() { 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())), + async_methods, ) } - for (resource, (trait_name, methods)) in traits.iter() { + for (resource, (trait_name, methods, async_methods)) in traits.iter() { + if *async_methods { + uwriteln!(self.src, "#[async_trait::async_trait(?Send)]"); + } uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); let resource = resource.unwrap(); let resource_name = self.resolve.types[resource].name.as_ref().unwrap(); @@ -290,7 +307,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) => { @@ -302,7 +319,7 @@ 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 { @@ -339,7 +356,11 @@ macro_rules! {macro_name} {{ trait_name: &str, methods: &[Source], resource_traits: impl Iterator, + async_methods: bool, ) { + if async_methods { + uwriteln!(self.src, "#[async_trait::async_trait(?Send)]"); + } uwriteln!(self.src, "pub trait {trait_name} {{"); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] @@ -355,9 +376,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); } } @@ -443,12 +468,24 @@ macro_rules! {macro_name} {{ map.push((module, module_path)) } - fn generate_guest_import(&mut self, func: &Function) { + fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { if self.gen.skip.contains(&func.name) { return; } - let mut sig = FnSig::default(); + 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) => { @@ -467,13 +504,14 @@ macro_rules! {macro_name} {{ self.src.push_str("{\n"); self.src.push_str("unsafe {\n"); - let mut f = FunctionBindgen::new(self, params); + let mut f = FunctionBindgen::new(self, params, async_); abi::call( f.gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, &mut f, + async_, ); let FunctionBindgen { needs_cleanup_list, @@ -512,17 +550,23 @@ macro_rules! {macro_name} {{ } } - fn generate_guest_export(&mut self, func: &Function, trait_name: &str) { + fn generate_guest_export(&mut self, func: &Function, trait_name: &str, async_: bool) { let name_snake = func.name.to_snake_case().replace('.', "_"); + 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 = if async_ { + self.push_str("() -> *mut u8"); + Vec::new() + } else { + self.print_export_sig(func) + }; self.push_str(" {"); if !self.gen.opts.disable_run_ctors_once_workaround { @@ -541,13 +585,14 @@ macro_rules! {macro_name} {{ ); } - let mut f = FunctionBindgen::new(self, params); + let mut f = FunctionBindgen::new(self, params, async_); abi::call( f.gen.resolve, AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, func, &mut f, + async_, ); let FunctionBindgen { needs_cleanup_list, @@ -570,13 +615,13 @@ macro_rules! {macro_name} {{ #[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_); + abi::post_return(f.gen.resolve, func, &mut f, async_); let FunctionBindgen { needs_cleanup_list, src, @@ -590,7 +635,13 @@ 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)), @@ -606,7 +657,12 @@ macro_rules! {macro_name} {{ ", ); - let params = self.print_export_sig(func); + let params = if async_ { + self.push_str("() -> *mut u8"); + Vec::new() + } else { + self.print_export_sig(func) + }; self.push_str(" {\n"); uwriteln!( self.src, @@ -1936,6 +1992,14 @@ macro_rules! {macro_name} {{ self.path_from_runtime_module(RuntimeItem::StdAllocModule, "alloc") } + pub fn path_to_await_result(&mut self) -> String { + self.path_from_runtime_module(RuntimeItem::AsyncSupport, "await_result") + } + + pub fn path_to_first_poll(&mut self) -> String { + self.path_from_runtime_module(RuntimeItem::AsyncSupport, "first_poll") + } + fn path_from_runtime_module( &mut self, item: RuntimeItem, @@ -1993,7 +2057,7 @@ 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), diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index cfebf82a1..86a14f21e 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -66,6 +66,7 @@ enum RuntimeItem { AsF64, ResourceType, BoxType, + AsyncSupport, } #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -82,6 +83,23 @@ fn parse_with(s: &str) -> Result<(String, String), String> { Ok((k.to_string(), v.to_string())) } +#[derive(Default, Debug, Clone)] +pub enum AsyncConfig { + #[default] + None, + Some { + imports: Vec, + exports: Vec, + }, + All, +} + +#[cfg(feature = "clap")] +fn parse_async(s: &str) -> Result { + _ = s; + Err("todo: parse `AsyncConfig`".into()) +} + #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Args))] pub struct Opts { @@ -182,6 +200,10 @@ pub struct Opts { /// candidate for being exported outside of the crate. #[cfg_attr(feature = "clap", arg(long))] pub pub_export_macro: bool, + + /// Determines which functions to lift or lower `async`, if any. + #[cfg_attr(feature = "clap", arg(long = "async", value_parser = parse_async))] + pub async_: AsyncConfig, } impl Opts { @@ -201,7 +223,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> { @@ -548,6 +570,10 @@ impl Drop for Resource { "#, ); } + + RuntimeItem::AsyncSupport => { + self.src.push_str(include_str!("async_support.rs")); + } } } @@ -880,7 +906,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, ); @@ -890,7 +916,7 @@ impl WorldGenerator for RustWasm { } gen.types(id); - gen.generate_imports(resolve.interfaces[id].functions.values()); + gen.generate_imports(resolve.interfaces[id].functions.values(), Some(name)); gen.finish_append_submodule(&snake, module_path); } @@ -904,9 +930,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); @@ -920,7 +946,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(()); @@ -934,7 +966,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); @@ -949,14 +986,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); @@ -971,7 +1008,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); } diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 3a25c513c..34d89d4fd 100644 --- a/crates/teavm-java/src/lib.rs +++ b/crates/teavm-java/src/lib.rs @@ -499,6 +499,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, + false, ); let src = bindgen.src; @@ -568,6 +569,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, + false, ); assert!(!bindgen.needs_cleanup_list); @@ -621,7 +623,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; @@ -2026,6 +2028,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { "Memory.free(Address.fromInt({address}), ({length}) * {size}, {align});" ); } + + Instruction::AsyncMalloc { .. } + | Instruction::AsyncCallStart { .. } + | Instruction::AsyncPostCallInterface { .. } + | Instruction::AsyncCallReturn { .. } => todo!(), } } From 8de70477a02f0a7b8d1bb2118e3d241e8f657efa Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 27 Mar 2024 11:57:54 -0600 Subject: [PATCH 2/7] more work on async lift/lower Signed-off-by: Joel Dice --- crates/core/src/abi.rs | 6 ++-- crates/rust/src/interface.rs | 63 ++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 5d30256c5..d3c232a28 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -888,7 +888,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { assert_eq!(self.stack.len(), 3); self.emit(&Instruction::AsyncCallWasm { - name: &func.name, + name: &format!("[async]{}", func.name), size: params_size, align: params_align, }); @@ -968,7 +968,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.resolve.push_flat(ty, &mut params); } - let name = &format!("[start]{}", func.name); + let name = &format!("[async-start]{}", func.name); if params.len() > MAX_FLAT_RESULTS { let (size, align) = self @@ -1092,7 +1092,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } if let Some(results) = async_results { - let name = &format!("[return]{}", func.name); + let name = &format!("[async-return]{}", func.name); self.emit(&Instruction::AsyncCallReturn { name, diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 6664a0643..f65b4f27a 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -135,7 +135,7 @@ impl InterfaceGenerator<'_> { let mut funcs_to_export = Vec::new(); let mut resources_to_drop = Vec::new(); - traits.insert(None, ("Guest".to_string(), Vec::new(), false)); + traits.insert(None, ("Guest".to_string(), Vec::new())); if let Some((id, _)) = interface { for (name, id) in self.resolve.interfaces[id].types.iter() { @@ -145,7 +145,7 @@ impl InterfaceGenerator<'_> { } resources_to_drop.push(name); let camel = name.to_upper_camel_case(); - traits.insert(Some(*id), (format!("Guest{camel}"), Vec::new(), false)); + traits.insert(Some(*id), (format!("Guest{camel}"), Vec::new())); } } @@ -173,8 +173,7 @@ impl InterfaceGenerator<'_> { }; funcs_to_export.push((func, resource, async_)); - let (trait_name, methods, async_methods) = traits.get_mut(&resource).unwrap(); - *async_methods |= async_; + let (trait_name, methods) = traits.get_mut(&resource).unwrap(); self.generate_guest_export(func, &trait_name, async_); let prev = mem::take(&mut self.src); @@ -188,13 +187,13 @@ 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); } - let (name, methods, async_methods) = traits.remove(&None).unwrap(); + let (name, methods) = traits.remove(&None).unwrap(); if !methods.is_empty() || !traits.is_empty() { self.generate_interface_trait( &name, @@ -202,14 +201,10 @@ impl InterfaceGenerator<'_> { traits .iter() .map(|(resource, (trait_name, ..))| (resource.unwrap(), trait_name.as_str())), - async_methods, ) } - for (resource, (trait_name, methods, async_methods)) in traits.iter() { - if *async_methods { - uwriteln!(self.src, "#[async_trait::async_trait(?Send)]"); - } + for (resource, (trait_name, methods)) in traits.iter() { uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); let resource = resource.unwrap(); let resource_name = self.resolve.types[resource].name.as_ref().unwrap(); @@ -356,11 +351,7 @@ macro_rules! {macro_name} {{ trait_name: &str, methods: &[Source], resource_traits: impl Iterator, - async_methods: bool, ) { - if async_methods { - uwriteln!(self.src, "#[async_trait::async_trait(?Send)]"); - } uwriteln!(self.src, "pub trait {trait_name} {{"); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] @@ -500,7 +491,7 @@ 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"); @@ -649,6 +640,11 @@ macro_rules! {macro_name} {{ }; let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); let export_name = func.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, "\ @@ -785,7 +781,7 @@ macro_rules! {macro_name} {{ sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig); + self.print_signature(func, true, &sig, true); self.src.push_str("{ unreachable!() }\n"); } @@ -843,12 +839,18 @@ macro_rules! {macro_name} {{ // } } - fn print_signature(&mut self, func: &Function, params_owned: bool, sig: &FnSig) -> Vec { - let params = self.print_docs_and_params(func, params_owned, sig); + fn print_signature( + &mut self, + func: &Function, + params_owned: bool, + sig: &FnSig, + use_async_sugar: bool, + ) -> Vec { + let params = self.print_docs_and_params(func, params_owned, sig, use_async_sugar); if let FunctionKind::Constructor(_) = &func.kind { self.push_str(" -> Self") } else { - self.print_results(&func.results); + self.print_results(&func.results, sig.async_ && !use_async_sugar); } params } @@ -858,6 +860,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"); @@ -870,7 +873,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 "); @@ -960,18 +963,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()); @@ -981,6 +990,10 @@ macro_rules! {macro_name} {{ self.push_str(")") } } + + if async_ { + self.push_str("> + 'static"); + } } /// Calculates the `TypeMode` to be used for the `ty` specified. From e907a96fa9e7e2c0048bf41bdc54e37df430985b Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 27 Mar 2024 13:06:21 -0600 Subject: [PATCH 3/7] emit callback (and no post-return) for async exports Signed-off-by: Joel Dice --- crates/rust/src/async_support.rs | 4 ++++ crates/rust/src/interface.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/rust/src/async_support.rs b/crates/rust/src/async_support.rs index 191b21663..364daa62f 100644 --- a/crates/rust/src/async_support.rs +++ b/crates/rust/src/async_support.rs @@ -43,3 +43,7 @@ pub async unsafe fn await_result( _ => todo!(), } } + +pub fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 { + todo!() +} diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index f65b4f27a..90a8c1369 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -599,7 +599,19 @@ 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 callback = self.path_to_callback(); + 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 {{ + {callback}(ctx, event0, event1, event2) + }} + " + ); + } else if abi::guest_export_needs_post_return(self.resolve, func) { uwrite!( self.src, "\ @@ -667,8 +679,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, "\ @@ -2013,6 +2035,10 @@ macro_rules! {macro_name} {{ self.path_from_runtime_module(RuntimeItem::AsyncSupport, "first_poll") } + pub fn path_to_callback(&mut self) -> String { + self.path_from_runtime_module(RuntimeItem::AsyncSupport, "callback") + } + fn path_from_runtime_module( &mut self, item: RuntimeItem, From b9766d56933f3965a5b5409ce69b7e28e8085454 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 9 Apr 2024 17:06:41 -0600 Subject: [PATCH 4/7] flesh out async support Signed-off-by: Joel Dice --- crates/core/src/abi.rs | 5 +- crates/rust/src/async_support.rs | 85 +++++++++++++++++++++++++------- crates/rust/src/bindgen.rs | 45 +++++++++++------ 3 files changed, 97 insertions(+), 38 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index d3c232a28..4533b57e7 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -531,7 +531,7 @@ def_instruction! { results: &'a [WasmType] } : [params.len()] => [results.len()], - AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len()], + AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len() + 1], AsyncCallReturn { name: &'a str, params: &'a [WasmType] } : [params.len()] => [0], } @@ -1102,9 +1102,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { results }, }); - self.emit(&Instruction::ConstZero { - tys: &[WasmType::Pointer], - }); self.emit(&Instruction::Return { func, amt: 1 }); } else { self.emit(&Instruction::Return { diff --git a/crates/rust/src/async_support.rs b/crates/rust/src/async_support.rs index 364daa62f..990979b7f 100644 --- a/crates/rust/src/async_support.rs +++ b/crates/rust/src/async_support.rs @@ -1,34 +1,50 @@ -use std::{ - alloc::Layout, - future::Future, - pin::pin, - sync::Arc, - task::{Context, Poll, Wake}, +use { + futures::{channel::oneshot, future::FutureExt}, + once_cell::sync::Lazy, + std::{ + alloc::Layout, + collections::HashMap, + future::Future, + pin::{pin, Pin}, + ptr, + sync::Arc, + task::{Context, Poll, Wake, Waker}, + }, }; -pub fn first_poll(future: impl Future + 'static) -> Result { +type BoxFuture = Pin + 'static>>; + +struct FutureState(BoxFuture); + +static mut CALLS: Lazy>> = Lazy::new(HashMap::new); + +fn dummy_waker() -> Waker { struct DummyWaker; impl Wake for DummyWaker { fn wake(self: Arc) {} } - let mut future = pin!(future); + static WAKER: Lazy> = Lazy::new(|| Arc::new(DummyWaker)); + + WAKER.clone().into() +} + +pub fn first_poll( + future: impl Future + 'static, + fun: impl FnOnce(T) + 'static, +) -> *mut u8 { + let mut future = Box::pin(future.map(fun)) as BoxFuture; match future .as_mut() - .poll(&mut Context::from_waker(&Arc::new(DummyWaker).into())) + .poll(&mut Context::from_waker(&dummy_waker())) { - Poll::Ready(result) => Ok(result), - Poll::Pending => todo!(), + Poll::Ready(()) => ptr::null_mut(), + Poll::Pending => Box::into_raw(Box::new(FutureState(future))) as _, } } -const STATUS_NOT_STARTED: i32 = 0; -const STATUS_PARAMS_READ: i32 = 1; -const STATUS_RESULTS_WRITTEN: i32 = 2; -const STATUS_DONE: i32 = 3; - pub async unsafe fn await_result( import: unsafe extern "C" fn(*mut u8, *mut u8, *mut u8) -> i32, params_layout: Layout, @@ -36,7 +52,18 @@ pub async unsafe fn await_result( results: *mut u8, call: *mut u8, ) { + const STATUS_NOT_STARTED: i32 = 0; + const STATUS_PARAMS_READ: i32 = 1; + const STATUS_RESULTS_WRITTEN: i32 = 2; + const STATUS_DONE: i32 = 3; + match import(params, results, call) { + STATUS_PARAMS_READ => { + alloc::dealloc(params, params_layout); + let (tx, rx) = oneshot::channel(); + CALLS.insert(*call.cast::(), tx); + rx.await.unwrap() + } STATUS_DONE => { alloc::dealloc(params, params_layout); } @@ -44,6 +71,28 @@ pub async unsafe fn await_result( } } -pub fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 { - todo!() +pub unsafe fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 { + const EVENT_CALL_STARTED: i32 = 0; + const EVENT_CALL_RETURNED: i32 = 1; + const EVENT_CALL_DONE: i32 = 2; + + match event0 { + EVENT_CALL_DONE => { + CALLS.remove(&event1).unwrap().send(()); + + match (*(ctx as *mut FutureState)) + .0 + .as_mut() + .poll(&mut Context::from_waker(&dummy_waker())) + { + Poll::Ready(()) => { + // TODO: consider spawned task before returning "done" here + drop(Box::from_raw(ctx as *mut FutureState)); + 1 + } + Poll::Pending => 0, + } + } + _ => todo!(), + } } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 76c0b284d..f4d901a56 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -885,21 +885,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(ptr); } - Instruction::AsyncPostCallInterface { func } => { - let result = &operands[0]; - self.let_results(func.results.len(), results); - let first_poll = self.gen.path_to_first_poll(); - uwriteln!( - self.src, - "\ - match {first_poll}({result}) {{ - Ok(results) => results, - Err(ctx) => return ctx, - }};\ - " - ); - } - Instruction::AsyncCallStart { name, params, @@ -915,10 +900,38 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "{func}({});", operands.join(", ")); } + 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 first_poll = self.gen.path_to_first_poll(); + uwriteln!( + self.src, + "\ + let result = {first_poll}({result}, |{params}| {{ + " + ); + } + Instruction::AsyncCallReturn { name, params } => { let func = self.declare_import(self.gen.wasm_import_module, name, params, &[]); - uwriteln!(self.src, "{func}({});", operands.join(", ")); + uwriteln!( + self.src, + "\ + {func}({}); + }}); + ", + operands.join(", ") + ); } Instruction::Return { amt, .. } => { From 78696dfc0f812da3a0f2a7bf761beff73198fe23 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 15 Apr 2024 17:22:49 -0600 Subject: [PATCH 5/7] flesh out async_support.rs Signed-off-by: Joel Dice --- crates/rust/src/async_support.rs | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/rust/src/async_support.rs b/crates/rust/src/async_support.rs index 990979b7f..53ac54275 100644 --- a/crates/rust/src/async_support.rs +++ b/crates/rust/src/async_support.rs @@ -58,16 +58,22 @@ pub async unsafe fn await_result( const STATUS_DONE: i32 = 3; match import(params, results, call) { + STATUS_NOT_STARTED => { + let (tx, rx) = oneshot::channel(); + CALLS.insert(*call.cast::(), tx); + rx.await.unwrap(); + alloc::dealloc(params, params_layout); + } STATUS_PARAMS_READ => { alloc::dealloc(params, params_layout); let (tx, rx) = oneshot::channel(); CALLS.insert(*call.cast::(), tx); rx.await.unwrap() } - STATUS_DONE => { + STATUS_RESULTS_WRITTEN | STATUS_DONE => { alloc::dealloc(params, params_layout); } - _ => todo!(), + status => unreachable!(), } } @@ -77,22 +83,30 @@ pub unsafe fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i const EVENT_CALL_DONE: i32 = 2; match event0 { - EVENT_CALL_DONE => { - CALLS.remove(&event1).unwrap().send(()); + EVENT_CALL_STARTED => { + // TODO: could dealloc params here if we attached the pointer to the call + 1 + } + EVENT_CALL_RETURNED | EVENT_CALL_DONE => { + if let Some(call) = CALLS.remove(&event1) { + call.send(()); - match (*(ctx as *mut FutureState)) - .0 - .as_mut() - .poll(&mut Context::from_waker(&dummy_waker())) - { - Poll::Ready(()) => { - // TODO: consider spawned task before returning "done" here - drop(Box::from_raw(ctx as *mut FutureState)); - 1 + match (*(ctx as *mut FutureState)) + .0 + .as_mut() + .poll(&mut Context::from_waker(&dummy_waker())) + { + Poll::Ready(()) => { + // TODO: consider spawned task before returning "done" here + drop(Box::from_raw(ctx as *mut FutureState)); + 1 + } + Poll::Pending => 0, } - Poll::Pending => 0, + } else { + 1 } } - _ => todo!(), + _ => unreachable!(), } } From 0197779538b81468d3bce05395787da6babc1d24 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 1 May 2024 19:21:18 -0600 Subject: [PATCH 6/7] add future and stream support Signed-off-by: Joel Dice --- Cargo.toml | 10 +- crates/core/src/abi.rs | 129 ++++++++-- crates/core/src/lib.rs | 9 +- crates/guest-rust/macro/src/lib.rs | 16 +- crates/rust/src/async_support.rs | 296 ++++++++++++++++++++-- crates/rust/src/bindgen.rs | 87 +++++-- crates/rust/src/interface.rs | 386 ++++++++++++++++++++++++++--- crates/rust/src/lib.rs | 15 ++ src/bin/wit-bindgen.rs | 1 + 9 files changed, 829 insertions(+), 120 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d16c03ae8..decf552f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,11 @@ clap = { version = "4.3.19", features = ["derive"] } env_logger = "0.10.0" indexmap = "2.0.0" -wasmparser = "0.201.0" -wasm-encoder = "0.201.0" -wasm-metadata = "0.201.0" -wit-parser = "0.201.0" -wit-component = "0.201.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.22.0' } wit-bindgen-c = { path = 'crates/c', version = '0.22.0' } diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 4533b57e7..a8541ddc4 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -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. + ErrorLower { + ty: TypeId, + } : [1] => [1], + + /// Create a error from an `i32`. + ErrorLift { + 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 { @@ -534,6 +568,8 @@ def_instruction! { AsyncPostCallInterface { func: &'a Function } : [1] => [func.results.len() + 1], AsyncCallReturn { name: &'a str, params: &'a [WasmType] } : [params.len()] => [0], + + Flush { amt: usize } : [*amt] => [*amt], } } @@ -751,7 +787,9 @@ fn needs_post_return(resolve: &Resolve, ty: &Type) -> bool { .filter_map(|t| t.as_ref()) .any(|t| needs_post_return(resolve, t)), TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => false, - TypeDefKind::Future(_) | TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Error => { + unimplemented!() + } TypeDefKind::Unknown => unreachable!(), }, @@ -764,8 +802,8 @@ fn needs_post_return(resolve: &Resolve, ty: &Type) -> bool { | Type::S32 | Type::U64 | Type::S64 - | Type::Float32 - | Type::Float64 + | Type::F32 + | Type::F64 | Type::Char => false, } } @@ -867,6 +905,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); self.stack.pop().unwrap() } + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } }; lower_to_memory(self, ptr); } @@ -920,7 +961,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let ptr = match self.variant { // imports into guests means it's a wasm module // calling an imported function. We supplied the - // return poitner as the last argument (saved in + // return pointer as the last argument (saved in // `self.return_pointer`) so we use that to read // the result of the function from memory. AbiVariant::GuestImport => { @@ -932,9 +973,16 @@ impl<'a, B: Bindgen> Generator<'a, B> { // calling wasm so wasm returned a pointer to where // the result is stored AbiVariant::GuestExport => self.stack.pop().unwrap(), + + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } }; self.read_results_from_memory(&func.results, ptr.clone(), 0); + self.emit(&Instruction::Flush { + amt: func.results.len(), + }); if let Some((size, align)) = dealloc_size_align { self.stack.push(ptr); @@ -1088,6 +1136,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.write_params_to_memory(func.results.iter_types(), ptr.clone(), 0); self.stack.push(ptr); } + + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsync => { + unreachable!() + } } } @@ -1204,8 +1256,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::S64 => self.emit(&I64FromS64), Type::U64 => self.emit(&I64FromU64), Type::Char => self.emit(&I32FromChar), - Type::Float32 => self.emit(&F32FromFloat32), - Type::Float64 => self.emit(&F64FromFloat64), + Type::F32 => self.emit(&F32FromFloat32), + Type::F64 => self.emit(&F64FromFloat64), Type::String => { let realloc = self.list_realloc(); self.emit(&StringLower { realloc }); @@ -1305,8 +1357,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::Error => { + self.emit(&ErrorLower { ty: id }); + } TypeDefKind::Unknown => unreachable!(), }, } @@ -1393,8 +1458,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::S64 => self.emit(&S64FromI64), Type::U64 => self.emit(&U64FromI64), Type::Char => self.emit(&CharFromI32), - Type::Float32 => self.emit(&Float32FromF32), - Type::Float64 => self.emit(&Float64FromF64), + Type::F32 => self.emit(&Float32FromF32), + Type::F64 => self.emit(&Float64FromF64), Type::String => self.emit(&StringLift), Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.lift(t), @@ -1490,8 +1555,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::Error => { + self.emit(&ErrorLift { ty: id }); + } TypeDefKind::Unknown => unreachable!(), }, } @@ -1551,15 +1629,18 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lower_and_emit(ty, addr, &I32Store { offset }) } Type::U64 | Type::S64 => self.lower_and_emit(ty, addr, &I64Store { offset }), - Type::Float32 => self.lower_and_emit(ty, addr, &F32Store { offset }), - Type::Float64 => self.lower_and_emit(ty, addr, &F64Store { offset }), + Type::F32 => self.lower_and_emit(ty, addr, &F32Store { offset }), + Type::F64 => self.lower_and_emit(ty, addr, &F64Store { offset }), Type::String => self.write_list_to_memory(ty, addr, offset), Type::Id(id) => match &self.resolve.types[id].kind { 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::Error + | 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. @@ -1649,8 +1730,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!(), }, } @@ -1739,8 +1818,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::S16 => self.emit_and_lift(ty, addr, &I32Load16S { offset }), Type::U32 | Type::S32 | Type::Char => self.emit_and_lift(ty, addr, &I32Load { offset }), Type::U64 | Type::S64 => self.emit_and_lift(ty, addr, &I64Load { offset }), - Type::Float32 => self.emit_and_lift(ty, addr, &F32Load { offset }), - Type::Float64 => self.emit_and_lift(ty, addr, &F64Load { offset }), + Type::F32 => self.emit_and_lift(ty, addr, &F32Load { offset }), + Type::F64 => self.emit_and_lift(ty, addr, &F64Load { offset }), Type::String => self.read_list_from_memory(ty, addr, offset), Type::Id(id) => match &self.resolve.types[id].kind { @@ -1748,7 +1827,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::Error + | TypeDefKind::Handle(_) => self.emit_and_lift(ty, addr, &I32Load { offset }), TypeDefKind::Resource => { todo!(); @@ -1832,8 +1914,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!(), }, } @@ -1936,8 +2016,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { | Type::Char | Type::U64 | Type::S64 - | Type::Float32 - | Type::Float64 => {} + | Type::F32 + | Type::F64 => {} Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.deallocate(t, addr, offset), @@ -2004,6 +2084,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::Error => todo!("read error from memory"), TypeDefKind::Unknown => unreachable!(), }, } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 378d4f340..c4ad0abda 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -208,13 +208,7 @@ impl Types { info = self.optional_type_info(resolve, r.ok.as_ref()); info |= self.optional_type_info(resolve, r.err.as_ref()); } - TypeDefKind::Future(ty) => { - info = self.optional_type_info(resolve, ty.as_ref()); - } - TypeDefKind::Stream(stream) => { - info = self.optional_type_info(resolve, stream.element.as_ref()); - info |= self.optional_type_info(resolve, stream.end.as_ref()); - } + TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Error => {} TypeDefKind::Unknown => unreachable!(), } let prev = self.type_info.insert(ty, info); @@ -396,6 +390,7 @@ pub trait InterfaceGenerator<'a> { TypeDefKind::Future(_) => todo!("generate for future"), TypeDefKind::Stream(_) => todo!("generate for stream"), TypeDefKind::Handle(_) => todo!("generate for handle"), + TypeDefKind::Error => todo!("generate for error"), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 74b2c2ad6..a4518d4c7 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -30,6 +30,7 @@ struct Config { resolve: Resolve, world: WorldId, files: Vec, + debug: bool, } /// The source of the wit package definition @@ -47,6 +48,7 @@ impl Parse for Config { let mut world = None; let mut source = None; let mut async_configured = false; + let mut debug = false; if input.peek(token::Brace) { let content; @@ -114,6 +116,9 @@ impl Parse for Config { Opt::PubExportMacro(enable) => { opts.pub_export_macro = enable.value(); } + Opt::Debug(enable) => { + debug = enable.value(); + } Opt::Async(val, span) => { if async_configured { return Err(Error::new(span, "cannot specify second async config")); @@ -139,6 +144,7 @@ impl Parse for Config { resolve, world, files, + debug, }) } } @@ -164,6 +170,8 @@ fn parse_source(source: &Option) -> anyhow::Result<(Resolve, PackageId, None => parse(&root.join("wit"))?, }; + resolve.add_future_and_stream_results(); + Ok((resolve, pkg, files)) } @@ -181,7 +189,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; @@ -240,6 +248,7 @@ mod kw { syn::custom_keyword!(export_macro_name); syn::custom_keyword!(pub_export_macro); syn::custom_keyword!(imports); + syn::custom_keyword!(debug); } #[derive(Clone)] @@ -295,6 +304,7 @@ enum Opt { ExportMacroName(syn::LitStr), PubExportMacro(syn::LitBool), Async(AsyncConfig, Span), + Debug(syn::LitBool), } impl Parse for Opt { @@ -413,6 +423,10 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::PubExportMacro(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::()?; diff --git a/crates/rust/src/async_support.rs b/crates/rust/src/async_support.rs index 53ac54275..3ef923220 100644 --- a/crates/rust/src/async_support.rs +++ b/crates/rust/src/async_support.rs @@ -1,10 +1,18 @@ use { - futures::{channel::oneshot, future::FutureExt}, + futures::{ + channel::oneshot, + future::FutureExt, + sink::Sink, + stream::{FuturesUnordered, Stream, StreamExt}, + }, once_cell::sync::Lazy, std::{ - alloc::Layout, + alloc::{self, Layout}, collections::HashMap, - future::Future, + fmt::{self, Debug, Display}, + future::{Future, IntoFuture}, + marker::PhantomData, + mem::ManuallyDrop, pin::{pin, Pin}, ptr, sync::Arc, @@ -14,10 +22,12 @@ use { type BoxFuture = Pin + 'static>>; -struct FutureState(BoxFuture); +struct FutureState(FuturesUnordered); static mut CALLS: Lazy>> = Lazy::new(HashMap::new); +static mut SPAWNED: Vec = Vec::new(); + fn dummy_waker() -> Waker { struct DummyWaker; @@ -30,18 +40,34 @@ fn dummy_waker() -> Waker { WAKER.clone().into() } +unsafe fn poll(state: *mut FutureState) -> Poll<()> { + loop { + let poll = pin!((*state).0.next()).poll(&mut Context::from_waker(&dummy_waker())); + + if SPAWNED.is_empty() { + match poll { + Poll::Ready(Some(())) => (), + Poll::Ready(None) => break Poll::Ready(()), + Poll::Pending => break Poll::Pending, + } + } else { + (*state).0.extend(SPAWNED.drain(..)); + } + } +} + pub fn first_poll( future: impl Future + 'static, fun: impl FnOnce(T) + 'static, ) -> *mut u8 { - let mut future = Box::pin(future.map(fun)) as BoxFuture; - - match future - .as_mut() - .poll(&mut Context::from_waker(&dummy_waker())) - { + let state = Box::into_raw(Box::new(FutureState( + [Box::pin(future.map(fun)) as BoxFuture] + .into_iter() + .collect(), + ))); + match unsafe { poll(state) } { Poll::Ready(()) => ptr::null_mut(), - Poll::Pending => Box::into_raw(Box::new(FutureState(future))) as _, + Poll::Pending => state as _, } } @@ -91,13 +117,8 @@ pub unsafe fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i if let Some(call) = CALLS.remove(&event1) { call.send(()); - match (*(ctx as *mut FutureState)) - .0 - .as_mut() - .poll(&mut Context::from_waker(&dummy_waker())) - { + match poll(ctx as *mut FutureState) { Poll::Ready(()) => { - // TODO: consider spawned task before returning "done" here drop(Box::from_raw(ctx as *mut FutureState)); 1 } @@ -110,3 +131,244 @@ pub unsafe fn callback(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i _ => unreachable!(), } } + +#[doc(hidden)] +pub trait FuturePayload: Sized + 'static { + fn new() -> (u32, u32); + async fn send(sender: u32, value: Self) -> Result<(), Error>; + async fn receive(receiver: u32) -> Result; + fn drop_sender(sender: u32); + fn drop_receiver(receiver: u32); +} + +pub struct FutureSender { + handle: u32, + _phantom: PhantomData, +} + +impl FutureSender { + pub async fn send(self, v: T) -> Result<(), Error> { + T::send(ManuallyDrop::new(self).handle, v).await + } +} + +impl Drop for FutureSender { + fn drop(&mut self) { + T::drop_sender(self.handle) + } +} + +pub struct FutureReceiver { + handle: u32, + _phantom: PhantomData, +} + +impl FutureReceiver { + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + ManuallyDrop::new(self).handle + } +} + +impl IntoFuture for FutureReceiver { + type Output = Result; + type IntoFuture = Pin + 'static>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(T::receive(ManuallyDrop::new(self).handle)) + } +} + +impl Drop for FutureReceiver { + fn drop(&mut self) { + T::drop_receiver(self.handle) + } +} + +#[doc(hidden)] +pub trait StreamPayload: Unpin + Sized + 'static { + fn new() -> (u32, u32); + async fn send(sender: u32, values: Vec) -> Result<(), Error>; + async fn receive(receiver: u32) -> Option, Error>>; + fn drop_sender(sender: u32); + fn drop_receiver(receiver: u32); +} + +pub struct StreamSender { + handle: u32, + future: Option> + 'static>>>, + _phantom: PhantomData, +} + +impl Sink> for StreamSender { + type Error = Error; + + 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(v) => { + me.future = None; + Poll::Ready(v) + } + 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()); + self.get_mut().future = Some(Box::pin(T::send(self.handle, item))); + 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 StreamSender { + fn drop(&mut self) { + T::drop_sender(self.handle) + } +} + +pub struct StreamReceiver { + handle: u32, + future: Option, Error>>> + 'static>>>, + _phantom: PhantomData, +} + +impl StreamReceiver { + #[doc(hidden)] + pub fn from_handle(handle: u32) -> Self { + Self { + handle, + future: None, + _phantom: PhantomData, + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + ManuallyDrop::new(self).handle + } +} + +impl Stream for StreamReceiver { + type Item = Result, Error>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + + if me.future.is_none() { + me.future = Some(Box::pin(T::receive(me.handle))); + } + + 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 StreamReceiver { + fn drop(&mut self) { + T::drop_receiver(self.handle) + } +} + +pub struct Error { + handle: u32, +} + +impl Error { + #[doc(hidden)] + pub fn from_handle(handle: u32) -> Self { + Self { handle } + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + self.handle + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Error").finish() + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error") + } +} + +impl std::error::Error for Error {} + +impl Drop for Error { + fn drop(&mut self) { + #[cfg(not(target_arch = "wasm32"))] + { + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[error-drop]"] + fn drop(_: u32); + } + if self.handle != 0 { + unsafe { drop(self.handle) } + } + } + } +} + +pub fn new_future() -> (FutureSender, FutureReceiver) { + let (tx, rx) = T::new(); + ( + FutureSender { + handle: tx, + _phantom: PhantomData, + }, + FutureReceiver { + handle: rx, + _phantom: PhantomData, + }, + ) +} + +pub fn new_stream() -> (StreamSender, StreamReceiver) { + let (tx, rx) = T::new(); + ( + StreamSender { + handle: tx, + future: None, + _phantom: PhantomData, + }, + StreamReceiver { + handle: rx, + future: None, + _phantom: PhantomData, + }, + ) +} + +pub fn spawn(future: impl Future + 'static) { + unsafe { SPAWNED.push(Box::pin(future)) } +} diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index f4d901a56..7dcd561cd 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -9,6 +9,7 @@ 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)>)>, @@ -25,11 +26,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { 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(), @@ -61,13 +64,7 @@ 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(); @@ -82,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, " @@ -460,6 +458,43 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(result); } + Instruction::FutureLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).into_handle() as i32")) + } + + Instruction::FutureLift { .. } => { + let async_support = self.gen.path_to_async_support(); + let op = &operands[0]; + results.push(format!( + "{async_support}::FutureReceiver::from_handle({op} as u32)" + )) + } + + Instruction::StreamLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).into_handle() as i32")) + } + + Instruction::StreamLift { .. } => { + let async_support = self.gen.path_to_async_support(); + let op = &operands[0]; + results.push(format!( + "{async_support}::StreamReceiver::from_handle({op} as u32)" + )) + } + + Instruction::ErrorLower { .. } => { + let op = &operands[0]; + results.push(format!("({op}).handle() as i32")) + } + + Instruction::ErrorLift { .. } => { + let async_support = self.gen.path_to_async_support(); + let op = &operands[0]; + results.push(format!("{async_support}::Error::from_handle({op} as u32)")) + } + Instruction::RecordLower { ty, record, .. } => { self.record_lower(*ty, record, &operands[0], results); } @@ -783,12 +818,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, - 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() { @@ -802,14 +832,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::AsyncCallWasm { name, size, align } => { - let func = self.declare_import( - self.gen.wasm_import_module, - name, - &[WasmType::Pointer; 3], - &[WasmType::I32], - ); + let func = self.declare_import(name, &[WasmType::Pointer; 3], &[WasmType::I32]); - let await_result = self.gen.path_to_await_result(); + 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(); @@ -819,7 +844,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let operands = operands.join(", "); uwriteln!( self.src, - "{await_result}({func}, {layout}, {operands}).await;" + "{async_support}::await_result({func}, {layout}, {operands}).await;" ); } @@ -890,8 +915,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { params, results: call_results, } => { - let func = - self.declare_import(self.gen.wasm_import_module, name, params, call_results); + let func = self.declare_import(name, params, call_results); if !call_results.is_empty() { self.push_str("let ret = "); @@ -912,17 +936,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { }) .collect::>() .join(", "); - let first_poll = self.gen.path_to_first_poll(); + let async_support = self.gen.path_to_async_support(); uwriteln!( self.src, "\ - let result = {first_poll}({result}, |{params}| {{ + let result = {async_support}::first_poll({result}, |{params}| {{ " ); } Instruction::AsyncCallReturn { name, params } => { - let func = self.declare_import(self.gen.wasm_import_module, name, params, &[]); + let func = self.declare_import(name, params, &[]); uwriteln!( self.src, @@ -934,6 +958,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } + 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 { @@ -963,7 +996,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 90a8c1369..b230c8201 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -159,7 +159,7 @@ impl InterfaceGenerator<'_> { AsyncConfig::All => true, AsyncConfig::Some { exports, .. } => { exports.contains(&if let Some((_, key)) = interface { - format!("{}/{}", self.resolve.name_world_key(key), func.name) + format!("{}#{}", self.resolve.name_world_key(key), func.name) } else { func.name.clone() }) @@ -174,7 +174,7 @@ impl InterfaceGenerator<'_> { funcs_to_export.push((func, resource, async_)); let (trait_name, methods) = traits.get_mut(&resource).unwrap(); - self.generate_guest_export(func, &trait_name, async_); + self.generate_guest_export(func, interface.map(|(_, k)| k), &trait_name, async_); let prev = mem::take(&mut self.src); let mut sig = FnSig { @@ -269,7 +269,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") @@ -320,7 +320,7 @@ macro_rules! {macro_name} {{ for name in resources_to_drop { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), - Identifier::World(_) => unreachable!(), + Identifier::None | Identifier::World(_) => unreachable!(), }; let camel = name.to_upper_camel_case(); uwriteln!( @@ -459,16 +459,308 @@ macro_rules! {macro_name} {{ map.push((module, module_path)) } + 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 payload_result = self.gen.payload_results[&ty]; + 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 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 send = Function { + name: format!("[future-send-{index}]{func_name}"), + kind: FunctionKind::Freestanding, + params: if let Some(payload_type) = payload_type { + vec![ + ("sender".into(), Type::U32), + ("value".into(), *payload_type), + ] + } else { + vec![("sender".into(), Type::U32)] + }, + results: Results::Anon(Type::Id(self.gen.unit_result.unwrap())), + docs: Default::default(), + }; + let old = mem::take(&mut self.src); + self.generate_guest_import_body( + &module, + &send, + if payload_type.is_some() { + vec!["sender".into(), "value".into()] + } else { + vec!["sender".into()] + }, + true, + ); + let send = String::from(mem::replace(&mut self.src, old)); + + let receive = Function { + name: format!("[future-receive-{index}]{func_name}"), + kind: FunctionKind::Freestanding, + params: vec![("receiver".into(), Type::U32)], + results: Results::Anon(Type::Id(payload_result)), + docs: Default::default(), + }; + let old = mem::take(&mut self.src); + self.generate_guest_import_body( + &module, + &receive, + vec!["receiver".into()], + true, + ); + let receive = String::from(mem::replace(&mut self.src, old)); + + uwriteln!( + self.src, + r#" +impl {async_support}::FuturePayload for {name} {{ + fn new() -> (u32, 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(_: *mut u32); + }} + let mut results = [0u32; 2]; + unsafe {{ new(results.as_mut_ptr()) }}; + (results[0], results[1]) + }} + }} + + async fn send(sender: u32, value: Self) -> Result<(), {async_support}::Error> {{ + unsafe {{ {send} }} + }} + + async fn receive(receiver: u32) -> Result {{ + unsafe {{ {receive} }} + }} + + fn drop_sender(sender: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-drop-sender-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(sender) }} + }} + }} + + fn drop_receiver(receiver: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[future-drop-receiver-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(receiver) }} + }} + }} +}} + "#, + ); + } + } + 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)) + }; + + let TypeDefKind::Option(Type::Id(result_ty)) = + &self.resolve.types[payload_result].kind + else { + unreachable!() + }; + let TypeDefKind::Result(Result_ { + ok: Some(list_ty), .. + }) = &self.resolve.types[*result_ty].kind + else { + unreachable!() + }; + + if self.gen.stream_payloads_emitted.insert(full_name) { + let send = Function { + name: format!("[stream-send-{index}]{func_name}"), + kind: FunctionKind::Freestanding, + params: vec![("sender".into(), Type::U32), ("values".into(), *list_ty)], + results: Results::Anon(Type::Id(self.gen.unit_result.unwrap())), + docs: Default::default(), + }; + let old = mem::take(&mut self.src); + self.generate_guest_import_body( + &module, + &send, + vec!["sender".into(), "values".into()], + true, + ); + let send = String::from(mem::replace(&mut self.src, old)); + + let receive = Function { + name: format!("[stream-receive-{index}]{func_name}"), + kind: FunctionKind::Freestanding, + params: vec![("receiver".into(), Type::U32)], + results: Results::Anon(Type::Id(payload_result)), + docs: Default::default(), + }; + let old_src = mem::take(&mut self.src); + self.generate_guest_import_body( + &module, + &receive, + vec!["receiver".into()], + true, + ); + let receive = String::from(mem::replace(&mut self.src, old_src)); + + uwriteln!( + self.src, + r#" +impl {async_support}::StreamPayload for {name} {{ + fn new() -> (u32, 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(_: *mut u32); + }} + let mut results = [0u32; 2]; + unsafe {{ new(results.as_mut_ptr()) }}; + (results[0], results[1]) + }} + }} + + async fn send(sender: u32, values: Vec) -> Result<(), {async_support}::Error> {{ + unsafe {{ {send} }} + }} + + async fn receive(receiver: u32) -> Option, {async_support}::Error>> {{ + unsafe {{ {receive} }} + }} + + fn drop_sender(sender: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-drop-sender-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(sender) }} + }} + }} + + fn drop_receiver(receiver: u32) {{ + #[cfg(not(target_arch = "wasm32"))] + {{ + unreachable!(); + }} + + #[cfg(target_arch = "wasm32")] + {{ + #[link(wasm_import_module = "{module}")] + extern "C" {{ + #[link_name = "[stream-drop-receiver-{index}]{func_name}"] + fn drop(_: u32); + }} + unsafe {{ drop(receiver) }} + }} + }} +}} + "# + ); + } + } + _ => unreachable!(), + } + } + } + fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { if self.gen.skip.contains(&func.name) { return; } + 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) + format!("{}#{}", self.resolve.name_world_key(key), func.name) } else { func.name.clone() }), @@ -495,7 +787,27 @@ macro_rules! {macro_name} {{ self.src.push_str("{\n"); self.src.push_str("unsafe {\n"); - let mut f = FunctionBindgen::new(self, params, async_); + 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 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, @@ -529,21 +841,19 @@ 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, async_: bool) { + 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, "\ @@ -576,7 +886,7 @@ macro_rules! {macro_name} {{ ); } - let mut f = FunctionBindgen::new(self, params, async_); + let mut f = FunctionBindgen::new(self, params, async_, self.wasm_import_module); abi::call( f.gen.resolve, AbiVariant::GuestExport, @@ -600,14 +910,14 @@ macro_rules! {macro_name} {{ self.src.push_str("}\n"); if async_ { - let callback = self.path_to_callback(); + 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 {{ - {callback}(ctx, event0, event1, event2) + {async_support}::callback(ctx, event0, event1, event2) }} " ); @@ -623,7 +933,7 @@ macro_rules! {macro_name} {{ let params = self.print_post_return_sig(func); self.src.push_str("{\n"); - let mut f = FunctionBindgen::new(self, params, async_); + 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, @@ -649,6 +959,7 @@ macro_rules! {macro_name} {{ let wasm_module_export_name = match self.identifier { Identifier::Interface(_, key) => Some(self.resolve.name_world_key(key)), Identifier::World(_) => None, + Identifier::None => unreachable!(), }; let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); let export_name = func.core_export_name(wasm_module_export_name.as_deref()); @@ -1198,8 +1509,8 @@ macro_rules! {macro_name} {{ Type::S16 => self.push_str("i16"), Type::S32 => self.push_str("i32"), Type::S64 => self.push_str("i64"), - Type::Float32 => self.push_str("f32"), - Type::Float64 => self.push_str("f64"), + Type::F32 => self.push_str("f32"), + Type::F64 => self.push_str("f64"), Type::Char => self.push_str("char"), Type::String => { assert_eq!(mode.lists_borrowed, mode.lifetime.is_some()); @@ -1334,17 +1645,21 @@ macro_rules! {macro_name} {{ panic!("unsupported anonymous type reference: enum") } TypeDefKind::Future(ty) => { - self.push_str("Future<"); + let async_support = self.path_to_async_support(); + uwrite!(self.src, "{async_support}::FutureReceiver<"); self.print_optional_ty(ty.as_ref(), mode); self.push_str(">"); } - TypeDefKind::Stream(stream) => { - self.push_str("Stream<"); - self.print_optional_ty(stream.element.as_ref(), mode); - self.push_str(","); - self.print_optional_ty(stream.end.as_ref(), mode); + TypeDefKind::Stream(ty) => { + let async_support = self.path_to_async_support(); + uwrite!(self.src, "{async_support}::StreamReceiver<"); + self.print_ty(ty, mode); self.push_str(">"); } + TypeDefKind::Error => { + let async_support = self.path_to_async_support(); + uwrite!(self.src, "{async_support}::Error"); + } TypeDefKind::Handle(Handle::Own(ty)) => { self.print_ty(&Type::Id(*ty), mode); @@ -2027,16 +2342,8 @@ macro_rules! {macro_name} {{ self.path_from_runtime_module(RuntimeItem::StdAllocModule, "alloc") } - pub fn path_to_await_result(&mut self) -> String { - self.path_from_runtime_module(RuntimeItem::AsyncSupport, "await_result") - } - - pub fn path_to_first_poll(&mut self) -> String { - self.path_from_runtime_module(RuntimeItem::AsyncSupport, "first_poll") - } - - pub fn path_to_callback(&mut self) -> String { - self.path_from_runtime_module(RuntimeItem::AsyncSupport, "callback") + pub fn path_to_async_support(&mut self) -> String { + self.path_from_runtime_module(RuntimeItem::AsyncSupport, "async_support") } fn path_from_runtime_module( @@ -2101,6 +2408,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), Identifier::World(_) => unimplemented!("resource exports from worlds"), + Identifier::None => unreachable!(), }; let box_path = self.path_to_box(); uwriteln!( diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 86a14f21e..a3dad54f3 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -46,6 +46,11 @@ struct RustWasm { rt_module: IndexSet, export_macros: Vec<(String, String)>, with: HashMap, + + unit_result: Option, + payload_results: HashMap, + future_payloads_emitted: HashSet, + stream_payloads_emitted: HashSet, } #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] @@ -339,6 +344,9 @@ impl RustWasm { } } self.src.push_str("}\n"); + if emitted.contains(&RuntimeItem::AsyncSupport) { + self.src.push_str("pub use _rt::async_support;\n"); + } } fn emit_runtime_item(&mut self, item: RuntimeItem) { @@ -572,7 +580,9 @@ impl Drop for Resource { } RuntimeItem::AsyncSupport => { + self.src.push_str("pub mod async_support {"); self.src.push_str(include_str!("async_support.rs")); + self.src.push_str("}"); } } } @@ -893,6 +903,10 @@ impl WorldGenerator for RustWasm { for (k, v) in self.opts.with.iter() { self.with.insert(k.clone(), v.clone()); } + + let (unit_result, payload_results) = resolve.find_future_and_stream_results(); + self.unit_result = unit_result; + self.payload_results = payload_results; } fn import_interface( @@ -1163,6 +1177,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/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 4e306ede3..81276e170 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -156,6 +156,7 @@ fn gen_world( ) -> Result<()> { let mut resolve = Resolve::default(); let (pkg, _files) = resolve.push_path(&opts.wit)?; + self.resolve.add_future_and_stream_results(); let world = resolve.select_world(pkg, opts.world.as_deref())?; generator.generate(&resolve, world, files)?; From 32c35e990ea510a3b4fde7492d12ca10f5c76fe6 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 28 May 2024 08:18:30 -0600 Subject: [PATCH 7/7] support `task.wait` Signed-off-by: Joel Dice --- crates/rust/src/async_support.rs | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/rust/src/async_support.rs b/crates/rust/src/async_support.rs index 3ef923220..df3e41996 100644 --- a/crates/rust/src/async_support.rs +++ b/crates/rust/src/async_support.rs @@ -372,3 +372,40 @@ pub fn new_stream() -> (StreamSender, StreamReceiver) { pub fn spawn(future: impl Future + 'static) { unsafe { SPAWNED.push(Box::pin(future)) } } + +fn wait(state: &mut FutureState) { + #[cfg(not(target_arch = "wasm32"))] + { + 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]); + } + } +} + +// TODO: refactor so `'static` bounds aren't necessary +pub fn block_on(future: impl Future + 'static) -> T { + let (mut tx, mut rx) = oneshot::channel(); + let state = &mut FutureState( + [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 => wait(state), + } + } +}