From 3b839cfbf50a030acaf5f3102a01b72a80c7ddaf Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 14 Jan 2025 14:26:52 -0700 Subject: [PATCH 01/13] drop borrows earlier for async exports in Rust generator (#1124) While testing runtime borrow enforcement in Wasmtime, I discovered that the guest code generated by `wit-bindgen` was not dropping borrows early enough for async exports. Specifically, all borrows need to be dropped before `task.return` is called, which is what this patch does. Signed-off-by: Joel Dice --- crates/rust/src/bindgen.rs | 15 +++++++++++---- crates/rust/src/interface.rs | 13 +++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 8d3c10269..3b38c1798 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -20,6 +20,7 @@ pub(super) struct FunctionBindgen<'a, 'b> { pub import_return_pointer_area_align: usize, pub handle_decls: Vec, always_owned: bool, + pub async_result_name: Option, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -45,6 +46,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { import_return_pointer_area_align: 0, handle_decls: Vec::new(), always_owned, + async_result_name: None, } } @@ -877,10 +879,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::CallInterface { func, .. } => { if self.async_ { - let tmp = self.tmp(); - let result = format!("result{tmp}"); - self.push_str(&format!("let {result} = ")); - results.push(result); + self.push_str(&format!("let result = ")); + results.push("result".into()); } else { self.let_results(func.results.len(), results); }; @@ -950,6 +950,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::AsyncPostCallInterface { func } => { let result = &operands[0]; + self.async_result_name = Some(result.clone()); results.push("result".into()); let params = (0..func.results.len()) .map(|_| { @@ -973,12 +974,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { // `abi::Generator` emit a `AsyncCallReturn` first, which would // push a closure expression we can consume here). // + // Also, see `InterfaceGenerator::generate_guest_export` for + // where we emit the open bracket corresponding to the `};` we + // emit here. + // // The async-specific `Instruction`s will probably need to be // refactored anyway once we start implementing support for // other languages besides Rust. uwriteln!( self.src, "\ + {result} + }}; let result = {async_support}::first_poll({result}, |{params}| {{ " ); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 62ef4a286..9e159461c 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1085,9 +1085,22 @@ pub mod vtable{ordinal} {{ needs_cleanup_list, src, handle_decls, + async_result_name, .. } = f; assert!(!needs_cleanup_list); + if let Some(name) = async_result_name { + // When `async_result_name` is `Some(_)`, we wrap the call and any + // `handle_decls` in a block scope to ensure any resource handles + // are dropped before we call `async_support::first_poll`, which may + // call `task.return` at which point any borrow handles must already + // have been dropped. + // + // The corresponding close bracket is emitted in the + // `Instruction::AsyncPostCallInterface` case inside + // `FunctionBindgen::emit`. + uwriteln!(self.src, "let {name} = {{"); + } for decl in handle_decls { self.src.push_str(&decl); self.src.push_str("\n"); From 4f4a95373a8aa4723d282ef81f2f8eddf77f4d87 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 15 Jan 2025 08:49:35 -0700 Subject: [PATCH 02/13] drop borrows just prior to calling `task.return` (#1125) Prior to #1124, we were dropping borrows too late. That PR fixed that problem, but dropped borrows too early and didn't allow application code to capture the borrows in the `Future` it returned. This patch improves the situation by dropping borrows as late as possible, but no later. As a side effect, it simplfies the code and makes implementing the generated trait(s) much more ergonomic. Signed-off-by: Joel Dice --- crates/rust/src/bindgen.rs | 3 +++ crates/rust/src/interface.rs | 46 +++++++++--------------------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 3b38c1798..bb9f536be 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -932,6 +932,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { ")".into() }); } + if self.async_ { + self.push_str(".await"); + } self.push_str(";\n"); } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 9e159461c..7fd98dd92 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -189,7 +189,7 @@ impl<'i> InterfaceGenerator<'i> { sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig, false); + self.print_signature(func, true, &sig); self.src.push_str(";\n"); let trait_method = mem::replace(&mut self.src, prev); methods.push(trait_method); @@ -959,7 +959,7 @@ pub mod vtable{ordinal} {{ } } self.src.push_str("#[allow(unused_unsafe, clippy::all)]\n"); - let params = self.print_signature(func, false, &sig, true); + let params = self.print_signature(func, false, &sig); self.src.push_str("{\n"); self.src.push_str("unsafe {\n"); @@ -1099,7 +1099,7 @@ pub mod vtable{ordinal} {{ // The corresponding close bracket is emitted in the // `Instruction::AsyncPostCallInterface` case inside // `FunctionBindgen::emit`. - uwriteln!(self.src, "let {name} = {{"); + uwriteln!(self.src, "let {name} = async move {{"); } for decl in handle_decls { self.src.push_str(&decl); @@ -1326,14 +1326,8 @@ pub mod vtable{ordinal} {{ sig.self_arg = Some("&self".into()); sig.self_is_first_param = true; } - self.print_signature(func, true, &sig, false); - let call = if async_ { - let async_support = self.path_to_async_support(); - format!("{{ #[allow(unreachable_code)]{async_support}::futures::future::ready(unreachable!()) }}\n") - } else { - "{ unreachable!() }\n".into() - }; - self.src.push_str(&call); + self.print_signature(func, true, &sig); + self.src.push_str("{ unreachable!() }\n"); } self.src.push_str("}\n"); @@ -1390,22 +1384,12 @@ pub mod vtable{ordinal} {{ // } } - 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); + fn print_signature(&mut self, func: &Function, params_owned: bool, sig: &FnSig) -> Vec { + let params = self.print_docs_and_params(func, params_owned, sig); if let FunctionKind::Constructor(_) = &func.kind { - self.push_str(if sig.async_ && !use_async_sugar { - " -> impl ::core::future::Future" - } else { - " -> Self" - }) + self.push_str(" -> Self") } else { - self.print_results(&func.results, sig.async_ && !use_async_sugar); + self.print_results(&func.results); } params } @@ -1415,7 +1399,6 @@ pub mod vtable{ordinal} {{ func: &Function, params_owned: bool, sig: &FnSig, - use_async_sugar: bool, ) -> Vec { self.rustdoc(&func.docs); self.rustdoc_params(&func.params, "Parameters"); @@ -1428,7 +1411,7 @@ pub mod vtable{ordinal} {{ if sig.unsafe_ { self.push_str("unsafe "); } - if sig.async_ && use_async_sugar { + if sig.async_ { self.push_str("async "); } self.push_str("fn "); @@ -1518,11 +1501,8 @@ pub mod vtable{ordinal} {{ params } - fn print_results(&mut self, results: &Results, async_: bool) { + fn print_results(&mut self, results: &Results) { self.push_str(" -> "); - if async_ { - self.push_str("impl ::core::future::Future { @@ -1545,10 +1525,6 @@ pub mod vtable{ordinal} {{ self.push_str(")") } } - - if async_ { - self.push_str("> + 'static"); - } } /// Calculates the `TypeMode` to be used for the `ty` specified. From 341c47375d44bb82dc726d34e94bbb2e92b4b595 Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:55:39 +0900 Subject: [PATCH 03/13] feat: add error-context.{new,debug-message} helpers (#1126) This commit adds helpers for new `error-context` canon functions that are part of the WASI P3 async effort. Signed-off-by: Victor Adossi Co-authored-by: Joel Dice --- crates/guest-rust/rt/src/async_support.rs | 90 +++++++++++++++++------ 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 1afec3549..464500760 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -2,29 +2,23 @@ #![allow(static_mut_refs)] extern crate std; - -use { - futures::{ - channel::oneshot, - future::FutureExt, - stream::{FuturesUnordered, StreamExt}, - }, - once_cell::sync::Lazy, - std::{ - alloc::{self, Layout}, - any::Any, - boxed::Box, - collections::hash_map, - collections::HashMap, - fmt::{self, Debug, Display}, - future::Future, - pin::Pin, - ptr, - sync::Arc, - task::{Context, Poll, Wake, Waker}, - vec::Vec, - }, -}; +use std::alloc::{self, Layout}; +use std::any::Any; +use std::boxed::Box; +use std::collections::{hash_map, HashMap}; +use std::fmt::{self, Debug, Display}; +use std::future::Future; +use std::pin::Pin; +use std::ptr; +use std::string::String; +use std::sync::Arc; +use std::task::{Context, Poll, Wake, Waker}; +use std::vec::Vec; + +use futures::channel::oneshot; +use futures::future::FutureExt; +use futures::stream::{FuturesUnordered, StreamExt}; +use once_cell::sync::Lazy; mod future_support; mod stream_support; @@ -330,6 +324,31 @@ impl ErrorContext { pub fn handle(&self) -> u32 { self.handle } + + /// Extract the debug message from a given [`ErrorContext`] + pub fn debug_message(&self) -> String { + #[cfg(not(target_arch = "wasm32"))] + { + _ = self; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[error-context-debug-message;encoding=utf8;realloc=cabi_realloc]"] + fn error_context_debug_message(_: u32, _: *mut u8); + } + + unsafe { + let mut ret = [0u32; 2]; + error_context_debug_message(self.handle, ret.as_mut_ptr() as *mut _); + let len = usize::try_from(ret[1]).unwrap(); + String::from_raw_parts(usize::try_from(ret[0]).unwrap() as *mut _, len, len) + } + } + } } impl Debug for ErrorContext { @@ -469,3 +488,28 @@ pub fn task_backpressure(enabled: bool) { } } } + +/// Call the `error-context.new` canonical built-in function. +pub fn error_context_new(debug_message: &str) -> ErrorContext { + #[cfg(not(target_arch = "wasm32"))] + { + _ = debug_message; + unreachable!(); + } + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "$root")] + extern "C" { + #[link_name = "[error-context-new;encoding=utf8]"] + fn context_new(_: *const u8, _: usize) -> i32; + } + + unsafe { + let handle = context_new(debug_message.as_ptr(), debug_message.len()); + // SAFETY: Handles (including error context handles are guaranteed to + // fit inside u32 by the Component Model ABI + ErrorContext::from_handle(u32::try_from(handle).unwrap()) + } + } +} From 0bb8697182753701c71b8b084f455b587d4635f2 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Wed, 15 Jan 2025 16:18:30 -0800 Subject: [PATCH 04/13] c#: Add cli option to generate Result types (#1115) * c#: Add cli option to generate Result types Signed-off-by: James Sturtevant * Apply suggestions from code review Co-authored-by: yowl Co-authored-by: Joel Dice * Fix indentation Signed-off-by: James Sturtevant --------- Signed-off-by: James Sturtevant Co-authored-by: yowl Co-authored-by: Joel Dice --- crates/csharp/src/function.rs | 248 ++++++++++-------- crates/csharp/src/interface.rs | 13 +- crates/csharp/src/lib.rs | 4 + crates/csharp/tests/codegen.rs | 1 + tests/runtime/main.rs | 45 ++-- .../runtime/results/wasm_with_wit_results.cs | 76 ++++++ 6 files changed, 266 insertions(+), 121 deletions(-) create mode 100644 tests/runtime/results/wasm_with_wit_results.cs diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index faa6aad35..9317c344e 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -225,6 +225,148 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { results.push(lifted); } + + fn handle_result_import(&mut self, operands: &mut Vec) { + if self.interface_gen.csharp_gen.opts.with_wit_results { + uwriteln!(self.src, "return {};", operands[0]); + return; + } + + let mut payload_is_void = false; + let mut previous = operands[0].clone(); + let mut vars: Vec<(String, Option)> = Vec::with_capacity(self.results.len()); + if let Direction::Import = self.interface_gen.direction { + for ty in &self.results { + let tmp = self.locals.tmp("tmp"); + uwrite!( + self.src, + "\ + if ({previous}.IsOk) + {{ + var {tmp} = {previous}.AsOk; + " + ); + let TypeDefKind::Result(result) = &self.interface_gen.resolve.types[*ty].kind + else { + unreachable!(); + }; + let exception_name = result + .err + .map(|ty| self.interface_gen.type_name_with_qualifier(&ty, true)); + vars.push((previous.clone(), exception_name)); + payload_is_void = result.ok.is_none(); + previous = tmp; + } + } + uwriteln!( + self.src, + "return {};", + if payload_is_void { "" } else { &previous } + ); + for (level, var) in vars.iter().enumerate().rev() { + self.interface_gen.csharp_gen.needs_wit_exception = true; + let (var_name, exception_name) = var; + let exception_name = match exception_name { + Some(type_name) => &format!("WitException<{}>", type_name), + None => "WitException", + }; + uwrite!( + self.src, + "\ + }} + else + {{ + throw new {exception_name}({var_name}.AsErr!, {level}); + }} + " + ); + } + } + + fn handle_result_call( + &mut self, + func: &&wit_parser::Function, + target: String, + func_name: String, + oper: String, + ) -> String { + let ret = self.locals.tmp("ret"); + if self.interface_gen.csharp_gen.opts.with_wit_results { + uwriteln!(self.src, "var {ret} = {target}.{func_name}({oper});"); + return ret; + } + + // otherwise generate exception code + let ty = self + .interface_gen + .type_name_with_qualifier(func.results.iter_types().next().unwrap(), true); + uwriteln!(self.src, "{ty} {ret};"); + let mut cases = Vec::with_capacity(self.results.len()); + let mut oks = Vec::with_capacity(self.results.len()); + let mut payload_is_void = false; + for (index, ty) in self.results.iter().enumerate() { + let TypeDefKind::Result(result) = &self.interface_gen.resolve.types[*ty].kind else { + unreachable!(); + }; + let err_ty = if let Some(ty) = result.err { + self.interface_gen.type_name_with_qualifier(&ty, true) + } else { + "None".to_owned() + }; + let ty = self + .interface_gen + .type_name_with_qualifier(&Type::Id(*ty), true); + let head = oks.concat(); + let tail = oks.iter().map(|_| ")").collect::>().concat(); + cases.push(format!( + "\ + case {index}: + {{ + ret = {head}{ty}.Err(({err_ty}) e.Value){tail}; + break; + }} + " + )); + oks.push(format!("{ty}.Ok(")); + payload_is_void = result.ok.is_none(); + } + if !self.results.is_empty() { + self.src.push_str( + " + try + {\n + ", + ); + } + let head = oks.concat(); + let tail = oks.iter().map(|_| ")").collect::>().concat(); + let val = if payload_is_void { + uwriteln!(self.src, "{target}.{func_name}({oper});"); + "new None()".to_owned() + } else { + format!("{target}.{func_name}({oper})") + }; + uwriteln!(self.src, "{ret} = {head}{val}{tail};"); + if !self.results.is_empty() { + self.interface_gen.csharp_gen.needs_wit_exception = true; + let cases = cases.join("\n"); + uwriteln!( + self.src, + r#"}} + catch (WitException e) + {{ + switch (e.NestingLevel) + {{ + {cases} + + default: throw new ArgumentException($"invalid nesting level: {{e.NestingLevel}}"); + }} + }} + "# + ); + } + ret + } } impl Bindgen for FunctionBindgen<'_, '_> { @@ -814,70 +956,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match func.results.len() { 0 => uwriteln!(self.src, "{target}.{func_name}({oper});"), 1 => { - let ret = self.locals.tmp("ret"); - let ty = self.interface_gen.type_name_with_qualifier( - func.results.iter_types().next().unwrap(), - true - ); - uwriteln!(self.src, "{ty} {ret};"); - let mut cases = Vec::with_capacity(self.results.len()); - let mut oks = Vec::with_capacity(self.results.len()); - let mut payload_is_void = false; - for (index, ty) in self.results.iter().enumerate() { - let TypeDefKind::Result(result) = &self.interface_gen.resolve.types[*ty].kind else { - unreachable!(); - }; - let err_ty = if let Some(ty) = result.err { - self.interface_gen.type_name_with_qualifier(&ty, true) - } else { - "None".to_owned() - }; - let ty = self.interface_gen.type_name_with_qualifier(&Type::Id(*ty), true); - let head = oks.concat(); - let tail = oks.iter().map(|_| ")").collect::>().concat(); - cases.push( - format!( - "\ - case {index}: {{ - ret = {head}{ty}.Err(({err_ty}) e.Value){tail}; - break; - }} - " - ) - ); - oks.push(format!("{ty}.Ok(")); - payload_is_void = result.ok.is_none(); - } - if !self.results.is_empty() { - self.src.push_str("try {\n"); - } - let head = oks.concat(); - let tail = oks.iter().map(|_| ")").collect::>().concat(); - let val = if payload_is_void { - uwriteln!(self.src, "{target}.{func_name}({oper});"); - "new None()".to_owned() - } else { - format!("{target}.{func_name}({oper})") - }; - uwriteln!( - self.src, - "{ret} = {head}{val}{tail};" - ); - if !self.results.is_empty() { - self.interface_gen.csharp_gen.needs_wit_exception = true; - let cases = cases.join("\n"); - uwriteln!( - self.src, - r#"}} catch (WitException e) {{ - switch (e.NestingLevel) {{ - {cases} - - default: throw new ArgumentException($"invalid nesting level: {{e.NestingLevel}}"); - }} - }} - "# - ); - } + let ret = self.handle_result_call(func, target, func_name, oper); results.push(ret); } _ => { @@ -927,46 +1006,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match func.results.len() { 0 => (), 1 => { - let mut payload_is_void = false; - let mut previous = operands[0].clone(); - let mut vars: Vec<(String, Option)> = Vec::with_capacity(self.results.len()); - if let Direction::Import = self.interface_gen.direction { - for ty in &self.results { - let tmp = self.locals.tmp("tmp"); - uwrite!( - self.src, - "\ - if ({previous}.IsOk) {{ - var {tmp} = {previous}.AsOk; - " - ); - let TypeDefKind::Result(result) = &self.interface_gen.resolve.types[*ty].kind else { - unreachable!(); - }; - let exception_name = result.err - .map(|ty| self.interface_gen.type_name_with_qualifier(&ty, true)); - vars.push((previous.clone(), exception_name)); - payload_is_void = result.ok.is_none(); - previous = tmp; - } - } - uwriteln!(self.src, "return {};", if payload_is_void { "" } else { &previous }); - for (level, var) in vars.iter().enumerate().rev() { - self.interface_gen.csharp_gen.needs_wit_exception = true; - let (var_name, exception_name) = var; - let exception_name = match exception_name { - Some(type_name) => &format!("WitException<{}>",type_name), - None => "WitException", - }; - uwrite!( - self.src, - "\ - }} else {{ - throw new {exception_name}({var_name}.AsErr!, {level}); - }} - " - ); - } + self.handle_result_import(operands); } _ => { let results = operands.join(", "); diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 1cb5dd24b..49e56cc0b 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -212,6 +212,7 @@ impl InterfaceGenerator<'_> { let (payload, results) = payload_and_results( self.resolve, *func.results.iter_types().next().unwrap(), + self.csharp_gen.opts.with_wit_results, ); ( if let Some(ty) = payload { @@ -359,6 +360,7 @@ impl InterfaceGenerator<'_> { let (payload, results) = payload_and_results( self.resolve, *func.results.iter_types().next().unwrap(), + self.csharp_gen.opts.with_wit_results, ); ( if let Some(ty) = payload { @@ -844,6 +846,7 @@ impl InterfaceGenerator<'_> { let (payload, _) = payload_and_results( self.resolve, *func.results.iter_types().next().unwrap(), + self.csharp_gen.opts.with_wit_results, ); if let Some(ty) = payload { self.csharp_gen.needs_result = true; @@ -1177,7 +1180,15 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { } } -fn payload_and_results(resolve: &Resolve, ty: Type) -> (Option, Vec) { +fn payload_and_results( + resolve: &Resolve, + ty: Type, + with_wit_results: bool, +) -> (Option, Vec) { + if with_wit_results { + return (Some(ty), Vec::new()); + } + fn recurse(resolve: &Resolve, ty: Type, results: &mut Vec) -> Option { if let Type::Id(id) = ty { if let TypeDefKind::Result(result) = &resolve.types[id].kind { diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index 06b3dea10..84e80397f 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -30,6 +30,10 @@ pub struct Opts { /// Skip generating `cabi_realloc`, `WasmImportLinkageAttribute`, and component type files #[cfg_attr(feature = "clap", arg(long))] pub skip_support_files: bool, + + /// Generate code for WIT `Result` types instead of exceptions + #[cfg_attr(feature = "clap", arg(long))] + pub with_wit_results: bool, } impl Opts { diff --git a/crates/csharp/tests/codegen.rs b/crates/csharp/tests/codegen.rs index 9501e6df0..7461d235c 100644 --- a/crates/csharp/tests/codegen.rs +++ b/crates/csharp/tests/codegen.rs @@ -33,6 +33,7 @@ macro_rules! codegen_test { runtime: wit_bindgen_csharp::CSharpRuntime::Mono, internal: false, skip_support_files: false, + with_wit_results: false, } .build() .generate(resolve, world, files) diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 53bd38d7c..60c66ce91 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -663,17 +663,26 @@ fn tests(name: &str, dir_name: &str) -> Result> { let (resolve, world) = resolve_wit_dir(&dir); for path in c_sharp.iter() { let world_name = &resolve.worlds[world].name; - let out_dir = out_dir.join(format!("csharp-{}", world_name)); - drop(fs::remove_dir_all(&out_dir)); - fs::create_dir_all(&out_dir).unwrap(); - for csharp_impl in &c_sharp { - fs::copy( - &csharp_impl, - &out_dir.join(csharp_impl.file_name().unwrap()), - ) - .unwrap(); - } + let gen_option: &str = &path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap() + .split('_') + .skip(1) + .collect::>() + .join("-"); + + let test_dir = if gen_option.is_empty() { + out_dir.join(format!("csharp-{}", world_name)) + } else { + out_dir.join(format!("csharp-{}-{}", world_name, gen_option)) + }; + + drop(fs::remove_dir_all(&test_dir)); + fs::create_dir_all(&test_dir).unwrap(); + + fs::copy(&path, &test_dir.join(path.file_name().unwrap())).unwrap(); let snake = world_name.replace("-", "_"); let camel = format!("{}World", snake.to_upper_camel_case()); @@ -683,10 +692,14 @@ fn tests(name: &str, dir_name: &str) -> Result> { path.file_stem().and_then(|s| s.to_str()).unwrap() ); - let out_wasm = out_dir.join(&assembly_name); + let out_wasm = test_dir.join(&assembly_name); let mut files = Default::default(); let mut opts = wit_bindgen_csharp::Opts::default(); + match gen_option { + "with-wit-results" => opts.with_wit_results = true, + _ => {} + }; if let Some(path) = path.file_name().and_then(|s| s.to_str()) { if path.contains("utf16") { opts.string_encoding = wit_component::StringEncoding::UTF16; @@ -695,17 +708,17 @@ fn tests(name: &str, dir_name: &str) -> Result> { opts.build().generate(&resolve, world, &mut files).unwrap(); for (file, contents) in files.iter() { - let dst = out_dir.join(file); + let dst = test_dir.join(file); fs::write(dst, contents).unwrap(); } let mut csproj = - wit_bindgen_csharp::CSProject::new(out_dir.clone(), &assembly_name, world_name); + wit_bindgen_csharp::CSProject::new(test_dir.clone(), &assembly_name, world_name); csproj.aot(); // Copy test file to target location to be included in compilation let file_name = path.file_name().unwrap(); - fs::copy(path, out_dir.join(file_name.to_str().unwrap()))?; + fs::copy(path, test_dir.join(file_name.to_str().unwrap()))?; csproj.generate()?; @@ -720,11 +733,11 @@ fn tests(name: &str, dir_name: &str) -> Result> { let mut wasm_filename = out_wasm.join(assembly_name); wasm_filename.set_extension("wasm"); - cmd.current_dir(&out_dir); + cmd.current_dir(&test_dir); // add .arg("/bl") to diagnose dotnet build problems cmd.arg("publish") - .arg(out_dir.join(format!("{camel}.csproj"))) + .arg(test_dir.join(format!("{camel}.csproj"))) .arg("-r") .arg("wasi-wasm") .arg("-c") diff --git a/tests/runtime/results/wasm_with_wit_results.cs b/tests/runtime/results/wasm_with_wit_results.cs new file mode 100644 index 000000000..e32ab8bb9 --- /dev/null +++ b/tests/runtime/results/wasm_with_wit_results.cs @@ -0,0 +1,76 @@ +namespace ResultsWorld.wit.exports.test.results +{ + public class TestImpl : ITest + { + public static Result StringError(float a) + { + return imports.test.results.TestInterop.StringError(a); + } + + public static Result EnumError(float a) + { + var result = imports.test.results.TestInterop.EnumError(a); + if (result.IsOk) { + return Result.Ok(result.AsOk); + } else { + switch (result.AsErr){ + case imports.test.results.ITest.E.A: + return Result.Err(ITest.E.A); + case imports.test.results.ITest.E.B: + return Result.Err(ITest.E.B); + case imports.test.results.ITest.E.C: + return Result.Err(ITest.E.C); + default: + throw new Exception("unreachable"); + } + } + } + + public static Result RecordError(float a) + { + var result = imports.test.results.TestInterop.RecordError(a); + if (result.IsOk) { + return Result.Ok(result.AsOk); + } else { + switch (result.AsErr) { + case imports.test.results.ITest.E2: + return Result.Err(new ITest.E2(result.AsErr.line, result.AsErr.column)); + default: + throw new Exception("unreachable"); + } + } + } + + public static Result VariantError(float a) + { + var result = imports.test.results.TestInterop.VariantError(a); + if (result.IsOk) { + return Result.Ok(result.AsOk); + } else { + switch (result.AsErr) { + case imports.test.results.ITest.E3: + switch (result.AsErr.Tag){ + case imports.test.results.ITest.E3.Tags.E1: + return Result.Err(ITest.E3.E1((ITest.E)Enum.Parse(typeof(ITest.E), result.AsErr.AsE1.ToString()))); + case imports.test.results.ITest.E3.Tags.E2: + return Result.Err(ITest.E3.E2(new ITest.E2(result.AsErr.AsE2.line, result.AsErr.AsE2.column))); + default: + throw new Exception("unreachable"); + } + default: + throw new Exception("unreachable"); + } + } + } + + public static Result EmptyError(uint a) + { + return imports.test.results.TestInterop.EmptyError(a); + } + + public static Result, string> DoubleError(uint a) + { + return imports.test.results.TestInterop.DoubleError(a); + } + } +} From b0f6fd8c2dc802f642db03202883e7e19d5b69b9 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Wed, 15 Jan 2025 17:15:42 -0800 Subject: [PATCH 05/13] c#: Remove Copy of data during import call for base types (#1122) * When calling an import we don't need to copy data to a new structure for cannoncal types. This avoid the extra copy of data in this scenario Signed-off-by: James Sturtevant * Use a pinned gc handle to get a pointer to the list Using a span and fixed keyword won't work with variants due to the fact that the external import call requires different types. Nesting of the fixed commands also become unwiedly Signed-off-by: James Sturtevant * Apply suggestions from code review Co-authored-by: Joel Dice --------- Signed-off-by: James Sturtevant Co-authored-by: Joel Dice --- crates/csharp/src/function.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 9317c344e..9f50b66f6 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -723,21 +723,24 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::ListCanonLower { element, realloc } => { - let list = &operands[0]; - let (_size, ty) = list_element_info(element); - + let list: &String = &operands[0]; match self.interface_gen.direction { Direction::Import => { - let buffer: String = self.locals.tmp("buffer"); + let ptr: String = self.locals.tmp("listPtr"); + let handle: String = self.locals.tmp("gcHandle"); + // Despite the name GCHandle.Alloc here this does not actually allocate memory on the heap. + // It pins the array with the garbage collector so that it can be passed to unmanaged code. + // It is required to free the pin after use which is done in the Cleanup section. uwrite!( self.src, " - void* {buffer} = stackalloc {ty}[({list}).Length]; - {list}.AsSpan<{ty}>().CopyTo(new Span<{ty}>({buffer}, {list}.Length)); + var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned); + var {ptr} = {handle}.AddrOfPinnedObject(); " ); - results.push(format!("(int){buffer}")); + results.push(format!("{ptr}")); results.push(format!("({list}).Length")); + self.cleanup.push(Cleanup { address: handle }); } Direction::Export => { let address = self.locals.tmp("address"); @@ -1013,7 +1016,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "return ({results});") } } - // Close all the fixed blocks. for _ in 0..self.fixed { uwriteln!(self.src, "}}"); @@ -1103,7 +1105,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match direction { Direction::Import => { - let import_name = self.interface_gen.type_name_with_qualifier(&Type::Id(id), true); + let import_name = self.interface_gen.type_name_with_qualifier(&Type::Id(id), true); if let FunctionKind::Constructor(_) = self.kind { resource = "this".to_owned(); @@ -1122,13 +1124,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { Direction::Export => { self.interface_gen.csharp_gen.needs_rep_table = true; - let export_name = self.interface_gen.csharp_gen.all_resources[&id].export_impl_name(); + let export_name = self.interface_gen.csharp_gen.all_resources[&id].export_impl_name(); if is_own { uwriteln!( self.src, "var {resource} = ({export_name}) {export_name}.repTable.Get\ - ({export_name}.WasmInterop.wasmImportResourceRep({op})); - {resource}.Handle = {op};" + ({export_name}.WasmInterop.wasmImportResourceRep({op})); + {resource}.Handle = {op};" ); } else { uwriteln!(self.src, "var {resource} = ({export_name}) {export_name}.repTable.Get({op});"); From 59050939c360884523fe5b7ed5d7059f284ac820 Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Fri, 17 Jan 2025 02:58:45 +0900 Subject: [PATCH 06/13] refactor(rust): add default value for async usage (#1127) This commit introduces a default value for the `--async` option as used with Rust bindgen. Signed-off-by: Victor Adossi --- crates/rust/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 9b2ab6e53..d8558a6b7 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -294,7 +294,7 @@ pub struct Opts { /// - some=[,...], where each is of the form: /// - import: or /// - export: - #[cfg_attr(feature = "clap", arg(long = "async", value_parser = parse_async))] + #[cfg_attr(feature = "clap", arg(long = "async", value_parser = parse_async, default_value = "none"))] pub async_: AsyncConfig, } From e566d45fe1dbd02eda110d7a7701ca69cd4a17c9 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 17 Jan 2025 09:42:56 -0700 Subject: [PATCH 07/13] fix lowering results for async exports (#1129) Previously, we were either dropping the result too early (e.g. `wit_bindgen_rt::async_support::ErrorContext`) or not at all (e.g. `String` and `Vec`) due to the lowering code expecting to pass ownership to the caller, which would later free memory using a post-return function. But async exports don't have post-return functions; they use `task.return` instead, and they should free any memory (and/or drop handles) after `task.return` returns. So now we do that! Signed-off-by: Joel Dice --- crates/core/src/abi.rs | 11 ++++++----- crates/guest-rust/rt/src/async_support.rs | 4 ++-- crates/rust/src/bindgen.rs | 9 ++++++++- crates/rust/src/interface.rs | 9 ++++++++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index e331188fd..6d02ad788 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1474,11 +1474,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn list_realloc(&self) -> Option<&'static str> { - // Lowering parameters calling a wasm import means - // we don't need to pass ownership, but we pass - // ownership in all other cases. - match (self.variant, self.lift_lower) { - (AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults) => None, + // Lowering parameters calling a wasm import _or_ returning a result + // from an async-lifted wasm export means we don't need to pass + // ownership, but we pass ownership in all other cases. + match (self.variant, self.lift_lower, self.async_) { + (AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, _) + | (AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, true) => None, _ => Some("cabi_realloc"), } } diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 464500760..b60cabaf8 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -122,12 +122,12 @@ unsafe fn poll(state: *mut FutureState) -> Poll<()> { #[doc(hidden)] pub fn first_poll( future: impl Future + 'static, - fun: impl FnOnce(T) + 'static, + fun: impl FnOnce(&T) + 'static, ) -> *mut u8 { let state = Box::into_raw(Box::new(FutureState { todo: 0, tasks: Some( - [Box::pin(future.map(fun)) as BoxFuture] + [Box::pin(future.map(|v| fun(&v))) as BoxFuture] .into_iter() .collect(), ), diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index bb9f536be..b200fcacc 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -21,6 +21,7 @@ pub(super) struct FunctionBindgen<'a, 'b> { pub handle_decls: Vec, always_owned: bool, pub async_result_name: Option, + emitted_cleanup: bool, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -47,10 +48,15 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { handle_decls: Vec::new(), always_owned, async_result_name: None, + emitted_cleanup: false, } } fn emit_cleanup(&mut self) { + if self.emitted_cleanup { + return; + } + self.emitted_cleanup = true; for (ptr, layout) in mem::take(&mut self.cleanup) { let alloc = self.gen.path_to_std_alloc_module(); self.push_str(&format!( @@ -1001,10 +1007,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src, "\ {func}({}); - }}); ", operands.join(", ") ); + self.emit_cleanup(); + self.src.push_str("});\n"); } Instruction::Flush { amt } => { diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 7fd98dd92..70b0d0de2 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1088,7 +1088,14 @@ pub mod vtable{ordinal} {{ async_result_name, .. } = f; - assert!(!needs_cleanup_list); + if async_ { + if needs_cleanup_list { + let vec = self.path_to_vec(); + uwriteln!(self.src, "let mut cleanup_list = {vec}::new();"); + } + } else { + assert!(!needs_cleanup_list); + } if let Some(name) = async_result_name { // When `async_result_name` is `Some(_)`, we wrap the call and any // `handle_decls` in a block scope to ensure any resource handles From 94ba15617543815de6bdf21025a27d4f4a324625 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Fri, 17 Jan 2025 15:13:46 -0800 Subject: [PATCH 08/13] Use Stackbased return pointer for imports (#1132) Signed-off-by: James Sturtevant --- crates/csharp/src/function.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 9f50b66f6..baa7cc253 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -27,7 +27,6 @@ pub(crate) struct FunctionBindgen<'a, 'b> { cleanup: Vec, import_return_pointer_area_size: usize, import_return_pointer_area_align: usize, - fixed: usize, // Number of `fixed` blocks that need to be closed. pub(crate) resource_drops: Vec<(String, String)>, } @@ -61,7 +60,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { cleanup: Vec::new(), import_return_pointer_area_size: 0, import_return_pointer_area_align: 0, - fixed: 0, resource_drops: Vec::new(), } } @@ -1016,10 +1014,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "return ({results});") } } - // Close all the fixed blocks. - for _ in 0..self.fixed { - uwriteln!(self.src, "}}"); - } } } @@ -1160,8 +1154,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { fn return_pointer(&mut self, size: usize, align: usize) -> String { let ptr = self.locals.tmp("ptr"); - // Use a stack-based return area for imports, because exports need - // their return area to be live until the post-return call. match self.interface_gen.direction { Direction::Import => { self.import_return_pointer_area_size = @@ -1173,25 +1165,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.import_return_pointer_area_align, ); let ret_area = self.locals.tmp("retArea"); - let ret_area_byte0 = self.locals.tmp("retAreaByte0"); + // We can use the stack here to get a return pointer when importing. + // We do need to do a slight over-allocation since C# doesn't provide a way + // to align the allocation via the stackalloc command, unlike with a fixed array where the pointer will be aligned. + // We get the final ptr to pass to the wasm runtime by shifting to the + // correctly aligned pointer (sometimes it can be already aligned). uwrite!( self.src, " - var {2} = new {0}[{1}]; - fixed ({0}* {3} = &{2}[0]) - {{ - var {ptr} = (nint){3}; - ", - element_type, - array_size, - ret_area, - ret_area_byte0 + var {ret_area} = stackalloc {element_type}[{array_size}+1]; + var {ptr} = ((int){ret_area}) + ({align} - 1) & -{align}; + " ); - self.fixed = self.fixed + 1; - format!("{ptr}") } Direction::Export => { + // exports need their return area to be live until the post-return call. self.interface_gen.csharp_gen.return_area_size = self.interface_gen.csharp_gen.return_area_size.max(size); self.interface_gen.csharp_gen.return_area_align = From 629ced7fb309ce10b3d60960052c0395ef8beba1 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Tue, 21 Jan 2025 08:03:21 -0800 Subject: [PATCH 09/13] Bump dotnet version for tests (#1133) Signed-off-by: James Sturtevant --- .github/workflows/main.yml | 14 +++++--------- crates/csharp/src/csproj.rs | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7d46bc65..be739cd4b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,15 +75,11 @@ jobs: - uses: ./.github/actions/install-wasi-sdk if: matrix.lang == 'c' || (matrix.lang == 'csharp' && matrix.os == 'windows-latest') - - run: | - curl.exe -LO https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 - powershell -File dotnet-install.ps1 -Channel 8.0.1xx -Verbose - echo DOTNET_ROOT=$LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_ENV - export DOTNET_ROOT=$LOCALAPPDATA\\Microsoft\\dotnet - echo $LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_PATH - echo $LOCALAPPDATA'\Microsoft\dotnet\tools' >> $GITHUB_PATH - $LOCALAPPDATA/Microsoft/dotnet/dotnet --info - if: matrix.os == 'windows-latest' && matrix.lang == 'csharp' + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + if: matrix.lang == 'csharp' # Hacky work-around for https://github.com/dotnet/runtime/issues/80619 - run: dotnet new console -o /tmp/foo diff --git a/crates/csharp/src/csproj.rs b/crates/csharp/src/csproj.rs index a315a5435..415472614 100644 --- a/crates/csharp/src/csproj.rs +++ b/crates/csharp/src/csproj.rs @@ -65,7 +65,7 @@ impl CSProjectLLVMBuilder { " - net8.0 + net9.0 preview {name} enable From 967d075e31ca5c06ea2f82cfa271fd657af01e0b Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 21 Jan 2025 18:59:54 +0100 Subject: [PATCH 10/13] fix: re-export `async_support` in `rt` (#1136) Signed-off-by: Roman Volosatovs --- crates/guest-rust/src/lib.rs | 3 ++ crates/rust/src/bindgen.rs | 12 +++--- crates/rust/src/interface.rs | 20 ++++----- crates/rust/src/lib.rs | 78 +++++++++++++++++++----------------- 4 files changed, 59 insertions(+), 54 deletions(-) diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index f752a97db..4e8f60366 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -877,5 +877,8 @@ pub mod rt { #[cfg(all(feature = "realloc", not(target_env = "p2")))] pub use wit_bindgen_rt::cabi_realloc; + #[cfg(feature = "async")] + pub use wit_bindgen_rt::async_support; + pub use crate::pre_wit_bindgen_0_20_0::*; } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index b200fcacc..602639c86 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -481,7 +481,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { payload, .. } => { - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); let op = &operands[0]; let name = payload .as_ref() @@ -504,7 +504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::StreamLift { payload, .. } => { - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); let op = &operands[0]; let name = self .gen @@ -523,7 +523,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::ErrorContextLift { .. } => { - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); let op = &operands[0]; results.push(format!( "{async_support}::ErrorContext::from_handle({op} as u32)" @@ -869,7 +869,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::AsyncCallWasm { name, size, align } => { let func = self.declare_import(name, &[WasmType::Pointer; 2], &[WasmType::I32]); - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); let tmp = self.tmp(); let layout = format!("layout{tmp}"); let alloc = self.gen.path_to_std_alloc_module(); @@ -906,7 +906,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { .unwrap() .to_upper_camel_case(); let call = if self.async_ { - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); format!("{async_support}::futures::FutureExt::map(T::new") } else { format!("{ty}::new(T::new",) @@ -975,7 +975,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { params }; - let async_support = self.gen.path_to_async_support(); + let async_support = self.gen.gen.async_support_path(); // TODO: This relies on `abi::Generator` emitting // `AsyncCallReturn` immediately after this instruction to // complete the incomplete expression we generate here. We diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 70b0d0de2..687b232ec 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -497,7 +497,7 @@ macro_rules! {macro_name} {{ .unwrap_or_else(|| "$root".into()) ); let func_name = &func.name; - let async_support = self.path_to_async_support(); + let async_support = self.gen.async_support_path(); match &self.resolve.types[ty].kind { TypeDefKind::Future(payload_type) => { @@ -1116,7 +1116,7 @@ pub mod vtable{ordinal} {{ self.src.push_str("}\n"); if async_ { - let async_support = self.path_to_async_support(); + let async_support = self.gen.async_support_path(); uwrite!( self.src, "\ @@ -2513,10 +2513,6 @@ pub mod vtable{ordinal} {{ self.path_from_runtime_module(RuntimeItem::StdAllocModule, "alloc") } - pub fn path_to_async_support(&mut self) -> String { - "::wit_bindgen_rt::async_support".into() - } - fn path_from_runtime_module( &mut self, item: RuntimeItem, @@ -2837,7 +2833,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_future(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { - let async_support = self.path_to_async_support(); + let async_support = self.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2854,7 +2850,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - let async_support = self.path_to_async_support(); + let async_support = self.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2871,7 +2867,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_error_context(&mut self, _id: TypeId, name: &str, docs: &Docs) { - let async_support = self.path_to_async_support(); + let async_support = self.gen.async_support_path(); self.rustdoc(docs); self.push_str(&format!("pub type {} = ", name.to_upper_camel_case())); self.push_str(&format!("{async_support}::ErrorContext")); @@ -2967,7 +2963,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_future(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { - let async_support = self.interface.path_to_async_support(); + let async_support = self.interface.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2980,7 +2976,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_stream(&mut self, _id: TypeId, ty: &Type, _docs: &Docs) { - let async_support = self.interface.path_to_async_support(); + let async_support = self.interface.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2993,7 +2989,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_error_context(&mut self) { - let async_support = self.interface.path_to_async_support(); + let async_support = self.interface.gen.async_support_path(); self.interface .push_str(&format!("{async_support}::ErrorContext")); } diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index d8558a6b7..0137974ec 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -395,6 +395,10 @@ impl RustWasm { .unwrap_or(format!("{}::bitflags", self.runtime_path())) } + fn async_support_path(&self) -> String { + format!("{}::async_support", self.runtime_path()) + } + fn name_interface( &mut self, resolve: &Resolve, @@ -456,71 +460,73 @@ impl RustWasm { self.src.push_str("}\n"); if !self.future_payloads.is_empty() { - self.src.push_str( + let async_support = self.async_support_path(); + self.src.push_str(&format!( "\ -pub mod wit_future { +pub mod wit_future {{ #![allow(dead_code, unused_variables, clippy::all)] #[doc(hidden)] - pub trait FuturePayload: Unpin + Sized + 'static { - fn new() -> (u32, &'static ::wit_bindgen_rt::async_support::FutureVtable); - }", - ); + pub trait FuturePayload: Unpin + Sized + 'static {{ + fn new() -> (u32, &'static {async_support}::FutureVtable); + }}" + )); for code in self.future_payloads.values() { self.src.push_str(code); } - self.src.push_str( + self.src.push_str(&format!( "\ /// Creates a new Component Model `future` with the specified payload type. - pub fn new() -> (::wit_bindgen_rt::async_support::FutureWriter, ::wit_bindgen_rt::async_support::FutureReader) { + pub fn new() -> ({async_support}::FutureWriter, {async_support}::FutureReader) {{ let (handle, vtable) = T::new(); - ::wit_bindgen_rt::async_support::with_entry(handle, |entry| match entry { - ::std::collections::hash_map::Entry::Vacant(entry) => { - entry.insert(::wit_bindgen_rt::async_support::Handle::LocalOpen); - } + {async_support}::with_entry(handle, |entry| match entry {{ + ::std::collections::hash_map::Entry::Vacant(entry) => {{ + entry.insert({async_support}::Handle::LocalOpen); + }} ::std::collections::hash_map::Entry::Occupied(_) => unreachable!(), - }); + }}); ( - ::wit_bindgen_rt::async_support::FutureWriter::new(handle, vtable), - ::wit_bindgen_rt::async_support::FutureReader::new(handle, vtable), + {async_support}::FutureWriter::new(handle, vtable), + {async_support}::FutureReader::new(handle, vtable), ) - } -} + }} +}} ", - ); + )); } if !self.stream_payloads.is_empty() { - self.src.push_str( + let async_support = self.async_support_path(); + self.src.push_str(&format!( "\ -pub mod wit_stream { +pub mod wit_stream {{ #![allow(dead_code, unused_variables, clippy::all)] - pub trait StreamPayload: Unpin + Sized + 'static { - fn new() -> (u32, &'static ::wit_bindgen_rt::async_support::StreamVtable); - }", - ); + pub trait StreamPayload: Unpin + Sized + 'static {{ + fn new() -> (u32, &'static {async_support}::StreamVtable); + }}" + )); for code in self.stream_payloads.values() { self.src.push_str(code); } self.src.push_str( - "\ + &format!("\ /// Creates a new Component Model `stream` with the specified payload type. - pub fn new() -> (::wit_bindgen_rt::async_support::StreamWriter, ::wit_bindgen_rt::async_support::StreamReader) { + pub fn new() -> ({async_support}::StreamWriter, {async_support}::StreamReader) {{ let (handle, vtable) = T::new(); - ::wit_bindgen_rt::async_support::with_entry(handle, |entry| match entry { - ::std::collections::hash_map::Entry::Vacant(entry) => { - entry.insert(::wit_bindgen_rt::async_support::Handle::LocalOpen); - } + {async_support}::with_entry(handle, |entry| match entry {{ + ::std::collections::hash_map::Entry::Vacant(entry) => {{ + entry.insert({async_support}::Handle::LocalOpen); + }} ::std::collections::hash_map::Entry::Occupied(_) => unreachable!(), - }); + }}); ( - ::wit_bindgen_rt::async_support::StreamWriter::new(handle, vtable), - ::wit_bindgen_rt::async_support::StreamReader::new(handle, vtable), + {async_support}::StreamWriter::new(handle, vtable), + {async_support}::StreamReader::new(handle, vtable), ) - } -} - ", + }} +}} + "), ); } } From 98086319259f2c4a16088812be3f620873498ff5 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 22 Jan 2025 02:01:34 +0100 Subject: [PATCH 11/13] feat: add support for `stream` with no `` (#1130) * feat: add support for `stream` with no `` Signed-off-by: Roman Volosatovs test: return `future` in future return test WIT Signed-off-by: Roman Volosatovs build: update wasm-tools Signed-off-by: Roman Volosatovs avoid divide-by-zero in `StreamReader::poll_next` I hit this when testing "unit streams" (i.e. streams with no payload type) end-to-end. BTW, I can't wait to get some runtime tests going in this repo so we can catch this kind of thing in CI. Signed-off-by: Joel Dice * update to latest wasm-tools Signed-off-by: Joel Dice --------- Signed-off-by: Joel Dice Co-authored-by: Joel Dice --- Cargo.lock | 68 +++++++++---------- Cargo.toml | 10 +-- crates/c/src/lib.rs | 4 +- crates/core/src/abi.rs | 4 +- crates/core/src/lib.rs | 4 +- crates/csharp/src/interface.rs | 2 +- crates/go/src/interface.rs | 4 +- .../rt/src/async_support/stream_support.rs | 2 +- crates/markdown/src/lib.rs | 17 +++-- crates/moonbit/src/lib.rs | 2 +- crates/rust/src/bindgen.rs | 10 ++- crates/rust/src/interface.rs | 62 +++++++++++------ crates/teavm-java/src/lib.rs | 2 +- tests/codegen/futures.wit | 2 +- tests/codegen/streams.wit | 2 + 15 files changed, 113 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9f242910..0ef2ab75d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1503,10 +1503,10 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.223.0", + "wasm-encoder 0.224.0", "wit-bindgen-core", "wit-component", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -1781,19 +1781,19 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.223.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e636076193fa68103e937ac951b5f2f587624097017d764b8984d9c0f149464" +checksum = "b7249cf8cb0c6b9cb42bce90c0a5feb276fbf963fa385ff3d818ab3d90818ed6" dependencies = [ "leb128", - "wasmparser 0.223.0", + "wasmparser 0.224.0", ] [[package]] name = "wasm-metadata" -version = "0.223.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c730c3379d3d20e5a0245b0724b924483e853588ca8fba547c1e21f19e7d735" +checksum = "79d13d93febc749413cb6f327e4fdba8c84e4d03bd69fcc4a220c66f113c8de1" dependencies = [ "anyhow", "indexmap", @@ -1802,8 +1802,8 @@ dependencies = [ "serde_json", "spdx", "url", - "wasm-encoder 0.223.0", - "wasmparser 0.223.0", + "wasm-encoder 0.224.0", + "wasmparser 0.224.0", ] [[package]] @@ -1822,9 +1822,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.223.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a99faceb1a5a84dd6084ec4bfa4b2ab153b5793b43fd8f58b89232634afc35" +checksum = "65881a664fdd43646b647bb27bf186ab09c05bf56779d40aed4c6dce47d423f5" dependencies = [ "bitflags", "hashbrown 0.15.2", @@ -2143,24 +2143,24 @@ dependencies = [ [[package]] name = "wast" -version = "223.0.0" +version = "224.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59b2ba8a2ff9f06194b7be9524f92e45e70149f4dacc0d0c7ad92b59ac875e4" +checksum = "d722a51e62b669d17e5a9f6bc8ec210178b37d869114355aa46989686c5c6391" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.223.0", + "wasm-encoder 0.224.0", ] [[package]] name = "wat" -version = "1.223.0" +version = "1.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662786915c427e4918ff01eabb3c4756d4d947cd8f635761526b4cc9da2eaaad" +checksum = "71dece6a7dd5bcbcf8d256606c7fb3faa36286d46bf3f98185407719a5ceede2" dependencies = [ - "wast 223.0.0", + "wast 224.0.0", ] [[package]] @@ -2379,11 +2379,11 @@ dependencies = [ "clap", "heck 0.5.0", "test-helpers", - "wasm-encoder 0.223.0", + "wasm-encoder 0.224.0", "wasm-metadata", "wit-bindgen-core", "wit-component", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -2394,8 +2394,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-artifacts", - "wasm-encoder 0.223.0", - "wasmparser 0.223.0", + "wasm-encoder 0.224.0", + "wasmparser 0.224.0", "wasmtime", "wasmtime-wasi", "wit-bindgen-c", @@ -2407,7 +2407,7 @@ dependencies = [ "wit-bindgen-rust", "wit-bindgen-teavm-java", "wit-component", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -2416,7 +2416,7 @@ version = "0.37.0" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -2428,12 +2428,12 @@ dependencies = [ "heck 0.5.0", "indexmap", "test-helpers", - "wasm-encoder 0.223.0", + "wasm-encoder 0.224.0", "wasm-metadata", - "wasmparser 0.223.0", + "wasmparser 0.224.0", "wit-bindgen-core", "wit-component", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -2528,9 +2528,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.223.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10ed2aeee4c8ec5715875f62f4a3de3608d6987165c116810d8c2908aa9d93b" +checksum = "ad555ab4f4e676474df746d937823c7279c2d6dd36c3e97a61db893d4ef64ee5" dependencies = [ "anyhow", "bitflags", @@ -2539,11 +2539,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.223.0", + "wasm-encoder 0.224.0", "wasm-metadata", - "wasmparser 0.223.0", + "wasmparser 0.224.0", "wat", - "wit-parser 0.223.0", + "wit-parser 0.224.0", ] [[package]] @@ -2566,9 +2566,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.223.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92772f4dcacb804b275981eea1d920b12b377993b53307f1e33d87404e080281" +checksum = "23e2925a7365d2c6709ae17bdbb5777ffd8154fd70906b413fc01b75f0dba59e" dependencies = [ "anyhow", "id-arena", @@ -2579,7 +2579,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.223.0", + "wasmparser 0.224.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cd2360df4..daee7bb01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,11 @@ prettyplease = "0.2.20" syn = { version = "2.0.89", features = ["printing"] } futures = "0.3.31" -wasmparser = "0.223.0" -wasm-encoder = "0.223.0" -wasm-metadata = "0.223.0" -wit-parser = "0.223.0" -wit-component = "0.223.0" +wasmparser = "0.224.0" +wasm-encoder = "0.224.0" +wasm-metadata = "0.224.0" +wit-parser = "0.224.0" +wit-component = "0.224.0" wit-bindgen-core = { path = 'crates/core', version = '0.37.0' } wit-bindgen-c = { path = 'crates/c', version = '0.37.0' } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index fdcf018c8..5da00ff5c 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1352,7 +1352,7 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{ todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } @@ -1450,7 +1450,7 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> todo!("print_anonymous_type for future"); } - fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Type, _docs: &Docs) { + fn anonymous_type_stream(&mut self, _id: TypeId, _ty: &Option, _docs: &Docs) { todo!("print_anonymous_type for stream"); } diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 6d02ad788..915af7018 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -364,13 +364,13 @@ def_instruction! { /// Create an `i32` from a stream. StreamLower { - payload: &'a Type, + payload: &'a Option, ty: TypeId, } : [1] => [1], /// Create a stream from an `i32`. StreamLift { - payload: &'a Type, + payload: &'a Option, ty: TypeId, } : [1] => [1], diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index b193df61a..7511dc459 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -155,7 +155,7 @@ pub trait InterfaceGenerator<'a> { fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); fn type_error_context(&mut self, id: TypeId, name: &str, docs: &Docs); fn types(&mut self, iface: InterfaceId) { let iface = &self.resolve().interfaces[iface]; @@ -195,7 +195,7 @@ pub trait AnonymousTypeGenerator<'a> { fn anonymous_type_result(&mut self, id: TypeId, ty: &Result_, docs: &Docs); fn anonymous_type_list(&mut self, id: TypeId, ty: &Type, docs: &Docs); fn anonymous_type_future(&mut self, id: TypeId, ty: &Option, docs: &Docs); - fn anonymous_type_stream(&mut self, id: TypeId, ty: &Type, docs: &Docs); + fn anonymous_type_stream(&mut self, id: TypeId, ty: &Option, docs: &Docs); fn anonymous_type_type(&mut self, id: TypeId, ty: &Type, docs: &Docs); fn anonymous_type_error_context(&mut self); diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 49e56cc0b..26cb51e02 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -1169,7 +1169,7 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } diff --git a/crates/go/src/interface.rs b/crates/go/src/interface.rs index 39c54aef1..8d3224966 100644 --- a/crates/go/src/interface.rs +++ b/crates/go/src/interface.rs @@ -322,7 +322,7 @@ impl InterfaceGenerator<'_> { TypeDefKind::Stream(t) => { let mut src = String::new(); src.push_str("Stream"); - src.push_str(&self.ty_name(t)); + src.push_str(&self.optional_ty_name(t.as_ref())); src.push('T'); src } @@ -1271,7 +1271,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index c8e0a96b7..d80b96d60 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -342,7 +342,7 @@ impl Stream for StreamReader { }; Box::pin(async move { let mut buffer = iter::repeat_with(MaybeUninit::uninit) - .take(ceiling(64 * 1024, mem::size_of::())) + .take(ceiling(64 * 1024, mem::size_of::().max(1))) .collect::>(); let result = diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index a890e8200..43db5b690 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -413,11 +413,16 @@ impl InterfaceGenerator<'_> { self.push_str("future"); } }, - TypeDefKind::Stream(t) => { - self.push_str("stream<"); - self.print_ty(t); - self.push_str(">"); - } + TypeDefKind::Stream(t) => match t { + Some(t) => { + self.push_str("stream<"); + self.print_ty(t); + self.push_str(">"); + } + None => { + self.push_str("stream"); + } + }, TypeDefKind::ErrorContext => { self.push_str("error-context"); } @@ -661,7 +666,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 72be26664..b17b8dd3c 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1504,7 +1504,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 602639c86..a0f47889a 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -506,9 +506,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::StreamLift { payload, .. } => { let async_support = self.gen.gen.async_support_path(); let op = &operands[0]; - let name = self - .gen - .type_name_owned_with_id(payload, Identifier::StreamOrFuturePayload); + let name = payload + .as_ref() + .map(|ty| { + self.gen + .type_name_owned_with_id(ty, Identifier::StreamOrFuturePayload) + }) + .unwrap_or_else(|| "()".into()); let ordinal = self.gen.gen.stream_payloads.get_index_of(&name).unwrap(); let path = self.gen.path_to_root(); results.push(format!( diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 687b232ec..b7bf24f12 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -689,26 +689,33 @@ pub mod vtable{ordinal} {{ } } TypeDefKind::Stream(payload_type) => { - let name = self.type_name_owned(payload_type); + let name = if let Some(payload_type) = payload_type { + self.type_name_owned(payload_type) + } else { + "()".into() + }; if !self.gen.stream_payloads.contains_key(&name) { let ordinal = self.gen.stream_payloads.len(); - let size = self.sizes.size(payload_type).size_wasm32(); - let align = self.sizes.align(payload_type).align_wasm32(); + let (size, align) = if let Some(payload_type) = payload_type { + ( + self.sizes.size(payload_type), + self.sizes.align(payload_type), + ) + } else { + ( + ArchitectureSize { + bytes: 0, + pointers: 0, + }, + Alignment::default(), + ) + }; + let size = size.size_wasm32(); + let align = align.align_wasm32(); let alloc = self.path_to_std_alloc_module(); - let (lower_address, lower, lift_address, lift) = - if stream_direct(payload_type) { - let lower_address = - "let address = values.as_ptr() as *mut u8;".into(); - let lift_address = - "let address = values.as_mut_ptr() as *mut u8;".into(); - ( - lower_address, - String::new(), - lift_address, - "let value = ();\n".into(), - ) - } else { + let (lower_address, lower, lift_address, lift) = match payload_type { + Some(payload_type) if !stream_direct(payload_type) => { let address = format!( "let address = unsafe {{ {alloc}::alloc\ ({alloc}::Layout::from_size_align_unchecked\ @@ -744,7 +751,20 @@ for (index, dst) in values.iter_mut().take(count).enumerate() {{ "# ); (address.clone(), lower, address, lift) - }; + } + _ => { + let lower_address = + "let address = values.as_ptr() as *mut u8;".into(); + let lift_address = + "let address = values.as_mut_ptr() as *mut u8;".into(); + ( + lower_address, + String::new(), + lift_address, + "let value = ();\n".into(), + ) + } + }; let box_ = self.path_to_box(); let code = format!( @@ -2849,7 +2869,7 @@ impl<'a> {camel}Borrow<'a>{{ self.push_str(";\n"); } - fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { let async_support = self.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, @@ -2861,7 +2881,7 @@ impl<'a> {camel}Borrow<'a>{{ self.print_generics(mode.lifetime); self.push_str(" = "); self.push_str(&format!("{async_support}::StreamReader<")); - self.print_ty(ty, mode); + self.print_optional_ty(ty.as_ref(), mode); self.push_str(">"); self.push_str(";\n"); } @@ -2975,7 +2995,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< self.interface.push_str(">"); } - fn anonymous_type_stream(&mut self, _id: TypeId, ty: &Type, _docs: &Docs) { + fn anonymous_type_stream(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { let async_support = self.interface.gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, @@ -2984,7 +3004,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< }; self.interface .push_str(&format!("{async_support}::StreamReader<")); - self.interface.print_ty(ty, mode); + self.interface.print_optional_ty(ty.as_ref(), mode); self.interface.push_str(">"); } diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 014bd6ace..53d498fad 100644 --- a/crates/teavm-java/src/lib.rs +++ b/crates/teavm-java/src/lib.rs @@ -1075,7 +1075,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!() } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { _ = (id, name, ty, docs); todo!() } diff --git a/tests/codegen/futures.wit b/tests/codegen/futures.wit index 2d634a400..ff24a5098 100644 --- a/tests/codegen/futures.wit +++ b/tests/codegen/futures.wit @@ -13,7 +13,7 @@ interface futures { future-f32-param: func(x: future); future-f64-param: func(x: future); - future-ret: func(x: future); + future-ret: func() -> future; future-u8-ret: func() -> future; future-u16-ret: func() -> future; future-u32-ret: func() -> future; diff --git a/tests/codegen/streams.wit b/tests/codegen/streams.wit index 7ed696ed8..96ccfeed2 100644 --- a/tests/codegen/streams.wit +++ b/tests/codegen/streams.wit @@ -15,6 +15,7 @@ interface transmit { } interface streams { + stream-param: func(x: stream); stream-u8-param: func(x: stream); stream-u16-param: func(x: stream); stream-u32-param: func(x: stream); @@ -26,6 +27,7 @@ interface streams { stream-f32-param: func(x: stream); stream-f64-param: func(x: stream); + stream-ret: func() -> stream; stream-u8-ret: func() -> stream; stream-u16-ret: func() -> stream; stream-u32-ret: func() -> stream; From 2be426a59ff260f2b2e23eea5cd3b598986883c7 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Tue, 21 Jan 2025 18:01:31 -0800 Subject: [PATCH 12/13] Add generic clean up to support variants and strings (#1137) Signed-off-by: James Sturtevant --- crates/csharp/src/function.rs | 84 +++++++++++++--------------- crates/csharp/src/interface.rs | 2 - crates/csharp/src/world_generator.rs | 21 ------- 3 files changed, 40 insertions(+), 67 deletions(-) diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index baa7cc253..91f68c398 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -22,9 +22,7 @@ pub(crate) struct FunctionBindgen<'a, 'b> { block_storage: Vec, blocks: Vec, payloads: Vec, - pub(crate) needs_cleanup_list: bool, - needs_native_alloc_list: bool, - cleanup: Vec, + pub(crate) needs_cleanup: bool, import_return_pointer_area_size: usize, import_return_pointer_area_align: usize, pub(crate) resource_drops: Vec<(String, String)>, @@ -55,9 +53,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { block_storage: Vec::new(), blocks: Vec::new(), payloads: Vec::new(), - needs_cleanup_list: false, - needs_native_alloc_list: false, - cleanup: Vec::new(), + needs_cleanup: false, import_return_pointer_area_size: 0, import_return_pointer_area_align: 0, resource_drops: Vec::new(), @@ -729,16 +725,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { // Despite the name GCHandle.Alloc here this does not actually allocate memory on the heap. // It pins the array with the garbage collector so that it can be passed to unmanaged code. // It is required to free the pin after use which is done in the Cleanup section. + self.needs_cleanup = true; uwrite!( self.src, " var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned); var {ptr} = {handle}.AddrOfPinnedObject(); + cleanups.Add(()=> {handle}.Free()); " ); results.push(format!("{ptr}")); results.push(format!("({list}).Length")); - self.cleanup.push(Cleanup { address: handle }); } Direction::Export => { let address = self.locals.tmp("address"); @@ -756,9 +753,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); if realloc.is_none() { - self.cleanup.push(Cleanup { - address: gc_handle.clone(), - }); + self.needs_cleanup = true; + uwrite!( + self.src, + " + cleanups.Add(()=> {gc_handle}.Free()); + "); } results.push(format!("((IntPtr)({address})).ToInt32()")); results.push(format!("{list}.Length")); @@ -786,22 +786,39 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::StringLower { realloc } => { let op = &operands[0]; let interop_string = self.locals.tmp("interopString"); - let result_var = self.locals.tmp("result"); + let utf8_bytes = self.locals.tmp("utf8Bytes"); + let length = self.locals.tmp("length"); + let gc_handle = self.locals.tmp("gcHandle"); uwriteln!( self.src, " - var {result_var} = {op}; - IntPtr {interop_string} = InteropString.FromString({result_var}, out int length{result_var});" + var {utf8_bytes} = Encoding.UTF8.GetBytes({op}); + var {length} = {utf8_bytes}.Length; + var {gc_handle} = GCHandle.Alloc({utf8_bytes}, GCHandleType.Pinned); + var {interop_string} = {gc_handle}.AddrOfPinnedObject(); + " ); if realloc.is_none() { results.push(format!("{interop_string}.ToInt32()")); + self.needs_cleanup = true; + uwrite!( + self.src, + " + cleanups.Add(()=> {gc_handle}.Free()); + "); } else { results.push(format!("{interop_string}.ToInt32()")); } - results.push(format!("length{result_var}")); + results.push(format!("{length}")); - self.interface_gen.csharp_gen.needs_interop_string = true; + if FunctionKind::Freestanding == *self.kind || self.interface_gen.direction == Direction::Export { + self.interface_gen.require_interop_using("System.Text"); + self.interface_gen.require_interop_using("System.Runtime.InteropServices"); + } else { + self.interface_gen.require_using("System.Text"); + self.interface_gen.require_using("System.Runtime.InteropServices"); + } } Instruction::StringLift { .. } => { @@ -835,14 +852,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { let buffer_size = self.locals.tmp("bufferSize"); //TODO: wasm64 let align = self.interface_gen.csharp_gen.sizes.align(element).align_wasm32(); - self.needs_native_alloc_list = true; + self.needs_cleanup = true; uwrite!( self.src, " var {buffer_size} = {size} * (nuint){list}.Count; var {address} = NativeMemory.AlignedAlloc({buffer_size}, {align}); - nativeAllocs.Add((IntPtr){address}); + cleanups.Add(()=> NativeMemory.AlignedFree({address})); for (int {index} = 0; {index} < {list}.Count; ++{index}) {{ {ty} {block_element} = {list}[{index}]; @@ -988,19 +1005,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::Return { amt: _, func } => { - for Cleanup { address } in &self.cleanup { - uwriteln!(self.src, "{address}.Free();"); - } - - if self.needs_native_alloc_list { - self.src.insert_str(0, "var nativeAllocs = new List(); + if self.needs_cleanup { + self.src.insert_str(0, "var cleanups = new List(); "); uwriteln!(self.src, "\ - foreach (var nativeAlloc in nativeAllocs) - {{ - NativeMemory.AlignedFree((void*)nativeAlloc); - }}"); + foreach (var cleanup in cleanups) + {{ + cleanup(); + }}"); } if !matches!((self.interface_gen.direction, self.kind), (Direction::Import, FunctionKind::Constructor(_))) { @@ -1204,7 +1217,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { body: mem::take(&mut self.src), element: self.locals.tmp("element"), base: self.locals.tmp("basePtr"), - cleanup: mem::take(&mut self.cleanup), }); } @@ -1213,19 +1225,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { body, element, base, - cleanup, } = self.block_storage.pop().unwrap(); - if !self.cleanup.is_empty() { - //self.needs_cleanup_list = true; - - for Cleanup { address } in &self.cleanup { - uwriteln!(self.src, "{address}.Free();"); - } - } - - self.cleanup = cleanup; - self.blocks.push(Block { body: mem::replace(&mut self.src, body), results: mem::take(operands), @@ -1304,15 +1305,10 @@ struct Block { base: String, } -struct Cleanup { - address: String, -} - struct BlockStorage { body: String, element: String, base: String, - cleanup: Vec, } #[derive(Clone)] diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 26cb51e02..d99bc2f74 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -401,8 +401,6 @@ impl InterfaceGenerator<'_> { false, ); - assert!(!bindgen.needs_cleanup_list); - let src = bindgen.src; let vars = bindgen diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index 390814e41..2cb6fd986 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -33,7 +33,6 @@ pub struct CSharp { pub(crate) tuple_counts: HashSet, pub(crate) needs_result: bool, pub(crate) needs_option: bool, - pub(crate) needs_interop_string: bool, pub(crate) needs_export_return_area: bool, pub(crate) needs_rep_table: bool, pub(crate) needs_wit_exception: bool, @@ -456,26 +455,6 @@ impl WorldGenerator for CSharp { ) } - if self.needs_interop_string { - self.require_using("System.Text"); - self.require_using("System.Runtime.InteropServices"); - uwrite!( - src, - r#" - {access} static class InteropString - {{ - internal static IntPtr FromString(string input, out int length) - {{ - var utf8Bytes = Encoding.UTF8.GetBytes(input); - length = utf8Bytes.Length; - var gcHandle = GCHandle.Alloc(utf8Bytes, GCHandleType.Pinned); - return gcHandle.AddrOfPinnedObject(); - }} - }} - "#, - ) - } - if self.needs_wit_exception { uwrite!( src, From 953b183f67c6d556a22692b4514916aa3d0af393 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:37:05 -0700 Subject: [PATCH 13/13] Release wit-bindgen 0.38.0 (#1141) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 24 +++++++++--------- Cargo.toml | 20 +++++++-------- crates/guest-rust/Cargo.toml | 4 +-- crates/guest-rust/rt/src/cabi_realloc.c | 4 +-- crates/guest-rust/rt/src/cabi_realloc.o | Bin 261 -> 261 bytes crates/guest-rust/rt/src/cabi_realloc.rs | 2 +- .../rt/src/libwit_bindgen_cabi_realloc.a | Bin 412 -> 412 bytes 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ef2ab75d..3a1c9b239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2365,7 +2365,7 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.37.0" +version = "0.38.0" dependencies = [ "wit-bindgen-rt", "wit-bindgen-rust-macro", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "wit-bindgen-c" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "wit-bindgen-cli" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2412,7 +2412,7 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "heck 0.5.0", @@ -2421,7 +2421,7 @@ dependencies = [ [[package]] name = "wit-bindgen-csharp" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2438,7 +2438,7 @@ dependencies = [ [[package]] name = "wit-bindgen-go" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2450,7 +2450,7 @@ dependencies = [ [[package]] name = "wit-bindgen-markdown" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2461,7 +2461,7 @@ dependencies = [ [[package]] name = "wit-bindgen-moonbit" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2472,7 +2472,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.37.0" +version = "0.38.0" dependencies = [ "bitflags", "futures", @@ -2481,7 +2481,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -2502,7 +2502,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "prettyplease", @@ -2515,7 +2515,7 @@ dependencies = [ [[package]] name = "wit-bindgen-teavm-java" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index daee7bb01..f33d0b029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.37.0" +version = "0.38.0" license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" repository = "https://github.com/bytecodealliance/wasi-rs" @@ -39,15 +39,15 @@ wasm-metadata = "0.224.0" wit-parser = "0.224.0" wit-component = "0.224.0" -wit-bindgen-core = { path = 'crates/core', version = '0.37.0' } -wit-bindgen-c = { path = 'crates/c', version = '0.37.0' } -wit-bindgen-rust = { path = "crates/rust", version = "0.37.0" } -wit-bindgen-teavm-java = { path = 'crates/teavm-java', version = '0.37.0' } -wit-bindgen-go = { path = 'crates/go', version = '0.37.0' } -wit-bindgen-csharp = { path = 'crates/csharp', version = '0.37.0' } -wit-bindgen-markdown = { path = 'crates/markdown', version = '0.37.0' } -wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.37.0' } -wit-bindgen = { path = 'crates/guest-rust', version = '0.37.0', default-features = false } +wit-bindgen-core = { path = 'crates/core', version = '0.38.0' } +wit-bindgen-c = { path = 'crates/c', version = '0.38.0' } +wit-bindgen-rust = { path = "crates/rust", version = "0.38.0" } +wit-bindgen-teavm-java = { path = 'crates/teavm-java', version = '0.38.0' } +wit-bindgen-go = { path = 'crates/go', version = '0.38.0' } +wit-bindgen-csharp = { path = 'crates/csharp', version = '0.38.0' } +wit-bindgen-markdown = { path = 'crates/markdown', version = '0.38.0' } +wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.38.0' } +wit-bindgen = { path = 'crates/guest-rust', version = '0.38.0', default-features = false } [[bin]] name = "wit-bindgen" diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 84da8c63f..9b0459dc5 100644 --- a/crates/guest-rust/Cargo.toml +++ b/crates/guest-rust/Cargo.toml @@ -12,8 +12,8 @@ Used when compiling Rust programs to the component model. """ [dependencies] -wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.37.0" } -wit-bindgen-rt = { path = "./rt", version = "0.37.0", features = ["bitflags"] } +wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.38.0" } +wit-bindgen-rt = { path = "./rt", version = "0.38.0", features = ["bitflags"] } [features] default = ["macros", "realloc", "async"] diff --git a/crates/guest-rust/rt/src/cabi_realloc.c b/crates/guest-rust/rt/src/cabi_realloc.c index 17057a95e..931393670 100644 --- a/crates/guest-rust/rt/src/cabi_realloc.c +++ b/crates/guest-rust/rt/src/cabi_realloc.c @@ -2,9 +2,9 @@ #include -extern void *cabi_realloc_wit_bindgen_0_37_0(void *ptr, size_t old_size, size_t align, size_t new_size); +extern void *cabi_realloc_wit_bindgen_0_38_0(void *ptr, size_t old_size, size_t align, size_t new_size); __attribute__((__weak__, __export_name__("cabi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { - return cabi_realloc_wit_bindgen_0_37_0(ptr, old_size, align, new_size); + return cabi_realloc_wit_bindgen_0_38_0(ptr, old_size, align, new_size); } diff --git a/crates/guest-rust/rt/src/cabi_realloc.o b/crates/guest-rust/rt/src/cabi_realloc.o index 8f8fe5b983998d6649038dab20383fd7ff04c672..d2b5b741cd133b290fc98b2bd754fd9ce7351f20 100644 GIT binary patch delta 11 ScmZo=YGs<>$7nIpe;WW4Lj$}3 delta 11 ScmZo=YGs<>$7nv$e;WW4KLfh} diff --git a/crates/guest-rust/rt/src/cabi_realloc.rs b/crates/guest-rust/rt/src/cabi_realloc.rs index fd15629e0..bca39aa02 100644 --- a/crates/guest-rust/rt/src/cabi_realloc.rs +++ b/crates/guest-rust/rt/src/cabi_realloc.rs @@ -1,7 +1,7 @@ // This file is generated by ./ci/rebuild-libcabi-realloc.sh #[unsafe(no_mangle)] -pub unsafe extern "C" fn cabi_realloc_wit_bindgen_0_37_0( +pub unsafe extern "C" fn cabi_realloc_wit_bindgen_0_38_0( old_ptr: *mut u8, old_len: usize, align: usize, diff --git a/crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a b/crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a index 9a361264324131908793dbb80bd218ec620fa3ef..fd01a1c2d03abb332a734b096267d6bf000446de 100644 GIT binary patch delta 11 TcmbQkJcoJ06Gn@PPqzaA8qx(B delta 11 TcmbQkJcoJ06GroiPqzaA8qNh6