diff --git a/crates/csharp/src/csproj.rs b/crates/csharp/src/csproj.rs index 1d9d86aaa..c94b39064 100644 --- a/crates/csharp/src/csproj.rs +++ b/crates/csharp/src/csproj.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use std::{fs, path::PathBuf}; +use std::{env, fs, path::PathBuf}; use heck::ToUpperCamelCase; @@ -103,21 +103,36 @@ impl CSProjectLLVMBuilder { let os = match std::env::consts::OS { "windows" => "win", "linux" => std::env::consts::OS, + "macos" => "osx", other => todo!("OS {} not supported", other), }; + let arch = match std::env::consts::ARCH { + "aarch64" => "arm64", + "x86_64" => "x64", + other => todo!("ARCH {} not supported", other), + }; + + let ilc_version = env::var("ILC_VERSION").unwrap_or_else(|_| "10.0.0-*".to_string()); + csproj.push_str( &format!( r#" - - + + "#), ); + let local_source = match env::var("ILC_PACKAGES_PATH") { + Ok(path) => format!(r#""#), + Err(_) => String::new(), + }; + fs::write( self.dir.join("nuget.config"), - r#" + format!( + r#" @@ -126,11 +141,12 @@ impl CSProjectLLVMBuilder { + {local_source} - - "#, + "# + ), )?; } diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 8d7ab4a1e..49d5049c0 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -451,24 +451,102 @@ impl Bindgen for FunctionBindgen<'_, '_> { } .to_owned() })), - Instruction::I32Load { offset } - | Instruction::PointerLoad { offset } - | 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((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((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::I32Load { offset } | Instruction::LengthLoad { offset } => { + results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )) + } + Instruction::PointerLoad { offset } => results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + 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!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )), + Instruction::I32Load16S { offset } => results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )), + Instruction::I64Load { offset } => results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )), + Instruction::F32Load { offset } => results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )), + Instruction::F64Load { offset } => results.push(format!( + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0]", + operands[0], + offset = offset.size_wasm32() + )), + Instruction::I32Store { offset } | Instruction::LengthStore { offset } => uwriteln!( + self.src, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = {};", + operands[1], + operands[0], + offset = offset.size_wasm32() + ), + Instruction::PointerStore { offset } => uwriteln!( + self.src, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = (nint){};", + 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, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = (short){};", + operands[1], + operands[0], + offset = offset.size_wasm32() + ), + Instruction::I64Store { offset } => uwriteln!( + self.src, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = unchecked((long){});", + operands[1], + operands[0], + offset = offset.size_wasm32() + ), + Instruction::F32Store { offset } => uwriteln!( + self.src, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = unchecked((float){});", + operands[1], + operands[0], + offset = offset.size_wasm32() + ), + Instruction::F64Store { offset } => uwriteln!( + self.src, + "new global::System.Span((void*)((byte*){} + {offset}), 1)[0] = 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])), @@ -494,9 +572,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::S32FromI32 | Instruction::S64FromI64 => results.push(operands[0].clone()), - Instruction::Bitcasts { casts } => { - results.extend(casts.iter().zip(operands).map(|(cast, op)| perform_cast(op, cast))) - } + Instruction::Bitcasts { casts } => results.extend( + casts + .iter() + .zip(operands) + .map(|(cast, op)| perform_cast(op, cast)), + ), Instruction::I32FromBool => { results.push(format!("({} ? 1 : 0)", operands[0])); @@ -511,14 +592,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { if flags.flags.len() > 32 { results.push(format!( "unchecked((int)(((long){}) & uint.MaxValue))", - operands[0].to_string() - )); - results.push(format!( - "unchecked(((int)((long){} >> 32)))", - operands[0].to_string() + operands[0] )); + results.push(format!("unchecked(((int)((long){} >> 32)))", operands[0])); } else { - results.push(format!("(int){}", operands[0].to_string())); + results.push(format!("(int){}", operands[0])); } } @@ -531,9 +609,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if flags.flags.len() > 32 { results.push(format!( "({})(unchecked((uint)({})) | (ulong)(unchecked((uint)({}))) << 32)", - qualified_type_name, - operands[0].to_string(), - operands[1].to_string() + qualified_type_name, operands[0], operands[1] )); } else { results.push(format!("({})({})", qualified_type_name, operands[0])) @@ -555,7 +631,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let mut result = format!("new {qualified_type_name} (\n"); result.push_str(&operands.join(", ")); - result.push_str(")"); + result.push(')'); results.push(result); } @@ -627,52 +703,54 @@ impl Bindgen for FunctionBindgen<'_, '_> { let declarations = lowered .iter() .zip(lowered_types.iter()) - .map(|(lowered, ty)| format!("{} {lowered};", crate::world_generator::wasm_type(*ty))) + .map(|(lowered, ty)| { + format!("{} {lowered};", crate::world_generator::wasm_type(*ty)) + }) .collect::>() .join("\n"); let op = &operands[0]; let nesting = if let Type::Id(id) = payload { - matches!(&self.interface_gen.resolve.types[*id].kind, TypeDefKind::Option(_)) + matches!( + &self.interface_gen.resolve.types[*id].kind, + TypeDefKind::Option(_) + ) } else { false }; - let mut block = |ty: Option<&Type>, Block { body, results, .. }, payload, nesting| { - let payload = if let Some(ty) = self.interface_gen.non_empty_type(ty) { - let ty = self.interface_gen.type_name_with_qualifier(ty, true); - if nesting { - format!("var {payload} = {op}.Value;") + let mut block = + |ty: Option<&Type>, Block { body, results, .. }, payload, nesting| { + let payload = if let Some(ty) = self.interface_gen.non_empty_type(ty) { + let ty = self.interface_gen.type_name_with_qualifier(ty, true); + if nesting { + format!("var {payload} = {op}.Value;") + } else { + format!("var {payload} = ({ty}) {op};") + } } else { - format!("var {payload} = ({ty}) {op};") - } - } else { - String::new() - }; + String::new() + }; - let assignments = lowered - .iter() - .zip(&results) - .map(|(lowered, result)| format!("{lowered} = {result};\n")) - .collect::>() - .concat(); + let assignments = lowered + .iter() + .zip(&results) + .map(|(lowered, result)| format!("{lowered} = {result};\n")) + .collect::>() + .concat(); - format!( - "{payload} + format!( + "{payload} {body} {assignments}" - ) - }; + ) + }; let none = block(None, none, none_payload, nesting); let some = block(Some(payload), some, some_payload, nesting); - let test = if nesting { - ".HasValue" - } else { - " != null" - }; + let test = if nesting { ".HasValue" } else { " != null" }; uwrite!( self.src, @@ -692,12 +770,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { let some = self.blocks.pop().unwrap(); let _none = self.blocks.pop().unwrap(); - let ty = self.interface_gen.type_name_with_qualifier(&Type::Id(*ty), true); + let ty = self + .interface_gen + .type_name_with_qualifier(&Type::Id(*ty), true); let lifted = self.locals.tmp("lifted"); let op = &operands[0]; let nesting = if let Type::Id(id) = payload { - matches!(&self.interface_gen.resolve.types[*id].kind, TypeDefKind::Option(_)) + matches!( + &self.interface_gen.resolve.types[*id].kind, + TypeDefKind::Option(_) + ) } else { false }; @@ -762,7 +845,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::EnumLower { .. } => results.push(format!("(int){}", operands[0])), Instruction::EnumLift { ty, .. } => { - let t = self.interface_gen.type_name_with_qualifier(&Type::Id(*ty), true); + let t = self + .interface_gen + .type_name_with_qualifier(&Type::Id(*ty), true); let op = &operands[0]; results.push(format!("({t}){op}")); @@ -786,7 +871,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { item_to_pin: list.clone(), ptr_name: ptr.clone(), }); - }else if !self.is_block && self.parameter_type == ParameterType::Memory { + } else if !self.is_block && self.parameter_type == ParameterType::Memory { self.fixed_statments.push(Fixed { item_to_pin: format!("{list}.Span"), ptr_name: ptr.clone(), @@ -807,13 +892,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { " ); } - results.push(format!("(nint){ptr}")); + results.push(format!("(int){ptr}")); results.push(format!("({list}).Length")); } Direction::Export => { let (_, ty) = list_element_info(element); let address = self.locals.tmp("address"); - let size = self.interface_gen.csharp_gen.sizes.size(element).size_wasm32(); + let size = self + .interface_gen + .csharp_gen + .sizes + .size(element) + .size_wasm32(); let byte_length = self.locals.tmp("byteLength"); uwrite!( self.src, @@ -899,7 +989,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op0 = &operands[0]; let op1 = &operands[1]; - let get_str = format!("global::System.Text.Encoding.UTF8.GetString((byte*){op0}, {op1})"); + let get_str = + format!("global::System.Text.Encoding.UTF8.GetString((byte*){op0}, {op1})"); uwriteln!( self.src, @@ -924,30 +1015,46 @@ impl Bindgen for FunctionBindgen<'_, '_> { assert!(block_results.is_empty()); let list = &operands[0]; - let size = self.interface_gen.csharp_gen.sizes.size(element).size_wasm32(); + let size = self + .interface_gen + .csharp_gen + .sizes + .size(element) + .size_wasm32(); let ty = self.interface_gen.type_name_with_qualifier(element, true); let index = self.locals.tmp("index"); let address = self.locals.tmp("address"); let buffer_size = self.locals.tmp("bufferSize"); //TODO: wasm64 - let align = self.interface_gen.csharp_gen.sizes.align(element).align_wasm32(); + let align = self + .interface_gen + .csharp_gen + .sizes + .align(element) + .align_wasm32(); - let (array_size, element_type) = crate::world_generator::dotnet_aligned_array( - size, - align, - ); + let (array_size, element_type) = + crate::world_generator::dotnet_aligned_array(size, align); let ret_area = self.locals.tmp("retArea"); + let array_size = if align > 1 { + // Add one additional element in case the starting address is not aligned + format!("{array_size} * {list}.Count + 1") + } else { + format!("{array_size} * {list}.Count") + }; + match realloc { None => { self.needs_cleanup = true; + self.interface_gen.csharp_gen.needs_align_stack_ptr = true; uwrite!(self.src, " void* {address}; if (({size} * {list}.Count) < 1024) {{ - var {ret_area} = stackalloc {element_type}[({array_size}*{list}.Count)+1]; - {address} = (void*)(((int){ret_area}) + ({align} - 1) & -{align}); + var {ret_area} = stackalloc {element_type}[{array_size}]; + {address} = MemoryHelper.AlignStackPtr({ret_area}, {align}); }} else {{ @@ -969,7 +1076,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - uwrite!(self.src, + uwrite!( + self.src, " for (int {index} = 0; {index} < {list}.Count; ++{index}) {{ {ty} {block_element} = {list}[{index}]; @@ -994,7 +1102,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { let length = &operands[1]; let array = self.locals.tmp("array"); let ty = self.interface_gen.type_name_with_qualifier(element, true); - let size = self.interface_gen.csharp_gen.sizes.size(element).size_wasm32(); + let size = self + .interface_gen + .csharp_gen + .sizes + .size(element) + .size_wasm32(); let index = self.locals.tmp("index"); let result = match &block_results[..] { @@ -1038,9 +1151,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { assignment } - [] => { - String::new() - } + [] => String::new(), _ => unreachable!(), }; @@ -1050,17 +1161,29 @@ impl Bindgen for FunctionBindgen<'_, '_> { let (_namespace, interface_name) = &CSharp::get_class_name_from_qualified_name(self.interface_gen.name); - let mut interop_name = format!("{}ImportsInterop", interface_name.strip_prefix("I").unwrap() - .strip_suffix(if self.interface_gen.direction == Direction::Import { "Imports" } else { "Exports" }).unwrap().to_upper_camel_case()); + let mut interop_name = format!( + "{}ImportsInterop", + interface_name + .strip_prefix("I") + .unwrap() + .strip_suffix(if self.interface_gen.direction == Direction::Import { + "Imports" + } else { + "Exports" + }) + .unwrap() + .to_upper_camel_case() + ); - if self.interface_gen.is_world && self.interface_gen.direction == Direction::Import { + if self.interface_gen.is_world && self.interface_gen.direction == Direction::Import + { interop_name = format!("Imports.{interop_name}"); } let resource_type_name = match self.kind { - FunctionKind::Method(resource_type_id) | - FunctionKind::Static(resource_type_id) | - FunctionKind::Constructor(resource_type_id) => { + FunctionKind::Method(resource_type_id) + | FunctionKind::Static(resource_type_id) + | FunctionKind::Constructor(resource_type_id) => { format!( ".{}", self.interface_gen.csharp_gen.all_resources[resource_type_id] @@ -1108,21 +1231,26 @@ 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(); + let target = + self.interface_gen.csharp_gen.all_resources[id].export_impl_name(); let ret = self.locals.tmp("ret"); uwriteln!(self.src, "var {ret} = new {target}({oper});"); results.push(ret); } _ => { let target = match self.kind { - FunctionKind::Static(id) |FunctionKind::AsyncStatic(id)=> self.interface_gen.csharp_gen.all_resources[id].export_impl_name(), - FunctionKind::Method(_) |FunctionKind::AsyncMethod(_)=> operands[0].clone(), - _ => format!("{class_name_root}Impl") + FunctionKind::Static(id) | FunctionKind::AsyncStatic(id) => { + self.interface_gen.csharp_gen.all_resources[id].export_impl_name() + } + FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { + operands[0].clone() + } + _ => format!("{class_name_root}Impl"), }; match func.result { None => { - if is_async{ + if is_async { uwriteln!(self.src, "var ret = {target}.{func_name}({oper});"); } else { uwriteln!(self.src, "{target}.{func_name}({oper});"); @@ -1140,11 +1268,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { 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" - }; + None => "", + Some(_ty) => "ret.Result", + }; - uwriteln!(self.src, r#"if (ret.IsCompletedSuccessfully) + uwriteln!( + self.src, + r#"if (ret.IsCompletedSuccessfully) {{ {name}TaskReturn({ret_param}); return (uint)CallbackCode.Exit; @@ -1165,34 +1295,51 @@ impl Bindgen for FunctionBindgen<'_, '_> { // TODO: Defer dropping borrowed resources until a result is returned. ContextTask* contextTaskPtr = AsyncSupport.ContextGet(); return (uint)CallbackCode.Wait | (uint)(contextTaskPtr->WaitableSetHandle << 4); - "#); + "# + ); } - for (_, drop) in &self.resource_drops { + for (_, drop) in &self.resource_drops { uwriteln!(self.src, "{drop}?.Dispose();"); } } Instruction::Return { amt, .. } => { - if self.fixed_statments.len() > 0 { - let fixed: String = self.fixed_statments.iter().map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)).collect::>().join(", "); - self.src.insert_str(0, &format!("fixed (void* {fixed}) + if !self.fixed_statments.is_empty() { + let fixed: String = self + .fixed_statments + .iter() + .map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)) + .collect::>() + .join(", "); + self.src.insert_str( + 0, + &format!( + "fixed (void* {fixed}) {{ - ")); + " + ), + ); } if self.needs_cleanup { self.src.insert_str(0, "var cleanups = new global::System.Collections.Generic.List(); "); - uwriteln!(self.src, " + uwriteln!( + self.src, + " foreach (var cleanup in cleanups) {{ cleanup(); - }}"); + }}" + ); } - if !matches!((self.interface_gen.direction, self.kind), (Direction::Import, FunctionKind::Constructor(_))) { + if !matches!( + (self.interface_gen.direction, self.kind), + (Direction::Import, FunctionKind::Constructor(_)) + ) { match *amt { 0 => (), 1 => { @@ -1205,7 +1352,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - if self.fixed_statments.len() > 0 { + if !self.fixed_statments.is_empty() { uwriteln!(self.src, "}}"); } } @@ -1214,11 +1361,19 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::GuestDeallocate { .. } => { // the original alloc here comes from cabi_realloc implementation (wasi-libc in .net) - uwriteln!(self.src, r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, operands[0]); + uwriteln!( + self.src, + r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, + operands[0] + ); } Instruction::GuestDeallocateString => { - uwriteln!(self.src, r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, operands[0]); + uwriteln!( + self.src, + r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, + operands[0] + ); } Instruction::GuestDeallocateVariant { blocks } => { @@ -1239,19 +1394,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { .collect::>() .join("\n"); - let op = &operands[0]; + let op = &operands[0]; - uwrite!( - self.src, - " + uwrite!( + self.src, + " switch ({op}) {{ {cases} }} " - ); + ); } - Instruction::GuestDeallocateList { element: element_type } => { + Instruction::GuestDeallocateList { + element: element_type, + } => { let Block { body, results: block_results, @@ -1262,7 +1419,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - let size = self.interface_gen.csharp_gen.sizes.size(element_type).size_wasm32(); + let size = self + .interface_gen + .csharp_gen + .sizes + .size(element_type) + .size_wasm32(); if !body.trim().is_empty() { let index = self.locals.tmp("index"); @@ -1278,18 +1440,20 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - uwriteln!(self.src, r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, operands[0]); + uwriteln!( + self.src, + r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, + operands[0] + ); } - Instruction::HandleLower { - handle, - .. - } => { + Instruction::HandleLower { handle, .. } => { let (Handle::Own(ty) | Handle::Borrow(ty)) = handle; let is_own = matches!(handle, Handle::Own(_)); let handle = self.locals.tmp("handle"); let id = dealias(self.interface_gen.resolve, *ty); - let ResourceInfo { direction, .. } = &self.interface_gen.csharp_gen.all_resources[&id]; + let ResourceInfo { direction, .. } = + &self.interface_gen.csharp_gen.all_resources[&id]; let op = &operands[0]; uwriteln!(self.src, "var {handle} = {op}.Handle;"); @@ -1303,7 +1467,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Direction::Export => { self.interface_gen.csharp_gen.needs_rep_table = true; let local_rep = self.locals.tmp("localRep"); - 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 { // Note that we set `{op}.Handle` to zero below to ensure that application code doesn't // try to use the instance while the host has ownership. We'll set it back to non-zero @@ -1329,27 +1494,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } } - results.push(format!("{handle}")); + results.push(handle); } - Instruction::HandleLift { - handle, - .. - } => { + Instruction::HandleLift { handle, .. } => { let (Handle::Own(ty) | Handle::Borrow(ty)) = handle; let is_own = matches!(handle, Handle::Own(_)); let mut resource = self.locals.tmp("resource"); let id = dealias(self.interface_gen.resolve, *ty); - let ResourceInfo { direction, .. } = &self.interface_gen.csharp_gen.all_resources[&id]; + let ResourceInfo { direction, .. } = + &self.interface_gen.csharp_gen.all_resources[&id]; let op = &operands[0]; 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(); - uwriteln!(self.src,"{resource}.Handle = {op};"); + uwriteln!(self.src, "{resource}.Handle = {op};"); } else { let var = if is_own { "var" } else { "" }; uwriteln!( @@ -1364,7 +1529,8 @@ 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, @@ -1373,7 +1539,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { {resource}.Handle = {op};" ); } else { - uwriteln!(self.src, "var {resource} = ({export_name}) {export_name}.repTable.Get({op});"); + uwriteln!( + self.src, + "var {resource} = ({export_name}) {export_name}.repTable.Get({op});" + ); } self.resource_type_name = Some(export_name); } @@ -1386,21 +1555,29 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLower { payload, ty: _ } - | Instruction::StreamLower { payload, ty: _ }=> { + | Instruction::StreamLower { payload, ty: _ } => { let op = &operands[0]; let generic_type_name = match payload { - Some(generic_type) => { - &self.interface_gen.type_name_with_qualifier(generic_type, false) - } - None => "" + Some(generic_type) => &self + .interface_gen + .type_name_with_qualifier(generic_type, false), + None => "", }; match inst { Instruction::FutureLower { .. } => { - self.interface_gen.add_future(self.func_name, &generic_type_name, **payload); + self.interface_gen.add_future( + self.func_name, + &generic_type_name, + **payload, + ); } _ => { - self.interface_gen.add_stream(self.func_name, &generic_type_name, **payload); + self.interface_gen.add_stream( + self.func_name, + &generic_type_name, + **payload, + ); } } @@ -1412,49 +1589,62 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { payload, ty: _ } - | Instruction:: StreamLift { payload, ty: _ } => { - let generic_type_name_with_qualifier = match payload { - Some(generic_type) => { - &self.interface_gen.type_name_with_qualifier(generic_type, true) - } - None => "" + | Instruction::StreamLift { payload, ty: _ } => { + let generic_type_name_with_qualifier = match payload { + Some(generic_type) => &self + .interface_gen + .type_name_with_qualifier(generic_type, true), + None => "", }; - let generic_type_name = match payload { - Some(generic_type) => { - &self.interface_gen.type_name_with_qualifier(generic_type, false) - } - None => "" + let generic_type_name = match payload { + Some(generic_type) => &self + .interface_gen + .type_name_with_qualifier(generic_type, false), + None => "", }; let upper_camel = generic_type_name.to_upper_camel_case(); let bracketed_generic = match payload { Some(_) => { format!("<{generic_type_name_with_qualifier}>") } - None => String::new() + None => String::new(), }; - // let sig_type_name = "Void"; + // let sig_type_name = "Void"; let reader_var = self.locals.tmp("reader"); let module = self.interface_gen.name; - let (import_name, interface_name) = CSharp::get_class_name_from_qualified_name(module); - let base_interface_name = interface_name - .strip_prefix("I").unwrap(); + let (import_name, interface_name) = + CSharp::get_class_name_from_qualified_name(module); + let base_interface_name = interface_name.strip_prefix("I").unwrap(); let future_stream_name = match inst { - Instruction::FutureLift{payload: _, ty: _} => "Future", - Instruction::StreamLift{payload: _, ty: _} => "Stream", + Instruction::FutureLift { payload: _, ty: _ } => "Future", + Instruction::StreamLift { payload: _, ty: _ } => "Stream", _ => { panic!("Unexpected instruction for lift"); } }; - uwriteln!(self.src, "var {reader_var} = new {future_stream_name}Reader{bracketed_generic}({}, {import_name}.{base_interface_name}Interop.{future_stream_name}VTable{});", operands[0], upper_camel); + uwriteln!( + self.src, + "var {reader_var} = new {future_stream_name}Reader{bracketed_generic}({}, {import_name}.{base_interface_name}Interop.{future_stream_name}VTable{});", + operands[0], + upper_camel + ); results.push(reader_var); match inst { Instruction::FutureLift { .. } => { - self.interface_gen.add_future(self.func_name, &generic_type_name, **payload); + self.interface_gen.add_future( + self.func_name, + &generic_type_name, + **payload, + ); } _ => { - self.interface_gen.add_stream(self.func_name, &generic_type_name, **payload); + self.interface_gen.add_stream( + self.func_name, + &generic_type_name, + **payload, + ); } } @@ -1467,8 +1657,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::FixedLengthListLift { .. } | Instruction::FixedLengthListLower { .. } | Instruction::FixedLengthListLowerToMemory { .. } - | Instruction::FixedLengthListLiftFromMemory { .. } - => { + | Instruction::FixedLengthListLiftFromMemory { .. } => { dbg!(inst); todo!() } @@ -1495,15 +1684,23 @@ impl Bindgen for FunctionBindgen<'_, '_> { // 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). + let array_size = if self.import_return_pointer_area_align > 1 { + // Add one additional element in case the starting address is not aligned + array_size + 1 + } else { + array_size + }; + + self.interface_gen.csharp_gen.needs_align_stack_ptr = true; uwrite!( self.src, " - var {ret_area} = stackalloc {element_type}[{array_size} + 1]; - var {ptr} = ((int){ret_area}) + ({align} - 1) & -{align}; + var {ret_area} = stackalloc {element_type}[{array_size}]; + var {ptr} = (nint)MemoryHelper.AlignStackPtr({ret_area}, {align}); ", align = align.align_wasm32() ); - format!("{ptr}") + ptr } Direction::Export => { // exports need their return area to be live until the post-return call. @@ -1526,7 +1723,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); self.interface_gen.csharp_gen.needs_export_return_area = true; - format!("{ptr}") + ptr } } } @@ -1602,8 +1799,8 @@ fn perform_cast(op: &String, cast: &Bitcast) -> String { Bitcast::F64ToI64 => format!("global::System.BitConverter.DoubleToInt64Bits({op})"), Bitcast::I32ToI64 => format!("(long) ({op})"), Bitcast::I64ToI32 => format!("(int) ({op})"), - Bitcast::I64ToP64 => format!("{op}"), - Bitcast::P64ToI64 => format!("{op}"), + Bitcast::I64ToP64 => op.to_string(), + Bitcast::P64ToI64 => op.to_string(), Bitcast::LToI64 | Bitcast::PToP64 => format!("(long) ({op})"), Bitcast::I64ToL | Bitcast::P64ToP => format!("(int) ({op})"), Bitcast::I32ToP diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 49d55cd8c..21e4dd57b 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -597,12 +597,12 @@ impl InterfaceGenerator<'_> { fn gen_import_src( &mut self, func: &Function, - results: &Vec, + results: &[TypeId], parameter_type: ParameterType, ) -> (String, String) { let mut bindgen = FunctionBindgen::new( self, - &func.item_name(), + func.item_name(), &func.kind, func.params .iter() @@ -615,7 +615,7 @@ impl InterfaceGenerator<'_> { } }) .collect(), - results.clone(), + results.to_vec(), parameter_type, func.result, ); @@ -773,7 +773,7 @@ var {async_status_var} = {raw_name}({wasm_params}); let mut bindgen = FunctionBindgen::new( self, - &func.item_name(), + func.item_name(), &func.kind, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), results, @@ -1283,7 +1283,7 @@ var {async_status_var} = {raw_name}({wasm_params}); Direction::Export => { let prefix = key .map(|s| format!("{}#", self.resolve.name_world_key(s))) - .unwrap_or_else(String::new); + .unwrap_or_default(); uwrite!( self.csharp_interop_src, diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index 7c88b662d..343516dea 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -35,6 +35,7 @@ pub struct CSharp { pub(crate) needs_rep_table: bool, pub(crate) needs_wit_exception: bool, pub(crate) needs_async_support: bool, + pub(crate) needs_align_stack_ptr: bool, pub(crate) interface_fragments: HashMap, pub(crate) world_fragments: Vec, pub(crate) sizes: SizeAlign, @@ -388,7 +389,7 @@ impl WorldGenerator for CSharp { let world = &resolve.worlds[id]; let world_namespace = self.qualifier(); let world_namespace = world_namespace.strip_suffix(".").unwrap(); - let namespace = format!("{world_namespace}"); + let namespace = world_namespace; let name = world.name.to_upper_camel_case(); let version = env!("CARGO_PKG_VERSION"); @@ -623,13 +624,28 @@ impl WorldGenerator for CSharp { src.push_str(&ret_area_str); } + if self.needs_align_stack_ptr { + uwrite!( + src, + " + {access} static class MemoryHelper + {{ + {access} static unsafe void* AlignStackPtr(void* stackAddress, uint alignment) + {{ + return (void*)(((int)stackAddress) + ((int)alignment - 1) & -(int)alignment); + }} + }} + " + ); + } + if self.needs_rep_table { - src.push_str("\n"); + src.push('\n'); src.push_str(include_str!("RepTable.cs")); } if !&self.world_fragments.is_empty() { - src.push_str("\n"); + src.push('\n'); if self .world_fragments @@ -712,7 +728,7 @@ impl WorldGenerator for CSharp { src.push_str(&include_str!("FutureCommonSupport.cs")); } - src.push_str("\n"); + src.push('\n'); src.push_str("}\n"); @@ -740,11 +756,11 @@ impl WorldGenerator for CSharp { let (fragments, fully_qualified_namespace) = match stubs { Stubs::World(fragments) => { - let fully_qualified_namespace = format!("{namespace}"); + let fully_qualified_namespace = namespace.to_string(); (fragments, fully_qualified_namespace) } Stubs::Interface(fragments) => { - let fully_qualified_namespace = format!("{stub_namespace}"); + let fully_qualified_namespace = stub_namespace.clone(); (fragments, fully_qualified_namespace) } }; @@ -818,13 +834,9 @@ impl WorldGenerator for CSharp { // intended to be used non-interactively at link time, the // linker will have no additional information to resolve such // ambiguity. - let (resolve, world) = - wit_parser::decoding::decode_world(&wit_component::metadata::encode( - &resolve, - id, - self.opts.string_encoding, - None, - )?)?; + let (resolve, world) = wit_parser::decoding::decode_world( + &wit_component::metadata::encode(resolve, id, self.opts.string_encoding, None)?, + )?; let pkg = resolve.worlds[world].package.unwrap(); let mut printer = WitPrinter::default(); @@ -878,7 +890,7 @@ impl WorldGenerator for CSharp { .collect::>() .join("\n"); - if body.len() > 0 { + if !body.is_empty() { let body = format!( "{header} @@ -943,11 +955,12 @@ fn export_types(r#gen: &mut InterfaceGenerator, types: &[(&str, TypeId)]) { // We cant use "StructLayout.Pack" as dotnet will use the minimum of the type and the "Pack" field, // so for byte it would always use 1 regardless of the "Pack". pub fn dotnet_aligned_array(array_size: usize, required_alignment: usize) -> (usize, String) { + let num_elements = array_size.div_ceil(required_alignment); match required_alignment { - 1 => (array_size, "byte".to_owned()), - 2 => ((array_size + 1) / 2, "ushort".to_owned()), - 4 => ((array_size + 3) / 4, "uint".to_owned()), - 8 => ((array_size + 7) / 8, "ulong".to_owned()), + 1 => (num_elements, "byte".to_owned()), + 2 => (num_elements, "ushort".to_owned()), + 4 => (num_elements, "uint".to_owned()), + 8 => (num_elements, "ulong".to_owned()), _ => todo!("unsupported return_area_align {}", required_alignment), } } @@ -1033,11 +1046,7 @@ fn interface_name( ); if let Some(version) = &name.version { - let v = version - .to_string() - .replace('.', "_") - .replace('-', "_") - .replace('+', "_"); + let v = version.to_string().replace(['.', '-', '+'], "_"); ns = format!("{}v{}.", ns, &v); } ns diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index 8e11ca7f7..692221e02 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -99,6 +99,7 @@ impl LanguageMethods for Csharp { let os = match std::env::consts::OS { "windows" => "win", "linux" => std::env::consts::OS, + "macos" => "osx", other => todo!("OS {} not supported", other), }; diff --git a/tests/runtime/lists/runner.c b/tests/runtime/lists/runner.c index bddbfda49..4370bccfc 100644 --- a/tests/runtime/lists/runner.c +++ b/tests/runtime/lists/runner.c @@ -232,4 +232,33 @@ void exports_runner_run() { runner_list_f32_free(&ret.f0); runner_list_f64_free(&ret.f1); } + + { + runner_tuple2_string_list_u8_t headers_data[2]; + runner_string_set(&headers_data[0].f0, "Content-Type"); + uint8_t val0[] = "text/plain"; + headers_data[0].f1.ptr = val0; + headers_data[0].f1.len = 10; + + runner_string_set(&headers_data[1].f0, "Content-Length"); + uint8_t val1[] = "9"; + headers_data[1].f1.ptr = val1; + headers_data[1].f1.len = 1; + + runner_list_tuple2_string_list_u8_t headers = { headers_data, 2 }; + runner_list_tuple2_string_list_u8_t result; + test_lists_to_test_wasi_http_headers_roundtrip(&headers, &result); + + assert(result.len == 2); + assert(result.ptr[0].f0.len == 12); + assert(memcmp(result.ptr[0].f0.ptr, "Content-Type", 12) == 0); + assert(result.ptr[0].f1.len == 10); + assert(memcmp(result.ptr[0].f1.ptr, "text/plain", 10) == 0); + assert(result.ptr[1].f0.len == 14); + assert(memcmp(result.ptr[1].f0.ptr, "Content-Length", 14) == 0); + assert(result.ptr[1].f1.len == 1); + assert(result.ptr[1].f1.ptr[0] == '9'); + + runner_list_tuple2_string_list_u8_free(&result); + } } diff --git a/tests/runtime/lists/runner.cpp b/tests/runtime/lists/runner.cpp index cc898f29f..497e4b5bf 100644 --- a/tests/runtime/lists/runner.cpp +++ b/tests/runtime/lists/runner.cpp @@ -105,4 +105,18 @@ void exports::runner::Run() std::vector{DBL_MIN, DBL_MAX, -HUGE_VAL, HUGE_VAL} ) )); + + { + std::vector val0 = {'t', 'e', 'x', 't', '/', 'p', 'l', 'a', 'i', 'n'}; + std::vector val1 = {'9'}; + std::vector>> headers; + headers.push_back(std::make_tuple(std::string_view("Content-Type"), std::span(val0))); + headers.push_back(std::make_tuple(std::string_view("Content-Length"), std::span(val1))); + auto result = WasiHttpHeadersRoundtrip(headers); + assert(result.size() == 2); + assert(equal(std::get<0>(result[0]), "Content-Type")); + assert(equal(std::get<1>(result[0]), std::vector{'t', 'e', 'x', 't', '/', 'p', 'l', 'a', 'i', 'n'})); + assert(equal(std::get<0>(result[1]), "Content-Length")); + assert(equal(std::get<1>(result[1]), std::vector{'9'})); + } } diff --git a/tests/runtime/lists/runner.cs b/tests/runtime/lists/runner.cs index f353e56b5..a81bf68a9 100644 --- a/tests/runtime/lists/runner.cs +++ b/tests/runtime/lists/runner.cs @@ -159,5 +159,15 @@ public static void Run() && s[2] == double.NegativeInfinity && s[3] == double.PositiveInfinity); } + + { + var headers = new List<(string, byte[])>() { ("Content-Type", Encoding.UTF8.GetBytes("text/plain")), ("Content-Length", Encoding.UTF8.GetBytes("Not found".Count().ToString())) }; + var result = IToTestImports.WasiHttpHeadersRoundtrip(headers); + for (var i = 0; i < result.Count(); i++) + { + Debug.Assert(result[i].Item1 == headers[i].Item1); + Debug.Assert(headers[i].Item2.SequenceEqual(result[i].Item2)); + } + } } } diff --git a/tests/runtime/lists/runner.go b/tests/runtime/lists/runner.go index 5defefca5..e64f842cd 100644 --- a/tests/runtime/lists/runner.go +++ b/tests/runtime/lists/runner.go @@ -32,6 +32,19 @@ func Run() { assertEqual(test.ListResult2(), "hello!") assert(slices.Equal(test.ListResult3(), []string{"hello,", "world!"})) assert(slices.Equal(test.ListRoundtrip([]uint8{}), []uint8{})) + + { + headers := []Tuple2[string, []uint8]{ + {"Content-Type", []uint8("text/plain")}, + {"Content-Length", []uint8("9")}, + } + result := test.WasiHttpHeadersRoundtrip(headers) + assertEqual(len(result), 2) + assertEqual(result[0].F0, "Content-Type") + assert(slices.Equal(result[0].F1, []uint8("text/plain"))) + assertEqual(result[1].F0, "Content-Length") + assert(slices.Equal(result[1].F1, []uint8("9"))) + } } func assertEqual[T comparable](a T, b T) { diff --git a/tests/runtime/lists/runner.rs b/tests/runtime/lists/runner.rs index b6e77c75d..90e843727 100644 --- a/tests/runtime/lists/runner.rs +++ b/tests/runtime/lists/runner.rs @@ -162,5 +162,24 @@ impl Guest for Component { ), ); } + + { + let _guard = Guard::new(); + let headers = vec![ + ("Content-Type".to_owned(), b"text/plain".to_vec()), + ( + "Content-Length".to_owned(), + "Not found".len().to_string().into_bytes(), + ), + ]; + let result = wasi_http_headers_roundtrip(&headers); + assert_eq!(result[0].0, "Content-Type"); + assert_eq!(result[0].1, b"text/plain"); + assert_eq!(result[1].0, "Content-Length"); + assert_eq!( + result[1].1, + "Not found".len().to_string().into_bytes() + ); + } } } diff --git a/tests/runtime/lists/test.c b/tests/runtime/lists/test.c index e7a91bd97..66a3f54a0 100644 --- a/tests/runtime/lists/test.c +++ b/tests/runtime/lists/test.c @@ -161,3 +161,7 @@ void exports_test_lists_to_test_list_minmax_float(test_list_f32_t *a, test_list_ ret->f0 = *a; ret->f1 = *b; } + +void exports_test_lists_to_test_wasi_http_headers_roundtrip(test_list_tuple2_string_list_u8_t *a, test_list_tuple2_string_list_u8_t *ret0) { + *ret0 = *a; +} diff --git a/tests/runtime/lists/test.cpp b/tests/runtime/lists/test.cpp index c4073bdc9..f05016fff 100644 --- a/tests/runtime/lists/test.cpp +++ b/tests/runtime/lists/test.cpp @@ -129,3 +129,7 @@ std::tuple, wit::vector> exports::test::lists::to std::tuple, wit::vector> exports::test::lists::to_test::ListMinmaxFloat(wit::vector a, wit::vector b) { return std::make_tuple(std::move(a), std::move(b)); } + +wit::vector>> exports::test::lists::to_test::WasiHttpHeadersRoundtrip(wit::vector>> a) { + return a; +} diff --git a/tests/runtime/lists/test.cs b/tests/runtime/lists/test.cs index 77ff16410..6df8daabf 100644 --- a/tests/runtime/lists/test.cs +++ b/tests/runtime/lists/test.cs @@ -158,5 +158,10 @@ public static string StringRoundtrip(string a) { return a; } + + public static List<(string, byte[])> WasiHttpHeadersRoundtrip(List<(string, byte[])> a) + { + return a; + } } } diff --git a/tests/runtime/lists/test.go b/tests/runtime/lists/test.go index 455b9db9a..47b300460 100644 --- a/tests/runtime/lists/test.go +++ b/tests/runtime/lists/test.go @@ -111,3 +111,7 @@ func ListMinmax64(x []uint64, y []int64) ([]uint64, []int64) { func ListMinmaxFloat(x []float32, y []float64) ([]float32, []float64) { return x, y } + +func WasiHttpHeadersRoundtrip(x []Tuple2[string, []uint8]) []Tuple2[string, []uint8] { + return x +} diff --git a/tests/runtime/lists/test.mbt b/tests/runtime/lists/test.mbt index 2ae06a18c..6aa24a3fb 100644 --- a/tests/runtime/lists/test.mbt +++ b/tests/runtime/lists/test.mbt @@ -124,6 +124,11 @@ pub fn string_roundtrip(a : String) -> String { a } +///| +pub fn wasi_http_headers_roundtrip(a : Array[(String, FixedArray[Byte])]) -> Array[(String, FixedArray[Byte])] { + a +} + ///| pub fn allocated_bytes() -> UInt { // not quite sure about this diff --git a/tests/runtime/lists/test.rs b/tests/runtime/lists/test.rs index a0e858c87..000bfea95 100644 --- a/tests/runtime/lists/test.rs +++ b/tests/runtime/lists/test.rs @@ -96,4 +96,10 @@ impl exports::test::lists::to_test::Guest for Component { fn list_minmax_float(a: Vec, b: Vec) -> (Vec, Vec) { (a, b) } + + fn wasi_http_headers_roundtrip( + headers: Vec<(String, Vec)>, + ) -> Vec<(String, Vec)> { + headers + } } diff --git a/tests/runtime/lists/test.wit b/tests/runtime/lists/test.wit index e77c8fccc..5b8179175 100644 --- a/tests/runtime/lists/test.wit +++ b/tests/runtime/lists/test.wit @@ -27,6 +27,8 @@ interface to-test { string-roundtrip: func(a: string) -> string; + wasi-http-headers-roundtrip: func(a: list>>) -> list>>; + allocated-bytes: func() -> u32; }