From 28bc118d308771c003a828f187ec6130024cc2da Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 14 Nov 2025 14:25:14 -0700 Subject: [PATCH 1/3] reboot `wit-bindgen-go` This re-introduces Go support, with a new implementation written from scratch. While the previous incarnation targeted TinyGo and reused the C generator for lifting and lowering, this one targets "big" Go and handles lifting and lowering itself. In addition, it supports idiomatic, goroutine-based concurrency on top of the component model async ABI, including streams and futures. This implementation is also distinct from the [go-modules](https://github.com/bytecodealliance/go-modules) project, which has a number of limitations (e.g. no async support, not safe for use with "big" Go) and is no longer being actively maintained. Note that the async support currently requires [a small patch](https://github.com/dicej/go/commit/a1c83220fc9576cdb810e9624366cb998e69b22b) to the Go `runtime` library. I plan to work with the upstream project to make that patch unecessary in the future. Components that don't use async features should work with stock, unpatched Go. One of the tricky parts about lowering values and passing pointers to e.g. `{stream,future}.{read,write}` is that we must tell the Go garbage collector to pin the pointers (i.e. mark the pointee as both immovable and uncollectable) for as long as the host has access to them, but then unpin them promptly afterward to avoid leaks. We do this using `runtime.Pinner` instances, most of which are locally scoped and easy to reason about. However, we must use a couple of globally-scoped pinners as well: one for `cabi_realloc` allocations by the host when it lowers parameters (which we unpin after lifting those parameters) and another for returning values from sync-lifted exports (which we unpin in post-return functions). There are a couple of known missing features which I'll open GitHub issues for: 1. Resource handles are not being restored for unwritten items in the case of an incomplete stream or future write. 2. Importing and/or exporting multiple versions of the same package will cause name clashes. In addition, I plan to expand the test coverage beyond the basics covered in this commit. Signed-off-by: Joel Dice add ping-pong Go tests Signed-off-by: Joel Dice address review feedback Signed-off-by: Joel Dice add wit-bindgen-go to ci/publish.rs Signed-off-by: Joel Dice enable Go in test CI matrix I'm assuming the default version of Go on the GitHub worker image will be sufficient; if not, we can install a different one explicitly. I'm not enabling the async tests for Go yet, since that will require publishing and installing a build of Go with [this patch](https://github.com/dicej/go/commit/a1c83220fc9576cdb810e9624366cb998e69b22b); I'll need to do some homework to find out the best way to do that. Signed-off-by: Joel Dice fix codegen tests Signed-off-by: Joel Dice update Go runtime tests to use `run` export Signed-off-by: Joel Dice run cargo fmt Signed-off-by: Joel Dice remove `publish = false` from crates/go/Cargo.toml Signed-off-by: Joel Dice temporarily skip async codegen tests for Go Signed-off-by: Joel Dice add `async = true` config directive to `async-trait-function.wit` Signed-off-by: Joel Dice indicate that `async-trait-function.wit-no-std` works for Rust Signed-off-by: Joel Dice add error contexts to `Go::compile` Signed-off-by: Joel Dice add `actions/setup-go` to workflow Signed-off-by: Joel Dice add finalizers where appropriate; tweak `StreamReader` API Signed-off-by: Joel Dice update workspace to 2024 edition Signed-off-by: Joel Dice --- .github/workflows/main.yml | 8 +- Cargo.lock | 14 + Cargo.toml | 8 +- ci/publish.rs | 13 +- ci/rebuild-libwit-bindgen-cabi.sh | 2 +- crates/c/src/lib.rs | 4 +- crates/core/src/abi.rs | 15 +- crates/core/src/async_.rs | 2 +- crates/core/src/lib.rs | 2 +- crates/core/src/source.rs | 2 +- crates/cpp/src/lib.rs | 531 +-- crates/csharp/src/function.rs | 7 +- crates/csharp/src/interface.rs | 4 +- crates/csharp/src/world_generator.rs | 78 +- crates/go/Cargo.toml | 25 + crates/go/src/lib.rs | 2950 +++++++++++++++++ crates/go/src/wit_async.go | 209 ++ crates/go/src/wit_future.go | 132 + crates/go/src/wit_option.go | 41 + crates/go/src/wit_result.go | 37 + crates/go/src/wit_runtime.go | 110 + crates/go/src/wit_stream.go | 167 + crates/go/src/wit_unit.go | 3 + crates/guest-rust/macro/src/lib.rs | 4 +- crates/guest-rust/src/lib.rs | 14 +- crates/guest-rust/src/rt/async_support.rs | 36 +- .../src/rt/async_support/abi_buffer.rs | 2 +- .../guest-rust/src/rt/async_support/cabi.rs | 2 +- .../src/rt/async_support/error_context.rs | 2 +- .../src/rt/async_support/future_support.rs | 24 +- .../src/rt/async_support/stream_support.rs | 20 +- .../src/rt/async_support/subtask.rs | 4 +- .../src/rt/async_support/unit_stream.rs | 2 +- .../src/rt/async_support/waitable.rs | 4 +- .../src/rt/async_support/waitable_set.rs | 2 +- crates/guest-rust/src/rt/mod.rs | 26 +- crates/guest-rust/src/rt/wit_bindgen_cabi.c | 20 - crates/guest-rust/src/rt/wit_bindgen_cabi.o | Bin 414 -> 0 bytes .../src/rt/wit_bindgen_cabi_realloc.rs | 2 +- crates/markdown/src/lib.rs | 116 +- crates/moonbit/src/lib.rs | 296 +- crates/rust/src/bindgen.rs | 8 +- crates/rust/src/interface.rs | 8 +- crates/rust/src/lib.rs | 6 +- crates/test/src/config.rs | 2 +- crates/test/src/custom.rs | 2 +- crates/test/src/go.rs | 146 + crates/test/src/lib.rs | 13 +- crates/test/src/runner.rs | 2 +- crates/test/src/rust.rs | 6 +- src/bin/wit-bindgen.rs | 19 +- tests/codegen/async-trait-function.wit | 2 + tests/runtime-async/async/ping-pong/runner.go | 55 + tests/runtime-async/async/ping-pong/test.go | 19 + .../async/simple-call-import/runner.go | 7 + .../async/simple-call-import/test.go | 3 + .../async/simple-future/runner.go | 43 + .../runtime-async/async/simple-future/test.go | 12 + .../simple-import-params-results/runner.go | 20 + .../simple-import-params-results/test.go | 33 + .../async/simple-pending-import/runner.go | 7 + .../async/simple-pending-import/test.go | 9 + .../async/simple-stream-payload/runner.go | 49 + .../async/simple-stream-payload/test.go | 54 + .../async/simple-stream/runner.go | 45 + .../runtime-async/async/simple-stream/test.go | 35 + tests/runtime/demo/runner.go | 7 + tests/runtime/demo/test.go | 3 + tests/runtime/flavorful/runner.go | 66 + tests/runtime/flavorful/test.go | 103 + tests/runtime/lists/runner.go | 46 + tests/runtime/lists/test.go | 112 + tests/runtime/many-arguments/runner.go | 9 + tests/runtime/many-arguments/test.go | 43 + tests/runtime/numbers/runner.go | 73 + tests/runtime/numbers/test.go | 55 + tests/runtime/records/runner.go | 23 + tests/runtime/records/test.go | 34 + .../intermediate.go | 34 + .../resource-import-and-export/leaf-thing.go | 31 + .../leaf-toplevel.go | 7 + .../resource-import-and-export/runner.go | 29 + .../toplevel+intermediate.go | 9 + tests/runtime/resources/leaf.go | 30 + tests/runtime/resources/resources.go | 128 + tests/runtime/resources/runner.go | 52 + tests/runtime/results/intermediate.go | 30 + tests/runtime/results/leaf.go | 70 + tests/runtime/results/runner.go | 79 + tests/runtime/strings-simple/runner.go | 14 + tests/runtime/strings-simple/test.go | 13 + tests/runtime/strings/runner.go | 19 + tests/runtime/strings/test.go | 21 + tests/runtime/variants/runner.go | 95 + tests/runtime/variants/test.go | 51 + 95 files changed, 6186 insertions(+), 645 deletions(-) create mode 100644 crates/go/Cargo.toml create mode 100644 crates/go/src/lib.rs create mode 100644 crates/go/src/wit_async.go create mode 100644 crates/go/src/wit_future.go create mode 100644 crates/go/src/wit_option.go create mode 100644 crates/go/src/wit_result.go create mode 100644 crates/go/src/wit_runtime.go create mode 100644 crates/go/src/wit_stream.go create mode 100644 crates/go/src/wit_unit.go delete mode 100644 crates/guest-rust/src/rt/wit_bindgen_cabi.c delete mode 100644 crates/guest-rust/src/rt/wit_bindgen_cabi.o create mode 100644 crates/test/src/go.rs create mode 100644 tests/runtime-async/async/ping-pong/runner.go create mode 100644 tests/runtime-async/async/ping-pong/test.go create mode 100644 tests/runtime-async/async/simple-call-import/runner.go create mode 100644 tests/runtime-async/async/simple-call-import/test.go create mode 100644 tests/runtime-async/async/simple-future/runner.go create mode 100644 tests/runtime-async/async/simple-future/test.go create mode 100644 tests/runtime-async/async/simple-import-params-results/runner.go create mode 100644 tests/runtime-async/async/simple-import-params-results/test.go create mode 100644 tests/runtime-async/async/simple-pending-import/runner.go create mode 100644 tests/runtime-async/async/simple-pending-import/test.go create mode 100644 tests/runtime-async/async/simple-stream-payload/runner.go create mode 100644 tests/runtime-async/async/simple-stream-payload/test.go create mode 100644 tests/runtime-async/async/simple-stream/runner.go create mode 100644 tests/runtime-async/async/simple-stream/test.go create mode 100644 tests/runtime/demo/runner.go create mode 100644 tests/runtime/demo/test.go create mode 100644 tests/runtime/flavorful/runner.go create mode 100644 tests/runtime/flavorful/test.go create mode 100644 tests/runtime/lists/runner.go create mode 100644 tests/runtime/lists/test.go create mode 100644 tests/runtime/many-arguments/runner.go create mode 100644 tests/runtime/many-arguments/test.go create mode 100644 tests/runtime/numbers/runner.go create mode 100644 tests/runtime/numbers/test.go create mode 100644 tests/runtime/records/runner.go create mode 100644 tests/runtime/records/test.go create mode 100644 tests/runtime/resource-import-and-export/intermediate.go create mode 100644 tests/runtime/resource-import-and-export/leaf-thing.go create mode 100644 tests/runtime/resource-import-and-export/leaf-toplevel.go create mode 100644 tests/runtime/resource-import-and-export/runner.go create mode 100644 tests/runtime/resource-import-and-export/toplevel+intermediate.go create mode 100644 tests/runtime/resources/leaf.go create mode 100644 tests/runtime/resources/resources.go create mode 100644 tests/runtime/resources/runner.go create mode 100644 tests/runtime/results/intermediate.go create mode 100644 tests/runtime/results/leaf.go create mode 100644 tests/runtime/results/runner.go create mode 100644 tests/runtime/strings-simple/runner.go create mode 100644 tests/runtime/strings-simple/test.go create mode 100644 tests/runtime/strings/runner.go create mode 100644 tests/runtime/strings/test.go create mode 100644 tests/runtime/variants/runner.go create mode 100644 tests/runtime/variants/test.go 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..19c3f851a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,11 +15,11 @@ CLI tool to generate bindings for WIT documents and the component model. resolver = "2" [workspace.package] -edition = "2021" +edition = "2024" version = "0.49.0" license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" repository = "https://github.com/bytecodealliance/wit-bindgen" -rust-version = "1.82.0" +rust-version = "1.85.0" [workspace.dependencies] anyhow = "1.0.72" @@ -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/ci/rebuild-libwit-bindgen-cabi.sh b/ci/rebuild-libwit-bindgen-cabi.sh index 6ceaf6492..bbb57c1ea 100755 --- a/ci/rebuild-libwit-bindgen-cabi.sh +++ b/ci/rebuild-libwit-bindgen-cabi.sh @@ -56,7 +56,7 @@ pub unsafe extern "C" fn $realloc( align: usize, new_len: usize, ) -> *mut u8 { - crate::rt::cabi_realloc(old_ptr, old_len, align, new_len) + unsafe { crate::rt::cabi_realloc(old_ptr, old_len, align, new_len) } } EOF diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index c0c8340ea..05afbb119 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -10,8 +10,8 @@ use wit_bindgen_core::abi::{ self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmSignature, WasmType, }; use wit_bindgen_core::{ - dealias, uwrite, uwriteln, wit_parser::*, AnonymousTypeGenerator, AsyncFilterSet, Direction, - Files, InterfaceGenerator as _, Ns, WorldGenerator, + AnonymousTypeGenerator, AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, + WorldGenerator, dealias, uwrite, uwriteln, wit_parser::*, }; use wit_component::StringEncoding; diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index b3f806540..98e2bf998 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -3,8 +3,8 @@ use std::iter; pub use wit_parser::abi::{AbiVariant, FlatTypes, WasmSignature, WasmType}; use wit_parser::{ - align_to_arch, Alignment, ArchitectureSize, ElementInfo, Enum, Flags, FlagsRepr, Function, - Handle, Int, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, + Alignment, ArchitectureSize, ElementInfo, Enum, Flags, FlagsRepr, Function, Handle, Int, + Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, align_to_arch, }; // Helper macro for defining instructions without having to have tons of @@ -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/async_.rs b/crates/core/src/async_.rs index 11ce54055..e801e2ea3 100644 --- a/crates/core/src/async_.rs +++ b/crates/core/src/async_.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use std::collections::HashSet; use std::fmt; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; 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/core/src/source.rs b/crates/core/src/source.rs index fb2bfd2e7..3c40b0fc7 100644 --- a/crates/core/src/source.rs +++ b/crates/core/src/source.rs @@ -1,5 +1,5 @@ -use std::collections::btree_map::Entry; use std::collections::BTreeMap; +use std::collections::btree_map::Entry; use std::fmt::{self, Write}; use std::ops::Deref; diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index c00eb3370..cc2f8e98b 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -11,6 +11,7 @@ use std::{ use symbol_name::{make_external_component, make_external_symbol}; use wit_bindgen_c::to_c_ident; use wit_bindgen_core::{ + Files, InterfaceGenerator, Source, Types, WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, LiftLower, WasmSignature, WasmType}, name_package_module, uwrite, uwriteln, wit_parser::{ @@ -18,7 +19,6 @@ use wit_bindgen_core::{ Resolve, SizeAlign, Stability, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, }, - Files, InterfaceGenerator, Source, Types, WorldGenerator, }; // mod wamr; @@ -309,7 +309,7 @@ impl Cpp { CppInterfaceGenerator { _src: Source::default(), - gen: self, + r#gen: self, resolve, interface: None, _name: name, @@ -509,15 +509,15 @@ impl WorldGenerator for Cpp { self.imported_interfaces.insert(id); let wasm_import_module = resolve.name_world_key(name); let binding = Some(name); - let mut gen = self.interface(resolve, binding, true, Some(wasm_import_module)); - gen.interface = Some(id); - gen.types(id); - let namespace = namespace(resolve, &TypeOwner::Interface(id), false, &gen.gen.opts); + let mut r#gen = self.interface(resolve, binding, true, Some(wasm_import_module)); + r#gen.interface = Some(id); + r#gen.types(id); + let namespace = namespace(resolve, &TypeOwner::Interface(id), false, &r#gen.r#gen.opts); for (_name, func) in resolve.interfaces[id].functions.iter() { if matches!(func.kind, FunctionKind::Freestanding) { - gen.gen.h_src.change_namespace(&namespace); - gen.generate_function(func, &TypeOwner::Interface(id), AbiVariant::GuestImport); + r#gen.r#gen.h_src.change_namespace(&namespace); + r#gen.generate_function(func, &TypeOwner::Interface(id), AbiVariant::GuestImport); } } self.finish_file(&namespace, store); @@ -547,15 +547,15 @@ impl WorldGenerator for Cpp { self.imported_interfaces.remove(&id); let wasm_import_module = resolve.name_world_key(name); let binding = Some(name); - let mut gen = self.interface(resolve, binding, false, Some(wasm_import_module)); - gen.interface = Some(id); - gen.types(id); - let namespace = namespace(resolve, &TypeOwner::Interface(id), true, &gen.gen.opts); + let mut r#gen = self.interface(resolve, binding, false, Some(wasm_import_module)); + r#gen.interface = Some(id); + r#gen.types(id); + let namespace = namespace(resolve, &TypeOwner::Interface(id), true, &r#gen.r#gen.opts); for (_name, func) in resolve.interfaces[id].functions.iter() { if matches!(func.kind, FunctionKind::Freestanding) { - gen.gen.h_src.change_namespace(&namespace); - gen.generate_function(func, &TypeOwner::Interface(id), AbiVariant::GuestExport); + r#gen.r#gen.h_src.change_namespace(&namespace); + r#gen.generate_function(func, &TypeOwner::Interface(id), AbiVariant::GuestExport); } } self.finish_file(&namespace, store); @@ -573,13 +573,13 @@ impl WorldGenerator for Cpp { let name = WorldKey::Name("$root".to_string()); //WorldKey::Name(resolve.worlds[world].name.clone()); let wasm_import_module = resolve.name_world_key(&name); let binding = Some(name); - let mut gen = self.interface(resolve, binding.as_ref(), true, Some(wasm_import_module)); - let namespace = namespace(resolve, &TypeOwner::World(world), false, &gen.gen.opts); + let mut r#gen = self.interface(resolve, binding.as_ref(), true, Some(wasm_import_module)); + let namespace = namespace(resolve, &TypeOwner::World(world), false, &r#gen.r#gen.opts); for (_name, func) in funcs.iter() { if matches!(func.kind, FunctionKind::Freestanding) { - gen.gen.h_src.change_namespace(&namespace); - gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestImport); + r#gen.r#gen.h_src.change_namespace(&namespace); + r#gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestImport); } } } @@ -593,13 +593,13 @@ impl WorldGenerator for Cpp { ) -> anyhow::Result<()> { let name = WorldKey::Name(resolve.worlds[world].name.clone()); let binding = Some(name); - let mut gen = self.interface(resolve, binding.as_ref(), false, None); - let namespace = namespace(resolve, &TypeOwner::World(world), true, &gen.gen.opts); + let mut r#gen = self.interface(resolve, binding.as_ref(), false, None); + let namespace = namespace(resolve, &TypeOwner::World(world), true, &r#gen.r#gen.opts); for (_name, func) in funcs.iter() { if matches!(func.kind, FunctionKind::Freestanding) { - gen.gen.h_src.change_namespace(&namespace); - gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestExport); + r#gen.r#gen.h_src.change_namespace(&namespace); + r#gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestExport); } } Ok(()) @@ -612,9 +612,9 @@ impl WorldGenerator for Cpp { types: &[(&str, TypeId)], _files: &mut Files, ) { - let mut gen = self.interface(resolve, None, true, Some("$root".to_string())); + let mut r#gen = self.interface(resolve, None, true, Some("$root".to_string())); for (name, id) in types.iter() { - gen.define_type(name, *id); + r#gen.define_type(name, *id); } } @@ -809,7 +809,7 @@ impl SourceWithState { struct CppInterfaceGenerator<'a> { _src: Source, - gen: &'a mut Cpp, + r#gen: &'a mut Cpp, resolve: &'a Resolve, interface: Option, _name: Option<&'a WorldKey>, @@ -828,10 +828,10 @@ impl CppInterfaceGenerator<'_> { let ty = &self.resolve().types[*id]; if matches!(&ty.kind, TypeDefKind::Resource) { let pascal = name.to_upper_camel_case(); - let guest_import = self.gen.imported_interfaces.contains(&iface); - let namespc = namespace(self.resolve, &ty.owner, !guest_import, &self.gen.opts); - self.gen.h_src.change_namespace(&namespc); - uwriteln!(self.gen.h_src.src, "class {pascal};"); + let guest_import = self.r#gen.imported_interfaces.contains(&iface); + let namespc = namespace(self.resolve, &ty.owner, !guest_import, &self.r#gen.opts); + self.r#gen.h_src.change_namespace(&namespc); + uwriteln!(self.r#gen.h_src.src, "class {pascal};"); } } @@ -886,15 +886,16 @@ impl CppInterfaceGenerator<'_> { Default::default(), self.interface .map(TypeOwner::Interface) - .unwrap_or(TypeOwner::World(self.gen.world_id.unwrap())), + .unwrap_or(TypeOwner::World(self.r#gen.world_id.unwrap())), )); - let mut namespace = namespace(self.resolve, &owner, guest_export, &self.gen.opts); + let mut namespace = namespace(self.resolve, &owner, guest_export, &self.r#gen.opts); let is_drop = is_special_method(func); let func_name_h = if !matches!(&func.kind, FunctionKind::Freestanding) { namespace.push(object.clone()); if let FunctionKind::Constructor(_i) = &func.kind { // Fallible constructors return result and are static factory methods - let is_fallible_constructor = self.gen.is_fallible_constructor(self.resolve, func); + let is_fallible_constructor = + self.r#gen.is_fallible_constructor(self.resolve, func); if is_fallible_constructor { String::from("Create") @@ -984,11 +985,11 @@ impl CppInterfaceGenerator<'_> { res }); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, r#"extern "C" __attribute__((__export_name__("{module_prefix}{func_name}")))"# ); let return_via_pointer = false; - self.gen + self.r#gen .c_src .src .push_str(if signature.results.is_empty() || return_via_pointer { @@ -996,41 +997,41 @@ impl CppInterfaceGenerator<'_> { } else { wit_bindgen_c::wasm_type(signature.results[0]) }); - self.gen.c_src.src.push_str(" "); + self.r#gen.c_src.src.push_str(" "); let export_name = match module_name { Some(ref module_name) => make_external_symbol(module_name, &func_name, symbol_variant), None => make_external_component(&func_name), }; // Add prefix to C ABI export functions to avoid conflicts with C++ namespaces - self.gen.c_src.src.push_str("__wasm_export_"); - if let Some(prefix) = self.gen.opts.export_prefix.as_ref() { - self.gen.c_src.src.push_str(prefix); + self.r#gen.c_src.src.push_str("__wasm_export_"); + if let Some(prefix) = self.r#gen.opts.export_prefix.as_ref() { + self.r#gen.c_src.src.push_str(prefix); } - self.gen.c_src.src.push_str(&export_name); - self.gen.c_src.src.push_str("("); + self.r#gen.c_src.src.push_str(&export_name); + self.r#gen.c_src.src.push_str("("); let mut first_arg = true; let mut params = Vec::new(); for (n, ty) in signature.params.iter().enumerate() { let name = format!("arg{n}"); if !first_arg { - self.gen.c_src.src.push_str(", "); + self.r#gen.c_src.src.push_str(", "); } else { first_arg = false; } - self.gen.c_src.src.push_str(wit_bindgen_c::wasm_type(*ty)); - self.gen.c_src.src.push_str(" "); - self.gen.c_src.src.push_str(&name); + self.r#gen.c_src.src.push_str(wit_bindgen_c::wasm_type(*ty)); + self.r#gen.c_src.src.push_str(" "); + self.r#gen.c_src.src.push_str(&name); params.push(name); } if return_via_pointer { if !first_arg { - self.gen.c_src.src.push_str(", "); + self.r#gen.c_src.src.push_str(", "); } - self.gen.c_src.src.push_str(self.gen.opts.ptr_type()); - self.gen.c_src.src.push_str(" resultptr"); + self.r#gen.c_src.src.push_str(self.r#gen.opts.ptr_type()); + self.r#gen.c_src.src.push_str(" resultptr"); params.push("resultptr".into()); } - self.gen.c_src.src.push_str(")\n"); + self.r#gen.c_src.src.push_str(")\n"); params } @@ -1051,7 +1052,7 @@ impl CppInterfaceGenerator<'_> { // let mut sig = String::new(); // Check if this is a fallible constructor (returns result) - let is_fallible_constructor = self.gen.is_fallible_constructor(self.resolve, func); + let is_fallible_constructor = self.r#gen.is_fallible_constructor(self.resolve, func); // not for ctor nor imported dtor on guest (except fallible constructors) if (!matches!(&func.kind, FunctionKind::Constructor(_)) || is_fallible_constructor) @@ -1123,33 +1124,33 @@ impl CppInterfaceGenerator<'_> { import: bool, ) -> Vec { let is_special = is_special_method(func); - let from_namespace = self.gen.h_src.namespace.clone(); + let from_namespace = self.r#gen.h_src.namespace.clone(); let cpp_sig = self.high_level_signature(func, variant, &from_namespace); if cpp_sig.static_member { - self.gen.h_src.src.push_str("static "); + self.r#gen.h_src.src.push_str("static "); } - self.gen.h_src.src.push_str(&cpp_sig.result); + self.r#gen.h_src.src.push_str(&cpp_sig.result); if !cpp_sig.result.is_empty() { - self.gen.h_src.src.push_str(" "); + self.r#gen.h_src.src.push_str(" "); } - self.gen.h_src.src.push_str(&cpp_sig.name); - self.gen.h_src.src.push_str("("); + self.r#gen.h_src.src.push_str(&cpp_sig.name); + self.r#gen.h_src.src.push_str("("); for (num, (arg, typ)) in cpp_sig.arguments.iter().enumerate() { if num > 0 { - self.gen.h_src.src.push_str(", "); + self.r#gen.h_src.src.push_str(", "); } - self.gen.h_src.src.push_str(typ); - self.gen.h_src.src.push_str(" "); - self.gen.h_src.src.push_str(arg); + self.r#gen.h_src.src.push_str(typ); + self.r#gen.h_src.src.push_str(" "); + self.r#gen.h_src.src.push_str(arg); } - self.gen.h_src.src.push_str(")"); + self.r#gen.h_src.src.push_str(")"); if cpp_sig.const_member { - self.gen.h_src.src.push_str(" const"); + self.r#gen.h_src.src.push_str(" const"); } match (&is_special, false, &variant) { (SpecialMethod::Allocate, _, _) => { uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, "{{\ return {OWNED_CLASS_NAME}(new {}({}));\ }}", @@ -1167,14 +1168,14 @@ impl CppInterfaceGenerator<'_> { (SpecialMethod::Dtor, _, _ /*AbiVariant::GuestImport*/) | (SpecialMethod::ResourceDrop, true, _) => { uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, "{{\ delete {};\ }}", cpp_sig.arguments.first().unwrap().0 ); } - _ => self.gen.h_src.src.push_str(";\n"), + _ => self.r#gen.h_src.src.push_str(";\n"), } // we want to separate the lowered signature (wasm) and the high level signature @@ -1189,33 +1190,33 @@ impl CppInterfaceGenerator<'_> { self.print_export_signature(func, variant) } else { // recalulate with c file namespace - let c_namespace = self.gen.c_src.namespace.clone(); + let c_namespace = self.r#gen.c_src.namespace.clone(); let cpp_sig = self.high_level_signature(func, variant, &c_namespace); let mut params = Vec::new(); - self.gen.c_src.src.push_str(&cpp_sig.result); + self.r#gen.c_src.src.push_str(&cpp_sig.result); if !cpp_sig.result.is_empty() { - self.gen.c_src.src.push_str(" "); + self.r#gen.c_src.src.push_str(" "); } - self.gen.c_src.qualify(&cpp_sig.namespace); - self.gen.c_src.src.push_str(&cpp_sig.name); - self.gen.c_src.src.push_str("("); + self.r#gen.c_src.qualify(&cpp_sig.namespace); + self.r#gen.c_src.src.push_str(&cpp_sig.name); + self.r#gen.c_src.src.push_str("("); if cpp_sig.implicit_self { params.push("(*this)".into()); } for (num, (arg, typ)) in cpp_sig.arguments.iter().enumerate() { if num > 0 { - self.gen.c_src.src.push_str(", "); + self.r#gen.c_src.src.push_str(", "); } - self.gen.c_src.src.push_str(typ); - self.gen.c_src.src.push_str(" "); - self.gen.c_src.src.push_str(arg); + self.r#gen.c_src.src.push_str(typ); + self.r#gen.c_src.src.push_str(" "); + self.r#gen.c_src.src.push_str(arg); params.push(arg.clone()); } - self.gen.c_src.src.push_str(")"); + self.r#gen.c_src.src.push_str(")"); if cpp_sig.const_member { - self.gen.c_src.src.push_str(" const"); + self.r#gen.c_src.src.push_str(" const"); } - self.gen.c_src.src.push_str("\n"); + self.r#gen.c_src.src.push_str("\n"); params } } @@ -1234,7 +1235,7 @@ impl CppInterfaceGenerator<'_> { cifg.resolve, &owner.owner, matches!(variant, AbiVariant::GuestExport), - &cifg.gen.opts, + &cifg.r#gen.opts, ); namespace.push(owner.name.as_ref().unwrap().to_upper_camel_case()); namespace @@ -1250,15 +1251,15 @@ impl CppInterfaceGenerator<'_> { let params = self.print_signature(func, variant, !export); let special = is_special_method(func); if !matches!(special, SpecialMethod::Allocate) { - self.gen.c_src.src.push_str("{\n"); - let needs_dealloc = if self.gen.opts.api_style == APIStyle::Symmetric + self.r#gen.c_src.src.push_str("{\n"); + let needs_dealloc = if self.r#gen.opts.api_style == APIStyle::Symmetric && matches!(variant, AbiVariant::GuestExport) { - self.gen + self.r#gen .c_src .src .push_str("std::vector _deallocate;\n"); - self.gen.dependencies.needs_vector = true; + self.r#gen.dependencies.needs_vector = true; true } else { false @@ -1276,7 +1277,7 @@ impl CppInterfaceGenerator<'_> { let wasm_sig = self.declare_import(&module_name, &func.name, &[WasmType::I32], &[]); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, "{wasm_sig}({});", func.params.first().unwrap().0 ); @@ -1286,7 +1287,7 @@ impl CppInterfaceGenerator<'_> { let name = self.declare_import(&module_name, &func.name, &[WasmType::I32], &[]); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, " if (handle>=0) {{ {name}(handle); }}" @@ -1295,8 +1296,8 @@ impl CppInterfaceGenerator<'_> { }, SpecialMethod::Dtor => { let classname = class_namespace(self, func, variant).join("::"); - uwriteln!(self.gen.c_src.src, "(({classname}*)arg0)->handle=-1;"); - uwriteln!(self.gen.c_src.src, "{0}::Dtor(({0}*)arg0);", classname); + uwriteln!(self.r#gen.c_src.src, "(({classname}*)arg0)->handle=-1;"); + uwriteln!(self.r#gen.c_src.src, "{0}::Dtor(({0}*)arg0);", classname); } SpecialMethod::ResourceNew => { let module_name = @@ -1308,9 +1309,9 @@ impl CppInterfaceGenerator<'_> { &[WasmType::I32], ); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, "return {wasm_sig}(({}){});", - self.gen.opts.ptr_type(), + self.r#gen.opts.ptr_type(), func.params.first().unwrap().0 ); } @@ -1325,7 +1326,7 @@ impl CppInterfaceGenerator<'_> { ); let classname = class_namespace(self, func, variant).join("::"); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, "return ({}*){wasm_sig}({});", classname, func.params.first().unwrap().0 @@ -1339,7 +1340,7 @@ impl CppInterfaceGenerator<'_> { self.resolve, owner, matches!(variant, AbiVariant::GuestExport), - &self.gen.opts, + &self.r#gen.opts, ) } else { let owner = &self.resolve.types[match &func.kind { @@ -1356,7 +1357,7 @@ impl CppInterfaceGenerator<'_> { self.resolve, &owner.owner, matches!(variant, AbiVariant::GuestExport), - &self.gen.opts, + &self.r#gen.opts, ); namespace.push(owner.name.as_ref().unwrap().to_upper_camel_case()); namespace @@ -1368,13 +1369,13 @@ impl CppInterfaceGenerator<'_> { f.variant = variant; f.needs_dealloc = needs_dealloc; f.cabi_post = None; - abi::call(f.gen.resolve, variant, lift_lower, func, &mut f, false); + abi::call(f.r#gen.resolve, variant, lift_lower, func, &mut f, false); let ret_area_decl = f.emit_ret_area_if_needed(); let code = format!("{}{}", ret_area_decl, String::from(f.src)); - self.gen.c_src.src.push_str(&code); + self.r#gen.c_src.src.push_str(&code); } } - self.gen.c_src.src.push_str("}\n"); + self.r#gen.c_src.src.push_str("}\n"); // cabi_post if matches!(variant, AbiVariant::GuestExport) && abi::guest_export_needs_post_return(self.resolve, func) @@ -1394,30 +1395,30 @@ impl CppInterfaceGenerator<'_> { None => make_external_component(&func.name), }; uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, "extern \"C\" __attribute__((__weak__, __export_name__(\"cabi_post_{export_name}\")))" ); - uwrite!(self.gen.c_src.src, "void cabi_post_{import_name}("); + uwrite!(self.r#gen.c_src.src, "void cabi_post_{import_name}("); let mut params = Vec::new(); for (i, result) in sig.results.iter().enumerate() { let name = format!("arg{i}"); uwrite!( - self.gen.c_src.src, + self.r#gen.c_src.src, "{} {name}", wit_bindgen_c::wasm_type(*result) ); params.push(name); } - self.gen.c_src.src.push_str(") {\n"); + self.r#gen.c_src.src.push_str(") {\n"); let mut f = FunctionBindgen::new(self, params.clone()); f.params = params; - abi::post_return(f.gen.resolve, func, &mut f); + abi::post_return(f.r#gen.resolve, func, &mut f); let ret_area_decl = f.emit_ret_area_if_needed(); let code = format!("{}{}", ret_area_decl, String::from(f.src)); - self.gen.c_src.src.push_str(&code); - self.gen.c_src.src.push_str("}\n"); + self.r#gen.c_src.src.push_str(&code); + self.r#gen.c_src.src.push_str("}\n"); } } } @@ -1445,10 +1446,10 @@ impl CppInterfaceGenerator<'_> { let name = self.scoped_type_name(id, from_namespace, guest_export); if let Flavor::Argument(AbiVariant::GuestImport) = flavor { - match self.gen.opts.ownership { + match self.r#gen.opts.ownership { Ownership::Owning => name.to_string(), Ownership::CoarseBorrowing => { - if self.gen.types.get(id).has_own_handle { + if self.r#gen.types.get(id).has_own_handle { name.to_string() } else { format!("{}Param", name) @@ -1470,7 +1471,7 @@ impl CppInterfaceGenerator<'_> { guest_export: bool, ) -> String { let ty = &self.resolve.types[id]; - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); let mut relative = SourceWithState { namespace: Vec::from(from_namespace), ..Default::default() @@ -1499,22 +1500,22 @@ impl CppInterfaceGenerator<'_> { Type::F64 => "double".into(), Type::String => match flavor { Flavor::BorrowedArgument => { - self.gen.dependencies.needs_string_view = true; + self.r#gen.dependencies.needs_string_view = true; "std::string_view".into() } Flavor::Argument(var) if matches!(var, AbiVariant::GuestImport) - || self.gen.opts.api_style == APIStyle::Symmetric => + || self.r#gen.opts.api_style == APIStyle::Symmetric => { - self.gen.dependencies.needs_string_view = true; + self.r#gen.dependencies.needs_string_view = true; "std::string_view".into() } Flavor::Argument(AbiVariant::GuestExport) => { - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_wit = true; "wit::string".into() } _ => { - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_wit = true; "wit::string".into() } }, @@ -1586,7 +1587,7 @@ impl CppInterfaceGenerator<'_> { } a + &self.type_name(b, from_namespace, flavor) }); - self.gen.dependencies.needs_tuple = true; + self.r#gen.dependencies.needs_tuple = true; String::from("std::tuple<") + &types + ">" } TypeDefKind::Variant(_v) => { @@ -1606,7 +1607,7 @@ impl CppInterfaceGenerator<'_> { Flavor::Argument(AbiVariant::GuestImport) => Flavor::BorrowedArgument, _ => Flavor::InStruct, }; - self.gen.dependencies.needs_optional = true; + self.r#gen.dependencies.needs_optional = true; "std::optional<".to_string() + &self.type_name(o, from_namespace, template_flavor) + ">" @@ -1617,7 +1618,7 @@ impl CppInterfaceGenerator<'_> { let err_type = r.err.as_ref().map_or(String::from("wit::Void"), |ty| { self.type_name(ty, from_namespace, template_flavor) }); - self.gen.dependencies.needs_expected = true; + self.r#gen.dependencies.needs_expected = true; "std::expected<".to_string() + &self.optional_type_name(r.ok.as_ref(), from_namespace, template_flavor) + ", " @@ -1637,16 +1638,16 @@ impl CppInterfaceGenerator<'_> { let inner = self.type_name(ty, from_namespace, element_flavor); match flavor { Flavor::BorrowedArgument => { - self.gen.dependencies.needs_span = true; + self.r#gen.dependencies.needs_span = true; format!("std::span<{inner} const>") } Flavor::Argument(var) if matches!(var, AbiVariant::GuestImport) - || self.gen.opts.api_style == APIStyle::Symmetric => + || self.r#gen.opts.api_style == APIStyle::Symmetric => { - self.gen.dependencies.needs_span = true; + self.r#gen.dependencies.needs_span = true; // If the list has an owning handle, it must support moving, so can't be const - let constness = if self.gen.types.get(*id).has_own_handle { + let constness = if self.r#gen.types.get(*id).has_own_handle { "" } else { " const" @@ -1654,11 +1655,11 @@ impl CppInterfaceGenerator<'_> { format!("std::span<{inner}{constness}>") } Flavor::Argument(AbiVariant::GuestExport) => { - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_wit = true; format!("wit::vector<{inner}>") } _ => { - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_wit = true; format!("wit::vector<{inner}>") } } @@ -1683,8 +1684,9 @@ impl CppInterfaceGenerator<'_> { ) -> (String, String) { let mut extern_name = String::from("__wasm_import_"); extern_name.push_str(&make_external_symbol(module_name, name, variant)); - let import = format!("extern \"C\" __attribute__((import_module(\"{module_name}\")))\n __attribute__((import_name(\"{name}\")))\n {result} {extern_name}({args});\n") - ; + let import = format!( + "extern \"C\" __attribute__((import_module(\"{module_name}\")))\n __attribute__((import_name(\"{name}\")))\n {result} {extern_name}({args});\n" + ); (extern_name, import) } @@ -1709,7 +1711,7 @@ impl CppInterfaceGenerator<'_> { }; let variant = AbiVariant::GuestImport; let (name, code) = self.declare_import2(module_name, name, &args, result, variant); - self.gen.extern_c_decls.push_str(&code); + self.r#gen.extern_c_decls.push_str(&code); name } @@ -1731,10 +1733,10 @@ impl CppInterfaceGenerator<'_> { namespc: &[String], ) { let (flavor, needs_param_type) = { - match self.gen.opts.ownership { + match self.r#gen.opts.ownership { Ownership::Owning => (Flavor::InStruct, false), Ownership::CoarseBorrowing => { - if self.gen.types.get(id).has_own_handle { + if self.r#gen.types.get(id).has_own_handle { (Flavor::InStruct, false) } else { (Flavor::BorrowedArgument, true) @@ -1747,13 +1749,13 @@ impl CppInterfaceGenerator<'_> { if needs_param_type { let pascal = format!("{name}-param").to_pascal_case(); - uwriteln!(self.gen.h_src.src, "struct {pascal} {{"); + uwriteln!(self.r#gen.h_src.src, "struct {pascal} {{"); for field in record.fields.iter() { let typename = self.type_name(&field.ty, namespc, flavor); let fname = to_c_ident(&field.name); - uwriteln!(self.gen.h_src.src, "{typename} {fname};"); + uwriteln!(self.r#gen.h_src.src, "{typename} {fname};"); } - uwriteln!(self.gen.h_src.src, "}};"); + uwriteln!(self.r#gen.h_src.src, "}};"); } } @@ -1762,7 +1764,7 @@ impl CppInterfaceGenerator<'_> { TypeOwner::Interface(intf) => { // For resources used in export functions, check if the resource's owner // interface is in imported_interfaces (which was populated during import()) - !self.gen.imported_interfaces.contains(&intf) + !self.r#gen.imported_interfaces.contains(&intf) } TypeOwner::World(_) => { // World-level resources are treated as imports, not exports @@ -1787,21 +1789,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let ty = &self.resolve.types[id]; let guest_export = self.is_exported_type(ty); - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); - if self.gen.is_first_definition(&namespc, name) { - self.gen.h_src.change_namespace(&namespc); - Self::docs(&mut self.gen.h_src.src, docs); + if self.r#gen.is_first_definition(&namespc, name) { + self.r#gen.h_src.change_namespace(&namespc); + Self::docs(&mut self.r#gen.h_src.src, docs); let pascal = name.to_pascal_case(); - uwriteln!(self.gen.h_src.src, "struct {pascal} {{"); + uwriteln!(self.r#gen.h_src.src, "struct {pascal} {{"); for field in record.fields.iter() { - Self::docs(&mut self.gen.h_src.src, &field.docs); + Self::docs(&mut self.r#gen.h_src.src, &field.docs); let typename = self.type_name(&field.ty, &namespc, Flavor::InStruct); let fname = to_c_ident(&field.name); - uwriteln!(self.gen.h_src.src, "{typename} {fname};"); + uwriteln!(self.r#gen.h_src.src, "{typename} {fname};"); } - uwriteln!(self.gen.h_src.src, "}};"); + uwriteln!(self.r#gen.h_src.src, "}};"); self.type_record_param(id, name, record, namespc.as_slice()); } } @@ -1814,31 +1816,31 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let type_ = &self.resolve.types[id]; if let TypeOwner::Interface(intf) = type_.owner { - let guest_import = self.gen.imported_interfaces.contains(&intf); + let guest_import = self.r#gen.imported_interfaces.contains(&intf); let definition = !(guest_import); - let store = self.gen.start_new_file(Some(definition)); - let mut world_name = to_c_ident(&self.gen.world); + let store = self.r#gen.start_new_file(Some(definition)); + let mut world_name = to_c_ident(&self.r#gen.world); world_name.push_str("::"); - let namespc = namespace(self.resolve, &type_.owner, !guest_import, &self.gen.opts); + let namespc = namespace(self.resolve, &type_.owner, !guest_import, &self.r#gen.opts); let pascal = name.to_upper_camel_case(); let mut user_filename = namespc.clone(); user_filename.push(pascal.clone()); if definition { uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, r#"/* User class definition file, autogenerated once, then user modified * Updated versions of this file are generated into {pascal}.template. */"# ); } - self.gen.h_src.change_namespace(&namespc); + self.r#gen.h_src.change_namespace(&namespc); if !definition { - self.gen.dependencies.needs_imported_resources = true; + self.r#gen.dependencies.needs_imported_resources = true; } else { - self.gen.dependencies.needs_exported_resources = true; + self.r#gen.dependencies.needs_exported_resources = true; } - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_wit = true; let base_type = match (definition, false) { (true, false) => format!("wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}<{pascal}>"), @@ -1851,8 +1853,8 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> (true, true) => format!("wit::{RESOURCE_IMPORT_BASE_CLASS_NAME}<{pascal}>"), }; let derive = format!(" : public {base_type}"); - uwriteln!(self.gen.h_src.src, "class {pascal}{derive} {{\n"); - uwriteln!(self.gen.h_src.src, "public:\n"); + uwriteln!(self.r#gen.h_src.src, "class {pascal}{derive} {{\n"); + uwriteln!(self.r#gen.h_src.src, "public:\n"); let variant = if guest_import { AbiVariant::GuestImport } else { @@ -1894,7 +1896,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> // For non-fallible constructors on export side, generate a New allocator method // For fallible constructors, the user provides their own Create method let is_fallible_constructor = - self.gen.is_fallible_constructor(self.resolve, func); + self.r#gen.is_fallible_constructor(self.resolve, func); if matches!(func.kind, FunctionKind::Constructor(_)) && matches!(variant, AbiVariant::GuestExport) @@ -1917,15 +1919,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> if !definition { // consuming constructor from handle (bindings) - uwriteln!(self.gen.h_src.src, "{pascal}({base_type} &&);",); - uwriteln!(self.gen.h_src.src, "{pascal}({pascal}&&) = default;"); + uwriteln!(self.r#gen.h_src.src, "{pascal}({base_type} &&);",); + uwriteln!(self.r#gen.h_src.src, "{pascal}({pascal}&&) = default;"); uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, "{pascal}& operator=({pascal}&&) = default;" ); - self.gen.c_src.qualify(&namespc); + self.r#gen.c_src.qualify(&namespc); uwriteln!( - self.gen.c_src.src, + self.r#gen.c_src.src, "{pascal}::{pascal}({base_type}&&b) : {base_type}(std::move(b)) {{}}" ); } @@ -1961,35 +1963,35 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> }; self.generate_function(&func2, &TypeOwner::Interface(intf), variant); } - uwriteln!(self.gen.h_src.src, "}};\n"); - self.gen.finish_file(&user_filename, store); + uwriteln!(self.r#gen.h_src.src, "}};\n"); + self.r#gen.finish_file(&user_filename, store); } else if matches!(type_.owner, TypeOwner::World(_)) { // Handle world-level resources - treat as imported resources let guest_export = false; // World-level resources are treated as imports - let namespc = namespace(self.resolve, &type_.owner, guest_export, &self.gen.opts); - self.gen.h_src.change_namespace(&namespc); + let namespc = namespace(self.resolve, &type_.owner, guest_export, &self.r#gen.opts); + self.r#gen.h_src.change_namespace(&namespc); let pascal = name.to_upper_camel_case(); - self.gen.dependencies.needs_imported_resources = true; - self.gen.dependencies.needs_wit = true; + self.r#gen.dependencies.needs_imported_resources = true; + self.r#gen.dependencies.needs_wit = true; let base_type = format!("wit::{RESOURCE_IMPORT_BASE_CLASS_NAME}"); let derive = format!(" : public {base_type}"); - uwriteln!(self.gen.h_src.src, "class {pascal}{derive}{{\n"); - uwriteln!(self.gen.h_src.src, "public:\n"); + uwriteln!(self.r#gen.h_src.src, "class {pascal}{derive}{{\n"); + uwriteln!(self.r#gen.h_src.src, "public:\n"); // Add destructor and constructor - uwriteln!(self.gen.h_src.src, "~{pascal}();"); + uwriteln!(self.r#gen.h_src.src, "~{pascal}();"); uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, "{pascal}(wit::{RESOURCE_IMPORT_BASE_CLASS_NAME} &&);" ); - uwriteln!(self.gen.h_src.src, "{pascal}({pascal}&&) = default;"); + uwriteln!(self.r#gen.h_src.src, "{pascal}({pascal}&&) = default;"); uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, "{pascal}& operator=({pascal}&&) = default;" ); - uwriteln!(self.gen.h_src.src, "}};\n"); + uwriteln!(self.r#gen.h_src.src, "}};\n"); } } @@ -2002,22 +2004,22 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let ty = &self.resolve.types[id]; let guest_export = self.is_exported_type(ty); - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); - if self.gen.is_first_definition(&namespc, name) { - self.gen.h_src.change_namespace(&namespc); - Self::docs(&mut self.gen.h_src.src, docs); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); + if self.r#gen.is_first_definition(&namespc, name) { + self.r#gen.h_src.change_namespace(&namespc); + Self::docs(&mut self.r#gen.h_src.src, docs); let pascal = name.to_pascal_case(); let int_repr = wit_bindgen_c::int_repr(wit_bindgen_c::flags_repr(flags)); - uwriteln!(self.gen.h_src.src, "enum class {pascal} : {int_repr} {{"); - uwriteln!(self.gen.h_src.src, "k_None = 0,"); + uwriteln!(self.r#gen.h_src.src, "enum class {pascal} : {int_repr} {{"); + uwriteln!(self.r#gen.h_src.src, "k_None = 0,"); for (n, field) in flags.flags.iter().enumerate() { - Self::docs(&mut self.gen.h_src.src, &field.docs); + Self::docs(&mut self.r#gen.h_src.src, &field.docs); let fname = to_c_ident(&field.name).to_pascal_case(); - uwriteln!(self.gen.h_src.src, "k{fname} = (1ULL<<{n}),"); + uwriteln!(self.r#gen.h_src.src, "k{fname} = (1ULL<<{n}),"); } - uwriteln!(self.gen.h_src.src, "}};"); + uwriteln!(self.r#gen.h_src.src, "}};"); uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, r#"static inline {pascal} operator|({pascal} a, {pascal} b) {{ return {pascal}({int_repr}(a)|{int_repr}(b)); }} static inline {pascal} operator&({pascal} a, {pascal} b) {{ return {pascal}({int_repr}(a)&{int_repr}(b)); }}"# ); @@ -2043,32 +2045,35 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let ty = &self.resolve.types[id]; let guest_export = self.is_exported_type(ty); - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); - if self.gen.is_first_definition(&namespc, name) { - self.gen.h_src.change_namespace(&namespc); - Self::docs(&mut self.gen.h_src.src, docs); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); + if self.r#gen.is_first_definition(&namespc, name) { + self.r#gen.h_src.change_namespace(&namespc); + Self::docs(&mut self.r#gen.h_src.src, docs); let pascal = name.to_pascal_case(); - uwriteln!(self.gen.h_src.src, "struct {pascal} {{"); + uwriteln!(self.r#gen.h_src.src, "struct {pascal} {{"); let mut inner_namespace = namespc.clone(); inner_namespace.push(pascal.clone()); let mut all_types = String::new(); for case in variant.cases.iter() { - Self::docs(&mut self.gen.h_src.src, &case.docs); + Self::docs(&mut self.r#gen.h_src.src, &case.docs); let case_pascal = to_c_ident(&case.name).to_pascal_case(); if !all_types.is_empty() { all_types += ", "; } all_types += &case_pascal; - uwrite!(self.gen.h_src.src, "struct {case_pascal} {{"); + uwrite!(self.r#gen.h_src.src, "struct {case_pascal} {{"); if let Some(ty) = case.ty.as_ref() { let typestr = self.type_name(ty, &inner_namespace, Flavor::InStruct); - uwrite!(self.gen.h_src.src, " {typestr} value; ") + uwrite!(self.r#gen.h_src.src, " {typestr} value; ") } - uwriteln!(self.gen.h_src.src, "}};"); + uwriteln!(self.r#gen.h_src.src, "}};"); } - uwriteln!(self.gen.h_src.src, " std::variant<{all_types}> variants;"); - uwriteln!(self.gen.h_src.src, "}};"); - self.gen.dependencies.needs_variant = true; + uwriteln!( + self.r#gen.h_src.src, + " std::variant<{all_types}> variants;" + ); + uwriteln!(self.r#gen.h_src.src, "}};"); + self.r#gen.dependencies.needs_variant = true; } } @@ -2101,22 +2106,22 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let ty = &self.resolve.types[id]; let guest_export = self.is_exported_type(ty); - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); - if self.gen.is_first_definition(&namespc, name) { - self.gen.h_src.change_namespace(&namespc); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); + if self.r#gen.is_first_definition(&namespc, name) { + self.r#gen.h_src.change_namespace(&namespc); let pascal = name.to_pascal_case(); - Self::docs(&mut self.gen.h_src.src, docs); + Self::docs(&mut self.r#gen.h_src.src, docs); let int_t = wit_bindgen_c::int_repr(enum_.tag()); - uwriteln!(self.gen.h_src.src, "enum class {pascal} : {int_t} {{"); + uwriteln!(self.r#gen.h_src.src, "enum class {pascal} : {int_t} {{"); for (i, case) in enum_.cases.iter().enumerate() { - Self::docs(&mut self.gen.h_src.src, &case.docs); + Self::docs(&mut self.r#gen.h_src.src, &case.docs); uwriteln!( - self.gen.h_src.src, + self.r#gen.h_src.src, " k{} = {i},", to_c_ident(&case.name).to_pascal_case(), ); } - uwriteln!(self.gen.h_src.src, "}};\n"); + uwriteln!(self.r#gen.h_src.src, "}};\n"); } } @@ -2129,12 +2134,12 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ) { let ty = &self.resolve.types[id]; let guest_export = self.is_exported_type(ty); - let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.gen.opts); - self.gen.h_src.change_namespace(&namespc); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &self.r#gen.opts); + self.r#gen.h_src.change_namespace(&namespc); let pascal = name.to_pascal_case(); - Self::docs(&mut self.gen.h_src.src, docs); + Self::docs(&mut self.r#gen.h_src.src, docs); let typename = self.type_name(alias_type, &namespc, Flavor::InStruct); - uwriteln!(self.gen.h_src.src, "using {pascal} = {typename};"); + uwriteln!(self.r#gen.h_src.src, "using {pascal} = {typename};"); } fn type_list( @@ -2173,7 +2178,7 @@ struct CabiPostInformation { } struct FunctionBindgen<'a, 'b> { - gen: &'b mut CppInterfaceGenerator<'a>, + r#gen: &'b mut CppInterfaceGenerator<'a>, params: Vec, tmp: usize, namespace: Vec, @@ -2192,9 +2197,9 @@ struct FunctionBindgen<'a, 'b> { } impl<'a, 'b> FunctionBindgen<'a, 'b> { - fn new(gen: &'b mut CppInterfaceGenerator<'a>, params: Vec) -> Self { + fn new(r#gen: &'b mut CppInterfaceGenerator<'a>, params: Vec) -> Self { Self { - gen, + r#gen, params, tmp: 0, namespace: Default::default(), @@ -2310,7 +2315,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { }, Alignment::Pointer => "uintptr_t", }; - let static_var = if self.gen.in_guest_import { + let static_var = if self.r#gen.in_guest_import { "" } else { "static " @@ -2348,7 +2353,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { match inst { abi::Instruction::GetArg { nth } => { if *nth == 0 && self.params[0].as_str() == "self" { - if self.gen.in_guest_import { + if self.r#gen.in_guest_import { results.push("(*this)".to_string()); } else { results.push("(*lookup_resource(self))".to_string()); @@ -2361,7 +2366,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::Bitcasts { casts } => { for (cast, op) in casts.iter().zip(operands) { // let op = op; - results.push(self.gen.gen.perform_cast(op, cast)); + results.push(self.r#gen.r#gen.perform_cast(op, cast)); } } abi::Instruction::ConstZero { tys } => { @@ -2442,7 +2447,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {} = ({})({}.data());\n", ptr, - self.gen.gen.opts.ptr_type(), + self.r#gen.r#gen.opts.ptr_type(), val )); self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); @@ -2463,7 +2468,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {} = ({})({}.data());\n", ptr, - self.gen.gen.opts.ptr_type(), + self.r#gen.r#gen.opts.ptr_type(), val )); self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); @@ -2481,12 +2486,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let val = format!("vec{}", tmp); let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); - let size = self.gen.sizes.size(element); + let size = self.r#gen.sizes.size(element); self.push_str(&format!("auto&& {} = {};\n", val, operands[0])); self.push_str(&format!( "auto {} = ({})({}.data());\n", ptr, - self.gen.gen.opts.ptr_type(), + self.r#gen.r#gen.opts.ptr_type(), val )); self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); @@ -2510,10 +2515,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let len = format!("len{}", tmp); let inner = self - .gen + .r#gen .type_name(element, &self.namespace, Flavor::InStruct); self.push_str(&format!("auto {} = {};\n", len, operands[1])); - let result = if self.gen.gen.opts.api_style == APIStyle::Symmetric + let result = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { format!( @@ -2529,7 +2534,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let len = format!("len{}", tmp); uwriteln!(self.src, "auto {} = {};\n", len, operands[1]); - let result = if self.gen.gen.opts.api_style == APIStyle::Symmetric + let result = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { assert!(self.needs_dealloc); @@ -2547,16 +2552,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::ListLift { element, .. } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); - let size = self.gen.sizes.size(element); - let _align = self.gen.sizes.align(element); - let flavor = if self.gen.gen.opts.api_style == APIStyle::Symmetric + let size = self.r#gen.sizes.size(element); + let _align = self.r#gen.sizes.align(element); + let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { Flavor::BorrowedArgument } else { Flavor::InStruct }; - let vtype = self.gen.type_name(element, &self.namespace, flavor); + let vtype = self.r#gen.type_name(element, &self.namespace, flavor); let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); @@ -2573,7 +2578,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { "#, )); - if self.gen.gen.opts.api_style == APIStyle::Symmetric + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { assert!(self.needs_dealloc); @@ -2596,11 +2601,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!(self.src, "{result}.initialize(i, std::move(e{tmp}));"); uwriteln!(self.src, "}}"); - if self.gen.gen.opts.api_style == APIStyle::Symmetric + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { results.push(format!("{result}.get_const_view()")); - if self.gen.gen.opts.api_style == APIStyle::Symmetric + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { self.leak_on_insertion.replace(format!( @@ -2621,7 +2626,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::RecordLift { record, ty, .. } => { let mut result = - self.gen + self.r#gen .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); result.push('{'); for (_field, val) in record.fields.iter().zip(operands) { @@ -2637,12 +2642,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let op = &operands[0]; // Check if this is an imported or exported resource - let resource_ty = &self.gen.resolve.types[*ty]; + let resource_ty = &self.r#gen.resolve.types[*ty]; let resource_ty = match &resource_ty.kind { - TypeDefKind::Type(Type::Id(id)) => &self.gen.resolve.types[*id], + TypeDefKind::Type(Type::Id(id)) => &self.r#gen.resolve.types[*id], _ => resource_ty, }; - let is_exported = self.gen.is_exported_type(resource_ty); + let is_exported = self.r#gen.is_exported_type(resource_ty); if is_exported { // Exported resources use .release()->handle @@ -2674,7 +2679,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { AbiVariant::GuestImport => { let tmp = self.tmp(); let var = self.tempname("obj", tmp); - let tname = self.gen.type_name( + let tname = self.r#gen.type_name( &Type::Id(*ty), &self.namespace, Flavor::Argument(self.variant), @@ -2697,19 +2702,19 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { AbiVariant::GuestExport => { let tmp = self.tmp(); let var = self.tempname("obj", tmp); - let tname = self.gen.type_name( + let tname = self.r#gen.type_name( &Type::Id(*ty), &self.namespace, Flavor::Argument(self.variant), ); // Check if this is an imported or exported resource - let resource_ty = &self.gen.resolve.types[*ty]; + let resource_ty = &self.r#gen.resolve.types[*ty]; let resource_ty = match &resource_ty.kind { - TypeDefKind::Type(Type::Id(id)) => &self.gen.resolve.types[*id], + TypeDefKind::Type(Type::Id(id)) => &self.r#gen.resolve.types[*id], _ => resource_ty, }; - let is_exported = self.gen.is_exported_type(resource_ty); + let is_exported = self.r#gen.is_exported_type(resource_ty); if is_exported { // Exported resources use ::Owned typedef @@ -2732,7 +2737,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { AbiVariant::GuestExportAsyncStackful => todo!(), }, (Handle::Borrow(ty), true) => { - let tname = self.gen.type_name( + let tname = self.r#gen.type_name( &Type::Id(*ty), &self.namespace, Flavor::Argument(self.variant), @@ -2742,7 +2747,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { (Handle::Borrow(ty), false) => match self.variant { AbiVariant::GuestImport => results.push(op.clone()), AbiVariant::GuestExport => { - let tname = self.gen.type_name( + let tname = self.r#gen.type_name( &Type::Id(*ty), &self.namespace, Flavor::Argument(self.variant), @@ -2768,7 +2773,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { &(tuple .types .iter() - .map(|t| self.gen.type_name(t, &self.namespace, Flavor::InStruct))) + .map(|t| self.r#gen.type_name(t, &self.namespace, Flavor::InStruct))) .collect::>() .join(", "), ); @@ -2790,7 +2795,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } Int::U64 => { let name = - self.gen + self.r#gen .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); let tmp = self.tmp(); let tempname = self.tempname("flags", tmp); @@ -2804,7 +2809,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::FlagsLift { flags, ty, .. } => { let typename = - self.gen + self.r#gen .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); match wit_bindgen_c::flags_repr(flags) { Int::U8 | Int::U16 | Int::U32 => { @@ -2853,7 +2858,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let expr_to_match = format!("({}).variants.index()", operands[0]); let elem_ns = - self.gen + self.r#gen .type_name(&Type::Id(*var_ty), &self.namespace, Flavor::InStruct); uwriteln!(self.src, "switch ((int32_t) {}) {{", expr_to_match); @@ -2888,7 +2893,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .collect::>(); let ty = self - .gen + .r#gen .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); let resultno = self.tmp(); let result = format!("variant{resultno}"); @@ -2896,7 +2901,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let op0 = &operands[0]; // Use std::optional to avoid default constructor issues - self.gen.gen.dependencies.needs_optional = true; + self.r#gen.r#gen.dependencies.needs_optional = true; uwriteln!(self.src, "std::optional<{ty}> {result}_opt;"); uwriteln!(self.src, "switch ({op0}) {{"); for (i, (case, (block, block_results))) in @@ -2919,7 +2924,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::EnumLower { .. } => results.push(format!("int32_t({})", operands[0])), abi::Instruction::EnumLift { ty, .. } => { let typename = - self.gen + self.r#gen .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); results.push(format!("({typename}){}", &operands[0])); } @@ -2953,7 +2958,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } else { Flavor::InStruct }; - let ty = self.gen.type_name(payload, &self.namespace, flavor); + let ty = self.r#gen.type_name(payload, &self.namespace, flavor); let is_function_param = self.params.iter().any(|p| p == op0); let value_extract = if matches!(payload, Type::String) && matches!(self.variant, AbiVariant::GuestImport) @@ -2982,14 +2987,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let (_none, none_results) = self.blocks.pop().unwrap(); assert!(none_results.is_empty()); assert!(some_results.len() == 1); - let flavor = if self.gen.gen.opts.api_style == APIStyle::Symmetric + let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { Flavor::BorrowedArgument } else { Flavor::InStruct }; - let type_name = self.gen.type_name(payload, &self.namespace, flavor); + let type_name = self.r#gen.type_name(payload, &self.namespace, flavor); let full_type = format!("std::optional<{type_name}>"); let op0 = &operands[0]; @@ -3031,12 +3036,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } let op0 = &operands[0]; - let ok_ty = self.gen.optional_type_name( + let ok_ty = self.r#gen.optional_type_name( result.ok.as_ref(), &self.namespace, Flavor::InStruct, ); - let err_ty = self.gen.optional_type_name( + let err_ty = self.r#gen.optional_type_name( result.err.as_ref(), &self.namespace, Flavor::InStruct, @@ -3075,18 +3080,18 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } if result.err.is_none() { err.clear(); - self.gen.gen.dependencies.needs_wit = true; + self.r#gen.r#gen.dependencies.needs_wit = true; err_result = String::from("wit::Void{}"); } else { err_result = move_if_necessary(&err_results[0]); } - let ok_type = self.gen.optional_type_name( + let ok_type = self.r#gen.optional_type_name( result.ok.as_ref(), &self.namespace, Flavor::InStruct, ); let err_type = result.err.as_ref().map_or(String::from("wit::Void"), |ty| { - self.gen.type_name(ty, &self.namespace, Flavor::InStruct) + self.r#gen.type_name(ty, &self.namespace, Flavor::InStruct) }); let full_type = format!("std::expected<{ok_type}, {err_type}>",); let err_type = "std::unexpected"; @@ -3095,7 +3100,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let resultname = self.tempname("result", tmp); // Use std::optional to avoid default constructor issues with std::expected - self.gen.gen.dependencies.needs_optional = true; + self.r#gen.r#gen.dependencies.needs_optional = true; let ok_assign = if result.ok.is_some() { format!("{resultname}_opt.emplace({full_type}({ok_result}));") } else { @@ -3117,12 +3122,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::CallWasm { name, sig } => { let module_name = self - .gen + .r#gen .wasm_import_module .as_ref() .map(|e| { - self.gen - .gen + self.r#gen + .r#gen .import_prefix .as_ref() .cloned() @@ -3132,7 +3137,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .unwrap(); let func = self - .gen + .r#gen .declare_import(&module_name, name, &sig.params, &sig.results); // ... then call the function with all our operands @@ -3154,7 +3159,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::CallInterface { func, .. } => { // dbg!(func); self.let_results(if func.result.is_some() { 1 } else { 0 }, results); - let (namespace, func_name_h) = self.gen.func_namespace_name(func, true, true); + let (namespace, func_name_h) = self.r#gen.func_namespace_name(func, true, true); if matches!(func.kind, FunctionKind::Method(_)) { let this = operands.remove(0); uwrite!(self.src, "({this}).get()."); @@ -3187,12 +3192,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { _ => { assert!(*amt == operands.len()); // Fallible constructors return expected, not void - let is_fallible_constructor = - self.gen.gen.is_fallible_constructor(self.gen.resolve, func); + let is_fallible_constructor = self + .r#gen + .r#gen + .is_fallible_constructor(self.r#gen.resolve, func); match &func.kind { FunctionKind::Constructor(_) - if self.gen.gen.opts.is_only_handle(self.variant) + if self.r#gen.r#gen.opts.is_only_handle(self.variant) && !is_fallible_constructor => { // strange but works @@ -3230,7 +3237,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ret_type: _cabi_post_type, }) = self.cabi_post.as_ref() { - let cabi_post_name = self.gen.declare_import( + let cabi_post_name = self.r#gen.declare_import( &format!("cabi_post_{func_module}"), func_name, &[WasmType::Pointer], @@ -3239,7 +3246,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.src.push_str(&format!(", ret, {})", cabi_post_name)); } if matches!(func.kind, FunctionKind::Constructor(_)) - && self.gen.gen.opts.is_only_handle(self.variant) + && self.r#gen.r#gen.opts.is_only_handle(self.variant) && !is_fallible_constructor { // we wrapped the handle in an object, so unpack it @@ -3273,7 +3280,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!(self.src, "size_t {len} = {};", operands[1]); let i = self.tempname("i", tmp); uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); - let size = self.gen.sizes.size(element); + let size = self.r#gen.sizes.size(element); uwriteln!( self.src, "uint8_t* base = {ptr} + {i} * {size};", @@ -3302,14 +3309,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.src.push_str("}\n"); } abi::Instruction::PointerLoad { offset } => { - let ptr_type = self.gen.gen.opts.ptr_type(); + let ptr_type = self.r#gen.r#gen.opts.ptr_type(); self.load(ptr_type, *offset, operands, results) } abi::Instruction::LengthLoad { offset } => { self.load("size_t", *offset, operands, results) } abi::Instruction::PointerStore { offset } => { - let ptr_type = self.gen.gen.opts.ptr_type(); + let ptr_type = self.r#gen.r#gen.opts.ptr_type(); self.store(ptr_type, *offset, operands) } abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), @@ -3342,7 +3349,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!( self.src, "{} ptr{tmp} = ({0})(&ret_area);", - self.gen.gen.opts.ptr_type(), + self.r#gen.r#gen.opts.ptr_type(), ); format!("ptr{}", tmp) @@ -3360,7 +3367,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } fn sizes(&self) -> &wit_bindgen_core::wit_parser::SizeAlign { - &self.gen.sizes + &self.r#gen.sizes } fn is_list_canonical( @@ -3372,7 +3379,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { return false; } match ty { - Type::Id(id) => !self.gen.gen.types.get(*id).has_resource, + Type::Id(id) => !self.r#gen.r#gen.types.get(*id).has_resource, _ => true, } } diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 32d5a8128..c22389c2a 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -6,7 +6,7 @@ use std::fmt::Write; use std::mem; use std::ops::Deref; use wit_bindgen_core::abi::{self, Bindgen, Bitcast, Instruction}; -use wit_bindgen_core::{uwrite, uwriteln, Direction, Ns}; +use wit_bindgen_core::{Direction, Ns, uwrite, uwriteln}; use wit_parser::abi::WasmType; use wit_parser::{ Alignment, ArchitectureSize, Docs, FunctionKind, Handle, Resolve, SizeAlign, Type, TypeDefKind, @@ -385,7 +385,10 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { let address = self.locals.tmp("address"); let buffer_size = self.get_size_for_type(results); let align = self.get_align_for_type(results); - uwriteln!(self.src, "void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});"); + uwriteln!( + self.src, + "void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});" + ); // TODO: Store the address somewhere so we can free it when the task completes. address diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 7ed582e30..4f30b8f68 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -8,7 +8,7 @@ use std::fmt::Write; use std::ops::Deref; use wit_bindgen_core::abi::LiftLower; use wit_bindgen_core::{ - abi, uwrite, uwriteln, Direction, InterfaceGenerator as CoreInterfaceGenerator, + Direction, InterfaceGenerator as CoreInterfaceGenerator, abi, uwrite, uwriteln, }; use wit_parser::abi::AbiVariant; use wit_parser::{ @@ -674,7 +674,7 @@ impl InterfaceGenerator<'_> { match &ty.kind { TypeDefKind::Type(ty) => self.is_primative_list(ty), TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => { - return true + return true; } _ => false, } diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index 69c130cb0..205e47cba 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::ops::Deref; use std::{iter, mem}; -use wit_bindgen_core::{uwrite, Direction, Files, InterfaceGenerator as _, WorldGenerator}; +use wit_bindgen_core::{Direction, Files, InterfaceGenerator as _, WorldGenerator, uwrite}; use wit_component::WitPrinter; use wit_parser::abi::WasmType; use wit_parser::{ @@ -105,13 +105,13 @@ impl WorldGenerator for CSharp { ) -> anyhow::Result<()> { let name = interface_name(self, resolve, key, Direction::Import); self.interface_names.insert(id, name.clone()); - let mut gen = self.interface(resolve, &name, Direction::Import); + let mut r#gen = self.interface(resolve, &name, Direction::Import); - let mut old_resources = mem::take(&mut gen.csharp_gen.all_resources); - gen.types(id); - let new_resources = mem::take(&mut gen.csharp_gen.all_resources); + let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources); + r#gen.types(id); + let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources); old_resources.extend(new_resources.clone()); - gen.csharp_gen.all_resources = old_resources; + r#gen.csharp_gen.all_resources = old_resources; for (resource, funcs) in by_resource( resolve.interfaces[id] @@ -121,23 +121,23 @@ impl WorldGenerator for CSharp { new_resources.keys().copied(), ) { if let Some(resource) = resource { - gen.start_resource(resource, Some(key)); + r#gen.start_resource(resource, Some(key)); } let import_module_name = &resolve.name_world_key(key); for func in funcs { - gen.import(import_module_name, func); + r#gen.import(import_module_name, func); } if resource.is_some() { - gen.end_resource(); + r#gen.end_resource(); } } // for anonymous types - gen.define_interface_types(id); + r#gen.define_interface_types(id); - gen.add_interface_fragment(false); + r#gen.add_interface_fragment(false); Ok(()) } @@ -153,26 +153,26 @@ impl WorldGenerator for CSharp { let name = &format!("{}-world", resolve.worlds[world].name).to_upper_camel_case(); let name = &format!("{name}.I{name}"); - let mut gen = self.interface(resolve, name, Direction::Import); + let mut r#gen = self.interface(resolve, name, Direction::Import); for (resource, funcs) in by_resource( funcs.iter().copied(), - gen.csharp_gen.world_resources.keys().copied(), + r#gen.csharp_gen.world_resources.keys().copied(), ) { if let Some(resource) = resource { - gen.start_resource(resource, None); + r#gen.start_resource(resource, None); } for func in funcs { - gen.import("$root", func); + r#gen.import("$root", func); } if resource.is_some() { - gen.end_resource(); + r#gen.end_resource(); } } - gen.add_world_fragment(); + r#gen.add_world_fragment(); } fn export_interface( @@ -184,13 +184,13 @@ impl WorldGenerator for CSharp { ) -> anyhow::Result<()> { let name = interface_name(self, resolve, key, Direction::Export); self.interface_names.insert(id, name.clone()); - let mut gen = self.interface(resolve, &name, Direction::Export); + let mut r#gen = self.interface(resolve, &name, Direction::Export); - let mut old_resources = mem::take(&mut gen.csharp_gen.all_resources); - gen.types(id); - let new_resources = mem::take(&mut gen.csharp_gen.all_resources); + let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources); + r#gen.types(id); + let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources); old_resources.extend(new_resources.clone()); - gen.csharp_gen.all_resources = old_resources; + r#gen.csharp_gen.all_resources = old_resources; for (resource, funcs) in by_resource( resolve.interfaces[id] @@ -200,22 +200,22 @@ impl WorldGenerator for CSharp { new_resources.keys().copied(), ) { if let Some(resource) = resource { - gen.start_resource(resource, Some(key)); + r#gen.start_resource(resource, Some(key)); } for func in funcs { - gen.export(func, Some(key)); + r#gen.export(func, Some(key)); } if resource.is_some() { - gen.end_resource(); + r#gen.end_resource(); } } // for anonymous types - gen.define_interface_types(id); + r#gen.define_interface_types(id); - gen.add_interface_fragment(true); + r#gen.add_interface_fragment(true); Ok(()) } @@ -228,23 +228,23 @@ impl WorldGenerator for CSharp { ) -> anyhow::Result<()> { let name = &format!("{}-world", resolve.worlds[world].name).to_upper_camel_case(); let name = &format!("{name}.I{name}"); - let mut gen = self.interface(resolve, name, Direction::Export); + let mut r#gen = self.interface(resolve, name, Direction::Export); for (resource, funcs) in by_resource(funcs.iter().copied(), iter::empty()) { if let Some(resource) = resource { - gen.start_resource(resource, None); + r#gen.start_resource(resource, None); } for func in funcs { - gen.export(func, None); + r#gen.export(func, None); } if resource.is_some() { - gen.end_resource(); + r#gen.end_resource(); } } - gen.add_world_fragment(); + r#gen.add_world_fragment(); Ok(()) } @@ -257,18 +257,18 @@ impl WorldGenerator for CSharp { ) { let name = &format!("{}-world", resolve.worlds[world].name).to_upper_camel_case(); let name = &format!("{name}.I{name}"); - let mut gen = self.interface(resolve, name, Direction::Import); + let mut r#gen = self.interface(resolve, name, Direction::Import); - let mut old_resources = mem::take(&mut gen.csharp_gen.all_resources); + let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources); for (ty_name, ty) in types { - gen.define_type(ty_name, *ty); + r#gen.define_type(ty_name, *ty); } - let new_resources = mem::take(&mut gen.csharp_gen.all_resources); + let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources); old_resources.extend(new_resources.clone()); - gen.csharp_gen.all_resources = old_resources; - gen.csharp_gen.world_resources = new_resources; + r#gen.csharp_gen.all_resources = old_resources; + r#gen.csharp_gen.world_resources = new_resources; - gen.add_world_fragment(); + r#gen.add_world_fragment(); } fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> anyhow::Result<()> { diff --git a/crates/go/Cargo.toml b/crates/go/Cargo.toml new file mode 100644 index 000000000..8234e808c --- /dev/null +++ b/crates/go/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "wit-bindgen-go" +authors = ["Joel Dice "] +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +homepage = 'https://github.com/bytecodealliance/wit-bindgen' +description = """ +Go bindings generator for WIT and the component model, typically used through the +`wit-bindgen-cli` crate. +""" + +[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..8a6472d43 --- /dev/null +++ b/crates/go/src/lib.rs @@ -0,0 +1,2950 @@ +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 { + self.imports.insert("wit_runtime".into()); + self.need_runtime = true; + 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 *wit_runtime.Handle +}} + +func (self *{camel}) TakeHandle() int32 {{ + return self.handle.Take() +}} + +func (self *{camel}) Handle() int32 {{ + return self.handle.Use() +}} + +func (self *{camel}) Drop() {{ + handle := self.handle.TakeOrNil() + if handle != 0 {{ + resourceDrop{camel}(handle) + }} +}} + +func {camel}FromOwnHandle(handleValue int32) *{camel} {{ + handle := wit_runtime.MakeHandle(handleValue) + value := &{camel}{{handle}} + runtime.AddCleanup(value, func(_ int) {{ + handleValue := handle.TakeOrNil() + if handleValue != 0 {{ + resourceDrop{camel}(handleValue) + }} + }}, 0) + return value +}} + +func {camel}FromBorrowHandle(handleValue int32) *{camel} {{ + return {camel}FromOwnHandle(handleValue) +}} +"# + ); + } 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..d5b3ed9a0 --- /dev/null +++ b/crates/go/src/wit_future.go @@ -0,0 +1,132 @@ +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 *wit_runtime.Handle +} + +func (self *FutureReader[T]) Read() T { + handle := self.handle.Take() + defer self.vtable.DropReadable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + buffer := wit_runtime.Allocate(&pinner, uintptr(self.vtable.Size), uintptr(self.vtable.Align)) + + code, _ := wit_async.FutureOrStreamWait(self.vtable.Read(handle, buffer), handle) + + switch code { + case wit_async.RETURN_CODE_COMPLETED: + if self.vtable.Lift == nil { + return unsafe.Slice((*T)(buffer), 1)[0] + } else { + return self.vtable.Lift(buffer) + } + + case wit_async.RETURN_CODE_DROPPED: + panic("unreachable") + + default: + panic("todo: handle cancellation") + } +} + +func (self *FutureReader[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropReadable(handle) + } +} + +func (self *FutureReader[T]) TakeHandle() int32 { + return self.handle.Take() +} + +func MakeFutureReader[T any](vtable *FutureVtable[T], handleValue int32) *FutureReader[T] { + handle := wit_runtime.MakeHandle(handleValue) + value := &FutureReader[T]{vtable, handle} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} + +type FutureWriter[T any] struct { + vtable *FutureVtable[T] + handle *wit_runtime.Handle +} + +func (self *FutureWriter[T]) Write(item T) bool { + handle := self.handle.Take() + defer self.vtable.DropWritable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + var buffer unsafe.Pointer + if self.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData([]T{item})) + pinner.Pin(buffer) + } else { + buffer = wit_runtime.Allocate(&pinner, uintptr(self.vtable.Size), uintptr(self.vtable.Align)) + self.vtable.Lower(&pinner, item, buffer) + } + + code, _ := wit_async.FutureOrStreamWait(self.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 (self *FutureWriter[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropWritable(handle) + } +} + +func MakeFutureWriter[T any](vtable *FutureVtable[T], handleValue int32) *FutureWriter[T] { + handle := wit_runtime.MakeHandle(handleValue) + value := &FutureWriter[T]{vtable, handle} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} 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..ebfc6cc11 --- /dev/null +++ b/crates/go/src/wit_runtime.go @@ -0,0 +1,110 @@ +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 { + if self.value == 0 { + panic("nil handle") + } + value := self.value + self.value = 0 + return value +} + +func (self *Handle) TakeOrNil() int32 { + value := self.value + self.value = 0 + return value +} + +func MakeHandle(value int32) *Handle { + if value == 0 { + panic("nil handle") + } + return &Handle{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..168343ee3 --- /dev/null +++ b/crates/go/src/wit_stream.go @@ -0,0 +1,167 @@ +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 *wit_runtime.Handle + writerDropped bool +} + +func (self *StreamReader[T]) WriterDropped() bool { + return self.writerDropped +} + +func (self *StreamReader[T]) Read(dst []T) uint32 { + handle := self.handle.Use() + + if self.writerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + var buffer unsafe.Pointer + if self.vtable.Lift == nil { + buffer = unsafe.Pointer(unsafe.SliceData(dst)) + } else { + buffer = wit_runtime.Allocate( + &pinner, + uintptr(self.vtable.Size*uint32(len(dst))), + uintptr(self.vtable.Align), + ) + } + pinner.Pin(buffer) + + code, count := wit_async.FutureOrStreamWait(self.vtable.Read(handle, buffer, uint32(len(dst))), handle) + + if code == wit_async.RETURN_CODE_DROPPED { + self.writerDropped = true + } + + if self.vtable.Lift != nil { + for i := 0; i < int(count); i++ { + dst[i] = self.vtable.Lift(unsafe.Add(buffer, i*int(self.vtable.Size))) + } + } + + return count +} + +func (self *StreamReader[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropReadable(handle) + } +} + +func (self *StreamReader[T]) TakeHandle() int32 { + return self.handle.Take() +} + +func MakeStreamReader[T any](vtable *StreamVtable[T], handleValue int32) *StreamReader[T] { + handle := wit_runtime.MakeHandle(handleValue) + value := &StreamReader[T]{vtable, handle, false} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} + +type StreamWriter[T any] struct { + vtable *StreamVtable[T] + handle *wit_runtime.Handle + readerDropped bool +} + +func (self *StreamWriter[T]) ReaderDropped() bool { + return self.readerDropped +} + +func (self *StreamWriter[T]) Write(items []T) uint32 { + handle := self.handle.Use() + + if self.readerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + writeCount := uint32(len(items)) + + var buffer unsafe.Pointer + if self.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData(items)) + pinner.Pin(buffer) + } else { + buffer = wit_runtime.Allocate( + &pinner, + uintptr(self.vtable.Size*writeCount), + uintptr(self.vtable.Align), + ) + for index, item := range items { + self.vtable.Lower(&pinner, item, unsafe.Add(buffer, index*int(self.vtable.Size))) + } + } + + code, count := wit_async.FutureOrStreamWait(self.vtable.Write(handle, buffer, writeCount), handle) + + // TODO: restore handles to any unwritten resources, streams, or futures + + if code == wit_async.RETURN_CODE_DROPPED { + self.readerDropped = true + } + + return count +} + +func (self *StreamWriter[T]) WriteAll(items []T) uint32 { + offset := uint32(0) + count := uint32(len(items)) + for offset < count && !self.readerDropped { + offset += self.Write(items[offset:]) + } + return offset +} + +func (self *StreamWriter[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropWritable(handle) + } +} + +func MakeStreamWriter[T any](vtable *StreamVtable[T], handleValue int32) *StreamWriter[T] { + handle := wit_runtime.MakeHandle(handleValue) + value := &StreamWriter[T]{vtable, handle, false} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} 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/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index ef23291f9..c2b616f97 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -6,9 +6,9 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{braced, token, LitStr, Token}; -use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; +use syn::{LitStr, Token, braced, token}; use wit_bindgen_core::AsyncFilterSet; +use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; use wit_bindgen_rust::{Opts, Ownership, WithOption}; #[proc_macro] diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index b03e61e89..a2a35d5b9 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -873,15 +873,15 @@ pub mod examples; #[doc(hidden)] pub mod rt; -#[cfg(feature = "async-spawn")] -pub use rt::async_support::spawn; #[cfg(feature = "inter-task-wakeup")] pub use rt::async_support::UnitStreamOps; +#[cfg(feature = "async-spawn")] +pub use rt::async_support::spawn; #[cfg(feature = "async")] pub use rt::async_support::{ - backpressure_dec, backpressure_inc, block_on, yield_async, yield_blocking, AbiBuffer, - FutureOps, FutureRead, FutureReader, FutureWrite, FutureWriteCancel, FutureWriteError, - FutureWriter, RawFutureRead, RawFutureReader, RawFutureWrite, RawFutureWriter, RawStreamRead, - RawStreamReader, RawStreamWrite, RawStreamWriter, StreamOps, StreamRead, StreamReader, - StreamResult, StreamWrite, StreamWriter, + AbiBuffer, FutureOps, FutureRead, FutureReader, FutureWrite, FutureWriteCancel, + FutureWriteError, FutureWriter, RawFutureRead, RawFutureReader, RawFutureWrite, + RawFutureWriter, RawStreamRead, RawStreamReader, RawStreamWrite, RawStreamWriter, StreamOps, + StreamRead, StreamReader, StreamResult, StreamWrite, StreamWriter, backpressure_dec, + backpressure_inc, block_on, yield_async, yield_blocking, }; diff --git a/crates/guest-rust/src/rt/async_support.rs b/crates/guest-rust/src/rt/async_support.rs index e9974516f..3c216956b 100644 --- a/crates/guest-rust/src/rt/async_support.rs +++ b/crates/guest-rust/src/rt/async_support.rs @@ -31,7 +31,7 @@ macro_rules! rtdebug { macro_rules! extern_wasm { ( $(#[$extern_attr:meta])* - extern "C" { + unsafe extern "C" { $( $(#[$func_attr:meta])* $vis:vis fn $func_name:ident ( $($args:tt)* ) $(-> $ret:ty)?; @@ -48,7 +48,7 @@ macro_rules! extern_wasm { #[cfg(target_family = "wasm")] $(#[$extern_attr])* - extern "C" { + unsafe extern "C" { $( $(#[$func_attr])* $vis fn $func_name($($args)*) $(-> $ret)?; @@ -344,20 +344,24 @@ unsafe extern "C" fn waitable_register( ) -> *mut c_void { let ptr = ptr.cast::>(); assert!(!ptr.is_null()); - (*ptr).add_waitable(waitable); - match (*ptr).waitables.insert(waitable, (callback_ptr, callback)) { - Some((prev, _)) => prev, - None => ptr::null_mut(), + unsafe { + (*ptr).add_waitable(waitable); + match (*ptr).waitables.insert(waitable, (callback_ptr, callback)) { + Some((prev, _)) => prev, + None => ptr::null_mut(), + } } } unsafe extern "C" fn waitable_unregister(ptr: *mut c_void, waitable: u32) -> *mut c_void { let ptr = ptr.cast::>(); assert!(!ptr.is_null()); - (*ptr).remove_waitable(waitable); - match (*ptr).waitables.remove(&waitable) { - Some((prev, _)) => prev, - None => ptr::null_mut(), + unsafe { + (*ptr).remove_waitable(waitable); + match (*ptr).waitables.remove(&waitable) { + Some((prev, _)) => prev, + None => ptr::null_mut(), + } } } @@ -573,7 +577,7 @@ pub fn block_on(future: impl Future) -> T { pub fn yield_blocking() -> bool { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[thread-yield]"] fn yield_() -> bool; } @@ -626,7 +630,7 @@ pub async fn yield_async() { pub fn backpressure_inc() { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[backpressure-inc]"] fn backpressure_inc(); } @@ -639,7 +643,7 @@ pub fn backpressure_inc() { pub fn backpressure_dec() { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[backpressure-dec]"] fn backpressure_dec(); } @@ -651,7 +655,7 @@ pub fn backpressure_dec() { fn context_get() -> *mut u8 { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[context-get-0]"] fn get() -> *mut u8; } @@ -663,7 +667,7 @@ fn context_get() -> *mut u8 { unsafe fn context_set(value: *mut u8) { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[context-set-0]"] fn set(value: *mut u8); } @@ -693,7 +697,7 @@ impl Drop for TaskCancelOnDrop { fn drop(&mut self) { extern_wasm! { #[link(wasm_import_module = "[export]$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[task-cancel]"] fn cancel(); } diff --git a/crates/guest-rust/src/rt/async_support/abi_buffer.rs b/crates/guest-rust/src/rt/async_support/abi_buffer.rs index d9a425e8d..5179ddbcc 100644 --- a/crates/guest-rust/src/rt/async_support/abi_buffer.rs +++ b/crates/guest-rust/src/rt/async_support/abi_buffer.rs @@ -1,5 +1,5 @@ -use crate::rt::async_support::StreamOps; use crate::rt::Cleanup; +use crate::rt::async_support::StreamOps; use std::alloc::Layout; use std::mem::{self, MaybeUninit}; use std::ptr; diff --git a/crates/guest-rust/src/rt/async_support/cabi.rs b/crates/guest-rust/src/rt/async_support/cabi.rs index f31e945c2..d696d1e55 100644 --- a/crates/guest-rust/src/rt/async_support/cabi.rs +++ b/crates/guest-rust/src/rt/async_support/cabi.rs @@ -47,7 +47,7 @@ use core::ffi::c_void; extern_wasm! { - extern "C" { + unsafe extern "C" { /// Sets the global task pointer to `ptr` provided. Returns the previous /// value. /// diff --git a/crates/guest-rust/src/rt/async_support/error_context.rs b/crates/guest-rust/src/rt/async_support/error_context.rs index c1a72882d..a9fc4faf3 100644 --- a/crates/guest-rust/src/rt/async_support/error_context.rs +++ b/crates/guest-rust/src/rt/async_support/error_context.rs @@ -72,7 +72,7 @@ struct RetPtr { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[error-context-new-utf8]"] fn new(_: *const u8, _: usize) -> u32; #[link_name = "[error-context-drop]"] diff --git a/crates/guest-rust/src/rt/async_support/future_support.rs b/crates/guest-rust/src/rt/async_support/future_support.rs index afed15d24..54eeddb5a 100644 --- a/crates/guest-rust/src/rt/async_support/future_support.rs +++ b/crates/guest-rust/src/rt/async_support/future_support.rs @@ -111,9 +111,9 @@ //! alive until that write completes but otherwise shouldn't hinder anything //! else. -use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation}; -use crate::rt::async_support::ReturnCode; use crate::rt::Cleanup; +use crate::rt::async_support::ReturnCode; +use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation}; use std::alloc::Layout; use std::fmt; use std::future::{Future, IntoFuture}; @@ -219,31 +219,31 @@ impl FutureOps for &FutureVtable { self.layout } unsafe fn lower(&mut self, payload: Self::Payload, dst: *mut u8) { - (self.lower)(payload, dst) + unsafe { (self.lower)(payload, dst) } } unsafe fn dealloc_lists(&mut self, dst: *mut u8) { - (self.dealloc_lists)(dst) + unsafe { (self.dealloc_lists)(dst) } } unsafe fn lift(&mut self, dst: *mut u8) -> Self::Payload { - (self.lift)(dst) + unsafe { (self.lift)(dst) } } unsafe fn start_write(&mut self, future: u32, val: *const u8) -> u32 { - (self.start_write)(future, val) + unsafe { (self.start_write)(future, val) } } unsafe fn start_read(&mut self, future: u32, val: *mut u8) -> u32 { - (self.start_read)(future, val) + unsafe { (self.start_read)(future, val) } } unsafe fn cancel_read(&mut self, future: u32) -> u32 { - (self.cancel_read)(future) + unsafe { (self.cancel_read)(future) } } unsafe fn cancel_write(&mut self, future: u32) -> u32 { - (self.cancel_write)(future) + unsafe { (self.cancel_write)(future) } } unsafe fn drop_readable(&mut self, future: u32) { - (self.drop_readable)(future) + unsafe { (self.drop_readable)(future) } } unsafe fn drop_writable(&mut self, future: u32) { - (self.drop_writable)(future) + unsafe { (self.drop_writable)(future) } } } @@ -259,7 +259,7 @@ pub unsafe fn future_new( vtable: &'static FutureVtable, ) -> (FutureWriter, FutureReader) { let (tx, rx) = unsafe { raw_future_new(vtable) }; - (FutureWriter::new(tx, default), rx) + (unsafe { FutureWriter::new(tx, default) }, rx) } /// Helper function to create a new read/write pair for a component model diff --git a/crates/guest-rust/src/rt/async_support/stream_support.rs b/crates/guest-rust/src/rt/async_support/stream_support.rs index 26a228272..6e4771827 100644 --- a/crates/guest-rust/src/rt/async_support/stream_support.rs +++ b/crates/guest-rust/src/rt/async_support/stream_support.rs @@ -2,7 +2,7 @@ //! module documentation in `future_support.rs`. use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation}; -use crate::rt::async_support::{AbiBuffer, ReturnCode, DROPPED}; +use crate::rt::async_support::{AbiBuffer, DROPPED, ReturnCode}; use { crate::rt::Cleanup, std::{ @@ -126,34 +126,34 @@ unsafe impl StreamOps for &StreamVtable { } unsafe fn lower(&mut self, payload: Self::Payload, dst: *mut u8) { if let Some(f) = self.lower { - f(payload, dst) + unsafe { f(payload, dst) } } } unsafe fn dealloc_lists(&mut self, dst: *mut u8) { if let Some(f) = self.dealloc_lists { - f(dst) + unsafe { f(dst) } } } unsafe fn lift(&mut self, dst: *mut u8) -> Self::Payload { - (self.lift.unwrap())(dst) + unsafe { (self.lift.unwrap())(dst) } } unsafe fn start_write(&mut self, stream: u32, val: *const u8, amt: usize) -> u32 { - (self.start_write)(stream, val, amt) + unsafe { (self.start_write)(stream, val, amt) } } unsafe fn start_read(&mut self, stream: u32, val: *mut u8, amt: usize) -> u32 { - (self.start_read)(stream, val, amt) + unsafe { (self.start_read)(stream, val, amt) } } unsafe fn cancel_read(&mut self, stream: u32) -> u32 { - (self.cancel_read)(stream) + unsafe { (self.cancel_read)(stream) } } unsafe fn cancel_write(&mut self, stream: u32) -> u32 { - (self.cancel_write)(stream) + unsafe { (self.cancel_write)(stream) } } unsafe fn drop_readable(&mut self, stream: u32) { - (self.drop_readable)(stream) + unsafe { (self.drop_readable)(stream) } } unsafe fn drop_writable(&mut self, stream: u32) { - (self.drop_writable)(stream) + unsafe { (self.drop_writable)(stream) } } } diff --git a/crates/guest-rust/src/rt/async_support/subtask.rs b/crates/guest-rust/src/rt/async_support/subtask.rs index 9d47fd4d2..9236d533f 100644 --- a/crates/guest-rust/src/rt/async_support/subtask.rs +++ b/crates/guest-rust/src/rt/async_support/subtask.rs @@ -7,12 +7,12 @@ //! the canonical ABI are additionally leaked with it which should be memory //! safe. +use crate::rt::Cleanup; use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation}; use crate::rt::async_support::{ STATUS_RETURNED, STATUS_RETURNED_CANCELLED, STATUS_STARTED, STATUS_STARTED_CANCELLED, STATUS_STARTING, }; -use crate::rt::Cleanup; use std::alloc::Layout; use std::future::Future; use std::marker; @@ -275,7 +275,7 @@ impl InProgress { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[subtask-cancel]"] fn cancel(handle: u32) -> u32; #[link_name = "[subtask-drop]"] diff --git a/crates/guest-rust/src/rt/async_support/unit_stream.rs b/crates/guest-rust/src/rt/async_support/unit_stream.rs index 0379efd99..985462874 100644 --- a/crates/guest-rust/src/rt/async_support/unit_stream.rs +++ b/crates/guest-rust/src/rt/async_support/unit_stream.rs @@ -11,7 +11,7 @@ pub struct UnitStreamOps; extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[stream-new-unit]"] fn unit_new() -> u64; #[link_name = "[async-lower][stream-write-unit]"] diff --git a/crates/guest-rust/src/rt/async_support/waitable.rs b/crates/guest-rust/src/rt/async_support/waitable.rs index 850cd8f3c..516d5ef42 100644 --- a/crates/guest-rust/src/rt/async_support/waitable.rs +++ b/crates/guest-rust/src/rt/async_support/waitable.rs @@ -204,7 +204,7 @@ where } unsafe extern "C" fn cabi_wake(ptr: *mut c_void, code: u32) { - let ptr: &mut CompletionStatus = &mut *ptr.cast::(); + let ptr: &mut CompletionStatus = unsafe { &mut *ptr.cast::() }; ptr.code = Some(code); ptr.waker.take().unwrap().wake() } @@ -397,7 +397,7 @@ where // situation no cancellation is necessary, the async // operation is now inert, and we can immediately return. Poll::Ready(result) => { - return self.as_mut().pin_project().0.result_into_cancel(result) + return self.as_mut().pin_project().0.result_into_cancel(result); } // The operation, despite receiving an update via a code, diff --git a/crates/guest-rust/src/rt/async_support/waitable_set.rs b/crates/guest-rust/src/rt/async_support/waitable_set.rs index 8d7e4a268..f0960946f 100644 --- a/crates/guest-rust/src/rt/async_support/waitable_set.rs +++ b/crates/guest-rust/src/rt/async_support/waitable_set.rs @@ -67,7 +67,7 @@ impl Drop for WaitableSet { extern_wasm! { #[link(wasm_import_module = "$root")] - extern "C" { + unsafe extern "C" { #[link_name = "[waitable-set-new]"] fn new() -> u32; #[link_name = "[waitable-set-drop]"] diff --git a/crates/guest-rust/src/rt/mod.rs b/crates/guest-rust/src/rt/mod.rs index 099297d2f..a85a37f12 100644 --- a/crates/guest-rust/src/rt/mod.rs +++ b/crates/guest-rust/src/rt/mod.rs @@ -81,7 +81,7 @@ mod wit_bindgen_cabi_realloc; pub fn maybe_link_cabi_realloc() { #[cfg(all(target_family = "wasm", not(target_env = "p2")))] { - extern "C" { + unsafe extern "C" { fn cabi_realloc( old_ptr: *mut u8, old_len: usize, @@ -117,19 +117,21 @@ pub unsafe fn cabi_realloc( align: usize, new_len: usize, ) -> *mut u8 { - use alloc::alloc::{alloc as allocate, handle_alloc_error, realloc, Layout}; + use alloc::alloc::{Layout, alloc as allocate, handle_alloc_error, realloc}; let layout; - let ptr = if old_len == 0 { - if new_len == 0 { - return align as *mut u8; + let ptr = unsafe { + if old_len == 0 { + if new_len == 0 { + return align as *mut u8; + } + layout = Layout::from_size_align_unchecked(new_len, align); + allocate(layout) + } else { + debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!"); + layout = Layout::from_size_align_unchecked(old_len, align); + realloc(old_ptr, layout, new_len) } - layout = Layout::from_size_align_unchecked(new_len, align); - allocate(layout) - } else { - debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!"); - layout = Layout::from_size_align_unchecked(old_len, align); - realloc(old_ptr, layout, new_len) }; if ptr.is_null() { // Print a nice message in debug mode, but in release mode don't @@ -164,7 +166,7 @@ pub fn run_ctors_once() { // exported function to (unconditionally) run ctors. By using // this function, the linked module is opting into "manually" // running ctors. - extern "C" { + unsafe extern "C" { fn __wasm_call_ctors(); } __wasm_call_ctors(); diff --git a/crates/guest-rust/src/rt/wit_bindgen_cabi.c b/crates/guest-rust/src/rt/wit_bindgen_cabi.c deleted file mode 100644 index e7cf56f5f..000000000 --- a/crates/guest-rust/src/rt/wit_bindgen_cabi.c +++ /dev/null @@ -1,20 +0,0 @@ -// This file is generated by ./ci/rebuild-libwit-bindgen-cabi.sh - -#include -#include - -extern void *cabi_realloc_wit_bindgen_0_41_0(void *ptr, size_t old_size, size_t align, size_t new_size); - -__attribute__((__weak__, __export_name__("cabi_realloc"))) -void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { - return cabi_realloc_wit_bindgen_0_41_0(ptr, old_size, align, new_size); -} - -static void *WASIP3_TASK = NULL; - -__attribute__((__weak__)) -void *wasip3_task_set(void *ptr) { - void *ret = WASIP3_TASK; - WASIP3_TASK = ptr; - return ret; -} diff --git a/crates/guest-rust/src/rt/wit_bindgen_cabi.o b/crates/guest-rust/src/rt/wit_bindgen_cabi.o deleted file mode 100644 index 905870b49dd98b81a4804f0324229e28f57e11b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414 zcmZ8d!AiqG5S`g=ur$Tws3I6o3Z=DD@gCGe@t{z_<8~9LVQI20yPMXY>L+;c7yJzW z%}u=&=P>YQ-n^MNAZ>*JfQM|_+WNrl6n$b-&PlH#Axfo58c|WTFdGKgl+SXRDPc&K zi9}gnm&jGFjSQWN1c)bWVM@S(5Fw Kttxb%-rX;8T5vl6 diff --git a/crates/guest-rust/src/rt/wit_bindgen_cabi_realloc.rs b/crates/guest-rust/src/rt/wit_bindgen_cabi_realloc.rs index d2ebaed02..b09c3be71 100644 --- a/crates/guest-rust/src/rt/wit_bindgen_cabi_realloc.rs +++ b/crates/guest-rust/src/rt/wit_bindgen_cabi_realloc.rs @@ -7,5 +7,5 @@ pub unsafe extern "C" fn cabi_realloc_wit_bindgen_0_49_0( align: usize, new_len: usize, ) -> *mut u8 { - crate::rt::cabi_realloc(old_ptr, old_len, align, new_len) + unsafe { crate::rt::cabi_realloc(old_ptr, old_len, align, new_len) } } diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index c6cd78fb4..355ffae27 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -1,10 +1,10 @@ use anyhow::Result; use heck::*; -use pulldown_cmark::{html, Event, LinkType, Parser, Tag}; +use pulldown_cmark::{Event, LinkType, Parser, Tag, html}; use std::collections::HashMap; use std::fmt::Write; use wit_bindgen_core::{ - uwriteln, wit_parser, Files, InterfaceGenerator as _, Source, WorldGenerator, + Files, InterfaceGenerator as _, Source, WorldGenerator, uwriteln, wit_parser, }; use wit_parser::*; @@ -52,63 +52,63 @@ impl WorldGenerator for Markdown { format!("#{}", world.name.to_snake_case()), ); - let mut gen = self.interface(resolve); + let mut r#gen = self.interface(resolve); - gen.docs(&world.docs); - gen.push_str("\n"); + r#gen.docs(&world.docs); + r#gen.push_str("\n"); // Produce a table of contents for the world. let mut first = true; for (name, import) in &world.imports { if first { - gen.push_str(" - Imports:\n"); + r#gen.push_str(" - Imports:\n"); first = false; } let name = &resolve.name_world_key(name); match import { WorldItem::Interface { .. } => { - gen.push_str(" - interface `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - interface `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } WorldItem::Function(_) => { - gen.push_str(" - function `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - function `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } WorldItem::Type(_) => { - gen.push_str(" - type `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - type `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } } } let mut first = true; for (name, export) in &world.exports { if first { - gen.push_str(" - Exports:\n"); + r#gen.push_str(" - Exports:\n"); first = false; } let name = &resolve.name_world_key(name); match export { WorldItem::Interface { .. } => { - gen.push_str(" - interface `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - interface `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } WorldItem::Function(_) => { - gen.push_str(" - function `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - function `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } WorldItem::Type(_) => { - gen.push_str(" - type `"); - gen.push_str(name); - gen.push_str("`\n"); + r#gen.push_str(" - type `"); + r#gen.push_str(name); + r#gen.push_str("`\n"); } } } - gen.push_str("\n"); + r#gen.push_str("\n"); } fn import_interface( @@ -126,11 +126,11 @@ impl WorldGenerator for Markdown { ); self.hrefs .insert(name.to_string(), format!("#{}", name.to_snake_case())); - let mut gen = self.interface(resolve); - gen.docs(&resolve.interfaces[id].docs); - gen.push_str("\n"); - gen.types(id); - gen.funcs(id); + let mut r#gen = self.interface(resolve); + r#gen.docs(&resolve.interfaces[id].docs); + r#gen.push_str("\n"); + r#gen.types(id); + r#gen.funcs(id); Ok(()) } @@ -144,9 +144,9 @@ impl WorldGenerator for Markdown { ) { let name = &resolve.worlds[world].name; uwriteln!(self.src, "## Imported functions to world `{name}`\n"); - let mut gen = self.interface(resolve); + let mut r#gen = self.interface(resolve); for (_, func) in funcs { - gen.func(func); + r#gen.func(func); } } @@ -165,9 +165,9 @@ impl WorldGenerator for Markdown { ); self.hrefs .insert(name.to_string(), format!("#{}", name.to_snake_case())); - let mut gen = self.interface(resolve); - gen.types(id); - gen.funcs(id); + let mut r#gen = self.interface(resolve); + r#gen.types(id); + r#gen.funcs(id); Ok(()) } @@ -180,9 +180,9 @@ impl WorldGenerator for Markdown { ) -> Result<()> { let name = &resolve.worlds[world].name; uwriteln!(self.src, "## Exported functions from world `{name}`\n"); - let mut gen = self.interface(resolve); + let mut r#gen = self.interface(resolve); for (_, func) in funcs { - gen.func(func); + r#gen.func(func); } Ok(()) } @@ -196,9 +196,9 @@ impl WorldGenerator for Markdown { ) { let name = &resolve.worlds[world].name; uwriteln!(self.src, "## Exported types from world `{name}`\n"); - let mut gen = self.interface(resolve); + let mut r#gen = self.interface(resolve); for (name, ty) in types { - gen.define_type(name, *ty); + r#gen.define_type(name, *ty); } } @@ -237,7 +237,7 @@ impl WorldGenerator for Markdown { impl Markdown { fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> InterfaceGenerator<'a> { InterfaceGenerator { - gen: self, + r#gen: self, resolve, types_header_printed: false, } @@ -245,7 +245,7 @@ impl Markdown { } struct InterfaceGenerator<'a> { - gen: &'a mut Markdown, + r#gen: &'a mut Markdown, resolve: &'a Resolve, types_header_printed: bool, } @@ -268,7 +268,7 @@ impl InterfaceGenerator<'_> { "#### `", func.name.to_snake_case() )); - self.gen + self.r#gen .hrefs .insert(func.name.clone(), format!("#{}", func.name.to_snake_case())); self.push_str(&func.name); @@ -305,7 +305,7 @@ impl InterfaceGenerator<'_> { } fn push_str(&mut self, s: &str) { - self.gen.src.push_str(s); + self.r#gen.src.push_str(s); } fn print_ty(&mut self, ty: &Type) { @@ -448,7 +448,7 @@ impl InterfaceGenerator<'_> { type_, name, )); - self.gen + self.r#gen .hrefs .insert(name.to_string(), format!("#{}", name.to_snake_case())); } @@ -471,16 +471,16 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { f = field.name.to_snake_case(), name = field.name, )); - self.gen.hrefs.insert( + self.r#gen.hrefs.insert( format!("{}::{}", name, field.name), format!("#{}.{}", name.to_snake_case(), field.name.to_snake_case()), ); self.print_ty(&field.ty); if field.docs.contents.is_some() { - self.gen.src.indent(1); + self.r#gen.src.indent(1); self.push_str("\n

"); self.docs(&field.docs); - self.gen.src.deindent(1); + self.r#gen.src.deindent(1); } self.push_str("\n"); } @@ -505,7 +505,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { f = i, name = i, )); - self.gen.hrefs.insert( + self.r#gen.hrefs.insert( format!("{}::{}", name, i), format!("#{}.{}", name.to_snake_case(), i), ); @@ -527,15 +527,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { f = flag.name.to_snake_case(), name = flag.name, )); - self.gen.hrefs.insert( + self.r#gen.hrefs.insert( format!("{}::{}", name, flag.name), format!("#{}.{}", name.to_snake_case(), flag.name.to_snake_case()), ); if flag.docs.contents.is_some() { - self.gen.src.indent(1); + self.r#gen.src.indent(1); self.push_str("\n

"); self.docs(&flag.docs); - self.gen.src.deindent(1); + self.r#gen.src.deindent(1); } self.push_str("\n"); } @@ -554,7 +554,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { c = case.name.to_snake_case(), name = case.name, )); - self.gen.hrefs.insert( + self.r#gen.hrefs.insert( format!("{}::{}", name, case.name), format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()), ); @@ -563,10 +563,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { self.print_ty(ty); } if case.docs.contents.is_some() { - self.gen.src.indent(1); + self.r#gen.src.indent(1); self.push_str("\n

"); self.docs(&case.docs); - self.gen.src.deindent(1); + self.r#gen.src.deindent(1); } self.push_str("\n"); } @@ -585,15 +585,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { c = case.name.to_snake_case(), name = case.name, )); - self.gen.hrefs.insert( + self.r#gen.hrefs.insert( format!("{}::{}", name, case.name), format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()), ); if case.docs.contents.is_some() { - self.gen.src.indent(1); + self.r#gen.src.indent(1); self.push_str("\n

"); self.docs(&case.docs); - self.gen.src.deindent(1); + self.r#gen.src.deindent(1); } self.push_str("\n"); } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index bdc05de49..42df98010 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -8,6 +8,8 @@ use std::{ ops::Deref, }; use wit_bindgen_core::{ + AsyncFilterSet, Direction, Files, InterfaceGenerator as CoreInterfaceGenerator, Ns, Source, + WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmSignature, WasmType}, dealias, uwrite, uwriteln, wit_parser::{ @@ -15,8 +17,6 @@ use wit_bindgen_core::{ Int, InterfaceId, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, Variant, WorldId, WorldKey, }, - AsyncFilterSet, Direction, Files, InterfaceGenerator as CoreInterfaceGenerator, Ns, Source, - WorldGenerator, }; // Assumptions: @@ -148,7 +148,7 @@ impl MoonBit { src: String::new(), stub: String::new(), ffi: String::new(), - gen: self, + r#gen: self, resolve, name, module, @@ -184,14 +184,14 @@ impl WorldGenerator for MoonBit { } let module = &resolve.name_world_key(key); - let mut gen = self.interface(resolve, &name, module, Direction::Import); - gen.types(id); + let mut r#gen = self.interface(resolve, &name, module, Direction::Import); + r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { - gen.import(Some(key), func); + r#gen.import(Some(key), func); } - gen.add_interface_fragment(); + r#gen.add_interface_fragment(); Ok(()) } @@ -204,13 +204,13 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = world_name(resolve, world); - let mut gen = self.interface(resolve, &name, "$root", Direction::Import); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); for (_, func) in funcs { - gen.import(None, func); // None is "$root" + r#gen.import(None, func); // None is "$root" } - gen.add_world_fragment(); + r#gen.add_world_fragment(); } fn export_interface( @@ -234,14 +234,14 @@ impl WorldGenerator for MoonBit { } let module = &resolve.name_world_key(key); - let mut gen = self.interface(resolve, &name, module, Direction::Export); - gen.types(id); + let mut r#gen = self.interface(resolve, &name, module, Direction::Export); + r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { - gen.export(Some(key), func, Some(name.clone())); + r#gen.export(Some(key), func, Some(name.clone())); } - gen.add_interface_fragment(); + r#gen.add_interface_fragment(); Ok(()) } @@ -253,13 +253,13 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) -> Result<()> { let name = format!("{}.{}", self.opts.gen_dir, world_name(resolve, world)); - let mut gen = self.interface(resolve, &name, "$root", Direction::Export); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Export); for (_, func) in funcs { - gen.export(None, func, Some(name.clone())); + r#gen.export(None, func, Some(name.clone())); } - gen.add_world_fragment(); + r#gen.add_world_fragment(); Ok(()) } @@ -271,13 +271,13 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = world_name(resolve, world); - let mut gen = self.interface(resolve, &name, "$root", Direction::Import); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); for (ty_name, ty) in types { - gen.define_type(ty_name, *ty); + r#gen.define_type(ty_name, *ty); } - gen.add_world_fragment(); + r#gen.add_world_fragment(); } fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { @@ -465,8 +465,8 @@ impl WorldGenerator for MoonBit { let export_dir = self.opts.gen_dir.clone(); // Export project entry point - let mut gen = self.interface(resolve, export_dir.as_str(), "", Direction::Export); - let ffi_qualifier = gen.qualify_package(FFI_DIR); + let mut r#gen = self.interface(resolve, export_dir.as_str(), "", Direction::Export); + let ffi_qualifier = r#gen.qualify_package(FFI_DIR); let mut body = Source::default(); wit_bindgen_core::generated_preamble(&mut body, version); @@ -556,7 +556,7 @@ struct InterfaceGenerator<'a> { src: String, stub: String, ffi: String, - gen: &'a mut MoonBit, + r#gen: &'a mut MoonBit, resolve: &'a Resolve, // The current interface getting generated name: &'a str, @@ -568,7 +568,7 @@ impl InterfaceGenerator<'_> { fn qualify_package(&mut self, name: &str) -> String { if name != self.name { let imports = self - .gen + .r#gen .package_import .entry(self.name.to_string()) .or_default(); @@ -590,11 +590,11 @@ impl InterfaceGenerator<'_> { } fn qualifier(&mut self, ty: &TypeDef) -> String { if let TypeOwner::Interface(id) = &ty.owner { - if let Some(name) = self.gen.export_interface_names.get(id) { + if let Some(name) = self.r#gen.export_interface_names.get(id) { if name != self.name { return self.qualify_package(&name.clone()); } - } else if let Some(name) = self.gen.import_interface_names.get(id) { + } else if let Some(name) = self.r#gen.import_interface_names.get(id) { if name != self.name { return self.qualify_package(&name.clone()); } @@ -612,7 +612,7 @@ impl InterfaceGenerator<'_> { fn add_interface_fragment(self) { match self.direction { Direction::Import => { - self.gen + self.r#gen .import_interface_fragments .entry(self.name.to_owned()) .or_default() @@ -623,7 +623,7 @@ impl InterfaceGenerator<'_> { }); } Direction::Export => { - self.gen + self.r#gen .export_interface_fragments .entry(self.name.to_owned()) .or_default() @@ -639,14 +639,14 @@ impl InterfaceGenerator<'_> { fn add_world_fragment(self) { match self.direction { Direction::Import => { - self.gen.import_world_fragments.push(InterfaceFragment { + self.r#gen.import_world_fragments.push(InterfaceFragment { src: self.src, stub: self.stub, ffi: self.ffi, }); } Direction::Export => { - self.gen.export_world_fragments.push(InterfaceFragment { + self.r#gen.export_world_fragments.push(InterfaceFragment { src: self.src, stub: self.stub, ffi: self.ffi, @@ -657,12 +657,12 @@ impl InterfaceGenerator<'_> { fn import(&mut self, module: Option<&WorldKey>, func: &Function) { let async_ = self - .gen + .r#gen .opts .async_ .is_async(self.resolve, module, func, false); if async_ { - self.gen.is_async = true; + self.r#gen.is_async = true; } let interface_name = match module { @@ -686,7 +686,7 @@ impl InterfaceGenerator<'_> { }; abi::call( - bindgen.gen.resolve, + bindgen.r#gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, @@ -697,7 +697,7 @@ impl InterfaceGenerator<'_> { let mut src = bindgen.src.clone(); let cleanup_list = if bindgen.needs_cleanup_list { - self.gen.needs_cleanup = true; + self.r#gen.needs_cleanup = true; let ffi_qualifier = self.qualify_package(FFI_DIR); @@ -782,8 +782,8 @@ impl InterfaceGenerator<'_> { } multiple_params => { let params = multiple_params.iter().map(|(_, ty)| ty); - let offsets = self.gen.sizes.field_offsets(params.clone()); - let elem_info = self.gen.sizes.params(params); + let offsets = self.r#gen.sizes.field_offsets(params.clone()); + let elem_info = self.r#gen.sizes.params(params); body.push_str(&format!( r#" let _lower_ptr : Int = {ffi}malloc({}) @@ -812,7 +812,7 @@ impl InterfaceGenerator<'_> { } else { let mut f = FunctionBindgen::new(self, "INVALID", self.name, Box::new([])); for (name, ty) in mbt_sig.params.iter() { - lower_params.extend(abi::lower_flat(f.gen.resolve, &mut f, name.clone(), ty)); + lower_params.extend(abi::lower_flat(f.r#gen.resolve, &mut f, name.clone(), ty)); } lower_results.push(f.src.clone()); } @@ -872,12 +872,12 @@ impl InterfaceGenerator<'_> { fn export(&mut self, interface: Option<&WorldKey>, func: &Function, _: Option) { let async_ = self - .gen + .r#gen .opts .async_ .is_async(self.resolve, interface, func, false); if async_ { - self.gen.is_async = true; + self.r#gen.is_async = true; } let variant = if async_ { @@ -890,9 +890,9 @@ impl InterfaceGenerator<'_> { let mbt_sig = self.mbt_sig(func, false); let func_sig = self.sig_string(&mbt_sig, async_); - let export_dir = self.gen.opts.gen_dir.clone(); + let export_dir = self.r#gen.opts.gen_dir.clone(); - let mut toplevel_generator = self.gen.interface( + let mut toplevel_generator = self.r#gen.interface( self.resolve, export_dir.as_str(), self.module, @@ -907,7 +907,7 @@ impl InterfaceGenerator<'_> { ); abi::call( - bindgen.gen.resolve, + bindgen.r#gen.resolve, variant, LiftLower::LiftArgsLowerResults, func, @@ -933,7 +933,7 @@ impl InterfaceGenerator<'_> { let camel_name = func.name.to_upper_camel_case(); - let func_name = self.gen.export_ns.tmp(&format!("wasmExport{camel_name}")); + let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{camel_name}")); let params = sig .params @@ -967,14 +967,14 @@ impl InterfaceGenerator<'_> { "#, ); - self.gen + self.r#gen .export .insert(func_name, format!("{async_export_prefix}{export_name}")); if async_ { - let snake = self.gen.name.to_lower_camel_case(); + let snake = self.r#gen.name.to_lower_camel_case(); let export_func_name = self - .gen + .r#gen .export_ns .tmp(&format!("wasmExport{snake}Async{camel_name}")); let DeferredTaskReturn::Emitted { @@ -987,7 +987,7 @@ impl InterfaceGenerator<'_> { }; let func_name = func.name.clone(); let import_module = self.resolve.name_world_key(interface.unwrap()); - self.gen.export.insert( + self.r#gen.export.insert( export_func_name.clone(), format!("[callback]{async_export_prefix}{export_name}"), ); @@ -1052,12 +1052,12 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); - abi::post_return(bindgen.gen.resolve, func, &mut bindgen); + abi::post_return(bindgen.r#gen.resolve, func, &mut bindgen); let src = bindgen.src; let func_name = self - .gen + .r#gen .export_ns .tmp(&format!("wasmExport{camel_name}PostReturn")); @@ -1069,7 +1069,7 @@ impl InterfaceGenerator<'_> { }} "# ); - self.gen + self.r#gen .export .insert(func_name, format!("cabi_post_{export_name}")); } @@ -1271,18 +1271,18 @@ impl InterfaceGenerator<'_> { fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - let result = abi::lift_from_memory(f.gen.resolve, &mut f, address.into(), ty); + let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); (f.src, result) } fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::lower_to_memory(f.gen.resolve, &mut f, address.into(), value.into(), ty); + abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); f.src } fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String { - let size = self.gen.sizes.size(ty).size_wasm32(); + let size = self.r#gen.sizes.size(ty).size_wasm32(); let ffi = self.qualify_package(FFI_DIR); format!("let {address} = {ffi}malloc({size} * {length});") } @@ -1318,7 +1318,7 @@ impl InterfaceGenerator<'_> { return format!("{ffi}ptr2{ty}_array({address}, {length})"); } - let size = self.gen.sizes.size(ty).size_wasm32(); + let size = self.r#gen.sizes.size(ty).size_wasm32(); format!( r#" FixedArray::makei( @@ -1351,7 +1351,7 @@ impl InterfaceGenerator<'_> { }; return format!("{ffi}{ty}_array2ptr({value})"); } - let size = self.gen.sizes.size(ty).size_wasm32(); + let size = self.r#gen.sizes.size(ty).size_wasm32(); format!( r#" let address = {ffi}malloc(({value}).length() * {size}); @@ -1414,13 +1414,13 @@ impl InterfaceGenerator<'_> { ty: TypeId, result_type: Option<&Type>, ) { - if let Some(set) = self.gen.futures.get(module) { + if let Some(set) = self.r#gen.futures.get(module) { if set.contains(&ty) { return; } } - self.gen + self.r#gen .futures .entry(module.to_string()) .or_default() @@ -1619,10 +1619,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("; "); let mut deriviation: Vec<_> = Vec::new(); - if self.gen.opts.derive_show { + if self.r#gen.opts.derive_show { deriviation.push("Show") } - if self.gen.opts.derive_eq { + if self.r#gen.opts.derive_eq { deriviation.push("Eq") } @@ -1643,13 +1643,13 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { let name = name.to_moonbit_type_ident(); let mut deriviation: Vec<_> = Vec::new(); - if self.gen.opts.derive_show { + if self.r#gen.opts.derive_show { deriviation.push("Show") } - if self.gen.opts.derive_eq { + if self.r#gen.opts.derive_eq { deriviation.push("Eq") } - let declaration = if self.gen.opts.derive_error && name.contains("Error") { + let declaration = if self.r#gen.opts.derive_error && name.contains("Error") { "suberror" } else { "struct" @@ -1719,12 +1719,12 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { "# ); - let func_name = self.gen.export_ns.tmp(&format!("wasmExport{name}Dtor")); + let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{name}Dtor")); - let export_dir = self.gen.opts.gen_dir.clone(); + let export_dir = self.r#gen.opts.gen_dir.clone(); - let mut gen = - self.gen + let mut r#gen = + self.r#gen .interface(self.resolve, export_dir.as_str(), "", Direction::Export); uwrite!( @@ -1734,10 +1734,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { {}{name}::dtor(handle) }} "#, - gen.qualify_package(self.name) + r#gen.qualify_package(self.name) ); - self.gen + self.r#gen .export .insert(func_name, format!("{module}#[dtor]{type_name}")); } @@ -1784,13 +1784,13 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("\n "); let mut deriviation: Vec<_> = Vec::new(); - if self.gen.opts.derive_show { + if self.r#gen.opts.derive_show { deriviation.push("Show") } - if self.gen.opts.derive_eq { + if self.r#gen.opts.derive_eq { deriviation.push("Eq") } - let declaration = if self.gen.opts.derive_error && name.contains("Error") { + let declaration = if self.r#gen.opts.derive_error && name.contains("Error") { "suberror" } else { "struct" @@ -1859,13 +1859,13 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("\n "); let mut deriviation: Vec<_> = Vec::new(); - if self.gen.opts.derive_show { + if self.r#gen.opts.derive_show { deriviation.push("Show") } - if self.gen.opts.derive_eq { + if self.r#gen.opts.derive_eq { deriviation.push("Eq") } - let declaration = if self.gen.opts.derive_error && name.contains("Error") { + let declaration = if self.r#gen.opts.derive_error && name.contains("Error") { "suberror" } else { "enum" @@ -1904,13 +1904,13 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("; "); let mut deriviation: Vec<_> = Vec::new(); - if self.gen.opts.derive_show { + if self.r#gen.opts.derive_show { deriviation.push("Show") } - if self.gen.opts.derive_eq { + if self.r#gen.opts.derive_eq { deriviation.push("Eq") } - let declaration = if self.gen.opts.derive_error && name.contains("Error") { + let declaration = if self.r#gen.opts.derive_error && name.contains("Error") { "suberror" } else { "enum" @@ -2024,7 +2024,7 @@ enum DeferredTaskReturn { } struct FunctionBindgen<'a, 'b> { - gen: &'b mut InterfaceGenerator<'a>, + r#gen: &'b mut InterfaceGenerator<'a>, func_name: &'b str, func_interface: &'b str, params: Box<[String]>, @@ -2040,7 +2040,7 @@ struct FunctionBindgen<'a, 'b> { impl<'a, 'b> FunctionBindgen<'a, 'b> { fn new( - gen: &'b mut InterfaceGenerator<'a>, + r#gen: &'b mut InterfaceGenerator<'a>, func_name: &'b str, func_interface: &'b str, params: Box<[String]>, @@ -2050,7 +2050,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { locals.tmp(str); }); Self { - gen, + r#gen, func_name, func_interface, params, @@ -2104,7 +2104,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .collect::>() .join(", "); - let payload = if self.gen.non_empty_type(ty.as_ref()).is_some() { + let payload = if self.r#gen.non_empty_type(ty.as_ref()).is_some() { payload } else if is_result { format!("_{payload}") @@ -2166,7 +2166,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .collect::>(); // Hacky way to get the type name without type parameter - let ty = self.gen.type_name(ty, false); + let ty = self.r#gen.type_name(ty, false); let lifted = self.locals.tmp("lifted"); let cases = cases @@ -2174,7 +2174,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .zip(blocks) .enumerate() .map(|(i, ((case_name, case_ty), Block { body, results, .. }))| { - let payload = if self.gen.non_empty_type(case_ty.as_ref()).is_some() { + let payload = if self.r#gen.non_empty_type(case_ty.as_ref()).is_some() { results.into_iter().next().unwrap() } else { String::new() @@ -2276,14 +2276,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32FromS8 => results.push(format!( "{}extend8({})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0] )), Instruction::S8FromI32 => results.push(format!("({} - 0x100)", operands[0])), Instruction::S16FromI32 => results.push(format!("({} - 0x10000)", operands[0])), Instruction::I32FromS16 => results.push(format!( "{}extend16({})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0] )), Instruction::U16FromI32 => results.push(format!( @@ -2313,7 +2313,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self.gen.type_name(&Type::Id(*ty), false); + let ty = self.r#gen.type_name(&Type::Id(*ty), false); uwriteln!( self.src, r#" @@ -2325,7 +2325,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U16 | Int::U32 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self.gen.type_name(&Type::Id(*ty), false); + let ty = self.r#gen.type_name(&Type::Id(*ty), false); uwriteln!( self.src, r#" @@ -2337,7 +2337,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U64 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self.gen.type_name(&Type::Id(*ty), false); + let ty = self.r#gen.type_name(&Type::Id(*ty), false); uwriteln!( self.src, r#" @@ -2353,21 +2353,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { results.push(format!( "{}({}.to_byte())", - self.gen.type_name(&Type::Id(*ty), true), + self.r#gen.type_name(&Type::Id(*ty), true), operands[0] )); } Int::U16 | Int::U32 => { results.push(format!( "{}({}.reinterpret_as_uint())", - self.gen.type_name(&Type::Id(*ty), true), + self.r#gen.type_name(&Type::Id(*ty), true), operands[0] )); } Int::U64 => { results.push(format!( "{}(({}).reinterpret_as_uint().to_uint64() | (({}).reinterpret_as_uint().to_uint64() << 32))", - self.gen.type_name(&Type::Id(*ty), true), + self.r#gen.type_name(&Type::Id(*ty), true), operands[0], operands[1] )); @@ -2377,7 +2377,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::HandleLower { ty, .. } => { let op = &operands[0]; let handle = self.locals.tmp("handle"); - let ty = self.gen.type_name(&Type::Id(*ty), false); + let ty = self.r#gen.type_name(&Type::Id(*ty), false); uwrite!( self.src, r#" @@ -2388,7 +2388,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::HandleLift { ty, .. } => { let op = &operands[0]; - let ty = self.gen.type_name(&Type::Id(*ty), false); + let ty = self.r#gen.type_name(&Type::Id(*ty), false); results.push(format!( "{}::{}({})", @@ -2418,7 +2418,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}::{{{ops}}}", - self.gen.type_name(&Type::Id(*ty), true) + self.r#gen.type_name(&Type::Id(*ty), true) )); } @@ -2554,11 +2554,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { let some = self.blocks.pop().unwrap(); let _none = self.blocks.pop().unwrap(); - let ty = self.gen.type_name(&Type::Id(*ty), true); + let ty = self.r#gen.type_name(&Type::Id(*ty), true); let lifted = self.locals.tmp("lifted"); let op = &operands[0]; - let payload = if self.gen.non_empty_type(Some(*payload)).is_some() { + let payload = if self.r#gen.non_empty_type(Some(*payload)).is_some() { some.results.into_iter().next().unwrap() } else { "None".into() @@ -2607,7 +2607,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::EnumLift { ty, .. } => results.push(format!( "{}::from({})", - self.gen.type_name(&Type::Id(*ty), true), + self.r#gen.type_name(&Type::Id(*ty), true), operands[0] )), @@ -2617,7 +2617,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}bytes2ptr({op})", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) )); results.push(format!("{op}.length()")); if realloc.is_none() { @@ -2639,7 +2639,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}{ty}_array2ptr({op})", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) )); results.push(format!("{op}.length()")); if realloc.is_none() { @@ -2660,7 +2660,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { " let {result} = {}ptr2bytes({address}, {length}) ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); results.push(result); @@ -2685,7 +2685,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { " let {result} = {}ptr2{ty}_array({address}, {length}) ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); results.push(result); @@ -2698,7 +2698,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}str2ptr({op})", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) )); results.push(format!("{op}.length()")); if realloc.is_none() { @@ -2716,7 +2716,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { " let {result} = {}ptr2str({address}, {length}) ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); results.push(result); @@ -2732,10 +2732,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { assert!(block_results.is_empty()); let op = &operands[0]; - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); + let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let align = self.r#gen.r#gen.sizes.align(element).align_wasm32(); let address = self.locals.tmp("address"); - let ty = self.gen.type_name(element, true); + let ty = self.r#gen.type_name(element, true); let index = self.locals.tmp("index"); uwrite!( @@ -2748,7 +2748,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { {body} }} ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); if realloc.is_none() { @@ -2773,9 +2773,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; let array = self.locals.tmp("array"); - let ty = self.gen.type_name(element, true); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - // let align = self.gen.gen.sizes.align(element); + let ty = self.r#gen.type_name(element, true); + let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + // let align = self.r#gen.r#gen.sizes.align(element); let index = self.locals.tmp("index"); let result = match &block_results[..] { @@ -2794,7 +2794,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { }} {}free({address}) ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); results.push(array); @@ -2847,7 +2847,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ) } FunctionKind::Constructor(ty) => { - let name = self.gen.type_name(&Type::Id(ty), false); + let name = self.r#gen.type_name(&Type::Id(ty), false); format!( "{}::{}", name, @@ -2858,7 +2858,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { | FunctionKind::Static(ty) | FunctionKind::AsyncMethod(ty) | FunctionKind::AsyncStatic(ty) => { - let name = self.gen.type_name(&Type::Id(ty), false); + let name = self.r#gen.type_name(&Type::Id(ty), false); format!( "{}::{}", name, @@ -2874,7 +2874,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match func.result { Some(ty) => { let res = self.locals.tmp("return_result"); - (res.clone(), res, self.gen.type_name(&ty, true)) + (res.clone(), res, self.r#gen.type_name(&ty, true)) } None => ("_ignore".into(), "".into(), "Unit".into()), }; @@ -2882,7 +2882,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if func.result.is_some() { results.push(async_func_result.clone()); } - let ffi = self.gen.qualify_package(FFI_DIR); + let ffi = self.r#gen.qualify_package(FFI_DIR); uwrite!( self.src, r#" @@ -2929,7 +2929,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - let ty = format!("({})", self.gen.type_name(&ty, true)); + let ty = format!("({})", self.r#gen.type_name(&ty, true)); let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); @@ -2957,7 +2957,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } => uwriteln!( self.src, "{}free({address})", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ), Cleanup::Object(obj) => uwriteln!(self.src, "ignore({obj})"), } @@ -2972,7 +2972,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { }}) ignore(ignoreList) ", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); } @@ -2990,56 +2990,56 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::PointerLoad { offset } | Instruction::LengthLoad { offset } => results.push(format!( "{}load32(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::I32Load8U { offset } => results.push(format!( "{}load8_u(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::I32Load8S { offset } => results.push(format!( "{}load8(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::I32Load16U { offset } => results.push(format!( "{}load16_u(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::I32Load16S { offset } => results.push(format!( "{}load16(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::I64Load { offset } => results.push(format!( "{}load64(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::F32Load { offset } => results.push(format!( "{}loadf32(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), Instruction::F64Load { offset } => results.push(format!( "{}loadf64(({}) + {offset})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0], offset = offset.size_wasm32() )), @@ -3049,7 +3049,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::LengthStore { offset } => uwriteln!( self.src, "{}store32(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3058,7 +3058,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store8 { offset } => uwriteln!( self.src, "{}store8(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3067,7 +3067,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store16 { offset } => uwriteln!( self.src, "{}store16(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3076,7 +3076,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I64Store { offset } => uwriteln!( self.src, "{}store64(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3085,7 +3085,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::F32Store { offset } => uwriteln!( self.src, "{}storef32(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3094,7 +3094,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::F64Store { offset } => uwriteln!( self.src, "{}storef64(({}) + {offset}, {})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[1], operands[0], offset = offset.size_wasm32() @@ -3104,7 +3104,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "{}malloc({})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), size.size_wasm32() ) } @@ -3113,7 +3113,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "{}free({})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0] ) } @@ -3122,7 +3122,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "{}free({})", - self.gen.qualify_package(FFI_DIR), + self.r#gen.qualify_package(FFI_DIR), operands[0] ) } @@ -3172,8 +3172,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - let size = self.gen.gen.sizes.size(element).size_wasm32(); - // let align = self.gen.gen.sizes.align(element); + let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + // let align = self.r#gen.r#gen.sizes.align(element); if !body.trim().is_empty() { let index = self.locals.tmp("index"); @@ -3192,7 +3192,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "{}free({address})", - self.gen.qualify_package(FFI_DIR) + self.r#gen.qualify_package(FFI_DIR) ); } @@ -3203,9 +3203,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::FutureLift { ty, .. } => { let result = self.locals.tmp("result"); let op = &operands[0]; - // let qualifier = self.r#gen.qualify_package(self.func_interface); - let ty = self.gen.type_name(&Type::Id(*ty), true); - let ffi = self.gen.qualify_package(FFI_DIR); + // let qualifier = self.r#r#gen.qualify_package(self.func_interface); + let ty = self.r#gen.type_name(&Type::Id(*ty), true); + let ffi = self.r#gen.qualify_package(FFI_DIR); let snake_name = format!("static_{}_future_table", ty.to_snake_case(),); @@ -3254,8 +3254,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let op = &operands[0]; let qualifier = self.r#gen.qualify_package(self.func_interface); - let ty = self.gen.type_name(&Type::Id(*ty), true); - let ffi = self.gen.qualify_package(FFI_DIR); + let ty = self.r#gen.type_name(&Type::Id(*ty), true); + let ffi = self.r#gen.qualify_package(FFI_DIR); let snake_name = format!( "static_{}_stream_table", ty.replace(&qualifier, "").to_snake_case(), @@ -3275,8 +3275,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { - if self.gen.direction == Direction::Import { - let ffi_qualifier = self.gen.qualify_package(FFI_DIR); + if self.r#gen.direction == Direction::Import { + let ffi_qualifier = self.r#gen.qualify_package(FFI_DIR); let address = self.locals.tmp("return_area"); uwriteln!( self.src, @@ -3290,8 +3290,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { }); address } else { - self.gen.gen.return_area_size = self.gen.gen.return_area_size.max(size); - self.gen.gen.return_area_align = self.gen.gen.return_area_align.max(align); + self.r#gen.r#gen.return_area_size = self.r#gen.r#gen.return_area_size.max(size); + self.r#gen.r#gen.return_area_align = self.r#gen.r#gen.return_area_align.max(align); "return_area".into() } } @@ -3342,7 +3342,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn sizes(&self) -> &SizeAlign { - &self.gen.gen.sizes + &self.r#gen.r#gen.sizes } fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool { diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index b83da0058..4aab0433e 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -1,12 +1,12 @@ use crate::{ - classify_constructor_return_type, int_repr, to_rust_ident, ConstructorReturnType, Identifier, - InterfaceGenerator, RustFlagsRepr, + ConstructorReturnType, Identifier, InterfaceGenerator, RustFlagsRepr, + classify_constructor_return_type, int_repr, to_rust_ident, }; use heck::*; use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{Bindgen, Instruction, LiftLower, WasmType}; -use wit_bindgen_core::{dealias, uwrite, uwriteln, wit_parser::*, Source}; +use wit_bindgen_core::{Source, dealias, uwrite, uwriteln, wit_parser::*}; pub(super) struct FunctionBindgen<'a, 'b> { pub r#gen: &'b mut InterfaceGenerator<'a>, @@ -736,7 +736,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::ListLower { element, realloc } => { let alloc = self.r#gen.path_to_std_alloc_module(); - let rt = self.gen.gen.runtime_path().to_string(); + let rt = self.r#gen.r#gen.runtime_path().to_string(); let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); let vec = format!("vec{tmp}"); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index a8291b6d8..c9a55eb14 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1,8 +1,8 @@ use crate::bindgen::{FunctionBindgen, POINTER_SIZE_EXPRESSION}; use crate::{ - classify_constructor_return_type, full_wit_type_name, int_repr, to_rust_ident, - to_upper_camel_case, wasm_type, ConstructorReturnType, FnSig, Identifier, InterfaceName, - Ownership, RuntimeItem, RustFlagsRepr, RustWasm, TypeGeneration, + ConstructorReturnType, FnSig, Identifier, InterfaceName, Ownership, RuntimeItem, RustFlagsRepr, + RustWasm, TypeGeneration, classify_constructor_return_type, full_wit_type_name, int_repr, + to_rust_ident, to_upper_camel_case, wasm_type, }; use anyhow::Result; use heck::*; @@ -11,7 +11,7 @@ use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{self, AbiVariant, LiftLower}; use wit_bindgen_core::{ - dealias, uwrite, uwriteln, wit_parser::*, AnonymousTypeGenerator, Source, TypeInfo, + AnonymousTypeGenerator, Source, TypeInfo, dealias, uwrite, uwriteln, wit_parser::*, }; pub struct InterfaceGenerator<'a> { diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index fd92b1953..dece973ba 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -1,5 +1,5 @@ use crate::interface::InterfaceGenerator; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use core::panic; use heck::*; use indexmap::{IndexMap, IndexSet}; @@ -9,8 +9,8 @@ use std::mem; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - dealias, name_package_module, uwrite, uwriteln, wit_parser::*, AsyncFilterSet, Files, - InterfaceGenerator as _, Source, Types, WorldGenerator, + AsyncFilterSet, Files, InterfaceGenerator as _, Source, Types, WorldGenerator, dealias, + name_package_module, uwrite, uwriteln, wit_parser::*, }; mod bindgen; diff --git a/crates/test/src/config.rs b/crates/test/src/config.rs index b2bf7e190..03fc4f928 100644 --- a/crates/test/src/config.rs +++ b/crates/test/src/config.rs @@ -28,8 +28,8 @@ use anyhow::Context; use anyhow::Result; -use serde::de::DeserializeOwned; use serde::Deserialize; +use serde::de::DeserializeOwned; use std::collections::HashMap; /// Configuration that can be placed at the top of runtime tests in source diff --git a/crates/test/src/custom.rs b/crates/test/src/custom.rs index c260d8640..7abc9e09b 100644 --- a/crates/test/src/custom.rs +++ b/crates/test/src/custom.rs @@ -1,5 +1,5 @@ use crate::{Bindgen, Compile, LanguageMethods, Runner, Verify}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use clap::Parser; use std::env; use std::path::Path; 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..327c3124e 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use clap::Parser; use rayon::prelude::*; use std::borrow::Cow; @@ -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/runner.rs b/crates/test/src/runner.rs index 22f2b04ce..4dec37576 100644 --- a/crates/test/src/runner.rs +++ b/crates/test/src/runner.rs @@ -17,7 +17,7 @@ impl TestRunner { return Ok(TestRunner { path: runner.clone(), args: Vec::new(), - }) + }); } Err(e) => e, }; 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..3415dd71a 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -1,8 +1,8 @@ -use anyhow::{bail, Context, Error, Result}; +use anyhow::{Context, Error, Result, bail}; use clap::Parser; use std::path::PathBuf; use std::str; -use wit_bindgen_core::{wit_parser, Files, WorldGenerator}; +use wit_bindgen_core::{Files, WorldGenerator, wit_parser}; use wit_parser::Resolve; /// Helper for passing VERSION to opt. @@ -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..4b822b894 --- /dev/null +++ b/tests/runtime-async/async/simple-stream-payload/test.go @@ -0,0 +1,54 @@ +package export_my_test_i + +import ( + "fmt" + "slices" + . "wit_component/wit_types" +) + +func ReadStream(x *StreamReader[uint8]) { + defer x.Drop() + + { + buffer := make([]uint8, 1) + count := x.Read(buffer) + assertEqual(count, 1) + assert(slices.Equal(buffer, []uint8{0})) + assert(!x.WriterDropped()) + } + + { + buffer := make([]uint8, 2) + count := x.Read(buffer) + assertEqual(count, 2) + assert(slices.Equal(buffer, []uint8{1, 2})) + assert(!x.WriterDropped()) + } + + { + buffer := make([]uint8, 1) + count := x.Read(buffer) + assertEqual(count, 1) + assert(slices.Equal(buffer, []uint8{3})) + assert(!x.WriterDropped()) + } + + { + buffer := make([]uint8, 1) + count := x.Read(buffer) + assertEqual(count, 1) + assert(slices.Equal(buffer, []uint8{4})) + } +} + +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/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..80251ded5 --- /dev/null +++ b/tests/runtime-async/async/simple-stream/test.go @@ -0,0 +1,35 @@ +package export_my_test_i + +import ( + "fmt" + . "wit_component/wit_types" +) + +func ReadStream(x *StreamReader[Unit]) { + defer x.Drop() + + { + buffer := make([]Unit, 1) + count := x.Read(buffer) + assertEqual(count, 1) + assert(!x.WriterDropped()) + } + + { + buffer := make([]Unit, 2) + count := x.Read(buffer) + assertEqual(count, 2) + } +} + +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 +} From 9d8d5112951962a79db61a08c1ec7004126855b9 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 5 Dec 2025 16:38:09 -0700 Subject: [PATCH 2/3] remove indirection in `wit_types.Option` Signed-off-by: Joel Dice --- crates/go/src/wit_option.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/go/src/wit_option.go b/crates/go/src/wit_option.go index 083c542a1..f76d48715 100644 --- a/crates/go/src/wit_option.go +++ b/crates/go/src/wit_option.go @@ -6,36 +6,33 @@ const ( ) type Option[T any] struct { - value *T + tag uint8 + value T } func (self Option[T]) Tag() uint8 { - if self.value == nil { - return OptionNone - } else { - return OptionSome - } + return self.tag } func (self Option[T]) Some() T { - if self.value == nil { + if self.tag != OptionSome { panic("tag mismatch") } - return *self.value + return self.value } func (self Option[T]) SomeOr(value T) T { - if self.value == nil { + if self.tag != OptionSome { return value } else { - return *self.value + return self.value } } func None[T any]() Option[T] { - return Option[T]{nil} + return Option[T]{OptionNone, make([]T, 1)[0]} } func Some[T any](value T) Option[T] { - return Option[T]{&value} + return Option[T]{OptionSome, value} } From 4d491c165246257d9d23efd78aefa027a5a575db Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 5 Dec 2025 17:41:42 -0700 Subject: [PATCH 3/3] add crates/go/README.md Signed-off-by: Joel Dice --- README.md | 6 ++- crates/go/README.md | 113 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 crates/go/README.md diff --git a/README.md b/README.md index afdee28e0..63050f19c 100644 --- a/README.md +++ b/README.md @@ -362,7 +362,11 @@ Now you can build with: dotnet publish ``` -Checkout out [componentize-dotnet](https://github.com/bytecodealliance/componentize-dotnet) for a simplified experience. +Check out [componentize-dotnet](https://github.com/bytecodealliance/componentize-dotnet) for a simplified experience. + +### Guest: Go + +See the [wit-bindgen-go README.md](crates/go/README.md) for details and generating and using Go bindings. ### Guest: Java diff --git a/crates/go/README.md b/crates/go/README.md new file mode 100644 index 000000000..a43340017 --- /dev/null +++ b/crates/go/README.md @@ -0,0 +1,113 @@ +# `wit-bindgen` Go Bindings Generator + +This tool generates Go bindings for a chosen WIT world. + +## Usage + +To generate bindings with this crate, issue the `go` subcommand to `wit-bindgen`: + +```bash +$ wit-bindgen go [OPTIONS] +``` + +See the output of `wit-bindgen help go` for available options. + +This command will generate a variable number of files, depending on the WIT +world provided: + +- `go.mod`: defines a minimal Go module with the name `wit_component` + - You can replace this with your own version (e.g. referencing third party dependencies) if desired +- `wit_bindings.go`: defines the `main` package for the module, including low-level, `//go:export`-annotated entrypoint functions corresponding to exported functions + - These entrypoint functions in turn call high-level functions which must be provided by the application developer +- `wit_runtime/wit_runtime.go`: defines low-level functions for supporting the component model ABI +- `/wit_bindings.go`: defines any types generated for the interface named `` (or `wit_world` for WIT types defined at the world level), plus any imported functions + - Note that the types placed in these files include all types for both imported and exported interfaces, except for exported resource types and any types which depend on exported resource types +- `export_/wit_bindings.go`: defines intrinsics for use with any exported resource types generated for the interface named `` (or `wit_world` for WIT types defined at the world level), plus any types which depend on those exported resource types, plus any exported functions + - The exported resource type definitions must be provided by the application developer + - The `export_` package is also the place to define any exported functions +- (if needed) `wit_types/wit_tuples.go`: defines `Tuple` types as required by the WIT world +- (if needed) `wit_types/wit_async.go`: defines low-level functions for integrating the Go scheduler with the component model async ABI +- (if needed) `wit_types/wit_option.go`: defines an `Option` type if required by the WIT world +- (if needed) `wit_types/wit_result.go`: defines an `Result` type if required by the WIT world +- (if needed) `wit_types/wit_unit.go`: defines an `Unit` type if required by the WIT world +- (if needed) `wit_types/wit_stream.go`: defines a `StreamReader` and `StreamWriter` types if required by the WIT world +- (if needed) `wit_types/wit_future.go`: defines a `FutureReader` and `FutureWriter` types if required by the WIT world + +## Example + +### Prerequisites + +- `wit-bindgen` +- `wasm-tools` +- `wasmtime` +- `curl` +- `bash` or similar + +Given the following WIT file, we can generate bindings for it, write code to +target the two worlds, and finally compose, build, and run the components. + +```shell +cat >world.wit < tuple; +} + +world test { + export foo; +} + +world runner { + import foo; + export run: func() -> tuple; +} +EOF + +curl -OL https://github.com/bytecodealliance/wasmtime/releases/download/v39.0.1/wasi_snapshot_preview1.reactor.wasm +mkdir test runner + +pushd test +wit-bindgen go -w test ../world.wit +mkdir export_test_test_foo +cat >export_test_test_foo/test.go <export_wit_world/runner.go <