diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8542c3d54..24e13c07d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, csharp, cpp] + lang: [c, rust, csharp, cpp, go] exclude: # For now csharp doesn't work on macos, so exclude it from testing. - os: macos-latest @@ -85,6 +85,12 @@ jobs: dotnet-version: '9.x' if: matrix.lang == 'csharp' + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.25.4 + if: matrix.lang == 'go' + # Hacky work-around for https://github.com/dotnet/runtime/issues/80619 - run: dotnet new console -o /tmp/foo if: matrix.os != 'windows-latest' && matrix.lang == 'csharp' diff --git a/Cargo.lock b/Cargo.lock index 2e6bf9ae0..09c0b395a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1265,6 +1265,7 @@ dependencies = [ "wit-bindgen-core", "wit-bindgen-cpp", "wit-bindgen-csharp", + "wit-bindgen-go", "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust", @@ -1312,6 +1313,19 @@ dependencies = [ "wit-parser", ] +[[package]] +name = "wit-bindgen-go" +version = "0.49.0" +dependencies = [ + "anyhow", + "clap", + "heck", + "wasm-encoder 0.243.0", + "wasm-metadata 0.243.0", + "wit-bindgen-core", + "wit-component", +] + [[package]] name = "wit-bindgen-markdown" version = "0.49.0" diff --git a/Cargo.toml b/Cargo.toml index 6af7c4594..91ed85f2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ wit-bindgen-rust = { path = "crates/rust", version = "0.49.0" } wit-bindgen-csharp = { path = 'crates/csharp', version = '0.49.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.49.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.49.0' } +wit-bindgen-go = { path = 'crates/go', version = '0.49.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.49.0', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.49.0' } @@ -64,6 +65,7 @@ wit-bindgen-cpp = { workspace = true, features = ['clap'], optional = true } wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true } wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } wit-bindgen-test = { workspace = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -84,7 +86,7 @@ c = ['dep:wit-bindgen-c'] cpp = ['dep:wit-bindgen-cpp'] rust = ['dep:wit-bindgen-rust'] markdown = ['dep:wit-bindgen-markdown'] -go = [] +go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] diff --git a/ci/publish.rs b/ci/publish.rs index 192777081..b5fec6431 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -24,6 +24,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-bindgen-csharp", "wit-bindgen-markdown", "wit-bindgen-moonbit", + "wit-bindgen-go", "wit-bindgen-rust-macro", "wit-bindgen-rt", "wit-bindgen", @@ -65,11 +66,13 @@ fn main() { bump_version(&krate, &crates, name == "bump-patch"); } // update the lock file - assert!(Command::new("cargo") - .arg("fetch") - .status() - .unwrap() - .success()); + assert!( + Command::new("cargo") + .arg("fetch") + .status() + .unwrap() + .success() + ); } "publish" => { diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index b3f806540..8e13945a6 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -806,6 +806,12 @@ pub fn guest_export_needs_post_return(resolve: &Resolve, func: &Function) -> boo .unwrap_or(false) } +pub fn guest_export_params_have_allocations(resolve: &Resolve, func: &Function) -> bool { + func.params + .iter() + .any(|(_, t)| needs_deallocate(resolve, &t, Deallocate::Lists)) +} + fn needs_deallocate(resolve: &Resolve, ty: &Type, what: Deallocate) -> bool { match ty { Type::String => true, @@ -1134,7 +1140,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { for (param_name, ty) in func.params.iter() { let Some(types) = flat_types(self.resolve, ty, Some(max_flat_params)) else { - panic!("failed to flatten types during direct parameter lifting ('{param_name}' in func '{}')", func.name); + panic!( + "failed to flatten types during direct parameter lifting ('{param_name}' in func '{}')", + func.name + ); }; for _ in 0..types.len() { self.emit(&Instruction::GetArg { nth: offset }); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 2932b7b4b..993583b06 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -148,7 +148,7 @@ pub trait InterfaceGenerator<'a> { fn type_record(&mut self, id: TypeId, name: &str, record: &Record, docs: &Docs); fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs); fn type_flags(&mut self, id: TypeId, name: &str, flags: &Flags, docs: &Docs); - fn type_tuple(&mut self, id: TypeId, name: &str, flags: &Tuple, docs: &Docs); + fn type_tuple(&mut self, id: TypeId, name: &str, tuple: &Tuple, docs: &Docs); fn type_variant(&mut self, id: TypeId, name: &str, variant: &Variant, docs: &Docs); fn type_option(&mut self, id: TypeId, name: &str, payload: &Type, docs: &Docs); fn type_result(&mut self, id: TypeId, name: &str, result: &Result_, docs: &Docs); diff --git a/crates/go/Cargo.toml b/crates/go/Cargo.toml new file mode 100644 index 000000000..61b2d4d76 --- /dev/null +++ b/crates/go/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wit-bindgen-go" +edition = "2024" +version = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +rust-version = "1.85.0" + +[dependencies] +wit-bindgen-core = { workspace = true } +wit-component = { workspace = true } +wasm-encoder = { workspace = true } +wasm-metadata = { workspace = true } +anyhow = { workspace = true } +heck = { workspace = true } +clap = { workspace = true, optional = true } + +[features] +clap = ['dep:clap', 'wit-bindgen-core/clap'] diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs new file mode 100644 index 000000000..d0adbc83d --- /dev/null +++ b/crates/go/src/lib.rs @@ -0,0 +1,2952 @@ +use anyhow::Result; +use heck::{ToLowerCamelCase as _, ToSnakeCase as _, ToUpperCamelCase as _}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, hash_map}; +use std::fmt::Write as _; +use std::iter; +use std::mem; +use wit_bindgen_core::abi::{ + self, AbiVariant, Bindgen, Bitcast, FlatTypes, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_core::wit_parser::{ + Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Handle, Int, + InterfaceId, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, TypeOwner, + Variant, WorldId, WorldKey, +}; +use wit_bindgen_core::{ + AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, WorldGenerator, uwriteln, +}; + +const MAX_FLAT_PARAMS: usize = 16; + +const POINTER_SIZE_EXPRESSION: &str = "4"; +const VARIANT_PAYLOAD_NAME: &str = "payload"; +const ITER_BASE_POINTER: &str = "base"; +const ITER_ELEMENT: &str = "element"; +const IMPORT_RETURN_AREA: &str = "returnArea"; +const EXPORT_RETURN_AREA: &str = "exportReturnArea"; +const SYNC_EXPORT_PINNER: &str = "syncExportPinner"; +const PINNER: &str = "pinner"; + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "clap", derive(clap::Parser))] +pub struct Opts { + #[cfg_attr(feature = "clap", clap(flatten))] + pub async_: AsyncFilterSet, + + /// If true, generate stub functions for any exported functions and/or + /// resources. + #[cfg_attr(feature = "clap", clap(long))] + pub generate_stubs: bool, +} + +impl Opts { + pub fn build(&self) -> Box { + Box::new(Go { + opts: self.clone(), + ..Go::default() + }) + } +} + +#[derive(Default)] +struct InterfaceData { + code: String, + imports: BTreeSet, + need_unsafe: bool, + need_runtime: bool, + need_math: bool, +} + +impl InterfaceData { + fn extend(&mut self, data: InterfaceData) { + self.code.push_str(&data.code); + self.imports.extend(data.imports); + self.need_unsafe |= data.need_unsafe; + self.need_runtime |= data.need_runtime; + self.need_math |= data.need_math; + } + + fn imports(&self) -> String { + self.imports + .iter() + .map(|v| format!(r#""wit_component/{v}""#)) + .chain(self.need_unsafe.then(|| r#""unsafe""#.into())) + .chain(self.need_runtime.then(|| r#""runtime""#.into())) + .chain(self.need_math.then(|| r#""math""#.into())) + .collect::>() + .join("\n") + } + + fn from_generator_and_code(generator: FunctionGenerator<'_>, code: String) -> Self { + Self { + code, + imports: generator.imports, + need_unsafe: generator.need_unsafe, + need_runtime: generator.need_pinner, + need_math: generator.need_math, + } + } +} + +impl From> for InterfaceData { + fn from(generator: InterfaceGenerator<'_>) -> Self { + Self { + code: generator.src, + imports: generator.imports, + need_unsafe: generator.need_unsafe, + need_runtime: generator.need_runtime, + need_math: false, + } + } +} + +#[derive(Default)] +struct Go { + opts: Opts, + src: String, + sizes: SizeAlign, + return_area_size: ArchitectureSize, + return_area_align: Alignment, + imports: BTreeSet, + tuples: BTreeSet, + need_option: bool, + need_result: bool, + need_math: bool, + need_unit: bool, + need_future: bool, + need_stream: bool, + need_async: bool, + need_unsafe: bool, + interface_names: HashMap, + interfaces: BTreeMap, + export_interfaces: BTreeMap, + types: HashSet, + resources: HashMap, + futures_and_streams: HashMap<(TypeId, bool), Option>, +} + +impl Go { + fn package_for_owner( + &mut self, + resolve: &Resolve, + owner: Option<&WorldKey>, + id: TypeId, + local: Option<&WorldKey>, + in_import: bool, + imports: &mut BTreeSet, + ) -> String { + let exported = self.has_exported_resource(resolve, Type::Id(id)); + + if local == owner && (exported ^ in_import) { + String::new() + } else { + let package = interface_name(resolve, owner); + let package = if exported { + format!("export_{package}") + } else { + package + }; + let prefix = format!("{package}."); + imports.insert(package); + prefix + } + } + + fn package( + &mut self, + resolve: &Resolve, + id: TypeId, + local: Option<&WorldKey>, + in_import: bool, + imports: &mut BTreeSet, + ) -> String { + let ty = &resolve.types[id]; + let owner = match ty.owner { + TypeOwner::World(_) => None, + TypeOwner::Interface(id) => Some( + self.interface_names + .get(&id) + .cloned() + .unwrap_or(WorldKey::Interface(id)), + ), + TypeOwner::None => unreachable!(), + }; + + self.package_for_owner(resolve, owner.as_ref(), id, local, in_import, imports) + } + + fn type_name( + &mut self, + resolve: &Resolve, + ty: Type, + local: Option<&WorldKey>, + in_import: bool, + imports: &mut BTreeSet, + ) -> String { + match ty { + Type::Bool => "bool".into(), + Type::U8 => "uint8".into(), + Type::S8 => "int8".into(), + Type::U16 => "uint16".into(), + Type::S16 => "int16".into(), + Type::U32 => "uint32".into(), + Type::S32 => "int32".into(), + Type::U64 => "uint64".into(), + Type::S64 => "int64".into(), + Type::F32 => "float32".into(), + Type::F64 => "float64".into(), + Type::Char => "rune".into(), + Type::String => "string".into(), + Type::Id(id) => { + let ty = &resolve.types[id]; + match &ty.kind { + TypeDefKind::Record(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Resource => { + let package = self.package(resolve, id, local, in_import, imports); + let name = ty.name.as_ref().unwrap().to_upper_camel_case(); + format!("{package}{name}") + } + TypeDefKind::Handle(Handle::Own(ty) | Handle::Borrow(ty)) => { + let name = + self.type_name(resolve, Type::Id(*ty), local, in_import, imports); + format!("*{name}") + } + TypeDefKind::Option(ty) => { + imports.insert("wit_types".into()); + let ty = self.type_name(resolve, *ty, local, in_import, imports); + format!("wit_types.Option[{ty}]") + } + TypeDefKind::List(ty) => { + let ty = self.type_name(resolve, *ty, local, in_import, imports); + format!("[]{ty}") + } + TypeDefKind::Result(result) => { + imports.insert("wit_types".into()); + let ok_type = result + .ok + .map(|ty| self.type_name(resolve, ty, local, in_import, imports)) + .unwrap_or_else(|| { + self.need_unit = true; + "wit_types.Unit".into() + }); + let err_type = result + .err + .map(|ty| self.type_name(resolve, ty, local, in_import, imports)) + .unwrap_or_else(|| { + self.need_unit = true; + "wit_types.Unit".into() + }); + format!("wit_types.Result[{ok_type}, {err_type}]") + } + TypeDefKind::Tuple(tuple) => { + imports.insert("wit_types".into()); + let count = tuple.types.len(); + self.tuples.insert(count); + let types = tuple + .types + .iter() + .map(|ty| self.type_name(resolve, *ty, local, in_import, imports)) + .collect::>() + .join(", "); + format!("wit_types.Tuple{count}[{types}]") + } + TypeDefKind::Future(ty) => { + self.need_future = true; + imports.insert("wit_types".into()); + let ty = ty + .map(|ty| self.type_name(resolve, ty, local, in_import, imports)) + .unwrap_or_else(|| { + self.need_unit = true; + "wit_types.Unit".into() + }); + format!("*wit_types.FutureReader[{ty}]") + } + TypeDefKind::Stream(ty) => { + self.need_stream = true; + imports.insert("wit_types".into()); + let ty = ty + .map(|ty| self.type_name(resolve, ty, local, in_import, imports)) + .unwrap_or_else(|| { + self.need_unit = true; + "wit_types.Unit".into() + }); + format!("*wit_types.StreamReader[{ty}]") + } + TypeDefKind::Type(ty) => { + self.type_name(resolve, *ty, local, in_import, imports) + } + _ => todo!("{:?}", ty.kind), + } + } + _ => todo!("{ty:?}"), + } + } + + #[expect(clippy::too_many_arguments)] + fn future_or_stream( + &mut self, + resolve: &Resolve, + ty: TypeId, + index: usize, + in_import: bool, + imported_type: bool, + interface: Option<&WorldKey>, + func_name: &str, + ) -> InterfaceData { + let prefix = if in_import { "" } else { "[export]" }; + + let module = format!( + "{prefix}{}", + interface + .as_ref() + .map(|name| resolve.name_world_key(name)) + .unwrap_or_else(|| "$root".into()) + ); + + let (payload_ty, kind, count) = match &resolve.types[ty].kind { + TypeDefKind::Future(ty) => (*ty, "future", ""), + TypeDefKind::Stream(ty) => (*ty, "stream", ", count uint32"), + _ => unreachable!(), + }; + + let upper_kind = kind.to_upper_camel_case(); + + let mut data = InterfaceData { + need_unsafe: true, + ..InterfaceData::default() + }; + data.imports.insert("wit_types".into()); + + let (payload, snake) = if let Some(ty) = payload_ty { + ( + self.type_name(resolve, ty, interface, imported_type, &mut data.imports), + self.mangle_name(resolve, ty, interface), + ) + } else { + self.need_unit = true; + ("wit_types.Unit".into(), "unit".into()) + }; + let camel = snake.to_upper_camel_case(); + + let abi = self.sizes.record(payload_ty.as_ref()); + let size = abi.size.format(POINTER_SIZE_EXPRESSION); + let align = abi.align.format(POINTER_SIZE_EXPRESSION); + + // TODO: Skip lifting/lowering other types that can be used directly in + // their canonical form: + let (lift, lift_name, lower, lower_name) = match payload_ty { + None => ( + format!( + "func wasm_{kind}_lift_{snake}(src unsafe.Pointer) {payload} {{ + return wit_types.Unit{{}} +}} +" + ), + format!("wasm_{kind}_lift_{snake}"), + String::new(), + "nil".to_string(), + ), + Some(Type::U8 | Type::S8) => ( + String::new(), + "nil".to_string(), + String::new(), + "nil".to_string(), + ), + Some(ty) => { + data.need_runtime = true; + + let mut generator = FunctionGenerator::new( + self, + None, + None, + interface, + "INVALID", + Vec::new(), + false, + imported_type, + ); + + let lift_result = + abi::lift_from_memory(resolve, &mut generator, "src".to_string(), &ty); + let lift = mem::take(&mut generator.src); + + abi::lower_to_memory( + resolve, + &mut generator, + "dst".to_string(), + "value".to_string(), + &ty, + ); + let lower = mem::take(&mut generator.src); + data.extend(InterfaceData::from_generator_and_code( + generator, + String::new(), + )); + + ( + format!( + "func wasm_{kind}_lift_{snake}(src unsafe.Pointer) {payload} {{ + {lift} + return {lift_result} +}} +" + ), + format!("wasm_{kind}_lift_{snake}"), + format!( + "func wasm_{kind}_lower_{snake}(pinner *runtime.Pinner, value {payload}, dst unsafe.Pointer) {{ + {lower} +}} +" + ), + format!("wasm_{kind}_lower_{snake}"), + ) + } + }; + + data.code = format!( + r#" +//go:wasmimport {module} [{kind}-new-{index}]{func_name} +func wasm_{kind}_new_{snake}() uint64 + +//go:wasmimport {module} [async-lower][{kind}-read-{index}]{func_name} +func wasm_{kind}_read_{snake}(handle int32, item unsafe.Pointer{count}) uint32 + +//go:wasmimport {module} [async-lower][{kind}-write-{index}]{func_name} +func wasm_{kind}_write_{snake}(handle int32, item unsafe.Pointer{count}) uint32 + +//go:wasmimport {module} [{kind}-drop-readable-{index}]{func_name} +func wasm_{kind}_drop_readable_{snake}(handle int32) + +//go:wasmimport {module} [{kind}-drop-writable-{index}]{func_name} +func wasm_{kind}_drop_writable_{snake}(handle int32) + +{lift} + +{lower} + +var wasm_{kind}_vtable_{snake} = wit_types.{upper_kind}Vtable[{payload}]{{ + {size}, + {align}, + wasm_{kind}_read_{snake}, + wasm_{kind}_write_{snake}, + nil, + nil, + wasm_{kind}_drop_readable_{snake}, + wasm_{kind}_drop_writable_{snake}, + {lift_name}, + {lower_name}, +}} + +func Make{upper_kind}{camel}() (*wit_types.{upper_kind}Writer[{payload}], *wit_types.{upper_kind}Reader[{payload}]) {{ + pair := wasm_{kind}_new_{snake}() + return wit_types.Make{upper_kind}Writer[{payload}](&wasm_{kind}_vtable_{snake}, int32(pair >> 32)), + wit_types.Make{upper_kind}Reader[{payload}](&wasm_{kind}_vtable_{snake}, int32(pair & 0xFFFFFFFF)) +}} + +func Lift{upper_kind}{camel}(handle int32) *wit_types.{upper_kind}Reader[{payload}] {{ + return wit_types.Make{upper_kind}Reader[{payload}](&wasm_{kind}_vtable_{snake}, handle) +}} +"# + ); + + data + } + + fn mangle_name(&self, resolve: &Resolve, ty: Type, local: Option<&WorldKey>) -> String { + // TODO: Ensure the returned name is always distinct for distinct types + // (e.g. by incorporating interface version numbers and/or additional + // mangling as needed). + match ty { + Type::Bool => "bool".into(), + Type::U8 => "u8".into(), + Type::U16 => "u16".into(), + Type::U32 => "u32".into(), + Type::U64 => "u64".into(), + Type::S8 => "s8".into(), + Type::S16 => "s16".into(), + Type::S32 => "s32".into(), + Type::S64 => "s64".into(), + Type::ErrorContext => "error_context".into(), + Type::F32 => "f32".into(), + Type::F64 => "f64".into(), + Type::Char => "char".into(), + Type::String => "string".into(), + Type::Id(id) => { + let ty = &resolve.types[id]; + match &ty.kind { + TypeDefKind::Record(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Resource => { + let package = match ty.owner { + TypeOwner::Interface(interface) => { + let key = self + .interface_names + .get(&interface) + .cloned() + .unwrap_or(WorldKey::Interface(interface)); + + if local == Some(&key) { + String::new() + } else { + format!( + "{}_", + interface_name( + resolve, + Some( + &self + .interface_names + .get(&interface) + .cloned() + .unwrap_or(WorldKey::Interface(interface)) + ) + ) + ) + } + } + _ => String::new(), + }; + + let name = ty.name.as_ref().unwrap().to_snake_case(); + + format!("{package}{name}") + } + TypeDefKind::Option(some) => { + format!("option_{}", self.mangle_name(resolve, *some, local)) + } + TypeDefKind::Result(result) => format!( + "result_{}_{}", + result + .ok + .map(|ty| self.mangle_name(resolve, ty, local)) + .unwrap_or_else(|| "unit".into()), + result + .err + .map(|ty| self.mangle_name(resolve, ty, local)) + .unwrap_or_else(|| "unit".into()) + ), + TypeDefKind::List(ty) => { + format!("list_{}", self.mangle_name(resolve, *ty, local)) + } + TypeDefKind::Tuple(tuple) => { + let types = tuple + .types + .iter() + .map(|ty| self.mangle_name(resolve, *ty, local)) + .collect::>() + .join("_"); + format!("tuple{}_{types}", tuple.types.len()) + } + TypeDefKind::Handle(Handle::Own(ty) | Handle::Borrow(ty)) => { + self.mangle_name(resolve, Type::Id(*ty), local) + } + TypeDefKind::Type(ty) => self.mangle_name(resolve, *ty, local), + TypeDefKind::Stream(ty) => { + format!( + "stream_{}", + ty.map(|ty| self.mangle_name(resolve, ty, local)) + .unwrap_or_else(|| "unit".into()) + ) + } + TypeDefKind::Future(ty) => { + format!( + "future_{}", + ty.map(|ty| self.mangle_name(resolve, ty, local)) + .unwrap_or_else(|| "unit".into()) + ) + } + kind => todo!("{kind:?}"), + } + } + } + } +} + +impl WorldGenerator for Go { + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { + _ = world; + self.sizes.fill(resolve); + self.imports.insert("wit_runtime".into()); + } + + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + if let WorldKey::Name(_) = name { + self.interface_names.insert(id, name.clone()); + } + + let mut data = { + let mut generator = InterfaceGenerator::new(self, resolve, Some((id, name)), true); + for (name, ty) in resolve.interfaces[id].types.iter() { + if !generator.generator.types.contains(ty) { + generator.generator.types.insert(*ty); + generator.define_type(name, *ty); + } + } + InterfaceData::from(generator) + }; + + for (_, func) in &resolve.interfaces[id].functions { + data.extend(self.import(resolve, func, Some(name))); + } + self.interfaces + .entry(interface_name(resolve, Some(name))) + .or_default() + .extend(data); + + Ok(()) + } + + fn import_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + let mut data = InterfaceData::default(); + for (_, func) in funcs { + data.extend(self.import(resolve, func, None)); + } + self.interfaces + .entry(interface_name(resolve, None)) + .or_default() + .extend(data); + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + if let WorldKey::Name(_) = name { + self.interface_names.insert(id, name.clone()); + } + + for (type_name, ty) in &resolve.interfaces[id].types { + let exported = matches!(resolve.types[*ty].kind, TypeDefKind::Resource) + || self.has_exported_resource(resolve, Type::Id(*ty)); + + let mut generator = InterfaceGenerator::new(self, resolve, Some((id, name)), false); + + if exported || !generator.generator.types.contains(ty) { + generator.generator.types.insert(*ty); + generator.define_type(type_name, *ty); + } + + let data = generator.into(); + + if exported { + &mut self.export_interfaces + } else { + &mut self.interfaces + } + .entry(interface_name(resolve, Some(name))) + .or_default() + .extend(data); + } + + for (_, func) in &resolve.interfaces[id].functions { + let code = self.export(resolve, func, Some(name)); + self.src.push_str(&code); + } + + Ok(()) + } + + fn export_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> Result<()> { + for (_, func) in funcs { + let code = self.export(resolve, func, None); + self.src.push_str(&code); + } + Ok(()) + } + + fn import_types( + &mut self, + resolve: &Resolve, + _world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + let mut generator = InterfaceGenerator::new(self, resolve, None, true); + for (name, ty) in types { + if !generator.generator.types.contains(ty) { + generator.generator.types.insert(*ty); + generator.define_type(name, *ty); + } + } + let data = generator.into(); + self.interfaces + .entry(interface_name(resolve, None)) + .or_default() + .extend(data); + } + + fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { + _ = (resolve, id); + + let src = mem::take(&mut self.src); + let align = self.return_area_align.format(POINTER_SIZE_EXPRESSION); + let size = self.return_area_size.format(POINTER_SIZE_EXPRESSION); + let imports = self + .imports + .iter() + .map(|v| format!(r#""wit_component/{v}""#)) + .chain(self.need_math.then(|| r#""math""#.into())) + .chain(self.need_unsafe.then(|| r#""unsafe""#.into())) + .collect::>() + .join("\n"); + + files.push( + "wit_bindings.go", + format!( + r#"package main + +import ( + "runtime" + {imports} +) + +var staticPinner = runtime.Pinner{{}} +var {EXPORT_RETURN_AREA} = uintptr(wit_runtime.Allocate(&staticPinner, {size}, {align})) +var {SYNC_EXPORT_PINNER} = runtime.Pinner{{}} + +{src} + +// Unused, but present to make the compiler happy +func main() {{}} +"# + ) + .as_bytes(), + ); + files.push("go.mod", b"module wit_component\n\ngo 1.25"); + files.push( + "wit_runtime/wit_runtime.go", + include_bytes!("wit_runtime.go"), + ); + + for (prefix, interfaces) in [("export_", &self.export_interfaces), ("", &self.interfaces)] { + for (name, data) in interfaces { + let imports = data.imports(); + let code = &data.code; + + files.push( + &format!("{prefix}{name}/wit_bindings.go"), + format!( + "package {prefix}{name} + +import ( + {imports} +) + +{code}" + ) + .as_bytes(), + ); + } + } + + if !self.tuples.is_empty() { + let tuples = self + .tuples + .iter() + .map(|&v| { + let types = (0..v) + .map(|index| format!("T{index} any")) + .collect::>() + .join(", "); + let fields = (0..v) + .map(|index| format!("F{index} T{index}")) + .collect::>() + .join("\n"); + format!( + "type Tuple{v}[{types}] struct {{ + {fields} +}}" + ) + }) + .collect::>() + .join("\n"); + + files.push( + "wit_types/wit_tuples.go", + format!( + r#"package wit_types + +{tuples} +"# + ) + .as_bytes(), + ); + } + + if self.need_async { + files.push("wit_async/wit_async.go", include_bytes!("wit_async.go")); + } + + if self.need_option { + files.push("wit_types/wit_option.go", include_bytes!("wit_option.go")); + } + + if self.need_result { + files.push("wit_types/wit_result.go", include_bytes!("wit_result.go")); + } + + if self.need_unit { + files.push("wit_types/wit_unit.go", include_bytes!("wit_unit.go")); + } + + if self.need_future { + files.push("wit_types/wit_future.go", include_bytes!("wit_future.go")); + } + + if self.need_stream { + files.push("wit_types/wit_stream.go", include_bytes!("wit_stream.go")); + } + + Ok(()) + } +} + +impl Go { + fn import( + &mut self, + resolve: &Resolve, + func: &Function, + interface: Option<&WorldKey>, + ) -> InterfaceData { + self.visit_futures_and_streams(true, resolve, func, interface); + + let async_ = self.opts.async_.is_async(resolve, interface, func, true); + + let (variant, prefix) = if async_ { + (AbiVariant::GuestImportAsync, "[async-lower]") + } else { + (AbiVariant::GuestImport, "") + }; + + let sig = resolve.wasm_signature(variant, func); + let import_name = &func.name; + let name = func.name.to_snake_case().replace('.', "_"); + let (camel, has_self) = func_declaration(resolve, func); + + let module = match interface { + Some(name) => resolve.name_world_key(name), + None => "$root".to_string(), + }; + + let params = sig + .params + .iter() + .enumerate() + .map(|(i, param)| format!("arg{i} {}", wasm_type(*param))) + .collect::>() + .join(", "); + + let results = match &sig.results[..] { + [] => "", + [result] => wasm_type(*result), + _ => unreachable!(), + }; + + let mut imports = BTreeSet::new(); + let go_params = + self.func_params(resolve, func, interface, true, &mut imports, has_self, ""); + let go_results = self.func_results(resolve, func, interface, true, &mut imports); + + let raw_name = format!("wasm_import_{name}"); + + let go_param_names = has_self + .then(|| "self".to_string()) + .into_iter() + .chain( + func.params + .iter() + .skip(if has_self { 1 } else { 0 }) + .map(|(name, _)| name.to_lower_camel_case()), + ) + .collect::>(); + + let mut generator = FunctionGenerator::new( + self, + None, + interface, + interface, + &raw_name, + go_param_names.clone(), + false, + true, + ); + generator.imports = imports; + + let code = if async_ { + generator.generator.need_async = true; + generator.imports.insert("wit_async".into()); + + let (lower, wasm_params) = if sig.indirect_params { + generator.imports.insert("wit_runtime".into()); + + let params_pointer = generator.locals.tmp("params"); + let abi = generator + .generator + .sizes + .record(func.params.iter().map(|(_, ty)| ty)); + let size = abi.size.format(POINTER_SIZE_EXPRESSION); + let align = abi.align.format(POINTER_SIZE_EXPRESSION); + let offsets = generator + .generator + .sizes + .field_offsets(func.params.iter().map(|(_, ty)| ty)); + + for (name, (offset, ty)) in go_param_names.iter().zip(offsets) { + let offset = offset.format(POINTER_SIZE_EXPRESSION); + abi::lower_to_memory( + resolve, + &mut generator, + format!("unsafe.Add(unsafe.Pointer({params_pointer}), {offset})"), + name.clone(), + ty, + ); + } + + let code = mem::take(&mut generator.src); + generator.need_pinner = true; + ( + format!( + "{params_pointer} := wit_runtime.Allocate({PINNER}, {size}, {align})\n{code}" + ), + vec![params_pointer], + ) + } else { + let wasm_params = go_param_names + .iter() + .zip(&func.params) + .flat_map(|(name, (_, ty))| { + abi::lower_flat(resolve, &mut generator, name.clone(), ty) + }) + .collect(); + (mem::take(&mut generator.src), wasm_params) + }; + + let wasm_params = wasm_params + .iter() + .map(|v| v.as_str()) + .chain(func.result.map(|_| IMPORT_RETURN_AREA)) + .collect::>() + .join(", "); + + let lift = if let Some(result) = func.result { + let result = abi::lift_from_memory( + resolve, + &mut generator, + IMPORT_RETURN_AREA.to_string(), + &result, + ); + let code = mem::take(&mut generator.src); + format!("{code}\nreturn {result}") + } else { + String::new() + }; + + format!( + "{lower} +wit_async.SubtaskWait(uint32({raw_name}({wasm_params}))) +{lift} +" + ) + } else { + abi::call( + resolve, + variant, + LiftLower::LowerArgsLiftResults, + func, + &mut generator, + false, + ); + mem::take(&mut generator.src) + }; + + let return_area = |generator: &mut FunctionGenerator<'_>, + size: ArchitectureSize, + align: Alignment| { + generator.imports.insert("wit_runtime".into()); + generator.need_pinner = true; + let size = size.format(POINTER_SIZE_EXPRESSION); + let align = align.format(POINTER_SIZE_EXPRESSION); + format!( + "{IMPORT_RETURN_AREA} := uintptr(wit_runtime.Allocate({PINNER}, {size}, {align}))" + ) + }; + + let return_area = if async_ && func.result.is_some() { + let abi = generator.generator.sizes.record(func.result.as_ref()); + return_area(&mut generator, abi.size, abi.align) + } else if !(async_ || generator.return_area_size.is_empty()) { + let size = generator.return_area_size; + let align = generator.return_area_align; + return_area(&mut generator, size, align) + } else { + String::new() + }; + + let pinner = if generator.need_pinner { + format!( + "{PINNER} := &runtime.Pinner{{}} +defer {PINNER}.Unpin() +" + ) + } else { + String::new() + }; + + InterfaceData::from_generator_and_code( + generator, + format!( + " +//go:wasmimport {module} {prefix}{import_name} +func {raw_name}({params}) {results} + +func {camel}({go_params}) {go_results} {{ + {pinner} + {return_area} + {code} +}} +" + ), + ) + } + + fn export( + &mut self, + resolve: &Resolve, + func: &Function, + interface: Option<&WorldKey>, + ) -> String { + self.visit_futures_and_streams(false, resolve, func, interface); + + let async_ = self.opts.async_.is_async(resolve, interface, func, false); + + let (variant, prefix) = if async_ { + (AbiVariant::GuestExportAsync, "[async-lift]") + } else { + (AbiVariant::GuestExport, "") + }; + + let sig = resolve.wasm_signature(variant, func); + let core_module_name = interface.map(|v| resolve.name_world_key(v)); + let export_name = func.legacy_core_export_name(core_module_name.as_deref()); + let name = func_name(resolve, interface, func); + + let params = sig + .params + .iter() + .enumerate() + .map(|(i, param)| format!("arg{i} {}", wasm_type(*param))) + .collect::>() + .join(", "); + + let results = match &sig.results[..] { + [] => "", + [result] => wasm_type(*result), + _ => unreachable!(), + }; + + let unpin_params = + sig.indirect_params || abi::guest_export_params_have_allocations(resolve, func); + + let param_names = (0..sig.params.len()).map(|i| format!("arg{i}")).collect(); + let mut generator = FunctionGenerator::new( + self, + Some(&name), + interface, + None, + "INVALID", + param_names, + unpin_params, + false, + ); + abi::call( + resolve, + variant, + LiftLower::LiftArgsLowerResults, + func, + &mut generator, + async_, + ); + let code = generator.src; + let imports = generator.imports; + let need_unsafe = generator.need_unsafe; + self.need_math |= generator.need_math; + self.need_unsafe |= need_unsafe; + self.imports.extend(imports); + + let (pinner, other, start, end) = if async_ { + self.need_async = true; + self.imports.insert("wit_async".into()); + + let module = match interface { + Some(name) => resolve.name_world_key(name), + None => "$root".to_string(), + }; + + let function = &func.name; + + let task_return_params = func + .result + .map(|ty| { + let mut storage = vec![WasmType::I32; MAX_FLAT_PARAMS]; + let mut flat = FlatTypes::new(&mut storage); + if resolve.push_flat(&ty, &mut flat) { + flat.to_vec() + } else { + vec![WasmType::I32] + } + }) + .unwrap_or_default() + .into_iter() + .enumerate() + .map(|(i, ty)| { + let ty = wasm_type(ty); + format!("arg{i} {ty}") + }) + .collect::>() + .join(", "); + + ( + if abi::guest_export_needs_post_return(resolve, func) { + format!("{PINNER} := &runtime.Pinner{{}}") + } else { + String::new() + }, + format!( + " + +//go:wasmexport [callback]{prefix}{export_name} +func wasm_export_callback_{name}(event0 uint32, event1 uint32, event2 uint32) uint32 {{ + return wit_async.Callback(event0, event1, event2) +}} + +//go:wasmimport [export]{module} [task-return]{function} +func wasm_export_task_return_{name}({task_return_params}) +" + ), + "return int32(wit_async.Run(func() {", + "}))", + ) + } else if abi::guest_export_needs_post_return(resolve, func) { + ( + format!("{PINNER} := &{SYNC_EXPORT_PINNER}"), + format!( + " + +//go:wasmexport cabi_post_{export_name} +func wasm_export_post_return_{name}(result {results}) {{ + syncExportPinner.Unpin() +}} +" + ), + "", + "", + ) + } else { + (String::new(), String::new(), "", "") + }; + + if self.opts.generate_stubs { + let (camel, has_self) = func_declaration(resolve, func); + + let mut imports = BTreeSet::new(); + let params = + self.func_params(resolve, func, interface, false, &mut imports, has_self, "_"); + let results = self.func_results(resolve, func, interface, false, &mut imports); + + self.export_interfaces + .entry(interface_name(resolve, interface)) + .or_default() + .extend(InterfaceData { + code: format!( + r#" +func {camel}({params}) {results} {{ + panic("not implemented") +}} +"# + ), + imports, + ..InterfaceData::default() + }); + } + + format!( + " +//go:wasmexport {prefix}{export_name} +func wasm_export_{name}({params}) {results} {{ + {start} + {pinner} + {code} + {end} +}}{other} +" + ) + } + + #[expect(clippy::too_many_arguments)] + fn func_params( + &mut self, + resolve: &Resolve, + func: &Function, + interface: Option<&WorldKey>, + in_import: bool, + imports: &mut BTreeSet, + has_self: bool, + prefix: &str, + ) -> String { + func.params + .iter() + .skip(if has_self { 1 } else { 0 }) + .map(|(name, ty)| { + let name = name.to_lower_camel_case(); + let ty = self.type_name(resolve, *ty, interface, in_import, imports); + format!("{prefix}{name} {ty}") + }) + .collect::>() + .join(", ") + } + + fn func_results( + &mut self, + resolve: &Resolve, + func: &Function, + interface: Option<&WorldKey>, + in_import: bool, + imports: &mut BTreeSet, + ) -> String { + if let Some(ty) = &func.result { + if let Type::Id(id) = ty + && let TypeDefKind::Tuple(tuple) = &resolve.types[*id].kind + { + let types = tuple + .types + .iter() + .map(|ty| self.type_name(resolve, *ty, interface, in_import, imports)) + .collect::>() + .join(", "); + format!("({types})") + } else { + self.type_name(resolve, *ty, interface, in_import, imports) + } + } else { + String::new() + } + } + + fn visit_futures_and_streams( + &mut self, + in_import: bool, + resolve: &Resolve, + func: &Function, + interface: Option<&WorldKey>, + ) { + for (index, ty) in func + .find_futures_and_streams(resolve) + .into_iter() + .enumerate() + { + self.need_async = true; + + let payload_type = match &resolve.types[ty].kind { + TypeDefKind::Future(ty) => { + self.need_future = true; + ty + } + TypeDefKind::Stream(ty) => { + self.need_stream = true; + ty + } + _ => unreachable!(), + }; + + let exported = payload_type + .map(|ty| self.has_exported_resource(resolve, ty)) + .unwrap_or(false); + + if let hash_map::Entry::Vacant(e) = self.futures_and_streams.entry((ty, exported)) { + e.insert(interface.cloned()); + + let data = self.future_or_stream( + resolve, + ty, + index, + in_import, + in_import || !exported, + interface, + &func.name, + ); + + if in_import || !exported { + &mut self.interfaces + } else { + &mut self.export_interfaces + } + .entry(interface_name(resolve, interface)) + .or_default() + .extend(data); + } + } + } + + fn has_exported_resource(&self, resolve: &Resolve, ty: Type) -> bool { + any(resolve, ty, &|ty| { + if let Type::Id(id) = ty + && let TypeDefKind::Resource = &resolve.types[id].kind + && let Direction::Export = self.resources.get(&id).unwrap() + { + true + } else { + false + } + }) + } +} + +struct FunctionGenerator<'a> { + generator: &'a mut Go, + name: Option<&'a str>, + interface: Option<&'a WorldKey>, + interface_for_types: Option<&'a WorldKey>, + function_to_call: &'a str, + param_names: Vec, + unpin_params: bool, + in_import: bool, + locals: Ns, + src: String, + block_storage: Vec, + blocks: Vec<(String, Vec)>, + need_unsafe: bool, + need_pinner: bool, + need_math: bool, + return_area_size: ArchitectureSize, + return_area_align: Alignment, + imports: BTreeSet, +} + +impl<'a> FunctionGenerator<'a> { + #[expect(clippy::too_many_arguments)] + fn new( + generator: &'a mut Go, + name: Option<&'a str>, + interface: Option<&'a WorldKey>, + interface_for_types: Option<&'a WorldKey>, + function_to_call: &'a str, + param_names: Vec, + unpin_params: bool, + in_import: bool, + ) -> Self { + let mut locals = Ns::default(); + for name in ¶m_names { + locals.insert(name).unwrap(); + } + + Self { + generator, + name, + interface, + interface_for_types, + function_to_call, + param_names, + unpin_params, + in_import, + locals, + src: String::new(), + block_storage: Vec::new(), + blocks: Vec::new(), + need_unsafe: false, + need_pinner: false, + need_math: false, + return_area_size: ArchitectureSize::default(), + return_area_align: Alignment::default(), + imports: BTreeSet::new(), + } + } + + fn type_name(&mut self, resolve: &Resolve, ty: Type) -> String { + self.generator.type_name( + resolve, + ty, + self.interface_for_types, + self.in_import, + &mut self.imports, + ) + } + + fn package_for_owner( + &mut self, + resolve: &Resolve, + owner: Option<&WorldKey>, + ty: TypeId, + ) -> String { + self.generator.package_for_owner( + resolve, + owner, + ty, + self.interface_for_types, + self.in_import, + &mut self.imports, + ) + } +} + +impl Bindgen for FunctionGenerator<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.generator.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + self.blocks.push((src, mem::take(operands))); + } + + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { + if self.in_import { + self.return_area_size = self.return_area_size.max(size); + self.return_area_align = self.return_area_align.max(align); + + if !self.return_area_size.is_empty() { + self.need_pinner = true; + self.imports.insert("wit_runtime".into()); + } + + IMPORT_RETURN_AREA.into() + } else { + self.generator.return_area_size = self.generator.return_area_size.max(size); + self.generator.return_area_align = self.generator.return_area_align.max(align); + EXPORT_RETURN_AREA.into() + } + } + + fn is_list_canonical(&self, _: &Resolve, ty: &Type) -> bool { + matches!( + ty, + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::F32 + | Type::F64 + ) + } + + fn emit( + &mut self, + resolve: &Resolve, + instruction: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let store = |me: &mut Self, src, pointer, offset: &ArchitectureSize, ty| { + me.need_unsafe = true; + let offset = offset.format(POINTER_SIZE_EXPRESSION); + uwriteln!( + me.src, + "*(*{ty})(unsafe.Add(unsafe.Pointer({pointer}), {offset})) = {src}" + ); + }; + let load = |me: &mut Self, + results: &mut Vec, + pointer, + offset: &ArchitectureSize, + ty, + cast: &dyn Fn(String) -> String| { + me.need_unsafe = true; + let offset = offset.format(POINTER_SIZE_EXPRESSION); + results.push(cast(format!( + "*(*{ty})(unsafe.Add(unsafe.Pointer({pointer}), {offset}))" + ))); + }; + + match instruction { + Instruction::GetArg { nth } => results.push(self.param_names[*nth].clone()), + Instruction::StringLower { .. } => { + self.need_pinner = true; + self.need_unsafe = true; + let string = &operands[0]; + let utf8 = self.locals.tmp("utf8"); + uwriteln!( + self.src, + "{utf8} := unsafe.Pointer(unsafe.StringData({string}))\n\ + {PINNER}.Pin({utf8})" + ); + results.push(format!("uintptr({utf8})")); + results.push(format!("uint32(len({string}))")); + } + Instruction::StringLift { .. } => { + self.need_unsafe = true; + let pointer = &operands[0]; + let length = &operands[1]; + let value = self.locals.tmp("value"); + uwriteln!( + self.src, + "{value} := unsafe.String((*uint8)(unsafe.Pointer({pointer})), {length})" + ); + results.push(value) + } + Instruction::ListCanonLower { .. } => { + self.need_pinner = true; + self.need_unsafe = true; + let slice = &operands[0]; + let data = self.locals.tmp("data"); + uwriteln!( + self.src, + "{data} := unsafe.Pointer(unsafe.SliceData({slice}))\n\ + {PINNER}.Pin({data})" + ); + results.push(format!("uintptr({data})")); + results.push(format!("uint32(len({slice}))")); + } + Instruction::ListCanonLift { element, .. } => { + self.need_unsafe = true; + let pointer = &operands[0]; + let length = &operands[1]; + let ty = self.type_name(resolve, **element); + let value = self.locals.tmp("value"); + uwriteln!( + self.src, + "{value} := unsafe.Slice((*{ty})(unsafe.Pointer({pointer})), {length})" + ); + results.push(value) + } + Instruction::ListLower { element, .. } => { + self.need_unsafe = true; + self.need_pinner = true; + self.imports.insert("wit_runtime".into()); + let (body, _) = self.blocks.pop().unwrap(); + let value = &operands[0]; + let slice = self.locals.tmp("slice"); + let result = self.locals.tmp("result"); + let length = self.locals.tmp("length"); + let size = self + .generator + .sizes + .size(element) + .format(POINTER_SIZE_EXPRESSION); + let align = self + .generator + .sizes + .align(element) + .format(POINTER_SIZE_EXPRESSION); + uwriteln!( + self.src, + "{slice} := {value} +{length} := uint32(len({slice})) +{result} := wit_runtime.Allocate({PINNER}, uintptr({length} * {size}), {align}) +for index, {ITER_ELEMENT} := range {slice} {{ + {ITER_BASE_POINTER} := unsafe.Add({result}, index * {size}) + {body} +}} +" + ); + results.push(format!("uintptr({result})")); + results.push(length); + } + Instruction::ListLift { element, .. } => { + self.need_unsafe = true; + let (body, body_results) = self.blocks.pop().unwrap(); + let value = &operands[0]; + let length = &operands[1]; + let result = self.locals.tmp("result"); + let size = self + .generator + .sizes + .size(element) + .format(POINTER_SIZE_EXPRESSION); + let element_type = self.type_name(resolve, **element); + let body_result = &body_results[0]; + uwriteln!( + self.src, + "{result} := make([]{element_type}, 0, {length}) +for index := 0; index < int({length}); index++ {{ + {ITER_BASE_POINTER} := unsafe.Add(unsafe.Pointer({value}), index * {size}) + {body} + {result} = append({result}, {body_result}) +}} +" + ); + results.push(result); + } + Instruction::CallInterface { func, .. } => { + if self.unpin_params { + self.imports.insert("wit_runtime".into()); + uwriteln!(self.src, "wit_runtime.Unpin()"); + } + + let name = func.item_name().to_upper_camel_case(); + let package = format!("export_{}", interface_name(resolve, self.interface)); + + let call = match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + let args = operands.join(", "); + let call = format!("{package}.{name}({args})"); + self.imports.insert(package); + call + } + FunctionKind::Constructor(ty) => { + let args = operands.join(", "); + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + let call = format!("{package}.Make{ty}({args})"); + self.imports.insert(package); + call + } + FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { + let target = &operands[0]; + let args = operands[1..].join(", "); + format!("({target}).{name}({args})") + } + FunctionKind::Static(ty) | FunctionKind::AsyncStatic(ty) => { + let args = operands.join(", "); + let ty = self.type_name(resolve, Type::Id(*ty)); + format!("{ty}{name}({args})") + } + }; + + if let Some(ty) = func.result { + let result = self.locals.tmp("result"); + if let Type::Id(ty) = ty + && let TypeDefKind::Tuple(tuple) = &resolve.types[ty].kind + { + let count = tuple.types.len(); + self.generator.tuples.insert(count); + self.imports.insert("wit_types".into()); + + let results = (0..count) + .map(|_| self.locals.tmp("result")) + .collect::>() + .join(", "); + + let types = tuple + .types + .iter() + .map(|&ty| self.type_name(resolve, ty)) + .collect::>() + .join(", "); + + uwriteln!( + self.src, + "{results} := {call} +{result} := wit_types.Tuple{count}[{types}]{{{results}}}" + ); + } else { + uwriteln!(self.src, "{result} := {call}"); + } + results.push(result); + } else { + uwriteln!(self.src, "{call}"); + } + } + Instruction::Return { func, .. } => { + if let Some(ty) = func.result { + let result = &operands[0]; + if self.in_import + && let Type::Id(ty) = ty + && let TypeDefKind::Tuple(tuple) = &resolve.types[ty].kind + { + let count = tuple.types.len(); + + let results = (0..count) + .map(|index| format!("({result}).F{index}")) + .collect::>() + .join(", "); + + uwriteln!(self.src, "return {results}"); + } else { + uwriteln!(self.src, "return {result}"); + } + } + } + Instruction::AsyncTaskReturn { .. } => { + let name = self.name.unwrap(); + let args = operands.join(", "); + uwriteln!(self.src, "wasm_export_task_return_{name}({args})"); + } + Instruction::LengthStore { offset } => store( + self, + &format!("uint32({})", operands[0]), + &operands[1], + offset, + "uint32", + ), + Instruction::PointerStore { offset } => store( + self, + &format!("uint32(uintptr({}))", operands[0]), + &operands[1], + offset, + "uint32", + ), + Instruction::I32Store8 { offset } => store( + self, + &format!("int8({})", operands[0]), + &operands[1], + offset, + "int8", + ), + Instruction::I32Store16 { offset } => store( + self, + &format!("int16({})", operands[0]), + &operands[1], + offset, + "int16", + ), + Instruction::I32Store { offset } => { + store(self, &operands[0], &operands[1], offset, "int32") + } + Instruction::I64Store { offset } => { + store(self, &operands[0], &operands[1], offset, "int64") + } + Instruction::F32Store { offset } => { + store(self, &operands[0], &operands[1], offset, "float32") + } + Instruction::F64Store { offset } => { + store(self, &operands[0], &operands[1], offset, "float64") + } + Instruction::LengthLoad { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| v) + } + Instruction::PointerLoad { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| { + format!("uintptr({v})") + }) + } + Instruction::I32Load8U { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| { + format!("uint8({v})") + }) + } + Instruction::I32Load8S { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| { + format!("int8({v})") + }) + } + Instruction::I32Load16U { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| { + format!("uint16({v})") + }) + } + Instruction::I32Load16S { offset } => { + load(self, results, &operands[0], offset, "uint32", &|v| { + format!("int16({v})") + }) + } + Instruction::I32Load { offset } => { + load(self, results, &operands[0], offset, "int32", &|v| v) + } + Instruction::I64Load { offset } => { + load(self, results, &operands[0], offset, "int64", &|v| v) + } + Instruction::F32Load { offset } => { + load(self, results, &operands[0], offset, "float32", &|v| v) + } + Instruction::F64Load { offset } => { + load(self, results, &operands[0], offset, "float64", &|v| v) + } + Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), + Instruction::U8FromI32 => results.push(format!("uint8({})", operands[0])), + Instruction::S8FromI32 => results.push(format!("int8({})", operands[0])), + Instruction::U16FromI32 => results.push(format!("uint16({})", operands[0])), + Instruction::S16FromI32 => results.push(format!("int16({})", operands[0])), + Instruction::U32FromI32 => results.push(format!("uint32({})", operands[0])), + Instruction::S32FromI32 | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()) + } + Instruction::U64FromI64 => results.push(format!("uint64({})", operands[0])), + Instruction::I32FromBool => { + let value = &operands[0]; + let result = self.locals.tmp("result"); + uwriteln!( + self.src, + "var {result} int32 +if {value} {{ + {result} = 1 +}} else {{ + {result} = 0 +}}" + ); + results.push(result); + } + Instruction::I32FromU8 + | Instruction::I32FromS8 + | Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU32 => { + results.push(format!("int32({})", operands[0])); + } + Instruction::I32FromS32 | Instruction::I64FromS64 => { + results.push(operands.pop().unwrap()) + } + Instruction::I64FromU64 => results.push(format!("int64({})", operands[0])), + Instruction::CoreF32FromF32 + | Instruction::CoreF64FromF64 + | Instruction::F32FromCoreF32 + | Instruction::F64FromCoreF64 => results.push(operands.pop().unwrap()), + Instruction::CharFromI32 => results.push(format!("rune({})", operands[0])), + Instruction::I32FromChar => results.push(format!("int32({})", operands[0])), + Instruction::TupleLower { tuple, .. } => { + let op = &operands[0]; + for index in 0..tuple.types.len() { + results.push(format!("({op}).F{index}")); + } + } + Instruction::TupleLift { tuple, .. } => { + let count = tuple.types.len(); + self.generator.tuples.insert(count); + let types = tuple + .types + .iter() + .map(|&ty| self.type_name(resolve, ty)) + .collect::>() + .join(", "); + let fields = operands.join(", "); + self.imports.insert("wit_types".into()); + results.push(format!("wit_types.Tuple{count}[{types}]{{{fields}}}")); + } + Instruction::FlagsLower { .. } => { + let value = operands.pop().unwrap(); + results.push(format!("int32({value})")) + } + Instruction::FlagsLift { flags, .. } => { + let value = operands.pop().unwrap(); + let repr = flags_repr(flags); + results.push(format!("{repr}({value})")) + } + Instruction::RecordLower { record, .. } => { + let op = &operands[0]; + for field in &record.fields { + let field = field.name.to_upper_camel_case(); + results.push(format!("({op}).{field}")); + } + } + Instruction::RecordLift { ty, .. } => { + let name = self.type_name(resolve, Type::Id(*ty)); + let fields = operands.join(", "); + results.push(format!("{name}{{{fields}}}")); + } + Instruction::OptionLower { + results: result_types, + .. + } => { + self.generator.need_option = true; + self.imports.insert("wit_types".into()); + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, none_results) = self.blocks.pop().unwrap(); + let value = &operands[0]; + + let result_names = (0..result_types.len()) + .map(|_| self.locals.tmp("option")) + .collect::>(); + + let declarations = result_types + .iter() + .zip(&result_names) + .map(|(ty, name)| { + let ty = wasm_type(*ty); + format!("var {name} {ty}") + }) + .collect::>() + .join("\n"); + + let some_result_assignments = some_results + .iter() + .zip(&result_names) + .map(|(result, name)| format!("{name} = {result}")) + .collect::>() + .join("\n"); + + let none_result_assignments = none_results + .iter() + .zip(&result_names) + .map(|(result, name)| format!("{name} = {result}")) + .collect::>() + .join("\n"); + + results.extend(result_names); + + uwriteln!( + self.src, + r#"{declarations} +switch {value}.Tag() {{ +case wit_types.OptionNone: + {none} + {none_result_assignments} +case wit_types.OptionSome: + {VARIANT_PAYLOAD_NAME} := {value}.Some() + {some} + {some_result_assignments} +default: + panic("unreachable") +}}"# + ); + } + Instruction::OptionLift { ty, payload } => { + self.generator.need_option = true; + self.imports.insert("wit_types".into()); + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, none_results) = self.blocks.pop().unwrap(); + assert!(none_results.is_empty()); + assert!(some_results.len() == 1); + let some_result = &some_results[0]; + let ty = self.type_name(resolve, Type::Id(*ty)); + let some_type = self.type_name(resolve, **payload); + let result = self.locals.tmp("option"); + let tag = &operands[0]; + uwriteln!( + self.src, + r#"var {result} {ty} +switch {tag} {{ +case 0: + {none} + {result} = wit_types.None[{some_type}]() +case 1: + {some} + {result} = wit_types.Some[{some_type}]({some_result}) +default: + panic("unreachable") +}}"# + ); + results.push(result); + } + Instruction::ResultLower { + result, + results: result_types, + .. + } => { + self.generator.need_result = true; + self.imports.insert("wit_types".into()); + let (err, err_results) = self.blocks.pop().unwrap(); + let (ok, ok_results) = self.blocks.pop().unwrap(); + let value = &operands[0]; + + let result_names = (0..result_types.len()) + .map(|_| self.locals.tmp("option")) + .collect::>(); + + let declarations = result_types + .iter() + .zip(&result_names) + .map(|(ty, name)| { + let ty = wasm_type(*ty); + format!("var {name} {ty}") + }) + .collect::>() + .join("\n"); + + let ok_result_assignments = ok_results + .iter() + .zip(&result_names) + .map(|(result, name)| format!("{name} = {result}")) + .collect::>() + .join("\n"); + + let err_result_assignments = err_results + .iter() + .zip(&result_names) + .map(|(result, name)| format!("{name} = {result}")) + .collect::>() + .join("\n"); + + results.extend(result_names); + + let ok_set_payload = if result.ok.is_some() { + format!("{VARIANT_PAYLOAD_NAME} := {value}.Ok()") + } else { + self.generator.need_unit = true; + String::new() + }; + + let err_set_payload = if result.err.is_some() { + format!("{VARIANT_PAYLOAD_NAME} := {value}.Err()") + } else { + self.generator.need_unit = true; + String::new() + }; + + uwriteln!( + self.src, + r#"{declarations} +switch {value}.Tag() {{ +case wit_types.ResultOk: + {ok_set_payload} + {ok} + {ok_result_assignments} +case wit_types.ResultErr: + {err_set_payload} + {err} + {err_result_assignments} +default: + panic("unreachable") +}}"# + ); + } + Instruction::ResultLift { ty, result, .. } => { + self.generator.need_result = true; + self.imports.insert("wit_types".into()); + let (err, err_results) = self.blocks.pop().unwrap(); + let (ok, ok_results) = self.blocks.pop().unwrap(); + assert_eq!(ok_results.is_empty(), result.ok.is_none()); + assert_eq!(err_results.is_empty(), result.err.is_none()); + let ok_result = if result.ok.is_some() { + &ok_results[0] + } else { + self.generator.need_unit = true; + "wit_types.Unit{}" + }; + let err_result = if result.err.is_some() { + &err_results[0] + } else { + self.generator.need_unit = true; + "wit_types.Unit{}" + }; + let ty = self.type_name(resolve, Type::Id(*ty)); + let ok_type = result + .ok + .map(|ty| self.type_name(resolve, ty)) + .unwrap_or_else(|| { + self.generator.need_unit = true; + "wit_types.Unit".into() + }); + let err_type = result + .err + .map(|ty| self.type_name(resolve, ty)) + .unwrap_or_else(|| { + self.generator.need_unit = true; + "wit_types.Unit".into() + }); + let result = self.locals.tmp("result"); + let tag = &operands[0]; + uwriteln!( + self.src, + r#"var {result} {ty} +switch {tag} {{ +case 0: + {ok} + {result} = wit_types.Ok[{ok_type}, {err_type}]({ok_result}) +case 1: + {err} + {result} = wit_types.Err[{ok_type}, {err_type}]({err_result}) +default: + panic("unreachable") +}}"# + ); + results.push(result); + } + Instruction::EnumLower { .. } => results.push(format!("int32({})", operands[0])), + Instruction::EnumLift { enum_, .. } => { + results.push(format!("{}({})", int_repr(enum_.tag()), operands[0])) + } + Instruction::VariantLower { + ty, + variant, + results: result_types, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let ty = self.type_name(resolve, Type::Id(*ty)); + let value = &operands[0]; + + let result_names = (0..result_types.len()) + .map(|_| self.locals.tmp("variant")) + .collect::>(); + + let declarations = result_types + .iter() + .zip(&result_names) + .map(|(ty, name)| { + let ty = wasm_type(*ty); + format!("var {name} {ty}") + }) + .collect::>() + .join("\n"); + + let cases = variant + .cases + .iter() + .zip(blocks) + .map(|(case, (block, block_results))| { + let assignments = result_names + .iter() + .zip(&block_results) + .map(|(name, result)| format!("{name} = {result}")) + .collect::>() + .join("\n"); + + let name = case.name.to_upper_camel_case(); + + let set_payload = if case.ty.is_some() { + format!("{VARIANT_PAYLOAD_NAME} := {value}.{name}()") + } else { + String::new() + }; + + format!( + "case {ty}{name}: + {set_payload} + {block} + {assignments} +" + ) + }) + .collect::>() + .join("\n"); + + results.extend(result_names); + + uwriteln!( + self.src, + r#"{declarations} +switch {value}.Tag() {{ +{cases} +default: + panic("unreachable") +}}"# + ); + } + Instruction::VariantLift { ty, variant, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let ty = self.type_name(resolve, Type::Id(*ty)); + let result = self.locals.tmp("variant"); + let tag = &operands[0]; + + let (package, name) = if let Some(index) = ty.find('.') { + (&ty[..index + 1], &ty[index + 1..]) + } else { + ("", ty.as_str()) + }; + + let cases = variant + .cases + .iter() + .zip(blocks) + .enumerate() + .map(|(index, (case, (block, block_results)))| { + assert_eq!(block_results.is_empty(), case.ty.is_none()); + let payload = if case.ty.is_some() { + &block_results[0] + } else { + "" + }; + let case = case.name.to_upper_camel_case(); + format!( + "case {index}: + {block} + {result} = {package}Make{name}{case}({payload}) +" + ) + }) + .collect::>() + .join("\n"); + + uwriteln!( + self.src, + r#"var {result} {ty} +switch {tag} {{ +{cases} +default: + panic("unreachable") +}}"# + ); + results.push(result); + } + Instruction::VariantPayloadName => results.push(VARIANT_PAYLOAD_NAME.into()), + Instruction::IterElem { .. } => results.push(ITER_ELEMENT.into()), + Instruction::IterBasePointer => results.push(ITER_BASE_POINTER.into()), + Instruction::I32Const { val } => results.push(format!("int32({val})")), + Instruction::ConstZero { tys } => { + results.extend(iter::repeat_with(|| "0".into()).take(tys.len())); + } + Instruction::Bitcasts { casts } => { + results.extend( + casts + .iter() + .zip(operands) + .map(|(which, op)| cast(op, which, &mut self.need_math)), + ); + } + Instruction::FutureLower { .. } + | Instruction::StreamLower { .. } + | Instruction::HandleLower { + handle: Handle::Own(_), + .. + } => results.push(format!("({}).TakeHandle()", operands[0])), + Instruction::HandleLower { + handle: Handle::Borrow(_), + .. + } => results.push(format!("({}).Handle()", operands[0])), + Instruction::HandleLift { handle, .. } => { + let (which, resource) = match handle { + Handle::Borrow(resource) => ("Borrow", resource), + Handle::Own(resource) => ("Own", resource), + }; + let handle = &operands[0]; + let ty = self.type_name(resolve, Type::Id(*resource)); + results.push(format!("{ty}From{which}Handle(int32(uintptr({handle})))")) + } + Instruction::CallWasm { sig, .. } => { + let assignment = match &sig.results[..] { + [] => String::new(), + [_] => { + let result = self.locals.tmp("result"); + let assignment = format!("{result} := "); + results.push(result); + assignment + } + _ => unreachable!(), + }; + let name = &self.function_to_call; + let params = operands.join(", "); + uwriteln!(self.src, "{assignment}{name}({params})") + } + Instruction::Flush { amt } => { + for op in operands.iter().take(*amt) { + let result = self.locals.tmp("result"); + uwriteln!(self.src, "{result} := {op};"); + results.push(result); + } + } + Instruction::FutureLift { ty, .. } => { + let exported = self.generator.has_exported_resource(resolve, Type::Id(*ty)); + let owner = self + .generator + .futures_and_streams + .get(&(*ty, exported)) + .unwrap() + .clone(); + let package = self.package_for_owner(resolve, owner.as_ref(), *ty); + let TypeDefKind::Future(payload_ty) = &resolve.types[*ty].kind else { + unreachable!() + }; + let camel = if let Some(ty) = payload_ty { + self.generator + .mangle_name(resolve, *ty, owner.as_ref()) + .to_upper_camel_case() + } else { + "Unit".into() + }; + let handle = &operands[0]; + results.push(format!("{package}LiftFuture{camel}({handle})")); + } + Instruction::StreamLift { ty, .. } => { + let exported = self.generator.has_exported_resource(resolve, Type::Id(*ty)); + let owner = self + .generator + .futures_and_streams + .get(&(*ty, exported)) + .unwrap() + .clone(); + let package = self.package_for_owner(resolve, owner.as_ref(), *ty); + let TypeDefKind::Stream(payload_ty) = &resolve.types[*ty].kind else { + unreachable!() + }; + let camel = if let Some(ty) = payload_ty { + self.generator + .mangle_name(resolve, *ty, owner.as_ref()) + .to_upper_camel_case() + } else { + "Unit".into() + }; + let handle = &operands[0]; + results.push(format!("{package}LiftStream{camel}({handle})")); + } + Instruction::GuestDeallocate { .. } => { + // Nothing to do here; should be handled when calling `pinner.Unpin()` + } + _ => unimplemented!("{instruction:?}"), + } + } +} + +struct InterfaceGenerator<'a> { + generator: &'a mut Go, + resolve: &'a Resolve, + interface: Option<(InterfaceId, &'a WorldKey)>, + in_import: bool, + src: String, + imports: BTreeSet, + need_unsafe: bool, + need_runtime: bool, +} + +impl<'a> InterfaceGenerator<'a> { + fn new( + generator: &'a mut Go, + resolve: &'a Resolve, + interface: Option<(InterfaceId, &'a WorldKey)>, + in_import: bool, + ) -> Self { + Self { + generator, + resolve, + interface, + in_import, + src: String::new(), + imports: BTreeSet::new(), + need_unsafe: false, + need_runtime: false, + } + } + + fn type_name(&mut self, resolve: &Resolve, ty: Type) -> String { + self.generator.type_name( + resolve, + ty, + self.interface.map(|(_, key)| key), + self.in_import || !self.generator.has_exported_resource(resolve, ty), + &mut self.imports, + ) + } +} + +impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { + fn resolve(&self) -> &'a Resolve { + self.resolve + } + + fn type_record(&mut self, _: TypeId, name: &str, record: &Record, docs: &Docs) { + let name = name.to_upper_camel_case(); + + let fields = record + .fields + .iter() + .map(|field| { + let ty = self.type_name(self.resolve, field.ty); + let docs = format_docs(&field.docs); + let field = field.name.to_upper_camel_case(); + format!("{docs}{field} {ty}") + }) + .collect::>() + .join("\n"); + + let docs = format_docs(docs); + + uwriteln!( + self.src, + " +{docs}type {name} struct {{ + {fields} +}}" + ) + } + + fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) { + self.generator.resources.insert( + id, + if self.in_import { + Direction::Import + } else { + Direction::Export + }, + ); + + let camel = name.to_upper_camel_case(); + let module = self + .interface + .map(|(_, key)| self.resolve.name_world_key(key)) + .unwrap_or_else(|| "$root".into()); + + if self.in_import { + let docs = format_docs(docs); + uwriteln!( + self.src, + r#" +//go:wasmimport {module} [resource-drop]{name} +func resourceDrop{camel}(handle int32) + +{docs}type {camel} struct {{ + handle int32 +}} + +func (self *{camel}) TakeHandle() int32 {{ + handle := self.handle + if handle == 0 {{ + panic("null handle") + }} else {{ + self.handle = 0 + return handle + }} +}} + +func (self *{camel}) Handle() int32 {{ + handle := self.handle + if handle == 0 {{ + panic("null handle") + }} else {{ + return handle + }} +}} + +func (self *{camel}) Drop() {{ + handle := self.handle + if self.handle != 0 {{ + self.handle = 0 + resourceDrop{camel}(handle) + }} +}} + +func {camel}FromOwnHandle(handle int32) *{camel} {{ + return &{camel}{{handle}} +}} + +func {camel}FromBorrowHandle(handle int32) *{camel} {{ + return &{camel}{{handle}} +}} +"# + ); + } else { + self.need_unsafe = true; + uwriteln!( + self.src, + r#" +//go:wasmimport [export]{module} [resource-new]{name} +func resourceNew{camel}(pointer unsafe.Pointer) int32 + +//go:wasmimport [export]{module} [resource-rep]{name} +func resourceRep{camel}(handle int32) unsafe.Pointer + +//go:wasmimport [export]{module} [resource-drop]{name} +func resourceDrop{camel}(handle int32) + +//go:wasmexport {module}#[dtor]{name} +func resourceDtor{camel}(rep int32) {{ + val := (*{camel})(unsafe.Pointer(uintptr(rep))) + val.handle = 0 + val.pinner.Unpin() + val.OnDrop() +}} + +func (self *{camel}) TakeHandle() int32 {{ + self.pinner.Pin(self) + self.handle = resourceNew{camel}(unsafe.Pointer(self)) + return self.handle +}} + +func (self *{camel}) Drop() {{ + handle := self.handle + if self.handle != 0 {{ + self.handle = 0 + resourceDrop{camel}(handle) + self.pinner.Unpin() + self.OnDrop() + }} +}} + +func {camel}FromOwnHandle(handle int32) *{camel} {{ + return (*{camel})(unsafe.Pointer(resourceRep{camel}(handle))) +}} + +func {camel}FromBorrowHandle(rep int32) *{camel} {{ + return (*{camel})(unsafe.Pointer(uintptr(rep))) +}} +"# + ); + + if self.generator.opts.generate_stubs { + self.need_runtime = true; + uwriteln!( + self.src, + r#" +type {camel} struct {{ + pinner runtime.Pinner + handle int32 +}} + +func (self *{camel}) OnDrop() {{}} +"# + ); + } + } + } + + fn type_flags(&mut self, _: TypeId, name: &str, flags: &Flags, docs: &Docs) { + let repr = flags_repr(flags); + + let name = name.to_upper_camel_case(); + + let constants = flags + .flags + .iter() + .enumerate() + .map(|(i, flag)| { + let docs = format_docs(&flag.docs); + let flag = flag.name.to_upper_camel_case(); + format!("{docs}{name}{flag} {repr} = 1 << {i}") + }) + .collect::>() + .join("\n"); + + let docs = format_docs(docs); + + uwriteln!( + self.src, + " +const ( +{constants} +) + +{docs}type {name} = {repr}" + ) + } + + fn type_tuple(&mut self, _: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { + self.imports.insert("wit_types".into()); + let count = tuple.types.len(); + self.generator.tuples.insert(count); + let name = name.to_upper_camel_case(); + let docs = format_docs(docs); + let types = tuple + .types + .iter() + .map(|ty| self.type_name(self.resolve, *ty)) + .collect::>() + .join(", "); + + uwriteln!( + self.src, + "{docs}type {name} = wit_types.Tuple{count}[{types}]" + ); + } + + fn type_variant(&mut self, _: TypeId, name: &str, variant: &Variant, docs: &Docs) { + let repr = int_repr(variant.tag()); + + let name = name.to_upper_camel_case(); + + let constants = variant + .cases + .iter() + .enumerate() + .map(|(i, case)| { + let docs = format_docs(&case.docs); + let case = case.name.to_upper_camel_case(); + format!("{docs}{name}{case} {repr} = {i}") + }) + .collect::>() + .join("\n"); + + let getters = variant + .cases + .iter() + .filter_map(|case| { + case.ty.map(|ty| { + let case = case.name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, ty); + format!( + r#"func (self {name}) {case}() {ty} {{ + if self.tag != {name}{case} {{ + panic("tag mismatch") + }} + return self.value.({ty}) +}} +"# + ) + }) + }) + .collect::>() + .concat(); + + let constructors = variant + .cases + .iter() + .map(|case| { + let (param, value) = if let Some(ty) = case.ty { + let ty = self.type_name(self.resolve, ty); + (format!("value {ty}"), "value") + } else { + (String::new(), "nil") + }; + let case = case.name.to_upper_camel_case(); + format!( + r#"func Make{name}{case}({param}) {name} {{ + return {name}{{{name}{case}, {value}}} +}} +"# + ) + }) + .collect::>() + .concat(); + + let docs = format_docs(docs); + + uwriteln!( + self.src, + " +const ( +{constants} +) + +{docs}type {name} struct {{ + tag {repr} + value any +}} + +func (self {name}) Tag() {repr} {{ + return self.tag +}} + +{getters} +{constructors} +" + ) + } + + fn type_option(&mut self, _: TypeId, name: &str, payload: &Type, docs: &Docs) { + self.generator.need_option = true; + self.imports.insert("wit_types".into()); + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, *payload); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = wit_types.Option[{ty}]"); + } + + fn type_result(&mut self, _: TypeId, name: &str, result: &Result_, docs: &Docs) { + self.generator.need_result = true; + self.imports.insert("wit_types".into()); + let name = name.to_upper_camel_case(); + let ok_type = result + .ok + .map(|ty| self.type_name(self.resolve, ty)) + .unwrap_or_else(|| { + self.generator.need_unit = true; + "wit_types.Unit".into() + }); + let err_type = result + .err + .map(|ty| self.type_name(self.resolve, ty)) + .unwrap_or_else(|| { + self.generator.need_unit = true; + "wit_types.Unit".into() + }); + let docs = format_docs(docs); + uwriteln!( + self.src, + "{docs}type {name} = wit_types.Result[{ok_type}, {err_type}]" + ); + } + + fn type_enum(&mut self, _: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let repr = int_repr(enum_.tag()); + + let name = name.to_upper_camel_case(); + + let constants = enum_ + .cases + .iter() + .enumerate() + .map(|(i, case)| { + let docs = format_docs(&case.docs); + let case = case.name.to_upper_camel_case(); + format!("{docs}{name}{case} {repr} = {i}") + }) + .collect::>() + .join("\n"); + + let docs = format_docs(docs); + + uwriteln!( + self.src, + " +const ( + {constants} +) +{docs}type {name} = {repr}" + ) + } + + fn type_alias(&mut self, _: TypeId, name: &str, ty: &Type, docs: &Docs) { + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, *ty); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = {ty}"); + } + + fn type_list(&mut self, _: TypeId, name: &str, ty: &Type, docs: &Docs) { + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, *ty); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = []{ty}"); + } + + fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + _ = (id, name, ty, docs); + todo!() + } + + fn type_future(&mut self, id: TypeId, name: &str, _: &Option, docs: &Docs) { + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, Type::Id(id)); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = {ty}"); + } + + fn type_stream(&mut self, id: TypeId, name: &str, _: &Option, docs: &Docs) { + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, Type::Id(id)); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = {ty}"); + } +} + +fn interface_name(resolve: &Resolve, interface: Option<&WorldKey>) -> String { + match interface { + Some(WorldKey::Name(name)) => name.to_snake_case(), + Some(WorldKey::Interface(id)) => { + let interface = &resolve.interfaces[*id]; + let package = &resolve.packages[interface.package.unwrap()]; + let package_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { + p.name.namespace == package.name.namespace + && p.name.name == package.name.name + && p.name.version != package.name.version + }); + let version = if package_has_multiple_versions { + if let Some(version) = &package.name.version { + format!("{}_", version.to_string().replace(['.', '-', '+'], "_")) + } else { + String::new() + } + } else { + String::new() + }; + let namespace = package.name.namespace.to_snake_case(); + let package = package.name.name.to_snake_case(); + let interface = interface.name.as_ref().unwrap().to_snake_case(); + format!("{namespace}_{package}_{version}{interface}") + } + None => "wit_world".into(), + } +} + +fn func_name(resolve: &Resolve, interface: Option<&WorldKey>, func: &Function) -> String { + let prefix = interface_name(resolve, interface); + let name = func.name.to_snake_case().replace('.', "_"); + + format!("{prefix}_{name}") +} + +fn wasm_type(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "int32", + WasmType::I64 => "int64", + WasmType::F32 => "float32", + WasmType::F64 => "float64", + WasmType::Pointer => "uintptr", + WasmType::PointerOrI64 => "int64", + WasmType::Length => "uint32", + } +} + +fn format_docs(docs: &Docs) -> String { + docs.contents + .as_ref() + .map(|v| { + v.trim() + .lines() + .map(|line| format!("// {line}\n")) + .collect::>() + .concat() + }) + .unwrap_or_default() +} + +fn flags_repr(flags: &Flags) -> &'static str { + match flags.repr() { + FlagsRepr::U8 => "uint8", + FlagsRepr::U16 => "uint16", + FlagsRepr::U32(1) => "uint32", + _ => unreachable!(), + } +} + +fn int_repr(int: Int) -> &'static str { + match int { + Int::U8 => "uint8", + Int::U16 => "uint16", + Int::U32 => "uint32", + Int::U64 => unreachable!(), + } +} + +fn cast(op: &str, which: &Bitcast, need_math: &mut bool) -> String { + match which { + Bitcast::I32ToF32 | Bitcast::I64ToF32 => { + *need_math = true; + format!("math.Float32frombits(uint32({op}))") + } + Bitcast::F32ToI32 => { + *need_math = true; + format!("int32(math.Float32bits({op}))") + } + Bitcast::F32ToI64 => { + *need_math = true; + format!("int64(math.Float32bits({op}))") + } + Bitcast::I64ToF64 => { + *need_math = true; + format!("math.Float64frombits(uint64({op}))") + } + Bitcast::F64ToI64 => { + *need_math = true; + format!("int64(math.Float64bits({op}))") + } + Bitcast::I32ToI64 | Bitcast::LToI64 => { + format!("int64({op})") + } + Bitcast::PToP64 => { + format!("int64({op})") + } + Bitcast::I64ToI32 | Bitcast::I64ToL | Bitcast::PToI32 => { + format!("int32({op})") + } + Bitcast::I64ToP64 | Bitcast::P64ToI64 => op.into(), + Bitcast::P64ToP | Bitcast::LToP | Bitcast::I32ToP => { + format!("uintptr({op})") + } + Bitcast::PToL => { + format!("uint32({op})") + } + Bitcast::I32ToL => { + format!("uint32({op})") + } + Bitcast::LToI32 => { + format!("uint32({op})") + } + Bitcast::None => op.to_string(), + Bitcast::Sequence(sequence) => { + let [first, second] = &**sequence; + let inner = cast(op, first, need_math); + cast(&inner, second, need_math) + } + } +} + +fn any(resolve: &Resolve, ty: Type, fun: &dyn Fn(Type) -> bool) -> bool { + if fun(ty) { + return true; + } + + match ty { + Type::Bool + | Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::F32 + | Type::F64 + | Type::Char + | Type::String => false, + Type::Id(id) => { + let ty = &resolve.types[id]; + match &ty.kind { + TypeDefKind::Flags(_) | TypeDefKind::Enum(_) | TypeDefKind::Resource => false, + TypeDefKind::Handle(Handle::Own(resource) | Handle::Borrow(resource)) => { + any(resolve, Type::Id(*resource), fun) + } + TypeDefKind::Record(record) => record + .fields + .iter() + .any(|field| any(resolve, field.ty, fun)), + TypeDefKind::Variant(variant) => variant + .cases + .iter() + .any(|case| case.ty.map(|ty| any(resolve, ty, fun)).unwrap_or(false)), + TypeDefKind::Option(ty) | TypeDefKind::List(ty) | TypeDefKind::Type(ty) => { + any(resolve, *ty, fun) + } + TypeDefKind::Result(result) => result + .ok + .map(|ty| any(resolve, ty, fun)) + .or_else(|| result.err.map(|ty| any(resolve, ty, fun))) + .unwrap_or(false), + TypeDefKind::Tuple(tuple) => tuple.types.iter().any(|ty| any(resolve, *ty, fun)), + TypeDefKind::Future(ty) | TypeDefKind::Stream(ty) => { + ty.map(|ty| any(resolve, ty, fun)).unwrap_or(false) + } + _ => todo!("{:?}", ty.kind), + } + } + _ => todo!("{ty:?}"), + } +} + +fn func_declaration(resolve: &Resolve, func: &Function) -> (String, bool) { + match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + (func.item_name().to_upper_camel_case(), false) + } + FunctionKind::Constructor(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + (format!("Make{ty}"), false) + } + FunctionKind::Method(ty) | FunctionKind::AsyncMethod(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + let camel = func.item_name().to_upper_camel_case(); + (format!("(self *{ty}) {camel}"), true) + } + FunctionKind::Static(ty) | FunctionKind::AsyncStatic(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + let camel = func.item_name().to_upper_camel_case(); + (format!("{ty}{camel}"), false) + } + } +} diff --git a/crates/go/src/wit_async.go b/crates/go/src/wit_async.go new file mode 100644 index 000000000..a5ca762ce --- /dev/null +++ b/crates/go/src/wit_async.go @@ -0,0 +1,209 @@ +package wit_async + +import ( + "fmt" + "runtime" + "unsafe" +) + +const EVENT_NONE uint32 = 0 +const EVENT_SUBTASK uint32 = 1 +const EVENT_STREAM_READ uint32 = 2 +const EVENT_STREAM_WRITE uint32 = 3 +const EVENT_FUTURE_READ uint32 = 4 +const EVENT_FUTURE_WRITE uint32 = 5 + +const STATUS_STARTING uint32 = 0 +const STATUS_STARTED uint32 = 1 +const STATUS_RETURNED uint32 = 2 + +const CALLBACK_CODE_EXIT uint32 = 0 +const CALLBACK_CODE_YIELD uint32 = 1 +const CALLBACK_CODE_WAIT uint32 = 2 +const CALLBACK_CODE_POLL uint32 = 3 + +const RETURN_CODE_BLOCKED uint32 = 0xFFFFFFFF +const RETURN_CODE_COMPLETED uint32 = 0 +const RETURN_CODE_DROPPED uint32 = 1 + +type unit struct{} + +type taskState struct { + channel chan unit + waitableSet uint32 + pending map[uint32]chan uint32 + yielding chan unit + pinner runtime.Pinner +} + +var state *taskState = nil + +func Run(closure func()) uint32 { + state = &taskState{ + make(chan unit), + 0, + make(map[uint32]chan uint32), + nil, + runtime.Pinner{}, + } + state.pinner.Pin(state) + + defer func() { + state = nil + }() + + go closure() + + return callback(EVENT_NONE, 0, 0) +} + +func Callback(event0, event1, event2 uint32) uint32 { + state = (*taskState)(contextGet()) + contextSet(nil) + + return callback(event0, event1, event2) +} + +func callback(event0, event1, event2 uint32) uint32 { + yielding := state.yielding + if state.yielding != nil { + state.yielding = nil + yielding <- unit{} + } + + switch event0 { + case EVENT_NONE: + + case EVENT_SUBTASK: + switch event2 { + case STATUS_STARTING: + panic(fmt.Sprintf("unexpected subtask status: %v", event2)) + + case STATUS_STARTED: + + case STATUS_RETURNED: + waitableJoin(event1, 0) + subtaskDrop(event1) + channel := state.pending[event1] + delete(state.pending, event1) + channel <- event2 + + default: + panic("todo") + } + + case EVENT_STREAM_READ, EVENT_STREAM_WRITE, EVENT_FUTURE_READ, EVENT_FUTURE_WRITE: + waitableJoin(event1, 0) + channel := state.pending[event1] + delete(state.pending, event1) + channel <- event2 + + default: + panic("todo") + } + + // Tell the Go scheduler to write to `state.channel` only after all + // goroutines have either blocked or exited. This allows us to reliably + // delay returning control to the host until there's truly nothing more + // we can do in the guest. + // + // Note that this function is _not_ currently part of upstream Go; it + // requires [this + // patch](https://github.com/dicej/go/commit/a1c83220fc9576cdb810e9624366cb998e69b22b) + runtime.WasiOnIdle(func() bool { + state.channel <- unit{} + return true + }) + defer runtime.WasiOnIdle(func() bool { + return false + }) + + // Block this goroutine until the scheduler wakes us up. + (<-state.channel) + + if state.yielding != nil { + contextSet(unsafe.Pointer(state)) + if len(state.pending) == 0 { + return CALLBACK_CODE_YIELD + } else { + if state.waitableSet == 0 { + panic("unreachable") + } + return CALLBACK_CODE_POLL | (state.waitableSet << 4) + } + } else if len(state.pending) == 0 { + state.pinner.Unpin() + if state.waitableSet != 0 { + waitableSetDrop(state.waitableSet) + } + return CALLBACK_CODE_EXIT + } else { + if state.waitableSet == 0 { + panic("unreachable") + } + contextSet(unsafe.Pointer(state)) + return CALLBACK_CODE_WAIT | (state.waitableSet << 4) + } +} + +func SubtaskWait(status uint32) { + subtask := status >> 4 + status = status & 0xF + + switch status { + case STATUS_STARTING, STATUS_STARTED: + if state.waitableSet == 0 { + state.waitableSet = waitableSetNew() + } + waitableJoin(subtask, state.waitableSet) + channel := make(chan uint32) + state.pending[subtask] = channel + (<-channel) + + case STATUS_RETURNED: + + default: + panic(fmt.Sprintf("unexpected subtask status: %v", status)) + } +} + +func FutureOrStreamWait(code uint32, handle int32) (uint32, uint32) { + if code == RETURN_CODE_BLOCKED { + if state.waitableSet == 0 { + state.waitableSet = waitableSetNew() + } + waitableJoin(uint32(handle), state.waitableSet) + channel := make(chan uint32) + state.pending[uint32(handle)] = channel + code = (<-channel) + } + + count := code >> 4 + code = code & 0xF + + return code, count +} + +func Yield() { + channel := make(chan unit) + state.yielding = channel + (<-channel) +} + +//go:wasmimport $root [waitable-set-new] +func waitableSetNew() uint32 + +//go:wasmimport $root [waitable-set-drop] +func waitableSetDrop(waitableSet uint32) + +//go:wasmimport $root [waitable-join] +func waitableJoin(waitable, waitableSet uint32) + +//go:wasmimport $root [context-get-0] +func contextGet() unsafe.Pointer + +//go:wasmimport $root [context-set-0] +func contextSet(value unsafe.Pointer) + +//go:wasmimport $root [subtask-drop] +func subtaskDrop(subtask uint32) diff --git a/crates/go/src/wit_future.go b/crates/go/src/wit_future.go new file mode 100644 index 000000000..9772d80ad --- /dev/null +++ b/crates/go/src/wit_future.go @@ -0,0 +1,134 @@ +package wit_types + +import ( + "runtime" + "unsafe" + "wit_component/wit_async" + "wit_component/wit_runtime" +) + +type FutureVtable[T any] struct { + Size uint32 + Align uint32 + Read func(handle int32, item unsafe.Pointer) uint32 + Write func(handle int32, item unsafe.Pointer) uint32 + CancelRead func(handle int32) uint32 + CancelWrite func(handle int32) uint32 + DropReadable func(handle int32) + DropWritable func(handle int32) + Lift func(src unsafe.Pointer) T + Lower func(pinner *runtime.Pinner, value T, dst unsafe.Pointer) +} + +type FutureReader[T any] struct { + vtable *FutureVtable[T] + handle int32 +} + +func (f *FutureReader[T]) Read() T { + if f.handle == 0 { + panic("null future handle") + } + + handle := f.handle + f.handle = 0 + defer f.vtable.DropReadable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + buffer := wit_runtime.Allocate(&pinner, uintptr(f.vtable.Size), uintptr(f.vtable.Align)) + + code, _ := wit_async.FutureOrStreamWait(f.vtable.Read(handle, buffer), handle) + + switch code { + case wit_async.RETURN_CODE_COMPLETED: + if f.vtable.Lift == nil { + return unsafe.Slice((*T)(buffer), 1)[0] + } else { + return f.vtable.Lift(buffer) + } + + case wit_async.RETURN_CODE_DROPPED: + panic("unreachable") + + default: + panic("todo: handle cancellation") + } +} + +func (f *FutureReader[T]) Drop() { + handle := f.handle + if handle != 0 { + f.handle = 0 + f.vtable.DropReadable(handle) + } +} + +func (f *FutureReader[T]) TakeHandle() int32 { + if f.handle == 0 { + panic("null future handle") + } + + handle := f.handle + f.handle = 0 + return handle +} + +func MakeFutureReader[T any](vtable *FutureVtable[T], handle int32) *FutureReader[T] { + return &FutureReader[T]{vtable, handle} +} + +type FutureWriter[T any] struct { + vtable *FutureVtable[T] + handle int32 +} + +func (f *FutureWriter[T]) Write(item T) bool { + if f.handle == 0 { + panic("null future handle") + } + + handle := f.handle + f.handle = 0 + defer f.vtable.DropWritable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + var buffer unsafe.Pointer + if f.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData([]T{item})) + pinner.Pin(buffer) + } else { + buffer = wit_runtime.Allocate(&pinner, uintptr(f.vtable.Size), uintptr(f.vtable.Align)) + f.vtable.Lower(&pinner, item, buffer) + } + + code, _ := wit_async.FutureOrStreamWait(f.vtable.Write(handle, buffer), handle) + + // TODO: restore handles to any unwritten resources, streams, or futures + + switch code { + case wit_async.RETURN_CODE_COMPLETED: + return true + + case wit_async.RETURN_CODE_DROPPED: + return false + + default: + panic("todo: handle cancellation") + } +} + +func (f *FutureWriter[T]) Drop() { + handle := f.handle + if handle != 0 { + f.handle = 0 + f.vtable.DropWritable(handle) + } +} + +func MakeFutureWriter[T any](vtable *FutureVtable[T], handle int32) *FutureWriter[T] { + return &FutureWriter[T]{vtable, handle} +} diff --git a/crates/go/src/wit_option.go b/crates/go/src/wit_option.go new file mode 100644 index 000000000..083c542a1 --- /dev/null +++ b/crates/go/src/wit_option.go @@ -0,0 +1,41 @@ +package wit_types + +const ( + OptionNone = 0 + OptionSome = 1 +) + +type Option[T any] struct { + value *T +} + +func (self Option[T]) Tag() uint8 { + if self.value == nil { + return OptionNone + } else { + return OptionSome + } +} + +func (self Option[T]) Some() T { + if self.value == nil { + panic("tag mismatch") + } + return *self.value +} + +func (self Option[T]) SomeOr(value T) T { + if self.value == nil { + return value + } else { + return *self.value + } +} + +func None[T any]() Option[T] { + return Option[T]{nil} +} + +func Some[T any](value T) Option[T] { + return Option[T]{&value} +} diff --git a/crates/go/src/wit_result.go b/crates/go/src/wit_result.go new file mode 100644 index 000000000..dd2a82f95 --- /dev/null +++ b/crates/go/src/wit_result.go @@ -0,0 +1,37 @@ +package wit_types + +const ( + ResultOk = 0 + ResultErr = 1 +) + +type Result[T any, U any] struct { + tag uint8 + value any +} + +func (self Result[T, U]) Tag() uint8 { + return self.tag +} + +func (self Result[T, U]) Ok() T { + if self.tag != ResultOk { + panic("tag mismatch") + } + return self.value.(T) +} + +func (self Result[T, U]) Err() U { + if self.tag != ResultErr { + panic("tag mismatch") + } + return self.value.(U) +} + +func Ok[T any, U any](value T) Result[T, U] { + return Result[T, U]{ResultOk, value} +} + +func Err[T any, U any](value U) Result[T, U] { + return Result[T, U]{ResultErr, value} +} diff --git a/crates/go/src/wit_runtime.go b/crates/go/src/wit_runtime.go new file mode 100644 index 000000000..24eebbefa --- /dev/null +++ b/crates/go/src/wit_runtime.go @@ -0,0 +1,94 @@ +package wit_runtime + +import ( + "fmt" + "runtime" + "unsafe" +) + +type Handle struct { + value int32 +} + +func (self *Handle) Use() int32 { + if self.value == 0 { + panic("nil handle") + } + return self.value +} + +func (self *Handle) Take() int32 { + value := self.value + self.value = 0 + return value +} + +func Allocate(pinner *runtime.Pinner, size, align uintptr) unsafe.Pointer { + pointer := allocateRaw(size, align) + pinner.Pin(pointer) + return pointer +} + +func allocateRaw(size, align uintptr) unsafe.Pointer { + if size == 0 { + return unsafe.Pointer(uintptr(0)) + } + + if size%align != 0 { + panic(fmt.Sprintf("size %v is not compatible with alignment %v", size, align)) + } + + switch align { + case 1: + return unsafe.Pointer(unsafe.SliceData(make([]uint8, size))) + case 2: + return unsafe.Pointer(unsafe.SliceData(make([]uint16, size/align))) + case 4: + return unsafe.Pointer(unsafe.SliceData(make([]uint32, size/align))) + case 8: + return unsafe.Pointer(unsafe.SliceData(make([]uint64, size/align))) + default: + panic(fmt.Sprintf("unsupported alignment: %v", align)) + } +} + +// NB: `cabi_realloc` may be called before the Go runtime has been initialized, +// in which case we need to use `runtime.sbrk` to do allocations. The following +// is an abbreviation of [Till's +// efforts](https://github.com/bytecodealliance/go-modules/pull/367). + +//go:linkname sbrk runtime.sbrk +func sbrk(n uintptr) unsafe.Pointer + +var useGCAllocations = false + +func init() { + useGCAllocations = true +} + +func offset(ptr, align uintptr) uintptr { + newptr := (ptr + align - 1) &^ (align - 1) + return newptr - ptr +} + +var pinner = runtime.Pinner{} + +func Unpin() { + pinner.Unpin() +} + +//go:wasmexport cabi_realloc +func cabiRealloc(oldPointer unsafe.Pointer, oldSize, align, newSize uintptr) unsafe.Pointer { + if oldPointer != nil || oldSize != 0 { + panic("todo") + } + + if useGCAllocations { + return Allocate(&pinner, newSize, align) + } else { + alignedSize := newSize + offset(newSize, align) + unaligned := sbrk(alignedSize) + off := offset(uintptr(unaligned), align) + return unsafe.Add(unaligned, off) + } +} diff --git a/crates/go/src/wit_stream.go b/crates/go/src/wit_stream.go new file mode 100644 index 000000000..fe91a7f7f --- /dev/null +++ b/crates/go/src/wit_stream.go @@ -0,0 +1,179 @@ +package wit_types + +import ( + "runtime" + "unsafe" + "wit_component/wit_async" + "wit_component/wit_runtime" +) + +type StreamVtable[T any] struct { + Size uint32 + Align uint32 + Read func(handle int32, items unsafe.Pointer, length uint32) uint32 + Write func(handle int32, items unsafe.Pointer, length uint32) uint32 + CancelRead func(handle int32) uint32 + CancelWrite func(handle int32) uint32 + DropReadable func(handle int32) + DropWritable func(handle int32) + Lift func(src unsafe.Pointer) T + Lower func(pinner *runtime.Pinner, value T, dst unsafe.Pointer) +} + +type StreamReader[T any] struct { + vtable *StreamVtable[T] + handle int32 + writerDropped bool +} + +func (s *StreamReader[T]) WriterDropped() bool { + return s.writerDropped +} + +func (s *StreamReader[T]) Read(maxCount uint32) []T { + if s.handle == 0 { + panic("null stream handle") + } + + if s.writerDropped { + return []T{} + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + buffer := wit_runtime.Allocate(&pinner, uintptr(s.vtable.Size*maxCount), uintptr(s.vtable.Align)) + + code, count := wit_async.FutureOrStreamWait(s.vtable.Read(s.handle, buffer, maxCount), s.handle) + + if code == wit_async.RETURN_CODE_DROPPED { + s.writerDropped = true + } + + if s.vtable.Lift == nil { + return unsafe.Slice((*T)(buffer), count) + } else { + result := make([]T, 0, count) + for i := 0; i < int(count); i++ { + result = append(result, s.vtable.Lift(unsafe.Add(buffer, i*int(s.vtable.Size)))) + } + return result + } +} + +func (s *StreamReader[T]) ReadInto(dst []T) uint32 { + if s.handle == 0 { + panic("null stream handle") + } + + if s.vtable.Lift != nil { + panic("cannot use `ReadInto` with this payload type") + } + + if s.writerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + buffer := unsafe.Pointer(unsafe.SliceData(dst)) + pinner.Pin(buffer) + + code, count := wit_async.FutureOrStreamWait(s.vtable.Read(s.handle, buffer, uint32(len(dst))), s.handle) + + if code == wit_async.RETURN_CODE_DROPPED { + s.writerDropped = true + } + + return count +} + +func (s *StreamReader[T]) Drop() { + handle := s.handle + if handle != 0 { + s.handle = 0 + s.vtable.DropReadable(handle) + } +} + +func (s *StreamReader[T]) TakeHandle() int32 { + if s.handle == 0 { + panic("null stream handle") + } + + handle := s.handle + s.handle = 0 + return handle +} + +func MakeStreamReader[T any](vtable *StreamVtable[T], handle int32) *StreamReader[T] { + return &StreamReader[T]{vtable, handle, false} +} + +type StreamWriter[T any] struct { + vtable *StreamVtable[T] + handle int32 + readerDropped bool +} + +func (s *StreamWriter[T]) ReaderDropped() bool { + return s.readerDropped +} + +func (s *StreamWriter[T]) Write(items []T) uint32 { + if s.handle == 0 { + panic("null stream handle") + } + + if s.readerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + writeCount := uint32(len(items)) + + var buffer unsafe.Pointer + if s.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData(items)) + pinner.Pin(buffer) + } else { + buffer = wit_runtime.Allocate(&pinner, uintptr(s.vtable.Size*writeCount), uintptr(s.vtable.Align)) + for index, item := range items { + s.vtable.Lower(&pinner, item, unsafe.Add(buffer, index*int(s.vtable.Size))) + } + } + + code, count := wit_async.FutureOrStreamWait(s.vtable.Write(s.handle, buffer, writeCount), s.handle) + + // TODO: restore handles to any unwritten resources, streams, or futures + + if code == wit_async.RETURN_CODE_DROPPED { + s.readerDropped = true + } + + return count +} + +func (s *StreamWriter[T]) WriteAll(items []T) uint32 { + offset := uint32(0) + count := uint32(len(items)) + for offset < count && !s.readerDropped { + offset += s.Write(items[offset:]) + } + return offset +} + +func (s *StreamWriter[T]) Drop() { + handle := s.handle + if handle != 0 { + s.handle = 0 + s.vtable.DropWritable(handle) + } +} + +func MakeStreamWriter[T any](vtable *StreamVtable[T], handle int32) *StreamWriter[T] { + return &StreamWriter[T]{vtable, handle, false} +} diff --git a/crates/go/src/wit_unit.go b/crates/go/src/wit_unit.go new file mode 100644 index 000000000..26f927b54 --- /dev/null +++ b/crates/go/src/wit_unit.go @@ -0,0 +1,3 @@ +package wit_types + +type Unit struct{} diff --git a/crates/test/src/go.rs b/crates/test/src/go.rs new file mode 100644 index 000000000..80f736564 --- /dev/null +++ b/crates/test/src/go.rs @@ -0,0 +1,146 @@ +use crate::{Compile, LanguageMethods, Runner, Verify}; +use anyhow::{Context as _, Result}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +pub struct Go; + +impl LanguageMethods for Go { + fn display(&self) -> &str { + "go" + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some("//@") + } + + fn should_fail_verify( + &self, + _name: &str, + config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + // TODO: We _do_ support async, but only with a build of Go that has + // [this + // patch](https://github.com/dicej/go/commit/a1c83220fc9576cdb810e9624366cb998e69b22b). + // Once we either publish builds containing that patch or upstream + // something equivalent, we can remove the ` || config.async_` here. + config.error_context || config.async_ + } + + fn default_bindgen_args_for_codegen(&self) -> &[&str] { + &["--generate-stubs"] + } + + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + let cwd = env::current_dir()?; + let dir = cwd.join(&runner.opts.artifacts).join("go"); + + super::write_if_different(&dir.join("test.go"), "package main\n\nfunc main() {}")?; + super::write_if_different(&dir.join("go.mod"), "module test\n\ngo 1.25")?; + + println!("Testing if `go build` works..."); + runner.run_command( + Command::new("go") + .current_dir(&dir) + .env("GOOS", "wasip1") + .env("GOARCH", "wasm") + .arg("build") + .arg("-buildmode=c-shared") + .arg("-ldflags=-checklinkname=0"), + ) + } + + fn compile(&self, runner: &Runner<'_>, compile: &Compile<'_>) -> Result<()> { + let output = compile.output.with_extension("core.wasm"); + + // Tests which involve importing and/or exporting more than one + // interface may require more than one file since we can't define more + // than one package in a single file in Go (AFAICT). Here we search for + // files related to `compile.component.path` based on a made-up naming + // convention. For example, if the filename is `test.go`, then we'll + // also include `${prefix}+test.go` for any value of `${prefix}`. + for path in all_paths(&compile.component.path)? { + let test = fs::read_to_string(&path) + .with_context(|| format!("unable to read `{}`", path.display()))?; + let package_name = package_name(&test); + let package_dir = compile.bindings_dir.join(package_name); + fs::create_dir_all(&package_dir) + .with_context(|| format!("unable to create `{}`", package_dir.display()))?; + let output = &package_dir.join(path.file_name().unwrap()); + fs::write(output, test.as_bytes()) + .with_context(|| format!("unable to write `{}`", output.display()))?; + } + + runner.run_command( + Command::new("go") + .current_dir(&compile.bindings_dir) + .env("GOOS", "wasip1") + .env("GOARCH", "wasm") + .arg("build") + .arg("-o") + .arg(&output) + .arg("-buildmode=c-shared") + .arg("-ldflags=-checklinkname=0"), + )?; + + runner.convert_p1_to_component(&output, compile)?; + + Ok(()) + } + + fn verify(&self, runner: &Runner<'_>, verify: &Verify<'_>) -> Result<()> { + runner.run_command( + Command::new("go") + .current_dir(&verify.bindings_dir) + .env("GOOS", "wasip1") + .env("GOARCH", "wasm") + .arg("build") + .arg("-o") + .arg(verify.artifacts_dir.join("tmp.wasm")) + .arg("-buildmode=c-shared") + .arg("-ldflags=-checklinkname=0"), + ) + } +} + +fn package_name(package: &str) -> &str { + package + .split_once('\n') + .unwrap() + .0 + .strip_prefix("package ") + .unwrap() + .trim() +} + +fn all_paths(path: &Path) -> Result> { + let mut paths = vec![path.into()]; + let suffix = ".go"; + if let Some(name) = path + .file_name() + .unwrap() + .to_str() + .and_then(|name| name.strip_suffix(suffix)) + { + let suffix = &format!("+{name}{suffix}"); + let parent = path.parent().unwrap(); + for entry in parent + .read_dir() + .with_context(|| format!("unable to read dir `{}`", parent.display()))? + { + let entry = entry?; + if entry + .file_name() + .to_str() + .and_then(|name| name.strip_suffix(suffix)) + .is_some() + { + paths.push(entry.path()); + } + } + } + Ok(paths) +} diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index d0af0f216..1cbf9333c 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -17,6 +17,7 @@ mod config; mod cpp; mod csharp; mod custom; +mod go; mod moonbit; mod runner; mod rust; @@ -195,6 +196,7 @@ enum Language { Wat, Csharp, MoonBit, + Go, Custom(custom::Language), } @@ -416,6 +418,7 @@ impl Runner<'_> { "wat" => Language::Wat, "cs" => Language::Csharp, "mbt" => Language::MoonBit, + "go" => Language::Go, other => Language::Custom(custom::Language::lookup(self, other)?), }; @@ -677,7 +680,7 @@ impl Runner<'_> { // Next, massage the data a bit. Create a map of all tests to where // their components are located. Then perform a product of runners/tests - // to generate a list of test cases. Finally actually execute the testj + // to generate a list of test cases. Finally actually execute the test // cases. let mut compiled_components = HashMap::new(); for (test, component, path) in compilations { @@ -741,7 +744,7 @@ impl Runner<'_> { ) -> Result<()> { /// Recursive function which walks over `worlds`, the list of worlds /// that `test` expects, one by one. For each world it finds a matching - /// component in `components` adn then recurses for the next item in the + /// component in `components` and then recurses for the next item in the /// `worlds` list. /// /// Once `worlds` is empty the `test` list, a temporary vector, is @@ -1091,7 +1094,7 @@ fn has_component_type_sections(wasm: &[u8]) -> bool { for payload in wasmparser::Parser::new(0).parse_all(wasm) { match payload { Ok(wasmparser::Payload::CustomSection(s)) if s.name().starts_with("component-type") => { - return true + return true; } _ => {} } @@ -1243,6 +1246,7 @@ impl Language { Language::Wat, Language::Csharp, Language::MoonBit, + Language::Go, ]; fn obj(&self) -> &dyn LanguageMethods { @@ -1253,6 +1257,7 @@ impl Language { Language::Wat => &wat::Wat, Language::Csharp => &csharp::Csharp, Language::MoonBit => &moonbit::MoonBit, + Language::Go => &go::Go, Language::Custom(custom) => custom, } } diff --git a/crates/test/src/rust.rs b/crates/test/src/rust.rs index 82335a9bb..34c9f2c09 100644 --- a/crates/test/src/rust.rs +++ b/crates/test/src/rust.rs @@ -61,7 +61,11 @@ impl LanguageMethods for Rust { args: &[String], ) -> bool { // no_std doesn't currently work with async - if config.async_ && args.iter().any(|s| s == "--std-feature") { + if config.async_ + && args.iter().any(|s| s == "--std-feature") + // Except this one actually _does_ work: + && name != "async-trait-function.wit-no-std" + { return true; } diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index d1d3666a8..6986ecbd8 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -55,9 +55,11 @@ enum Opt { args: Common, }, - /// Generates bindings for TinyGo-based Go guest modules (Deprecated) + /// Generates bindings for Go guest modules #[cfg(feature = "go")] - TinyGo { + Go { + #[clap(flatten)] + opts: wit_bindgen_go::Opts, #[clap(flatten)] args: Common, }, @@ -145,9 +147,7 @@ fn main() -> Result<()> { #[cfg(feature = "rust")] Opt::Rust { opts, args } => (opts.build(), args), #[cfg(feature = "go")] - Opt::TinyGo { args: _ } => { - bail!("Go bindgen has been moved to a separate repository. Please visit https://github.com/bytecodealliance/go-modules for the new Go bindings generator `wit-bindgen-go`.") - } + Opt::Go { opts, args } => (opts.build(), args), #[cfg(feature = "csharp")] Opt::Csharp { opts, args } => (opts.build(), args), Opt::Test { opts } => return opts.run(std::env::args_os().nth(0).unwrap().as_ref()), @@ -176,7 +176,10 @@ fn main() -> Result<()> { .any(|c| c.is_control() && !matches!(c, '\n' | '\r' | '\t')) && utf8_prev.lines().eq(utf8_contents.lines()) { - bail!("{} differs only in line endings (CRLF vs. LF). If this is a text file, configure git to mark the file as `text eol=lf`.", dst.display()); + bail!( + "{} differs only in line endings (CRLF vs. LF). If this is a text file, configure git to mark the file as `text eol=lf`.", + dst.display() + ); } } // The contents are binary or there are other differences; just diff --git a/tests/codegen/async-trait-function.wit b/tests/codegen/async-trait-function.wit index 061387b0d..473a812dc 100644 --- a/tests/codegen/async-trait-function.wit +++ b/tests/codegen/async-trait-function.wit @@ -1,3 +1,5 @@ +//@ async = true + package wha:hoo; interface iii { diff --git a/tests/runtime-async/async/ping-pong/runner.go b/tests/runtime-async/async/ping-pong/runner.go new file mode 100644 index 000000000..830a4f5c4 --- /dev/null +++ b/tests/runtime-async/async/ping-pong/runner.go @@ -0,0 +1,55 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/my_test_i" + "wit_component/wit_types" +) + +type Unit struct{} + +func Run() { + { + f1 := make(chan *wit_types.FutureReader[string]) + f2 := make(chan Unit) + + tx, rx := test.MakeFutureString() + go func() { + f1 <- test.Ping(rx, "world") + }() + + go func() { + tx.Write("hello") + f2 <- Unit{} + }() + + (<-f2) + rx2 := (<-f1) + assertEqual(rx2.Read(), "helloworld") + } + + { + f1 := make(chan Unit) + f2 := make(chan Unit) + + tx, rx := test.MakeFutureString() + go func() { + assertEqual(test.Pong(rx), "helloworld") + f1 <- Unit{} + }() + + go func() { + tx.Write("helloworld") + f2 <- Unit{} + }() + + (<-f2) + (<-f1) + } +} + +func assertEqual[T comparable](a, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime-async/async/ping-pong/test.go b/tests/runtime-async/async/ping-pong/test.go new file mode 100644 index 000000000..1526430ab --- /dev/null +++ b/tests/runtime-async/async/ping-pong/test.go @@ -0,0 +1,19 @@ +package export_my_test_i + +import ( + "wit_component/my_test_i" + . "wit_component/wit_types" +) + +func Ping(x *FutureReader[string], y string) *FutureReader[string] { + message := x.Read() + y + tx, rx := my_test_i.MakeFutureString() + go func() { + tx.Write(message) + }() + return rx +} + +func Pong(x *FutureReader[string]) string { + return x.Read() +} diff --git a/tests/runtime-async/async/simple-call-import/runner.go b/tests/runtime-async/async/simple-call-import/runner.go new file mode 100644 index 000000000..e0fdeea3d --- /dev/null +++ b/tests/runtime-async/async/simple-call-import/runner.go @@ -0,0 +1,7 @@ +package export_wit_world + +import test "wit_component/a_b_i" + +func Run() { + test.F() +} diff --git a/tests/runtime-async/async/simple-call-import/test.go b/tests/runtime-async/async/simple-call-import/test.go new file mode 100644 index 000000000..9310a77f6 --- /dev/null +++ b/tests/runtime-async/async/simple-call-import/test.go @@ -0,0 +1,3 @@ +package export_a_b_i + +func F() {} diff --git a/tests/runtime-async/async/simple-future/runner.go b/tests/runtime-async/async/simple-future/runner.go new file mode 100644 index 000000000..8118cbf19 --- /dev/null +++ b/tests/runtime-async/async/simple-future/runner.go @@ -0,0 +1,43 @@ +package export_wit_world + +import ( + test "wit_component/my_test_i" + "wit_component/wit_types" +) + +func Run() { + write := make(chan bool) + read := make(chan wit_types.Unit) + + { + tx, rx := test.MakeFutureUnit() + go func() { + write <- tx.Write(wit_types.Unit{}) + }() + go func() { + test.ReadFuture(rx) + read <- wit_types.Unit{} + }() + (<-read) + assert(<-write) + } + + { + tx, rx := test.MakeFutureUnit() + go func() { + write <- tx.Write(wit_types.Unit{}) + }() + go func() { + test.DropFuture(rx) + read <- wit_types.Unit{} + }() + (<-read) + assert(!(<-write)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime-async/async/simple-future/test.go b/tests/runtime-async/async/simple-future/test.go new file mode 100644 index 000000000..fefab7dbc --- /dev/null +++ b/tests/runtime-async/async/simple-future/test.go @@ -0,0 +1,12 @@ +package export_my_test_i + +import . "wit_component/wit_types" + +func ReadFuture(x *FutureReader[Unit]) { + defer x.Drop() + x.Read() +} + +func DropFuture(x *FutureReader[Unit]) { + x.Drop() +} diff --git a/tests/runtime-async/async/simple-import-params-results/runner.go b/tests/runtime-async/async/simple-import-params-results/runner.go new file mode 100644 index 000000000..f47ca7789 --- /dev/null +++ b/tests/runtime-async/async/simple-import-params-results/runner.go @@ -0,0 +1,20 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/a_b_i" +) + +func Run() { + test.OneArgument(1) + assertEqual(test.OneResult(), 2) + assertEqual(test.OneArgumentAndResult(3), 4) + test.TwoArguments(5, 6) + assertEqual(test.TwoArgumentsAndResult(7, 8), 9) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime-async/async/simple-import-params-results/test.go b/tests/runtime-async/async/simple-import-params-results/test.go new file mode 100644 index 000000000..234a6e1ab --- /dev/null +++ b/tests/runtime-async/async/simple-import-params-results/test.go @@ -0,0 +1,33 @@ +package export_a_b_i + +import "fmt" + +func OneArgument(x uint32) { + assertEqual(x, 1) +} + +func OneResult() uint32 { + return 2 +} + +func OneArgumentAndResult(x uint32) uint32 { + assertEqual(x, 3) + return 4 +} + +func TwoArguments(x, y uint32) { + assertEqual(x, 5) + assertEqual(y, 6) +} + +func TwoArgumentsAndResult(x, y uint32) uint32 { + assertEqual(x, 7) + assertEqual(y, 8) + return 9 +} + +func assertEqual[T comparable](a, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime-async/async/simple-pending-import/runner.go b/tests/runtime-async/async/simple-pending-import/runner.go new file mode 100644 index 000000000..e0fdeea3d --- /dev/null +++ b/tests/runtime-async/async/simple-pending-import/runner.go @@ -0,0 +1,7 @@ +package export_wit_world + +import test "wit_component/a_b_i" + +func Run() { + test.F() +} diff --git a/tests/runtime-async/async/simple-pending-import/test.go b/tests/runtime-async/async/simple-pending-import/test.go new file mode 100644 index 000000000..f537000c1 --- /dev/null +++ b/tests/runtime-async/async/simple-pending-import/test.go @@ -0,0 +1,9 @@ +package export_a_b_i + +import "wit_component/wit_async" + +func F() { + for i := 0; i < 10; i++ { + wit_async.Yield() + } +} diff --git a/tests/runtime-async/async/simple-stream-payload/runner.go b/tests/runtime-async/async/simple-stream-payload/runner.go new file mode 100644 index 000000000..251be8758 --- /dev/null +++ b/tests/runtime-async/async/simple-stream-payload/runner.go @@ -0,0 +1,49 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/my_test_i" +) + +type Unit struct{} + +func Run() { + write := make(chan Unit) + read := make(chan Unit) + + tx, rx := test.MakeStreamU8() + go func() { + assertEqual(tx.Write([]uint8{0}), 1) + assert(!tx.ReaderDropped()) + + assertEqual(tx.Write([]uint8{1, 2}), 2) + assert(!tx.ReaderDropped()) + + assertEqual(tx.Write([]uint8{3, 4}), 2) + + assertEqual(tx.Write([]uint8{0}), 0) + assert(tx.ReaderDropped()) + + write <- Unit{} + }() + + go func() { + test.ReadStream(rx) + read <- Unit{} + }() + + (<-read) + (<-write) +} + +func assertEqual[T comparable](a, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime-async/async/simple-stream-payload/test.go b/tests/runtime-async/async/simple-stream-payload/test.go new file mode 100644 index 000000000..58151a81d --- /dev/null +++ b/tests/runtime-async/async/simple-stream-payload/test.go @@ -0,0 +1,28 @@ +package export_my_test_i + +import ( + "slices" + . "wit_component/wit_types" +) + +func ReadStream(x *StreamReader[uint8]) { + defer x.Drop() + + assert(slices.Equal(x.Read(1), []uint8{0})) + assert(!x.WriterDropped()) + + assert(slices.Equal(x.Read(2), []uint8{1, 2})) + assert(!x.WriterDropped()) + + assert(slices.Equal(x.Read(1), []uint8{3})) + assert(!x.WriterDropped()) + + assert(slices.Equal(x.Read(1), []uint8{4})) + assert(!x.WriterDropped()) +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime-async/async/simple-stream/runner.go b/tests/runtime-async/async/simple-stream/runner.go new file mode 100644 index 000000000..0118985ae --- /dev/null +++ b/tests/runtime-async/async/simple-stream/runner.go @@ -0,0 +1,45 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/my_test_i" + "wit_component/wit_types" +) + +func Run() { + write := make(chan wit_types.Unit) + read := make(chan wit_types.Unit) + + tx, rx := test.MakeStreamUnit() + go func() { + assertEqual(tx.Write([]wit_types.Unit{wit_types.Unit{}}), 1) + assert(!tx.ReaderDropped()) + + assertEqual(tx.Write([]wit_types.Unit{wit_types.Unit{}, wit_types.Unit{}}), 2) + + assertEqual(tx.Write([]wit_types.Unit{wit_types.Unit{}, wit_types.Unit{}}), 0) + assert(tx.ReaderDropped()) + + write <- wit_types.Unit{} + }() + + go func() { + test.ReadStream(rx) + read <- wit_types.Unit{} + }() + + (<-read) + (<-write) +} + +func assertEqual[T comparable](a, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime-async/async/simple-stream/test.go b/tests/runtime-async/async/simple-stream/test.go new file mode 100644 index 000000000..00eab23e0 --- /dev/null +++ b/tests/runtime-async/async/simple-stream/test.go @@ -0,0 +1,28 @@ +package export_my_test_i + +import ( + "fmt" + . "wit_component/wit_types" +) + +func ReadStream(x *StreamReader[Unit]) { + defer x.Drop() + + assertEqual(len(x.Read(1)), 1) + assert(!x.WriterDropped()) + + assertEqual(len(x.Read(2)), 2) + assert(!x.WriterDropped()) +} + +func assertEqual[T comparable](a, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime/demo/runner.go b/tests/runtime/demo/runner.go new file mode 100644 index 000000000..0522edc41 --- /dev/null +++ b/tests/runtime/demo/runner.go @@ -0,0 +1,7 @@ +package export_wit_world + +import test "wit_component/a_b_the_test" + +func Run() { + test.X() +} diff --git a/tests/runtime/demo/test.go b/tests/runtime/demo/test.go new file mode 100644 index 000000000..893be301e --- /dev/null +++ b/tests/runtime/demo/test.go @@ -0,0 +1,3 @@ +package export_a_b_the_test + +func X() {} diff --git a/tests/runtime/flavorful/runner.go b/tests/runtime/flavorful/runner.go new file mode 100644 index 000000000..aed8f2f14 --- /dev/null +++ b/tests/runtime/flavorful/runner.go @@ -0,0 +1,66 @@ +package export_wit_world + +import ( + "fmt" + "slices" + test "wit_component/test_flavorful_to_test" + . "wit_component/wit_types" +) + +func Run() { + test.FListInRecord1(test.ListInRecord1{"list_in_record1"}) + + assertEqual(test.FListInRecord2().A, "list_in_record2") + + assertEqual(test.FListInRecord3(test.ListInRecord3{"list_in_record3 input"}).A, "list_in_record3 output") + + assertEqual(test.FListInRecord4(test.ListInAlias{"input4"}).A, "result4") + + test.FListInVariant1( + Some[string]("foo"), + Err[Unit, string]("bar"), + ) + + assertEqual(test.FListInVariant2().Some(), "list_in_variant2") + + assertEqual(test.FListInVariant3(Some[string]("input3")).Some(), "output3") + + assertEqual(test.ErrnoResult().Err(), test.MyErrnoB) + test.ErrnoResult().Ok() + + { + a, b := test.ListTypedefs("typedef1", []string{"typedef2"}) + assert(slices.Equal(a, []byte("typedef3"))) + assert(slices.Equal(b, []string{"typedef4"})) + } + + { + a, b, c := test.ListOfVariants( + []bool{true, false}, + []Result[Unit, Unit]{ + Ok[Unit, Unit](Unit{}), + Err[Unit, Unit](Unit{}), + }, + []test.MyErrno{test.MyErrnoSuccess, test.MyErrnoA}, + ) + assert(slices.Equal(a, []bool{false, true})) + assert(slices.Equal(b, []Result[Unit, Unit]{ + Err[Unit, Unit](Unit{}), + Ok[Unit, Unit](Unit{}), + }, + )) + assert(slices.Equal(c, []test.MyErrno{test.MyErrnoA, test.MyErrnoB})) + } +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime/flavorful/test.go b/tests/runtime/flavorful/test.go new file mode 100644 index 000000000..1e6c19584 --- /dev/null +++ b/tests/runtime/flavorful/test.go @@ -0,0 +1,103 @@ +package export_test_flavorful_to_test + +import ( + "slices" + . "wit_component/test_flavorful_to_test" + . "wit_component/wit_types" +) + +func FListInRecord1(x ListInRecord1) { + if x.A != "list_in_record1" { + panic("trouble") + } +} + +func FListInRecord2() ListInRecord2 { + return ListInRecord2{"list_in_record2"} +} + +func FListInRecord3(x ListInRecord3) ListInRecord3 { + if x.A != "list_in_record3 input" { + panic("trouble") + } + return ListInRecord3{"list_in_record3 output"} +} + +func FListInRecord4(x ListInAlias) ListInAlias { + if x.A != "input4" { + panic("trouble") + } + return ListInRecord4{"result4"} +} + +func FListInVariant1(x ListInVariant1V1, y ListInVariant1V2) { + if x.Some() != "foo" { + panic("trouble") + } + if y.Err() != "bar" { + panic("trouble") + } +} + +func FListInVariant2() Option[string] { + return Some[string]("list_in_variant2") +} + +func FListInVariant3(x ListInVariant3) Option[string] { + if x.Some() != "input3" { + panic("trouble") + } + return Some[string]("output3") +} + +var first bool = true + +func ErrnoResult() Result[Unit, MyErrno] { + if first { + first = false + return Err[Unit, MyErrno](MyErrnoB) + } else { + return Ok[Unit, MyErrno](Unit{}) + } +} + +func ListTypedefs(x ListTypedef, y ListTypedef3) (ListTypedef2, ListTypedef3) { + if x != "typedef1" { + panic("trouble") + } + if !slices.Equal(y, []string{"typedef2"}) { + panic("trouble") + } + return []uint8("typedef3"), []string{"typedef4"} +} + +func ListOfVariants(bools []bool, results []Result[Unit, Unit], enums []MyErrno) ( + []bool, + []Result[Unit, Unit], + []MyErrno, +) { + if !slices.Equal(bools, []bool{true, false}) { + panic("trouble") + } + if len(results) != 2 { + panic("trouble") + } + if results[0].Tag() != ResultOk { + panic("trouble") + } + if results[1].Tag() != ResultErr { + panic("trouble") + } + if len(enums) != 2 { + panic("trouble") + } + if enums[0] != MyErrnoSuccess { + panic("trouble") + } + if enums[1] != MyErrnoA { + panic("trouble") + } + return []bool{false, true}, + []Result[Unit, Unit]{Err[Unit, Unit](Unit{}), Ok[Unit, Unit](Unit{})}, + []MyErrno{MyErrnoA, MyErrnoB} +} diff --git a/tests/runtime/lists/runner.go b/tests/runtime/lists/runner.go new file mode 100644 index 000000000..364a21c8a --- /dev/null +++ b/tests/runtime/lists/runner.go @@ -0,0 +1,46 @@ +package export_wit_world + +import ( + "fmt" + "slices" + test "wit_component/test_lists_to_test" + . "wit_component/wit_types" +) + +func Run() { + test.EmptyListParam([]uint8{}) + test.EmptyStringParam("") + assertEqual(0, len(test.EmptyListResult())) + assertEqual(0, len(test.EmptyStringResult())) + test.ListParam([]uint8{1, 2, 3, 4}) + test.ListParam2("foo") + test.ListParam3([]string{"foo", "bar", "baz"}) + test.ListParam4([][]string{[]string{"foo", "bar"}, []string{"baz"}}) + test.ListParam5([]Tuple3[uint8, uint32, uint8]{ + Tuple3[uint8, uint32, uint8]{1, 2, 3}, + Tuple3[uint8, uint32, uint8]{4, 5, 6}, + }) + + large := make([]string, 0, 1000) + for i := 0; i < 1000; i++ { + large = append(large, "string") + } + test.ListParamLarge(large) + + assert(slices.Equal(test.ListResult(), []uint8{1, 2, 3, 4, 5})) + assertEqual(test.ListResult2(), "hello!") + assert(slices.Equal(test.ListResult3(), []string{"hello,", "world!"})) + assert(slices.Equal(test.ListRoundtrip([]uint8{}), []uint8{})) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime/lists/test.go b/tests/runtime/lists/test.go new file mode 100644 index 000000000..0d75ce2f6 --- /dev/null +++ b/tests/runtime/lists/test.go @@ -0,0 +1,112 @@ +package export_test_lists_to_test + +import ( + "slices" + . "wit_component/wit_types" +) + +func AllocatedBytes() uint32 { + return 0 +} + +func EmptyListParam(x []uint8) { + if len(x) != 0 { + panic("trouble") + } +} + +func EmptyStringParam(x string) { + if len(x) != 0 { + panic("trouble") + } +} + +func EmptyListResult() []uint8 { + return []uint8{} +} + +func EmptyStringResult() string { + return "" +} + +func ListParam(x []uint8) { + if !slices.Equal(x, []uint8{1, 2, 3, 4}) { + panic("trouble") + } +} + +func ListParam2(x string) { + if x != "foo" { + panic("trouble") + } +} + +func ListParam3(x []string) { + if !slices.Equal(x, []string{"foo", "bar", "baz"}) { + panic("trouble") + } +} + +func ListParam4(x [][]string) { + if !slices.Equal(x[0], []string{"foo", "bar"}) { + panic("trouble") + } + if !slices.Equal(x[1], []string{"baz"}) { + panic("trouble") + } +} + +func ListParam5(x []Tuple3[uint8, uint32, uint8]) { + if !slices.Equal(x, []Tuple3[uint8, uint32, uint8]{ + Tuple3[uint8, uint32, uint8]{1, 2, 3}, + Tuple3[uint8, uint32, uint8]{4, 5, 6}, + }) { + panic("trouble") + } +} + +func ListParamLarge(x []string) { + if len(x) != 1000 { + panic("trouble") + } +} + +func ListResult() []uint8 { + return []uint8{1, 2, 3, 4, 5} +} + +func ListResult2() string { + return "hello!" +} + +func ListResult3() []string { + return []string{"hello,", "world!"} +} + +func ListRoundtrip(x []uint8) []uint8 { + return x +} + +func StringRoundtrip(x string) string { + return x +} + +func ListMinmax8(x []uint8, y []int8) ([]uint8, []int8) { + return x, y +} + +func ListMinmax16(x []uint16, y []int16) ([]uint16, []int16) { + return x, y +} + +func ListMinmax32(x []uint32, y []int32) ([]uint32, []int32) { + return x, y +} + +func ListMinmax64(x []uint64, y []int64) ([]uint64, []int64) { + return x, y +} + +func ListMinmaxFloat(x []float32, y []float64) ([]float32, []float64) { + return x, y +} diff --git a/tests/runtime/many-arguments/runner.go b/tests/runtime/many-arguments/runner.go new file mode 100644 index 000000000..56a0d22a7 --- /dev/null +++ b/tests/runtime/many-arguments/runner.go @@ -0,0 +1,9 @@ +package export_wit_world + +import ( + test "wit_component/test_many_arguments_to_test" +) + +func Run() { + test.ManyArguments(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) +} diff --git a/tests/runtime/many-arguments/test.go b/tests/runtime/many-arguments/test.go new file mode 100644 index 000000000..be469d1cd --- /dev/null +++ b/tests/runtime/many-arguments/test.go @@ -0,0 +1,43 @@ +package export_test_many_arguments_to_test + +func ManyArguments( + a1 uint64, + a2 uint64, + a3 uint64, + a4 uint64, + a5 uint64, + a6 uint64, + a7 uint64, + a8 uint64, + a9 uint64, + a10 uint64, + a11 uint64, + a12 uint64, + a13 uint64, + a14 uint64, + a15 uint64, + a16 uint64, +) { + assertEqual(a1, 1) + assertEqual(a2, 2) + assertEqual(a3, 3) + assertEqual(a4, 4) + assertEqual(a5, 5) + assertEqual(a6, 6) + assertEqual(a7, 7) + assertEqual(a8, 8) + assertEqual(a9, 9) + assertEqual(a10, 10) + assertEqual(a11, 11) + assertEqual(a12, 12) + assertEqual(a13, 13) + assertEqual(a14, 14) + assertEqual(a15, 15) + assertEqual(a16, 16) +} + +func assertEqual(a uint64, b uint64) { + if a != b { + panic("trouble") + } +} diff --git a/tests/runtime/numbers/runner.go b/tests/runtime/numbers/runner.go new file mode 100644 index 000000000..683f2295e --- /dev/null +++ b/tests/runtime/numbers/runner.go @@ -0,0 +1,73 @@ +package export_wit_world + +import ( + "fmt" + "math" + test "wit_component/test_numbers_numbers" +) + +func Run() { + assertEqual(test.RoundtripU8(1), 1) + assertEqual(test.RoundtripU8(0), 0) + assertEqual(test.RoundtripU8(math.MaxUint8), math.MaxUint8) + + assertEqual(test.RoundtripS8(1), 1) + assertEqual(test.RoundtripS8(math.MinInt8), math.MinInt8) + assertEqual(test.RoundtripS8(math.MaxInt8), math.MaxInt8) + + assertEqual(test.RoundtripU16(1), 1) + assertEqual(test.RoundtripU16(0), 0) + assertEqual(test.RoundtripU16(math.MaxUint16), math.MaxUint16) + + assertEqual(test.RoundtripS16(1), 1) + assertEqual(test.RoundtripS16(math.MinInt16), math.MinInt16) + assertEqual(test.RoundtripS16(math.MaxInt16), math.MaxInt16) + + assertEqual(test.RoundtripU32(1), 1) + assertEqual(test.RoundtripU32(0), 0) + assertEqual(test.RoundtripU32(math.MaxUint32), math.MaxUint32) + + assertEqual(test.RoundtripS32(1), 1) + assertEqual(test.RoundtripS32(math.MinInt32), math.MinInt32) + assertEqual(test.RoundtripS32(math.MaxInt32), math.MaxInt32) + + assertEqual(test.RoundtripU64(1), 1) + assertEqual(test.RoundtripU64(0), 0) + assertEqual(test.RoundtripU64(math.MaxUint64), math.MaxUint64) + + assertEqual(test.RoundtripS64(1), 1) + assertEqual(test.RoundtripS64(math.MinInt64), math.MinInt64) + assertEqual(test.RoundtripS64(math.MaxInt64), math.MaxInt64) + + assertEqual(test.RoundtripF32(1.0), 1.0) + assertEqual(test.RoundtripF32(float32(math.Inf(1))), float32(math.Inf(1))) + assertEqual(test.RoundtripF32(float32(math.Inf(-1))), float32(math.Inf(-1))) + assert(math.IsNaN(float64(test.RoundtripF32(float32(math.NaN()))))) + + assertEqual(test.RoundtripF64(1.0), 1.0) + assertEqual(test.RoundtripF64(math.Inf(1)), math.Inf(1)) + assertEqual(test.RoundtripF64(math.Inf(-1)), math.Inf(-1)) + assert(math.IsNaN(test.RoundtripF64(math.NaN()))) + + assertEqual(test.RoundtripChar('a'), 'a') + assertEqual(test.RoundtripChar(' '), ' ') + assertEqual(test.RoundtripChar('🚩'), '🚩') + + test.SetScalar(2) + assertEqual(test.GetScalar(), 2) + + test.SetScalar(4) + assertEqual(test.GetScalar(), 4) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} + +func assert(v bool) { + if !v { + panic("assertion failed") + } +} diff --git a/tests/runtime/numbers/test.go b/tests/runtime/numbers/test.go new file mode 100644 index 000000000..de74072a2 --- /dev/null +++ b/tests/runtime/numbers/test.go @@ -0,0 +1,55 @@ +package export_test_numbers_numbers + +func RoundtripU8(v uint8) uint8 { + return v +} + +func RoundtripS8(v int8) int8 { + return v +} + +func RoundtripU16(v uint16) uint16 { + return v +} + +func RoundtripS16(v int16) int16 { + return v +} + +func RoundtripU32(v uint32) uint32 { + return v +} + +func RoundtripS32(v int32) int32 { + return v +} + +func RoundtripU64(v uint64) uint64 { + return v +} + +func RoundtripS64(v int64) int64 { + return v +} + +func RoundtripF32(v float32) float32 { + return v +} + +func RoundtripF64(v float64) float64 { + return v +} + +func RoundtripChar(v rune) rune { + return v +} + +var scalar uint32 = 0 + +func SetScalar(v uint32) { + scalar = v +} + +func GetScalar() uint32 { + return scalar +} diff --git a/tests/runtime/records/runner.go b/tests/runtime/records/runner.go new file mode 100644 index 000000000..08e781a7e --- /dev/null +++ b/tests/runtime/records/runner.go @@ -0,0 +1,23 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/test_records_to_test" + . "wit_component/wit_types" +) + +func Run() { + a, b := test.MultipleResults() + assertEqual(a, 4) + assertEqual(b, 5) + + c, d := test.SwapTuple(Tuple2[uint8, uint32]{1, 2}) + assertEqual(c, 2) + assertEqual(d, 1) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/records/test.go b/tests/runtime/records/test.go new file mode 100644 index 000000000..bbdc88a0a --- /dev/null +++ b/tests/runtime/records/test.go @@ -0,0 +1,34 @@ +package export_test_records_to_test + +import ( + . "wit_component/test_records_to_test" + "wit_component/wit_types" +) + +func MultipleResults() (uint8, uint16) { + return 4, 5 +} + +func SwapTuple(x wit_types.Tuple2[uint8, uint32]) (uint32, uint8) { + return x.F1, x.F0 +} + +func RoundtripFlags1(x F1) F1 { + return x +} + +func RoundtripFlags2(x F2) F2 { + return x +} + +func RoundtripFlags3(x Flag8, y Flag16, z Flag32) (Flag8, Flag16, Flag32) { + return x, y, z +} + +func RoundtripRecord1(x R1) R1 { + return x +} + +func Tuple1(x wit_types.Tuple1[uint8]) uint8 { + return x.F0 +} diff --git a/tests/runtime/resource-import-and-export/intermediate.go b/tests/runtime/resource-import-and-export/intermediate.go new file mode 100644 index 000000000..793a6e9bf --- /dev/null +++ b/tests/runtime/resource-import-and-export/intermediate.go @@ -0,0 +1,34 @@ +package export_test_resource_import_and_export_test + +import ( + "runtime" + test "wit_component/test_resource_import_and_export_test" +) + +type Thing struct { + pinner runtime.Pinner + handle int32 + thing *test.Thing +} + +func (self *Thing) Foo() uint32 { + return self.thing.Foo() + 2 +} + +func (self *Thing) Bar(a uint32) { + self.thing.Bar(a + 3) +} + +func (self *Thing) OnDrop() { + self.thing.Drop() +} + +func MakeThing(a uint32) *Thing { + return &Thing{runtime.Pinner{}, 0, test.MakeThing(a + 1)} +} + +func ThingBaz(a *Thing, b *Thing) *Thing { + defer a.Drop() + defer b.Drop() + return MakeThing(test.ThingBaz(a.thing, b.thing).Foo() + 4) +} diff --git a/tests/runtime/resource-import-and-export/leaf-thing.go b/tests/runtime/resource-import-and-export/leaf-thing.go new file mode 100644 index 000000000..7cf09c2c3 --- /dev/null +++ b/tests/runtime/resource-import-and-export/leaf-thing.go @@ -0,0 +1,31 @@ +package export_test_resource_import_and_export_test + +import ( + "runtime" +) + +type Thing struct { + pinner runtime.Pinner + handle int32 + a uint32 +} + +func (self *Thing) Foo() uint32 { + return self.a + 2 +} + +func (self *Thing) Bar(a uint32) { + self.a = a + 3 +} + +func (self *Thing) OnDrop() {} + +func MakeThing(a uint32) *Thing { + return &Thing{runtime.Pinner{}, 0, a + 1} +} + +func ThingBaz(a *Thing, b *Thing) *Thing { + defer a.Drop() + defer b.Drop() + return MakeThing(a.Foo() + b.Foo() + 4) +} diff --git a/tests/runtime/resource-import-and-export/leaf-toplevel.go b/tests/runtime/resource-import-and-export/leaf-toplevel.go new file mode 100644 index 000000000..a2f0df064 --- /dev/null +++ b/tests/runtime/resource-import-and-export/leaf-toplevel.go @@ -0,0 +1,7 @@ +package export_wit_world + +import test "wit_component/test_resource_import_and_export_test" + +func ToplevelExport(a *test.Thing) *test.Thing { + return a +} diff --git a/tests/runtime/resource-import-and-export/runner.go b/tests/runtime/resource-import-and-export/runner.go new file mode 100644 index 000000000..04c6af5d6 --- /dev/null +++ b/tests/runtime/resource-import-and-export/runner.go @@ -0,0 +1,29 @@ +package export_wit_world + +import ( + "fmt" + . "wit_component/test_resource_import_and_export_test" +) + +func Run() { + thing1 := MakeThing(42) + defer thing1.Drop() + // 42 + 1 (constructor) + 1 (constructor) + 2 (foo) + 2 (foo) + assertEqual(thing1.Foo(), 48) + + // 33 + 3 (bar) + 3 (bar) + 2 (foo) + 2 (foo) + thing1.Bar(33) + assertEqual(thing1.Foo(), 43) + + thing2 := MakeThing(81) + defer thing2.Drop() + thing3 := ThingBaz(thing1, thing2) + defer thing3.Drop() + assertEqual(thing3.Foo(), 33+3+3+81+1+1+2+2+4+1+2+4+1+1+2+2) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/resource-import-and-export/toplevel+intermediate.go b/tests/runtime/resource-import-and-export/toplevel+intermediate.go new file mode 100644 index 000000000..8df707637 --- /dev/null +++ b/tests/runtime/resource-import-and-export/toplevel+intermediate.go @@ -0,0 +1,9 @@ +package export_wit_world + +import ( + . "wit_component/wit_world" +) + +func ToplevelExport(a *Thing) *Thing { + return ToplevelImport(a) +} diff --git a/tests/runtime/resources/leaf.go b/tests/runtime/resources/leaf.go new file mode 100644 index 000000000..c4621fe48 --- /dev/null +++ b/tests/runtime/resources/leaf.go @@ -0,0 +1,30 @@ +package export_imports + +import ( + "runtime" +) + +type Y struct { + pinner runtime.Pinner + handle int32 + a int32 +} + +func (self *Y) GetA() int32 { + return self.a +} + +func (self *Y) SetA(a int32) { + self.a = a +} + +func (self *Y) OnDrop() {} + +func MakeY(a int32) *Y { + return &Y{runtime.Pinner{}, 0, a} +} + +func YAdd(y *Y, a int32) *Y { + defer y.Drop() + return &Y{runtime.Pinner{}, 0, a + y.a} +} diff --git a/tests/runtime/resources/resources.go b/tests/runtime/resources/resources.go new file mode 100644 index 000000000..fefae620a --- /dev/null +++ b/tests/runtime/resources/resources.go @@ -0,0 +1,128 @@ +package export_exports + +import ( + "fmt" + "runtime" + "wit_component/imports" + . "wit_component/wit_types" +) + +func Add(a *Z, b *Z) *Z { + return MakeZ(a.a + b.a) +} + +func Consume(x *X) { + x.Drop() +} + +func TestImports() Result[Unit, string] { + { + y1 := imports.MakeY(10) + defer y1.Drop() + assertEqual(y1.GetA(), 10) + y1.SetA(20) + assertEqual(y1.GetA(), 20) + + y2 := imports.YAdd(y1, 20) + defer y2.Drop() + assertEqual(y2.GetA(), 40) + } + + { + y1 := imports.MakeY(1) + defer y1.Drop() + y2 := imports.MakeY(2) + defer y2.Drop() + assertEqual(y1.GetA(), 1) + assertEqual(y2.GetA(), 2) + y1.SetA(10) + y2.SetA(20) + assertEqual(y1.GetA(), 10) + assertEqual(y2.GetA(), 20) + + y3 := imports.YAdd(y1, 20) + defer y3.Drop() + y4 := imports.YAdd(y2, 30) + defer y4.Drop() + assertEqual(y3.GetA(), 30) + assertEqual(y4.GetA(), 50) + } + + return Ok[Unit, string](Unit{}) +} + +type X struct { + pinner runtime.Pinner + handle int32 + a int32 +} + +func (self *X) GetA() int32 { + return self.a +} + +func (self *X) SetA(a int32) { + self.a = a +} + +func (self *X) OnDrop() {} + +func MakeX(a int32) *X { + return &X{runtime.Pinner{}, 0, a} +} + +func XAdd(x *X, a int32) *X { + defer x.Drop() + return &X{runtime.Pinner{}, 0, a + x.a} +} + +type Z struct { + pinner runtime.Pinner + handle int32 + a int32 +} + +func (self *Z) GetA() int32 { + return self.a +} + +func (self *Z) OnDrop() { + numDroppedZs++ +} + +func MakeZ(a int32) *Z { + return &Z{runtime.Pinner{}, 0, a} +} + +var numDroppedZs uint32 = 0 + +func ZNumDropped() uint32 { + return numDroppedZs +} + +type KebabCase struct { + pinner runtime.Pinner + handle int32 + a uint32 +} + +func (self *KebabCase) GetA() uint32 { + return self.a +} + +func (self *KebabCase) OnDrop() {} + +func MakeKebabCase(a uint32) *KebabCase { + return &KebabCase{runtime.Pinner{}, 0, a} +} + +func KebabCaseTakeOwned(k *KebabCase) uint32 { + defer k.Drop() + return k.a +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/resources/runner.go b/tests/runtime/resources/runner.go new file mode 100644 index 000000000..3fd77b235 --- /dev/null +++ b/tests/runtime/resources/runner.go @@ -0,0 +1,52 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/exports" +) + +func Run() { + { + val := test.TestImports() + val.Ok() + } + + x := test.MakeX(5) + defer x.Drop() + assertEqual(x.GetA(), 5) + x.SetA(10) + assertEqual(x.GetA(), 10) + + z1 := test.MakeZ(10) + defer z1.Drop() + assertEqual(z1.GetA(), 10) + + z2 := test.MakeZ(20) + defer z2.Drop() + assertEqual(z2.GetA(), 20) + + xadd := test.XAdd(x, 5) + defer xadd.Drop() + assertEqual(xadd.GetA(), 15) + + zadd := test.Add(z1, z2) + defer zadd.Drop() + assertEqual(zadd.GetA(), 30) + + droppedZsStart := test.ZNumDropped() + + z1.Drop() + z2.Drop() + + test.Consume(xadd) + + droppedZsEnd := test.ZNumDropped() + + assertEqual(droppedZsEnd, droppedZsStart+2) +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/results/intermediate.go b/tests/runtime/results/intermediate.go new file mode 100644 index 000000000..6be579887 --- /dev/null +++ b/tests/runtime/results/intermediate.go @@ -0,0 +1,30 @@ +package export_test_results_test + +import ( + imports "wit_component/test_results_test" + . "wit_component/wit_types" +) + +func StringError(x float32) Result[float32, string] { + return imports.StringError(x) +} + +func EnumError(x float32) Result[float32, imports.E] { + return imports.EnumError(x) +} + +func RecordError(x float32) Result[float32, imports.E2] { + return imports.RecordError(x) +} + +func VariantError(x float32) Result[float32, imports.E3] { + return imports.VariantError(x) +} + +func EmptyError(x uint32) Result[uint32, Unit] { + return imports.EmptyError(x) +} + +func DoubleError(x uint32) Result[Result[Unit, string], string] { + return imports.DoubleError(x) +} diff --git a/tests/runtime/results/leaf.go b/tests/runtime/results/leaf.go new file mode 100644 index 000000000..b76fdf00b --- /dev/null +++ b/tests/runtime/results/leaf.go @@ -0,0 +1,70 @@ +package export_test_results_test + +import ( + . "wit_component/test_results_test" + . "wit_component/wit_types" +) + +func StringError(x float32) Result[float32, string] { + if x == 0.0 { + return Err[float32, string]("zero") + } else { + return Ok[float32, string](x) + } +} + +func EnumError(x float32) Result[float32, E] { + if x == 0.0 { + return Err[float32, E](EA) + } else { + return Ok[float32, E](x) + } +} + +func RecordError(x float32) Result[float32, E2] { + if x == 0.0 { + return Err[float32, E2](E2{420, 0}) + } else if x == 1.0 { + return Err[float32, E2](E2{77, 2}) + } else { + return Ok[float32, E2](x) + } +} + +func VariantError(x float32) Result[float32, E3] { + if x == 0.0 { + return Err[float32, E3](MakeE3E2(E2{420, 0})) + } else if x == 1.0 { + return Err[float32, E3](MakeE3E1(EB)) + } else if x == 2.0 { + return Err[float32, E3](MakeE3E1(EC)) + } else { + return Ok[float32, E3](x) + } +} + +func EmptyError(x uint32) Result[uint32, Unit] { + if x == 0 { + return Err[uint32, Unit](Unit{}) + } else if x == 1 { + return Ok[uint32, Unit](42) + } else { + return Ok[uint32, Unit](x) + } +} + +func DoubleError(x uint32) Result[Result[Unit, string], string] { + if x == 0 { + return Ok[Result[Unit, string], string]( + Ok[Unit, string](Unit{}), + ) + } else if x == 1 { + return Ok[Result[Unit, string], string]( + Err[Unit, string]("one"), + ) + } else { + return Err[Result[Unit, string], string]( + "two", + ) + } +} diff --git a/tests/runtime/results/runner.go b/tests/runtime/results/runner.go new file mode 100644 index 000000000..2fe10569c --- /dev/null +++ b/tests/runtime/results/runner.go @@ -0,0 +1,79 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/test_results_test" +) + +func Run() { + { + val := test.StringError(0.0) + assertEqual(val.Err(), "zero") + + val = test.StringError(1.0) + assertEqual(val.Ok(), 1.0) + } + + { + val := test.EnumError(0.0) + assertEqual(val.Err(), test.EA) + + val = test.EnumError(1.0) + assertEqual(val.Ok(), 1.0) + } + + { + val := test.RecordError(0.0) + assertEqual(val.Err(), test.E2{420, 0}) + + val = test.RecordError(1.0) + assertEqual(val.Err(), test.E2{77, 2}) + + val = test.RecordError(2.0) + assertEqual(val.Ok(), 2.0) + } + + { + a := test.VariantError(0.0) + b := a.Err() + assertEqual(b.E2(), test.E2{420, 0}) + + a = test.VariantError(1.0) + b = a.Err() + assertEqual(b.E1(), test.EB) + + a = test.VariantError(2.0) + b = a.Err() + assertEqual(b.E1(), test.EC) + } + + { + val := test.EmptyError(0) + val.Err() + + val = test.EmptyError(1) + assertEqual(val.Ok(), 42) + + val = test.EmptyError(2) + assertEqual(val.Ok(), 2) + } + + { + a := test.DoubleError(0) + b := a.Ok() + b.Ok() + + a = test.DoubleError(1) + b = a.Ok() + assertEqual(b.Err(), "one") + + a = test.DoubleError(2) + assertEqual(a.Err(), "two") + } +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/strings-simple/runner.go b/tests/runtime/strings-simple/runner.go new file mode 100644 index 000000000..f42941244 --- /dev/null +++ b/tests/runtime/strings-simple/runner.go @@ -0,0 +1,14 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/cat" +) + +func Run() { + test.Foo("hello") + value := test.Bar() + if value != "world" { + panic(fmt.Sprintf("expected `world`; got `%v`", value)) + } +} diff --git a/tests/runtime/strings-simple/test.go b/tests/runtime/strings-simple/test.go new file mode 100644 index 000000000..6c8d92cab --- /dev/null +++ b/tests/runtime/strings-simple/test.go @@ -0,0 +1,13 @@ +package export_cat + +import "fmt" + +func Foo(x string) { + if x != "hello" { + panic(fmt.Sprintf("unexpected value: `%v`", x)) + } +} + +func Bar() string { + return "world" +} diff --git a/tests/runtime/strings/runner.go b/tests/runtime/strings/runner.go new file mode 100644 index 000000000..08ab8c6e6 --- /dev/null +++ b/tests/runtime/strings/runner.go @@ -0,0 +1,19 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/test_strings_to_test" +) + +func Run() { + test.TakeBasic("latin utf16") + assertEqual(test.ReturnUnicode(), "🚀🚀🚀 𠈄𓀀") + assertEqual(test.ReturnEmpty(), "") + assertEqual(test.Roundtrip("🚀🚀🚀 𠈄𓀀"), "🚀🚀🚀 𠈄𓀀") +} + +func assertEqual(a string, b string) { + if a != b { + panic(fmt.Sprintf("`%v` not equal to `%v`", a, b)) + } +} diff --git a/tests/runtime/strings/test.go b/tests/runtime/strings/test.go new file mode 100644 index 000000000..ae593790c --- /dev/null +++ b/tests/runtime/strings/test.go @@ -0,0 +1,21 @@ +package export_test_strings_to_test + +import "fmt" + +func TakeBasic(x string) { + if x != "latin utf16" { + panic(fmt.Sprintf("unexpected value: `%v`", x)) + } +} + +func ReturnUnicode() string { + return "🚀🚀🚀 𠈄𓀀" +} + +func ReturnEmpty() string { + return "" +} + +func Roundtrip(x string) string { + return x +} diff --git a/tests/runtime/variants/runner.go b/tests/runtime/variants/runner.go new file mode 100644 index 000000000..2c717656b --- /dev/null +++ b/tests/runtime/variants/runner.go @@ -0,0 +1,95 @@ +package export_wit_world + +import ( + "fmt" + test "wit_component/test_variants_to_test" + . "wit_component/wit_types" +) + +func Run() { + assertEqual(test.RoundtripOption(Some[float32](1.0)).Some(), 1) + assertEqual(test.RoundtripOption(None[float32]()).Tag(), OptionNone) + assertEqual(test.RoundtripOption(Some[float32](2.0)).Some(), 2) + + assertEqual(test.RoundtripResult(Ok[uint32, float32](2)).Ok(), 2.0) + assertEqual(test.RoundtripResult(Ok[uint32, float32](4)).Ok(), 4.0) + assertEqual(test.RoundtripResult(Err[uint32, float32](5.3)).Err(), 5) + + assertEqual(test.InvertBool(true), false) + assertEqual(test.InvertBool(false), true) + + { + a, b, c, d, e, f := test.VariantCasts(test.Casts{ + test.MakeC1A(1), + test.MakeC2A(2), + test.MakeC3A(3), + test.MakeC4A(4), + test.MakeC5A(5), + test.MakeC6A(6.0), + }) + assertEqual(a.A(), 1) + assertEqual(b.A(), 2) + assertEqual(c.A(), 3) + assertEqual(d.A(), 4) + assertEqual(e.A(), 5) + assertEqual(f.A(), 6.0) + } + + { + a, b, c, d, e, f := test.VariantCasts(test.Casts{ + test.MakeC1B(1), + test.MakeC2B(2.0), + test.MakeC3B(3.0), + test.MakeC4B(4.0), + test.MakeC5B(5.0), + test.MakeC6B(6.0), + }) + assertEqual(a.B(), 1) + assertEqual(b.B(), 2.0) + assertEqual(c.B(), 3.0) + assertEqual(d.B(), 4.0) + assertEqual(e.B(), 5.0) + assertEqual(f.B(), 6.0) + } + + { + a, b, c, d := test.VariantZeros(test.Zeros{ + test.MakeZ1A(1), + test.MakeZ2A(2), + test.MakeZ3A(3.0), + test.MakeZ4A(4.0), + }) + assertEqual(a.A(), 1) + assertEqual(b.A(), 2) + assertEqual(c.A(), 3.0) + assertEqual(d.A(), 4.0) + } + + { + a, b, c, d := test.VariantZeros(test.Zeros{ + test.MakeZ1B(), + test.MakeZ2B(), + test.MakeZ3B(), + test.MakeZ4B(), + }) + assertEqual(a.Tag(), test.Z1B) + assertEqual(b.Tag(), test.Z2B) + assertEqual(c.Tag(), test.Z3B) + assertEqual(d.Tag(), test.Z4B) + } + + test.VariantTypedefs(None[uint32](), false, Err[uint32, Unit](Unit{})) + + { + a, b, c := test.VariantEnums(true, Ok[Unit, Unit](Unit{}), test.MyErrnoSuccess) + assertEqual(a, true) + b.Ok() + assertEqual(c, test.MyErrnoSuccess) + } +} + +func assertEqual[T comparable](a T, b T) { + if a != b { + panic(fmt.Sprintf("%v not equal to %v", a, b)) + } +} diff --git a/tests/runtime/variants/test.go b/tests/runtime/variants/test.go new file mode 100644 index 000000000..e7a604f5d --- /dev/null +++ b/tests/runtime/variants/test.go @@ -0,0 +1,51 @@ +package export_test_variants_to_test + +import ( + . "wit_component/test_variants_to_test" + . "wit_component/wit_types" +) + +func RoundtripOption(x Option[float32]) Option[uint8] { + switch x.Tag() { + case OptionSome: + return Some[uint8](uint8(x.Some())) + case OptionNone: + return None[uint8]() + default: + panic("unreachable") + } +} + +func RoundtripResult(x Result[uint32, float32]) Result[float64, uint8] { + switch x.Tag() { + case ResultOk: + return Ok[float64, uint8](float64(x.Ok())) + case ResultErr: + return Err[float64, uint8](uint8(x.Err())) + default: + panic("unreachable") + } +} + +func RoundtripEnum(x E1) E1 { + return x +} + +func InvertBool(x bool) bool { + return !x +} + +func VariantCasts(x Casts) (C1, C2, C3, C4, C5, C6) { + return x.F0, x.F1, x.F2, x.F3, x.F4, x.F5 +} + +func VariantZeros(x Zeros) (Z1, Z2, Z3, Z4) { + return x.F0, x.F1, x.F2, x.F3 +} + +func VariantTypedefs(x Option[uint32], y bool, z Result[uint32, Unit]) { +} + +func VariantEnums(x bool, y Result[Unit, Unit], z MyErrno) (bool, Result[Unit, Unit], MyErrno) { + return x, y, z +}