diff --git a/crates/csharp/src/AsyncSupport.cs b/crates/csharp/src/AsyncSupport.cs new file mode 100644 index 000000000..a80ec6e4a --- /dev/null +++ b/crates/csharp/src/AsyncSupport.cs @@ -0,0 +1,9 @@ +/** + * Helpers for the async support. + */ + + public enum CallbackCode +{ + Exit = 0, + Yield = 1, +} diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 55c4fc8e3..f1716a749 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -5,7 +5,7 @@ use heck::ToUpperCamelCase; use std::fmt::Write; use std::mem; use std::ops::Deref; -use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction}; +use wit_bindgen_core::abi::{self, Bindgen, Bitcast, Instruction}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Ns}; use wit_parser::abi::WasmType; use wit_parser::{ @@ -32,6 +32,7 @@ pub(crate) struct FunctionBindgen<'a, 'b> { is_block: bool, fixed_statments: Vec, parameter_type: ParameterType, + result_type: Option, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -42,6 +43,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { params: Box<[String]>, results: Vec, parameter_type: ParameterType, + result_type: Option, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); // Ensure temporary variable names don't clash with parameter names: @@ -67,6 +69,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { is_block: false, fixed_statments: Vec::new(), parameter_type: parameter_type, + result_type: result_type, } } @@ -304,7 +307,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { let ty = self .interface_gen .type_name_with_qualifier(&func.result.unwrap(), true); - uwriteln!(self.src, "{ty} {ret};"); + let is_async = InterfaceGenerator::is_async(&func.kind); + + if is_async { + uwriteln!(self.src, "Task<{ty}> {ret};"); + } else { + 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; @@ -371,6 +380,44 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } ret } + + fn emit_allocation_for_type(&mut self, results: &[WasmType]) -> String { + let address = self.locals.tmp("address"); + let buffer_size = self.get_size_for_type(results); + let align = self.get_align_for_type(results); + uwriteln!(self.src, "void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});"); + + // TODO: Store the address somewhere so we can free it when the task completes. + address + } + + fn get_size_for_type(&self, results: &[WasmType]) -> usize { + match results { + [WasmType::I32] => 4, + [WasmType::I64] => 8, + [WasmType::F32] => 4, + [WasmType::F64] => 8, + [WasmType::Pointer, WasmType::Length] => 4, // TODO: Wasm64 + [WasmType::PointerOrI64, WasmType::Length] => 8, + _ => { + todo!("other types not yet supported"); + } + } + } + + fn get_align_for_type(&self, results: &[WasmType]) -> usize { + match results { + [WasmType::I32] => 4, + [WasmType::I64] => 8, + [WasmType::F32] => 4, + [WasmType::F64] => 8, + [WasmType::Pointer, WasmType::Length] => 4, // TODO: Wasm64 + [WasmType::PointerOrI64, WasmType::Length] => 8, + _ => { + todo!("other types not yet supported"); + } + } + } } impl Bindgen for FunctionBindgen<'_, '_> { @@ -400,22 +447,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { })), Instruction::I32Load { offset } | Instruction::PointerLoad { offset } - | Instruction::LengthLoad { offset } => results.push(format!("global::System.BitConverter.ToInt32(new global::System.Span((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())), - Instruction::I32Load8U { offset } => results.push(format!("new global::System.Span((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())), - Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new global::System.Span((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())), - Instruction::I32Load16U { offset } => results.push(format!("global::System.BitConverter.ToUInt16(new global::System.Span((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())), - Instruction::I32Load16S { offset } => results.push(format!("global::System.BitConverter.ToInt16(new global::System.Span((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())), - Instruction::I64Load { offset } => results.push(format!("global::System.BitConverter.ToInt64(new global::System.Span((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())), - Instruction::F32Load { offset } => results.push(format!("global::System.BitConverter.ToSingle(new global::System.Span((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())), - Instruction::F64Load { offset } => results.push(format!("global::System.BitConverter.ToDouble(new global::System.Span((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())), + | Instruction::LengthLoad { offset } => results.push(format!("global::System.BitConverter.ToInt32(new global::System.Span((byte*){} + {offset}, 4))",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load8U { offset } => results.push(format!("new global::System.Span((byte*){} + {offset}, 1)[0]",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new global::System.Span((byte*){} + {offset}, 1)[0]",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load16U { offset } => results.push(format!("global::System.BitConverter.ToUInt16(new global::System.Span((byte*){} + {offset}, 2))",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load16S { offset } => results.push(format!("global::System.BitConverter.ToInt16(new global::System.Span((byte*){} + {offset}, 2))",operands[0],offset = offset.size_wasm32())), + Instruction::I64Load { offset } => results.push(format!("global::System.BitConverter.ToInt64(new global::System.Span((byte*){} + {offset}, 8))",operands[0],offset = offset.size_wasm32())), + Instruction::F32Load { offset } => results.push(format!("global::System.BitConverter.ToSingle(new global::System.Span((byte*){} + {offset}, 4))",operands[0],offset = offset.size_wasm32())), + Instruction::F64Load { offset } => results.push(format!("global::System.BitConverter.ToDouble(new global::System.Span((byte*){} + {offset}, 8))",operands[0],offset = offset.size_wasm32())), Instruction::I32Store { offset } | Instruction::PointerStore { offset } - | Instruction::LengthStore { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((void*)({} + {offset}), 4), {});", operands[1], operands[0],offset = offset.size_wasm32()), + | Instruction::LengthStore { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((byte*){} + {offset}, 4), {});", operands[1], operands[0],offset = offset.size_wasm32()), Instruction::I32Store8 { offset } => uwriteln!(self.src, "*(byte*)({} + {offset}) = (byte){};", operands[1], operands[0],offset = offset.size_wasm32()), - Instruction::I32Store16 { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((void*)({} + {offset}), 2), (short){});", operands[1], operands[0],offset = offset.size_wasm32()), - Instruction::I64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((void*)({} + {offset}), 8), unchecked((long){}));", operands[1], operands[0],offset = offset.size_wasm32()), - Instruction::F32Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((void*)({} + {offset}), 4), unchecked((float){}));", operands[1], operands[0],offset = offset.size_wasm32()), - Instruction::F64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((void*)({} + {offset}), 8), unchecked((double){}));", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::I32Store16 { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((byte*){} + {offset}, 2), (short){});", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::I64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((byte*){} + {offset}, 8), unchecked((long){}));", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::F32Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((byte*){} + {offset}, 4), unchecked((float){}));", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::F64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span((byte*){} + {offset}, 8), unchecked((double){}));", operands[1], operands[0],offset = offset.size_wasm32()), Instruction::I64FromU64 => results.push(format!("unchecked((long)({}))", operands[0])), Instruction::I32FromChar => results.push(format!("((int){})", operands[0])), @@ -956,11 +1003,24 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::CallWasm { sig, .. } => { + let is_async = InterfaceGenerator::is_async(self.kind); + + let requires_async_return_buffer_param = is_async && sig.results.len() >= 1; + let async_return_buffer = if requires_async_return_buffer_param { + let buffer = self.emit_allocation_for_type(&sig.results); + uwriteln!(self.src, "//TODO: store somewhere with the TaskCompletionSource."); + Some(buffer) + } else { + None + }; + let assignment = match &sig.results[..] { [_] => { let result = self.locals.tmp("result"); let assignment = format!("var {result} = "); - results.push(result); + if !requires_async_return_buffer_param { + results.push(result); + } assignment } @@ -971,12 +1031,32 @@ impl Bindgen for FunctionBindgen<'_, '_> { let func_name = self.func_name.to_upper_camel_case(); - let operands = operands.join(", "); + // Async functions that return a result need to pass a buffer where the result will later be written. + let operands = match async_return_buffer { + Some(ref buffer) if operands.is_empty() => buffer.clone(), + Some(ref buffer) => format!("{}, {}", operands.join(", "), buffer), + None => operands.join(", "), + }; uwriteln!( self.src, "{assignment} {func_name}WasmInterop.wasmImport{func_name}({operands});" ); + + if let Some(buffer) = async_return_buffer { + let result = abi::lift_from_memory( + self.interface_gen.resolve, + self, + buffer.clone(), + &self.result_type.unwrap(), + ); + uwriteln!( + self.src, + "global::System.Runtime.InteropServices.NativeMemory.Free({});", + buffer + ); + results.push(result); + } } Instruction::CallInterface { func, .. } => { @@ -1003,6 +1083,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } + let is_async = InterfaceGenerator::is_async(self.kind); match self.kind { FunctionKind::Constructor(id) => { let target = self.interface_gen.csharp_gen.all_resources[id].export_impl_name(); @@ -1018,7 +1099,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { }; match func.result { - None => uwriteln!(self.src, "{target}.{func_name}({oper});"), + None => { + if is_async{ + uwriteln!(self.src, "var ret = {target}.{func_name}({oper});"); + } else { + uwriteln!(self.src, "{target}.{func_name}({oper});"); + } + } Some(_ty) => { let ret = self.handle_result_call(func, target, func_name, oper); results.push(ret); @@ -1027,6 +1114,24 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } + if is_async { + self.interface_gen.csharp_gen.needs_async_support = true; + let name = self.func_name.to_upper_camel_case(); + let ret_param = match func.result { + None => "", + Some(_ty) => "ret.Result" + }; + + uwriteln!(self.src, r#"if (ret.IsCompletedSuccessfully) + {{ + {name}TaskReturn({ret_param}); + return (uint)CallbackCode.Exit; + }} + + return (uint)CallbackCode.Yield; + "#); + } + for (_, drop) in &self.resource_drops { uwriteln!(self.src, "{drop}?.Dispose();"); } @@ -1243,15 +1348,25 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.extend(operands.iter().take(*amt).map(|v| v.clone())); } - Instruction::AsyncTaskReturn { .. } - | Instruction::FutureLower { .. } - | Instruction::FutureLift { .. } + Instruction::FutureLower { .. } => { + let op = &operands[0]; + results.push(format!("{op}.Handle")); + } + + Instruction::AsyncTaskReturn { name: _, params: _ } => { + uwriteln!(self.src, "// TODO_task_cancel.forget();"); + } + + Instruction::FutureLift { .. } | Instruction::StreamLower { .. } | Instruction::StreamLift { .. } | Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } | Instruction::DropHandle { .. } - => todo!(), + => { + dbg!(inst); + todo!() + } } } diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index fa4464134..3ee67d9c0 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -50,6 +50,15 @@ pub(crate) struct InterfaceGenerator<'a> { } impl InterfaceGenerator<'_> { + pub fn is_async(kind: &FunctionKind) -> bool { + matches!( + kind, + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) + | FunctionKind::AsyncMethod(_) + ) + } + pub(crate) fn define_interface_types(&mut self, id: InterfaceId) { let mut live = LiveTypes::default(); live.add_interface(self.resolve, id); @@ -117,6 +126,7 @@ impl InterfaceGenerator<'_> { // Handles don't require a separate definition beyond what we already define for the corresponding // resource types. } + TypeDefKind::Future(t) => self.type_future(type_id, typedef_name, t, &type_def.docs), _ => unreachable!(), } } @@ -170,69 +180,80 @@ impl InterfaceGenerator<'_> { } pub(crate) fn import(&mut self, import_module_name: &str, func: &Function) { - let (camel_name, modifiers) = match &func.kind { + let camel_name = match &func.kind { FunctionKind::Freestanding | FunctionKind::Static(_) | FunctionKind::AsyncFreestanding - | FunctionKind::AsyncStatic(_) => (func.item_name().to_upper_camel_case(), "static"), + | FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(), FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { - (func.item_name().to_upper_camel_case(), "") + func.item_name().to_upper_camel_case() + } + FunctionKind::Constructor(id) => { + self.csharp_gen.all_resources[id].name.to_upper_camel_case() } - FunctionKind::Constructor(id) => ( - self.csharp_gen.all_resources[id].name.to_upper_camel_case(), - "", - ), }; let access = self.csharp_gen.access_modifier(); - let extra_modifiers = extra_modifiers(func, &camel_name); + let modifiers = modifiers(func, &camel_name, Direction::Import); let interop_camel_name = func.item_name().to_upper_camel_case(); let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func); - let wasm_result_type = match &sig.results[..] { - [] => "void", + let is_async = matches!( + func.kind, + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) + | FunctionKind::AsyncMethod(_) + ); + + let mut wasm_result_type = match &sig.results[..] { + [] => { + if is_async { + "uint" + } else { + "void" + } + } [result] => crate::world_generator::wasm_type(*result), _ => unreachable!(), }; - let (result_type, results) = if let FunctionKind::Constructor(_) = &func.kind { - (String::new(), Vec::new()) + let (result_type, results) = self.func_payload_and_return_type(func); + + let is_async = InterfaceGenerator::is_async(&func.kind); + + let requires_async_return_buffer_param = is_async && !sig.results.is_empty(); + let sig_unsafe = if requires_async_return_buffer_param { + "unsafe " } else { - match func.result { - None => ("void".to_string(), Vec::new()), - Some(ty) => { - let (payload, results) = payload_and_results( - self.resolve, - ty, - self.csharp_gen.opts.with_wit_results, - ); - ( - if let Some(ty) = payload { - self.csharp_gen.needs_result = true; - self.type_name_with_qualifier(&ty, true) - } else { - "void".to_string() - }, - results, - ) + "" + }; + + let wasm_params: String = { + let param_list = sig + .params + .iter() + .enumerate() + .map(|(i, param)| { + let ty = crate::world_generator::wasm_type(*param); + format!("{ty} p{i}") + }) + .collect::>() + .join(", "); + + if requires_async_return_buffer_param { + if param_list.is_empty() { + "void *taskResultBuffer".to_string() + } else { + format!("{}, void *taskResultBuffer", param_list) } + } else { + param_list } }; - let wasm_params = sig - .params - .iter() - .enumerate() - .map(|(i, param)| { - let ty = crate::world_generator::wasm_type(*param); - format!("{ty} p{i}") - }) - .collect::>() - .join(", "); - let mut funcs: Vec<(String, String)> = Vec::new(); funcs.push(self.gen_import_src(func, &results, ParameterType::ABI)); @@ -251,7 +272,12 @@ impl InterfaceGenerator<'_> { funcs.push(self.gen_import_src(func, &results, ParameterType::Memory)); } - let import_name = &func.name; + let import_name = if is_async { + wasm_result_type = "uint"; + format!("[async-lower]{}", func.name) + } else { + func.name.to_string() + }; let target = if let FunctionKind::Freestanding = &func.kind { &mut self.csharp_interop_src @@ -265,7 +291,7 @@ impl InterfaceGenerator<'_> { internal static class {interop_camel_name}WasmInterop {{ [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "{import_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute] - internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params}); + internal static extern {sig_unsafe}{wasm_result_type} wasmImport{interop_camel_name}({wasm_params}); }} "# ); @@ -274,7 +300,7 @@ impl InterfaceGenerator<'_> { uwrite!( target, r#" - {access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params}) + {access} {modifiers} unsafe {result_type} {camel_name}({params}) {{ {src} }} @@ -283,6 +309,73 @@ impl InterfaceGenerator<'_> { } } + fn func_payload_and_return_type(&mut self, func: &Function) -> (String, Vec) { + let return_type = self.func_return_type(func); + let results = if let FunctionKind::Constructor(_) = &func.kind { + Vec::new() + } else { + let payload = match func.result { + None => Vec::new(), + Some(ty) => { + let (_payload, results) = payload_and_results( + self.resolve, + ty, + self.csharp_gen.opts.with_wit_results, + ); + results + } + }; + + payload + }; + + (return_type, results) + } + + fn func_return_type(&mut self, func: &Function) -> String { + let result_type = if let FunctionKind::Constructor(_) = &func.kind { + String::new() + } else { + let base_type = match func.result { + None => "void".to_string(), + Some(_ty) => { + let result_type = match func.result { + None => "void".to_string(), + Some(ty) => { + let (payload, _results) = payload_and_results( + self.resolve, + ty, + self.csharp_gen.opts.with_wit_results, + ); + if let Some(ty) = payload { + self.csharp_gen.needs_result = true; + self.type_name_with_qualifier(&ty, true) + } else { + "void".to_string() + } + } + }; + + result_type + } + }; + + let asyncified_type = match &func.kind { + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) + | FunctionKind::AsyncMethod(_) => match func.result { + None => "Task".to_string(), + Some(_ty) => format!("Task<{}>", base_type), + }, + _ => base_type, + }; + + asyncified_type + }; + + result_type + } + fn gen_import_src( &mut self, func: &Function, @@ -306,6 +399,7 @@ impl InterfaceGenerator<'_> { .collect(), results.clone(), parameter_type, + func.result, ); abi::call( @@ -340,49 +434,24 @@ impl InterfaceGenerator<'_> { } pub(crate) fn export(&mut self, func: &Function, interface_name: Option<&WorldKey>) { - let (camel_name, modifiers) = match &func.kind { + let camel_name = match &func.kind { FunctionKind::Freestanding | FunctionKind::Static(_) | FunctionKind::AsyncFreestanding - | FunctionKind::AsyncStatic(_) => { - (func.item_name().to_upper_camel_case(), "static abstract") - } + | FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(), FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { - (func.item_name().to_upper_camel_case(), "") + func.item_name().to_upper_camel_case() + } + FunctionKind::Constructor(id) => { + self.csharp_gen.all_resources[id].name.to_upper_camel_case() } - FunctionKind::Constructor(id) => ( - self.csharp_gen.all_resources[id].name.to_upper_camel_case(), - "", - ), }; - let extra_modifiers = extra_modifiers(func, &camel_name); + let modifiers = modifiers(func, &camel_name, Direction::Export); let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func); - let (result_type, results) = if let FunctionKind::Constructor(_) = &func.kind { - (String::new(), Vec::new()) - } else { - match func.result { - None => ("void".to_owned(), Vec::new()), - Some(ty) => { - let (payload, results) = payload_and_results( - self.resolve, - ty, - self.csharp_gen.opts.with_wit_results, - ); - ( - if let Some(ty) = payload { - self.csharp_gen.needs_result = true; - self.type_name(&ty) - } else { - "void".to_string() - }, - results, - ) - } - } - }; + let (result_type, results) = self.func_payload_and_return_type(func); let mut bindgen = FunctionBindgen::new( self, @@ -391,6 +460,14 @@ impl InterfaceGenerator<'_> { (0..sig.params.len()).map(|i| format!("p{i}")).collect(), results, ParameterType::ABI, + func.result, + ); + + let async_ = matches!( + func.kind, + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) + | FunctionKind::AsyncMethod(_) ); abi::call( @@ -399,7 +476,7 @@ impl InterfaceGenerator<'_> { LiftLower::LiftArgsLowerResults, func, &mut bindgen, - false, + async_, ); let src = bindgen.src; @@ -411,10 +488,14 @@ impl InterfaceGenerator<'_> { .collect::>() .join(";\n"); - let wasm_result_type = match &sig.results[..] { - [] => "void", - [result] => crate::world_generator::wasm_type(*result), - _ => unreachable!(), + let wasm_result_type = if async_ { + "uint" + } else { + match &sig.results[..] { + [] => "void", + [result] => crate::world_generator::wasm_type(*result), + _ => unreachable!(), + } }; let wasm_params = sig @@ -444,9 +525,17 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - let interop_name = format!("wasmExport{}", func.name.to_upper_camel_case()); + let wasm_func_name = func.name.clone(); + let interop_name = format!("wasmExport{}", wasm_func_name.to_upper_camel_case()); let core_module_name = interface_name.map(|s| self.resolve.name_world_key(s)); let export_name = func.legacy_core_export_name(core_module_name.as_deref()); + + let export_name = if async_ { + format!("[async-lift]{export_name}") + } else { + export_name.to_string() + }; + let access = self.csharp_gen.access_modifier(); uwrite!( @@ -479,6 +568,7 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), Vec::new(), ParameterType::ABI, + func.result, ); abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); @@ -496,10 +586,40 @@ impl InterfaceGenerator<'_> { ); } + if async_ { + let import_module_name = &self.resolve.name_world_key(interface_name.unwrap()); + + uwriteln!( + self.csharp_interop_src, + r#" + [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "[callback][async-lift]{import_module_name}#{wasm_func_name}")] + public static uint {camel_name}Callback(uint eventRaw, uint waitable, uint code) + {{ + // TODO: decode the parameters + return (uint)CallbackCode.Exit; + }} + "# + ); + + let task_return_param = match &sig.results[..] { + [] => "", + [_result] => &format!("{} result", wasm_result_type), + _ => unreachable!(), + }; + + uwriteln!( + self.csharp_interop_src, + r#" + [global::System.Runtime.InteropServices.DllImportAttribute("[export]{import_module_name}", EntryPoint = "[task-return]{wasm_func_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute] + internal static extern void {camel_name}TaskReturn({task_return_param}); + "# + ); + } + if !matches!(&func.kind, FunctionKind::Constructor(_)) { uwrite!( self.src, - r#"{extra_modifiers} {modifiers} {result_type} {camel_name}({params}); + r#"{modifiers} {result_type} {camel_name}({params}); "# ); @@ -659,6 +779,17 @@ impl InterfaceGenerator<'_> { let (Handle::Own(id) | Handle::Borrow(id)) = handle; self.type_name_with_qualifier(&Type::Id(*id), qualifier) } + TypeDefKind::Future(ty) => { + let name = ty + .as_ref() + .map(|ty| self.type_name_with_qualifier(ty, qualifier)) + .unwrap_or_else(|| "".to_owned()); + if name.is_empty() { + return "Task".to_owned(); + } else { + return format!("Task<{name}>"); + } + } _ => { if let Some(name) = &ty.name { format!( @@ -883,26 +1014,7 @@ impl InterfaceGenerator<'_> { } fn sig_string(&mut self, func: &Function, qualifier: bool) -> String { - let result_type = if let FunctionKind::Constructor(_) = &func.kind { - String::new() - } else { - match func.result { - None => "void".into(), - Some(ty) => { - let (payload, _) = payload_and_results( - self.resolve, - ty, - self.csharp_gen.opts.with_wit_results, - ); - if let Some(ty) = payload { - self.csharp_gen.needs_result = true; - self.type_name_with_qualifier(&ty, qualifier) - } else { - "void".to_string() - } - } - } - }; + let result_type = self.func_return_type(&func); let params = func .params @@ -1202,14 +1314,12 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { .direction = self.direction; } - fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() + fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { + self.type_name(&Type::Id(id)); } - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() + fn type_stream(&mut self, id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { + self.type_name(&Type::Id(id)); } } @@ -1251,17 +1361,41 @@ fn payload_and_results( (payload, results) } -fn extra_modifiers(func: &Function, name: &str) -> &'static str { - if let FunctionKind::Method(_) = &func.kind { +fn modifiers(func: &Function, name: &str, direction: Direction) -> String { + let new_modifier = match &func.kind { // Avoid warnings about name clashes. // // TODO: add other `object` method names here - if name == "GetType" { - return "new"; + FunctionKind::Method(_) if name == "GetType" => " new", + _ => "", + }; + + let static_modifiers = match &func.kind { + FunctionKind::Freestanding + | FunctionKind::Static(_) + | FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) => "static", + _ => "", + }; + + let abstract_modifier = if direction == Direction::Export { + " abstract" + } else { + "" + }; + + let async_modifier = match &func.kind { + FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncFreestanding + | FunctionKind::AsyncStatic(_) + if abstract_modifier == "" => + { + " async" } - } + _ => "", + }; - "" + format!("{static_modifiers}{abstract_modifier}{async_modifier}{new_modifier}") } fn int_type(int: Int) -> &'static str { diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index e8a31fc98..69c130cb0 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -32,6 +32,7 @@ pub struct CSharp { pub(crate) needs_export_return_area: bool, pub(crate) needs_rep_table: bool, pub(crate) needs_wit_exception: bool, + pub(crate) needs_async_support: bool, pub(crate) interface_fragments: HashMap, pub(crate) world_fragments: Vec, pub(crate) sizes: SizeAlign, @@ -507,6 +508,11 @@ impl WorldGenerator for CSharp { src.push_str("}\n"); } + if self.needs_async_support { + src.push_str("\n"); + src.push_str(include_str!("AsyncSupport.cs")); + } + src.push_str("\n"); src.push_str("}\n"); diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index 0bfeebbbc..d015c41a9 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -36,11 +36,19 @@ impl LanguageMethods for Csharp { fn should_fail_verify( &self, - _name: &str, - config: &crate::config::WitConfig, + name: &str, + _config: &crate::config::WitConfig, _args: &[String], ) -> bool { - config.async_ + // TODO: remove this exclusions as support is created + matches!( + name, + "resources-with-streams.wit" + | "resources-with-futures.wit" + | "futures.wit" + | "streams.wit" + | "error-context.wit" + ) } fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { @@ -89,6 +97,18 @@ impl LanguageMethods for Csharp { // .arg("/bl") // to diagnose dotnet build problems .arg("-o") .arg(&out_wasm); + + let os = match std::env::consts::OS { + "windows" => "win", + "linux" => std::env::consts::OS, + other => todo!("OS {} not supported", other), + }; + + // TODO: Workaround for no aarch64(arm64 in dotnet parlance) packages on Windows + if os == "win" && std::env::consts::ARCH == "aarch64" { + cmd.arg("/p:_hostArchitecture=x64"); + } + runner.run_command(&mut cmd)?; fs::copy(&wasm_filename, &compile.output)?; diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 468eaa309..448cc4263 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -811,7 +811,12 @@ impl Runner<'_> { } wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all()) .validate_all(&wasm) - .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?; + .with_context(|| { + format!( + "compiler produced invalid wasm file {output:?} for component {}", + component.name + ) + })?; Ok(output) } diff --git a/tests/runtime-async/async/simple-import-params-results/runner.cs b/tests/runtime-async/async/simple-import-params-results/runner.cs new file mode 100644 index 000000000..1098b9209 --- /dev/null +++ b/tests/runtime-async/async/simple-import-params-results/runner.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics; +using RunnerWorld.wit.imports.a.b; +using System.Text; + +public class Program +{ + public static async Task Main(string[] args) + { + var t = II.OneArgument(1); + Debug.Assert(t.IsCompletedSuccessfully); + + var tOneResult = II.OneResult(); + Debug.Assert(tOneResult.IsCompletedSuccessfully); + Debug.Assert(tOneResult.Result == 2); + + var tOneArgumentAndResult = II.OneArgumentAndResult(3); + Debug.Assert(tOneArgumentAndResult.IsCompletedSuccessfully); + Debug.Assert(tOneArgumentAndResult.Result == 4); + + var tTwoArguments = II.TwoArguments(5, 6); + Debug.Assert(tTwoArguments.IsCompletedSuccessfully); + + var tTwoArgumentsAndResult = II.TwoArgumentsAndResult(7, 8); + Debug.Assert(tTwoArgumentsAndResult.IsCompletedSuccessfully); + Debug.Assert(tTwoArgumentsAndResult.Result == 9); + } +} diff --git a/tests/runtime-async/async/simple-import-params-results/test.cs b/tests/runtime-async/async/simple-import-params-results/test.cs new file mode 100644 index 000000000..57c0ac905 --- /dev/null +++ b/tests/runtime-async/async/simple-import-params-results/test.cs @@ -0,0 +1,37 @@ +using System.Diagnostics; +using TestWorld.wit.exports.a.b; + +namespace TestWorld.wit.exports.a.b +{ + public class IImpl : II + { + public static async Task OneArgument(uint x) + { + Debug.Assert(x == 1); + } + + public static async Task OneResult() + { + return 2; + } + + public static async Task OneArgumentAndResult(uint x) + { + Debug.Assert(x == 3); + return 4; + } + + public static async Task TwoArguments(uint x, uint y) + { + Debug.Assert(x == 5); + Debug.Assert(y == 6); + } + + public static async Task TwoArgumentsAndResult(uint x, uint y) + { + Debug.Assert(x == 7); + Debug.Assert(y == 8); + return 9; + } + } +}