From 538bb51595d3d051b51aea145c372c1c89ba4ee0 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Dec 2025 14:26:16 -0700 Subject: [PATCH 01/13] fix: Return WasmI32 types from imported functions --- Cargo.lock | 2 +- cmd/gravity/src/codegen/func.rs | 27 ++- cmd/gravity/src/codegen/imports.rs | 46 +++-- cmd/gravity/src/go/imports.rs | 28 +++ cmd/gravity/tests/cmd/basic.stdout | 189 +++++++++++------- ...ing.stderr => iface-method-returns.stderr} | 0 ...ing.stdout => iface-method-returns.stdout} | 142 +++++++++---- ...-string.toml => iface-method-returns.toml} | 2 +- examples/generate.go | 4 +- .../Cargo.toml | 2 +- .../example_test.go | 25 ++- .../src/lib.rs | 4 + .../wit/example.wit | 3 + 13 files changed, 323 insertions(+), 151 deletions(-) rename cmd/gravity/tests/cmd/{iface-method-returns-string.stderr => iface-method-returns.stderr} (100%) rename cmd/gravity/tests/cmd/{iface-method-returns-string.stdout => iface-method-returns.stdout} (67%) rename cmd/gravity/tests/cmd/{iface-method-returns-string.toml => iface-method-returns.toml} (64%) rename examples/{iface-method-returns-string => iface-method-returns}/Cargo.toml (75%) rename examples/{iface-method-returns-string => iface-method-returns}/example_test.go (64%) rename examples/{iface-method-returns-string => iface-method-returns}/src/lib.rs (83%) rename examples/{iface-method-returns-string => iface-method-returns}/wit/example.wit (76%) diff --git a/Cargo.lock b/Cargo.lock index 345d262..beb2d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ dependencies = [ ] [[package]] -name = "example-iface-method-returns-string" +name = "example-iface-method-returns" version = "0.0.2" dependencies = [ "wit-bindgen", diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index a18c97b..1eb4ff1 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -62,11 +62,12 @@ impl<'a> Func<'a> { } /// Create a new exported function. - pub fn import(param_name: &'a GoIdentifier, result: GoResult, sizes: &'a SizeAlign) -> Self { + pub fn import(param_name: &'a GoIdentifier, sizes: &'a SizeAlign) -> Self { Self { direction: Direction::Import { param_name }, args: Vec::new(), - result, + // Imports use the stack so the generated function never has a return value + result: GoResult::Empty, tmp: 0, body: Tokens::new(), block_storage: Vec::new(), @@ -551,12 +552,22 @@ impl Bindgen for Func<'_> { } Instruction::ResultLift { .. } => todo!("implement instruction: {inst:?}"), Instruction::Return { amt, .. } => { - if *amt != 0 { - let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - return $operand - }; + if matches!(self.direction, Direction::Export) { + if *amt != 0 { + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + return $operand + }; + } + } else { + for idx in 0..*amt { + let operand = &operands[idx]; + quote_in! { self.body => + $['\r'] + stack[$idx] = $operand + }; + } } } Instruction::CallInterface { func, .. } => { diff --git a/cmd/gravity/src/codegen/imports.rs b/cmd/gravity/src/codegen/imports.rs index 8f828c1..76a2d24 100644 --- a/cmd/gravity/src/codegen/imports.rs +++ b/cmd/gravity/src/codegen/imports.rs @@ -18,7 +18,10 @@ use crate::{ }, go::{ GoIdentifier, GoResult, GoType, - imports::{CONTEXT_CONTEXT, WAZERO_API_MODULE}, + imports::{ + CONTEXT_CONTEXT, GoImport, WAZERO_API_DECODE_U32, WAZERO_API_GO_MODULE_FUNC, + WAZERO_API_MODULE, WAZERO_API_VALUE_TYPE, + }, }, resolve_type, }; @@ -414,21 +417,10 @@ impl<'a> ImportCodeGenerator<'a> { ) -> Tokens { let func_name = &method.name; - // Generate Wasm function parameters based on WIT types. - let wasm_params = vec![ - quote! { ctx $CONTEXT_CONTEXT }, - quote! { mod $WAZERO_API_MODULE }, - ]; - let wasm_sig = self .resolve .wasm_signature(AbiVariant::GuestImport, &method.wit_function); - let result = if wasm_sig.results.is_empty() { - GoResult::Empty - } else { - todo!("implement handling of wasm signatures with results"); - }; - let mut f = Func::import(param_name, result, self.sizes); + let mut f = Func::import(param_name, self.sizes); // Magic wit_bindgen_core::abi::call( @@ -441,14 +433,28 @@ impl<'a> ImportCodeGenerator<'a> { false, ); + let args = f.args().iter().enumerate(); + let wasm_params = wasm_sig.params.iter().map(GoImport::from); + let wasm_results = wasm_sig.results.iter().map(GoImport::from); + quote! { NewFunctionBuilder(). - WithFunc(func( - $(for param in wasm_params join (,$['\r']) => $param), - $(for param in f.args() join (,$['\r']) => $param uint32), - ) $(f.result()){ - $(f.body()) - }). + WithGoModuleFunction( + $WAZERO_API_GO_MODULE_FUNC(func( + ctx $CONTEXT_CONTEXT, + mod $WAZERO_API_MODULE, + stack []uint64, + ) { + $(for (idx, arg) in args join ($['\r']) => $arg := $WAZERO_API_DECODE_U32(stack[$idx])) + $(f.body()) + }), + []$WAZERO_API_VALUE_TYPE{ + $(for typ in wasm_params join ($['\r']) => $typ,) + }, + []$WAZERO_API_VALUE_TYPE{ + $(for typ in wasm_results join ($['\r']) => $typ,) + }, + ). Export($(quoted(func_name))). } } @@ -564,7 +570,7 @@ mod tests { // Should have only one uint32 parameter (plus ctx and mod) let code_str = result.to_string().unwrap(); - assert!(code_str.contains("arg0 uint32")); + assert!(code_str.contains("arg0 := api.DecodeU32(stack[0])")); assert!(!code_str.contains("arg1 uint32")); assert!(!code_str.contains("mod.Memory().Read")); // No string reading diff --git a/cmd/gravity/src/go/imports.rs b/cmd/gravity/src/go/imports.rs index 53ea3f2..41e3f1e 100644 --- a/cmd/gravity/src/go/imports.rs +++ b/cmd/gravity/src/go/imports.rs @@ -1,4 +1,5 @@ use genco::{Tokens, lang::Go, tokens::FormatInto}; +use wit_bindgen_core::abi::WasmType; #[derive(Debug, Clone, Copy)] pub struct GoImport(&'static str, &'static str); @@ -9,6 +10,21 @@ impl FormatInto for GoImport { } } +impl From<&WasmType> for GoImport { + fn from(typ: &WasmType) -> Self { + match typ { + WasmType::I32 => WAZERO_API_VALUE_TYPE_I32, + WasmType::I64 => WAZERO_API_VALUE_TYPE_I64, + WasmType::F32 => WAZERO_API_VALUE_TYPE_F32, + WasmType::F64 => WAZERO_API_VALUE_TYPE_F64, + // TODO: Verify that Gravity/Wazero "doesn't do anything special" and can treat these as such + WasmType::Pointer => WAZERO_API_VALUE_TYPE_I32, + WasmType::PointerOrI64 => WAZERO_API_VALUE_TYPE_I64, + WasmType::Length => WAZERO_API_VALUE_TYPE_I32, + } + } +} + pub static CONTEXT_CONTEXT: GoImport = GoImport("context", "Context"); pub static ERRORS_NEW: GoImport = GoImport("errors", "New"); pub static FMT_PRINTF: GoImport = GoImport("fmt", "Printf"); @@ -36,4 +52,16 @@ pub static WAZERO_API_ENCODE_F64: GoImport = GoImport("github.com/tetratelabs/wazero/api", "EncodeF64"); pub static WAZERO_API_DECODE_F64: GoImport = GoImport("github.com/tetratelabs/wazero/api", "DecodeF64"); +pub static WAZERO_API_VALUE_TYPE: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "ValueType"); +pub static WAZERO_API_VALUE_TYPE_I32: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "ValueTypeI32"); +pub static WAZERO_API_VALUE_TYPE_I64: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "ValueTypeI64"); +pub static WAZERO_API_VALUE_TYPE_F32: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "ValueTypeF32"); +pub static WAZERO_API_VALUE_TYPE_F64: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "ValueTypeF64"); +pub static WAZERO_API_GO_MODULE_FUNC: GoImport = + GoImport("github.com/tetratelabs/wazero/api", "GoModuleFunc"); pub static REFLECT_VALUE_OF: GoImport = GoImport("reflect", "ValueOf"); diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index 5fd7413..1632089 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -58,64 +58,96 @@ func NewBasicFactory( _, err0 := wazeroRuntime.NewHostModuleBuilder("arcjet:basic/logger"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - logger.Debug(ctx, str0) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + logger.Debug(ctx, str0) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("debug"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - logger.Info(ctx, str0) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + logger.Info(ctx, str0) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("info"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - logger.Warn(ctx, str0) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + logger.Warn(ctx, str0) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("warn"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - logger.Error(ctx, str0) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + logger.Error(ctx, str0) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("error"). Instantiate(ctx) if err0 != nil { @@ -123,28 +155,37 @@ func NewBasicFactory( } _, err1 := wazeroRuntime.NewHostModuleBuilder("arcjet:basic/utils"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - arg2 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - value1 := utils.Uppercase(ctx, str0) - memory2 := mod.Memory() - realloc2 := mod.ExportedFunction("cabi_realloc") - ptr2, len2, err2 := writeString(ctx, value1, memory2, realloc2) - if err2 != nil { - panic(err2) - } - mod.Memory().WriteUint32Le(arg2+4, uint32(len2)) - mod.Memory().WriteUint32Le(arg2+0, uint32(ptr2)) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + arg2 := api.DecodeU32(stack[2]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + value1 := utils.Uppercase(ctx, str0) + memory2 := mod.Memory() + realloc2 := mod.ExportedFunction("cabi_realloc") + ptr2, len2, err2 := writeString(ctx, value1, memory2, realloc2) + if err2 != nil { + panic(err2) + } + mod.Memory().WriteUint32Le(arg2+4, uint32(len2)) + mod.Memory().WriteUint32Le(arg2+0, uint32(ptr2)) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("uppercase"). Instantiate(ctx) if err1 != nil { diff --git a/cmd/gravity/tests/cmd/iface-method-returns-string.stderr b/cmd/gravity/tests/cmd/iface-method-returns.stderr similarity index 100% rename from cmd/gravity/tests/cmd/iface-method-returns-string.stderr rename to cmd/gravity/tests/cmd/iface-method-returns.stderr diff --git a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout b/cmd/gravity/tests/cmd/iface-method-returns.stdout similarity index 67% rename from cmd/gravity/tests/cmd/iface-method-returns-string.stdout rename to cmd/gravity/tests/cmd/iface-method-returns.stdout index f0c8a04..6e19377 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns.stdout @@ -19,6 +19,9 @@ type IExampleRuntime interface { Arch( ctx context.Context, ) string + GetU32( + ctx context.Context, + ) uint32 Puts( ctx context.Context, msg string, @@ -38,53 +41,92 @@ func NewExampleFactory( _, err0 := wazeroRuntime.NewHostModuleBuilder("arcjet:example/runtime"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - ) { - value0 := runtime.Os(ctx, ) - memory1 := mod.Memory() - realloc1 := mod.ExportedFunction("cabi_realloc") - ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) - if err1 != nil { - panic(err1) - } - mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) - mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + value0 := runtime.Os(ctx, ) + memory1 := mod.Memory() + realloc1 := mod.ExportedFunction("cabi_realloc") + ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) + if err1 != nil { + panic(err1) + } + mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) + mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) + }), + []api.ValueType{ + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("os"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - ) { - value0 := runtime.Arch(ctx, ) - memory1 := mod.Memory() - realloc1 := mod.ExportedFunction("cabi_realloc") - ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) - if err1 != nil { - panic(err1) - } - mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) - mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + value0 := runtime.Arch(ctx, ) + memory1 := mod.Memory() + realloc1 := mod.ExportedFunction("cabi_realloc") + ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) + if err1 != nil { + panic(err1) + } + mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) + mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) + }), + []api.ValueType{ + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("arch"). NewFunctionBuilder(). - WithFunc(func( - ctx context.Context, - mod api.Module, - arg0 uint32, - arg1 uint32, - ) { - buf0, ok0 := mod.Memory().Read(arg0, arg1) - if !ok0 { - panic(errors.New("failed to read bytes from memory")) - } - str0 := string(buf0) - runtime.Puts(ctx, str0) - }). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + value0 := runtime.GetU32(ctx, ) + result1 := api.EncodeU32(value0) + stack[0] = result1 + }), + []api.ValueType{}, + []api.ValueType{ + api.ValueTypeI32, + }, + ). + Export("get-u32"). + NewFunctionBuilder(). + WithGoModuleFunction( + api.GoModuleFunc(func( + ctx context.Context, + mod api.Module, + stack []uint64, + ) { + arg0 := api.DecodeU32(stack[0]) + arg1 := api.DecodeU32(stack[1]) + buf0, ok0 := mod.Memory().Read(arg0, arg1) + if !ok0 { + panic(errors.New("failed to read bytes from memory")) + } + str0 := string(buf0) + runtime.Puts(ctx, str0) + }), + []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, + []api.ValueType{}, + ). Export("puts"). Instantiate(ctx) if err0 != nil { @@ -225,3 +267,17 @@ func (i *ExampleInstance) Hello( return value8, err8 } +func (i *ExampleInstance) CallGetU32( + ctx context.Context, +) uint32 { + raw0, err0 := i.module.ExportedFunction("call-get-u32").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := api.DecodeU32(results0) + return result1 +} + diff --git a/cmd/gravity/tests/cmd/iface-method-returns-string.toml b/cmd/gravity/tests/cmd/iface-method-returns.toml similarity index 64% rename from cmd/gravity/tests/cmd/iface-method-returns-string.toml rename to cmd/gravity/tests/cmd/iface-method-returns.toml index 440e6fc..498ac76 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns-string.toml +++ b/cmd/gravity/tests/cmd/iface-method-returns.toml @@ -1,2 +1,2 @@ bin.name = "gravity" -args = "--world example ../../target/wasm32-unknown-unknown/release/example_iface_method_returns_string.wasm" +args = "--world example ../../target/wasm32-unknown-unknown/release/example_iface_method_returns.wasm" diff --git a/examples/generate.go b/examples/generate.go index 47058c8..d249d09 100644 --- a/examples/generate.go +++ b/examples/generate.go @@ -1,9 +1,9 @@ package examples //go:generate cargo build -p example-basic --target wasm32-unknown-unknown --release -//go:generate cargo build -p example-iface-method-returns-string --target wasm32-unknown-unknown --release +//go:generate cargo build -p example-iface-method-returns --target wasm32-unknown-unknown --release //go:generate cargo build -p example-instructions --target wasm32-unknown-unknown --release //go:generate cargo run --bin gravity -- --world basic --output ./basic/basic.go ../target/wasm32-unknown-unknown/release/example_basic.wasm -//go:generate cargo run --bin gravity -- --world example --output ./iface-method-returns-string/example.go ../target/wasm32-unknown-unknown/release/example_iface_method_returns_string.wasm +//go:generate cargo run --bin gravity -- --world example --output ./iface-method-returns/example.go ../target/wasm32-unknown-unknown/release/example_iface_method_returns.wasm //go:generate cargo run --bin gravity -- --world instructions --output ./instructions/bindings.go ../target/wasm32-unknown-unknown/release/example_instructions.wasm diff --git a/examples/iface-method-returns-string/Cargo.toml b/examples/iface-method-returns/Cargo.toml similarity index 75% rename from examples/iface-method-returns-string/Cargo.toml rename to examples/iface-method-returns/Cargo.toml index c53cf72..2c64e31 100644 --- a/examples/iface-method-returns-string/Cargo.toml +++ b/examples/iface-method-returns/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "example-iface-method-returns-string" +name = "example-iface-method-returns" version = "0.0.2" edition = "2024" diff --git a/examples/iface-method-returns-string/example_test.go b/examples/iface-method-returns/example_test.go similarity index 64% rename from examples/iface-method-returns-string/example_test.go rename to examples/iface-method-returns/example_test.go index 6e6a337..cc82b57 100644 --- a/examples/iface-method-returns-string/example_test.go +++ b/examples/iface-method-returns/example_test.go @@ -13,9 +13,10 @@ type Runtime struct { func (Runtime) Os(context.Context) string { return runtime.GOOS } func (Runtime) Arch(context.Context) string { return runtime.GOARCH } +func (Runtime) GetU32(context.Context) uint32 { return 42 } func (r *Runtime) Puts(_ context.Context, msg string) { r.msg = msg } -func TestBasic(t *testing.T) { +func TestHello(t *testing.T) { r := &Runtime{} fac, err := NewExampleFactory(t.Context(), r) if err != nil { @@ -44,3 +45,25 @@ func TestBasic(t *testing.T) { t.Errorf("wanted: %s, but got: %s", wantPutsMsg, r.msg) } } + +func TestCallGetU32(t *testing.T) { + r := &Runtime{} + fac, err := NewExampleFactory(t.Context(), r) + if err != nil { + t.Fatal(err) + } + defer fac.Close(t.Context()) + + ins, err := fac.Instantiate(t.Context()) + if err != nil { + t.Fatal(err) + } + defer ins.Close(t.Context()) + + value := ins.CallGetU32(t.Context()) + + var want uint32 = 42 + if value != want { + t.Errorf("wanted: %d, but got: %d", want, value) + } +} diff --git a/examples/iface-method-returns-string/src/lib.rs b/examples/iface-method-returns/src/lib.rs similarity index 83% rename from examples/iface-method-returns-string/src/lib.rs rename to examples/iface-method-returns/src/lib.rs index b50bf52..c7c5cb1 100644 --- a/examples/iface-method-returns-string/src/lib.rs +++ b/examples/iface-method-returns/src/lib.rs @@ -14,4 +14,8 @@ impl Guest for ExampleWorld { Ok("Hello, world!".into()) } + + fn call_get_u32() -> u32 { + runtime::get_u32() + } } diff --git a/examples/iface-method-returns-string/wit/example.wit b/examples/iface-method-returns/wit/example.wit similarity index 76% rename from examples/iface-method-returns-string/wit/example.wit rename to examples/iface-method-returns/wit/example.wit index 5e82f65..7b26735 100644 --- a/examples/iface-method-returns-string/wit/example.wit +++ b/examples/iface-method-returns/wit/example.wit @@ -4,6 +4,8 @@ interface runtime { os: func() -> string; arch: func() -> string; + get-u32: func() -> u32; + puts: func(msg: string); } @@ -11,4 +13,5 @@ world example { import runtime; export hello: func() -> result; + export call-get-u32: func() -> u32; } From 22ade39b891474e313905ef13d01aa4990ac2e16 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:06:40 -0700 Subject: [PATCH 02/13] remove Direction to implement as separate structs --- cmd/gravity/src/codegen/exports.rs | 7 +- cmd/gravity/src/codegen/func.rs | 336 ++---- cmd/gravity/src/codegen/imported_func.rs | 972 ++++++++++++++++++ cmd/gravity/src/codegen/imports.rs | 10 +- cmd/gravity/src/codegen/mod.rs | 2 + cmd/gravity/tests/cmd/basic.stdout | 42 +- .../tests/cmd/iface-method-returns.stdout | 12 +- 7 files changed, 1097 insertions(+), 284 deletions(-) create mode 100644 cmd/gravity/src/codegen/imported_func.rs diff --git a/cmd/gravity/src/codegen/exports.rs b/cmd/gravity/src/codegen/exports.rs index 9005aba..aa28749 100644 --- a/cmd/gravity/src/codegen/exports.rs +++ b/cmd/gravity/src/codegen/exports.rs @@ -1,7 +1,10 @@ use genco::prelude::*; use wit_bindgen_core::wit_parser::{Function, Resolve, SizeAlign, World, WorldItem}; -use crate::go::{GoIdentifier, GoResult, GoType, imports::CONTEXT_CONTEXT}; +use crate::{ + Func, + go::{GoIdentifier, GoResult, GoType, imports::CONTEXT_CONTEXT}, +}; pub struct ExportConfig<'a> { pub instance: &'a GoIdentifier, @@ -49,7 +52,7 @@ impl<'a> ExportGenerator<'a> { GoResult::Empty }; - let mut f = crate::Func::export(result, self.config.sizes); + let mut f = Func::new(result, self.config.sizes); wit_bindgen_core::abi::call( self.config.resolve, wit_bindgen_core::abi::AbiVariant::GuestExport, diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index 1eb4ff1..e259f1a 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -18,24 +18,7 @@ use crate::{ resolve_type, resolve_wasm_type, }; -/// The direction of a function. -/// -/// Functions in the Component Model can be imported into a world or -/// exported from a world. -enum Direction<'a> { - /// The function is imported into the world. - Import { - /// The name of the parameter representing the interface instance - /// in the generated host binding function. - param_name: &'a GoIdentifier, - }, - /// The function is exported from the world. - #[allow(dead_code, reason = "halfway through refactor of func bindings")] - Export, -} - pub struct Func<'a> { - direction: Direction<'a>, args: Vec, result: GoResult, tmp: usize, @@ -47,10 +30,8 @@ pub struct Func<'a> { impl<'a> Func<'a> { /// Create a new exported function. - #[allow(dead_code, reason = "halfway through refactor of func bindings")] - pub fn export(result: GoResult, sizes: &'a SizeAlign) -> Self { + pub fn new(result: GoResult, sizes: &'a SizeAlign) -> Self { Self { - direction: Direction::Export, args: Vec::new(), result, tmp: 0, @@ -61,21 +42,6 @@ impl<'a> Func<'a> { } } - /// Create a new exported function. - pub fn import(param_name: &'a GoIdentifier, sizes: &'a SizeAlign) -> Self { - Self { - direction: Direction::Import { param_name }, - args: Vec::new(), - // Imports use the stack so the generated function never has a return value - result: GoResult::Empty, - tmp: 0, - body: Tokens::new(), - block_storage: Vec::new(), - blocks: Vec::new(), - sizes, - } - } - fn tmp(&mut self) -> usize { let ret = self.tmp; self.tmp += 1; @@ -139,46 +105,33 @@ impl Bindgen for Func<'_> { let memory = &format!("memory{tmp}"); let realloc = &format!("realloc{tmp}"); let operand = &operands[0]; - match self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - $memory := i.module.Memory() - $realloc := i.module.ExportedFunction($(quoted(*realloc_name))) - $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) - $(match &self.result { - GoResult::Anon(GoType::ValueOrError(typ)) => { - if $err != nil { - var $default $(typ.as_ref()) - return $default, $err - } - } - GoResult::Anon(GoType::Error) => { - if $err != nil { - return $err - } - } - GoResult::Anon(_) | GoResult::Empty => { - $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) - if $err != nil { - panic($err) - } - } - }) + + quote_in! { self.body => + $['\r'] + $memory := i.module.Memory() + $realloc := i.module.ExportedFunction($(quoted(*realloc_name))) + $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) + $(match &self.result { + GoResult::Anon(GoType::ValueOrError(typ)) => { + if $err != nil { + var $default $(typ.as_ref()) + return $default, $err + } } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - $memory := mod.Memory() - $realloc := mod.ExportedFunction($(quoted(*realloc_name))) - $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) + GoResult::Anon(GoType::Error) => { + if $err != nil { + return $err + } + } + GoResult::Anon(_) | GoResult::Empty => { + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) if $err != nil { panic($err) } - }; - } + } + }) } + results.push(Operand::SingleValue(ptr.into())); results.push(Operand::SingleValue(len.into())); } @@ -437,44 +390,32 @@ impl Bindgen for Func<'_> { let str = &format!("str{tmp}"); let ptr = &operands[0]; let len = &operands[1]; - match self.direction { - Direction::Export { .. } => { - quote_in! { self.body => - $['\r'] - $buf, $ok := i.module.Memory().Read($ptr, $len) - $(match &self.result { - GoResult::Anon(GoType::ValueOrError(typ)) => { - if !$ok { - var $default $(typ.as_ref()) - return $default, $ERRORS_NEW("failed to read bytes from memory") - } - } - GoResult::Anon(GoType::Error) => { - if !$ok { - return $ERRORS_NEW("failed to read bytes from memory") - } - } - GoResult::Anon(_) | GoResult::Empty => { - $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) - if !$ok { - panic($ERRORS_NEW("failed to read bytes from memory")) - } - } - }) - $str := string($buf) - }; - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - $buf, $ok := mod.Memory().Read($ptr, $len) + + quote_in! { self.body => + $['\r'] + $buf, $ok := i.module.Memory().Read($ptr, $len) + $(match &self.result { + GoResult::Anon(GoType::ValueOrError(typ)) => { + if !$ok { + var $default $(typ.as_ref()) + return $default, $ERRORS_NEW("failed to read bytes from memory") + } + } + GoResult::Anon(GoType::Error) => { + if !$ok { + return $ERRORS_NEW("failed to read bytes from memory") + } + } + GoResult::Anon(_) | GoResult::Empty => { + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) if !$ok { panic($ERRORS_NEW("failed to read bytes from memory")) } - $str := string($buf) - }; - } - } + } + }) + $str := string($buf) + }; + results.push(Operand::SingleValue(str.into())); } Instruction::ResultLift { @@ -552,75 +493,16 @@ impl Bindgen for Func<'_> { } Instruction::ResultLift { .. } => todo!("implement instruction: {inst:?}"), Instruction::Return { amt, .. } => { - if matches!(self.direction, Direction::Export) { - if *amt != 0 { - let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - return $operand - }; - } - } else { - for idx in 0..*amt { - let operand = &operands[idx]; - quote_in! { self.body => - $['\r'] - stack[$idx] = $operand - }; - } + if *amt != 0 { + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + return $operand + }; } } - Instruction::CallInterface { func, .. } => { - let ident = GoIdentifier::public(&func.name); - let tmp = self.tmp(); - let args = quote!($(for op in operands.iter() join (, ) => $op)); - let returns = match &func.result { - None => GoType::Nothing, - Some(typ) => resolve_type(typ, resolve), - }; - let value = &format!("value{tmp}"); - let err = &format!("err{tmp}"); - let ok = &format!("ok{tmp}"); - match self.direction { - Direction::Export { .. } => todo!("TODO(#10): handle export direction"), - Direction::Import { param_name, .. } => { - quote_in! { self.body => - $['\r'] - $(match returns { - GoType::Nothing => $param_name.$ident(ctx, $args), - GoType::Bool | GoType::Uint32 | GoType::Interface | GoType::String | GoType::UserDefined(_) => $value := $param_name.$ident(ctx, $args), - GoType::Error => $err := $param_name.$ident(ctx, $args), - GoType::ValueOrError(_) => { - $value, $err := $param_name.$ident(ctx, $args) - } - GoType::ValueOrOk(_) => { - $value, $ok := $param_name.$ident(ctx, $args) - } - _ => $(comment(&["TODO(#9): handle return type"])) - }) - } - } - } - match returns { - GoType::Nothing => (), - GoType::Bool - | GoType::Uint32 - | GoType::Interface - | GoType::UserDefined(_) - | GoType::String => { - results.push(Operand::SingleValue(value.into())); - } - GoType::Error => { - results.push(Operand::SingleValue(err.into())); - } - GoType::ValueOrError(_) => { - results.push(Operand::MultiValue((value.into(), err.into()))); - } - GoType::ValueOrOk(_) => { - results.push(Operand::MultiValue((value.into(), ok.into()))) - } - _ => todo!("TODO(#9): handle return type - {returns:?}"), - } + Instruction::CallInterface { .. } => { + todo!("TODO(#10): handle exported CallInterface") } Instruction::VariantPayloadName => { results.push(Operand::SingleValue("variantPayload".into())); @@ -632,55 +514,26 @@ impl Bindgen for Func<'_> { let tag = &operands[0]; let ptr = &operands[1]; if let Operand::Literal(byte) = tag { - match &self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - i.module.Memory().WriteByte($ptr+$offset, $byte) - } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - mod.Memory().WriteByte($ptr+$offset, $byte) - } - } + quote_in! { self.body => + $['\r'] + i.module.Memory().WriteByte($ptr+$offset, $byte) } } else { let tmp = self.tmp(); let byte = format!("byte{tmp}"); - match &self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - var $(&byte) uint8 - switch $tag { - case 0: - $(&byte) = 0 - case 1: - $(&byte) = 1 - default: - $(comment(["TODO(#8): Return an error if the return type allows it"])) - panic($ERRORS_NEW("invalid int8 value encountered")) - } - i.module.Memory().WriteByte($ptr+$offset, $byte) - } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - var $(&byte) uint8 - switch $tag { - case 0: - $(&byte) = 0 - case 1: - $(&byte) = 1 - default: - panic($ERRORS_NEW("invalid int8 value encountered")) - } - mod.Memory().WriteByte($ptr+$offset, $byte) - } + quote_in! { self.body => + $['\r'] + var $(&byte) uint8 + switch $tag { + case 0: + $(&byte) = 0 + case 1: + $(&byte) = 1 + default: + $(comment(["TODO(#8): Return an error if the return type allows it"])) + panic($ERRORS_NEW("invalid int8 value encountered")) } + i.module.Memory().WriteByte($ptr+$offset, $byte) } } } @@ -689,19 +542,10 @@ impl Bindgen for Func<'_> { let offset = offset.size_wasm32(); let tag = &operands[0]; let ptr = &operands[1]; - match &self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - i.module.Memory().WriteUint32Le($ptr+$offset, $tag) - } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - mod.Memory().WriteUint32Le($ptr+$offset, $tag) - } - } + + quote_in! { self.body => + $['\r'] + i.module.Memory().WriteUint32Le($ptr+$offset, $tag) } } Instruction::LengthStore { offset } => { @@ -709,19 +553,10 @@ impl Bindgen for Func<'_> { let offset = offset.size_wasm32(); let len = &operands[0]; let ptr = &operands[1]; - match &self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - i.module.Memory().WriteUint32Le($ptr+$offset, uint32($len)) - } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - mod.Memory().WriteUint32Le($ptr+$offset, uint32($len)) - } - } + + quote_in! { self.body => + $['\r'] + i.module.Memory().WriteUint32Le($ptr+$offset, uint32($len)) } } Instruction::PointerStore { offset } => { @@ -729,19 +564,10 @@ impl Bindgen for Func<'_> { let offset = offset.size_wasm32(); let value = &operands[0]; let ptr = &operands[1]; - match &self.direction { - Direction::Export => { - quote_in! { self.body => - $['\r'] - i.module.Memory().WriteUint32Le($ptr+$offset, uint32($value)) - } - } - Direction::Import { .. } => { - quote_in! { self.body => - $['\r'] - mod.Memory().WriteUint32Le($ptr+$offset, uint32($value)) - } - } + + quote_in! { self.body => + $['\r'] + i.module.Memory().WriteUint32Le($ptr+$offset, uint32($value)) } } Instruction::ResultLower { diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs new file mode 100644 index 0000000..bfb2a7a --- /dev/null +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -0,0 +1,972 @@ +use std::mem; + +use genco::prelude::*; +use wit_bindgen_core::{ + abi::{Bindgen, Instruction}, + wit_parser::{Alignment, ArchitectureSize, Resolve, Result_, SizeAlign, Type}, +}; + +use crate::{ + go::{ + GoIdentifier, GoType, Operand, comment, + imports::{ + ERRORS_NEW, REFLECT_VALUE_OF, WAZERO_API_DECODE_F32, WAZERO_API_DECODE_F64, + WAZERO_API_DECODE_I32, WAZERO_API_DECODE_U32, WAZERO_API_ENCODE_F32, + WAZERO_API_ENCODE_F64, WAZERO_API_ENCODE_I32, WAZERO_API_ENCODE_U32, + }, + }, + resolve_type, resolve_wasm_type, +}; + +pub struct ImportedFunc<'a> { + param_name: &'a GoIdentifier, + tmp: usize, + body: Tokens, + block_storage: Vec>, + blocks: Vec<(Tokens, Vec)>, + sizes: &'a SizeAlign, +} + +impl<'a> ImportedFunc<'a> { + /// Create a new imported function. + pub fn new(param_name: &'a GoIdentifier, sizes: &'a SizeAlign) -> Self { + Self { + param_name, + tmp: 0, + body: Tokens::new(), + block_storage: Vec::new(), + blocks: Vec::new(), + sizes, + } + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + pub fn body(&self) -> &Tokens { + &self.body + } + + fn pop_block(&mut self) -> (Tokens, Vec) { + self.blocks.pop().expect("should have block to pop") + } +} + +impl Bindgen for ImportedFunc<'_> { + type Operand = Operand; + + fn emit( + &mut self, + resolve: &Resolve, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let iter_element = "e"; + let iter_base = "base"; + + match inst { + Instruction::GetArg { nth } => { + let arg = &format!("arg{nth}"); + let idx = *nth; + quote_in! { self.body => + $['\r'] + $arg := stack[$idx] + }; + results.push(Operand::SingleValue(arg.into())); + } + Instruction::ConstZero { tys } => { + for _ in tys.iter() { + results.push(Operand::Literal("0".into())) + } + } + Instruction::StringLower { realloc: None } => todo!("implement instruction: {inst:?}"), + Instruction::StringLower { + realloc: Some(realloc_name), + } => { + let tmp = self.tmp(); + let ptr = &format!("ptr{tmp}"); + let len = &format!("len{tmp}"); + let err = &format!("err{tmp}"); + let memory = &format!("memory{tmp}"); + let realloc = &format!("realloc{tmp}"); + let operand = &operands[0]; + + quote_in! { self.body => + $['\r'] + $memory := mod.Memory() + $realloc := mod.ExportedFunction($(quoted(*realloc_name))) + $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) + if $err != nil { + panic($err) + } + }; + results.push(Operand::SingleValue(ptr.into())); + results.push(Operand::SingleValue(len.into())); + } + Instruction::CallWasm { name, .. } => { + let tmp = self.tmp(); + let err = &format!("err{tmp}"); + // TODO(#17): Wrapping every argument in `uint64` is bad and we should instead be looking + // at the types and converting with proper guards in place + quote_in! { self.body => + $['\r'] + _, $err := i.module.ExportedFunction($(quoted(*name))).Call(ctx, $(for op in operands.iter() join (, ) => uint64($op))) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if $err != nil { + panic($err) + } + }; + } + Instruction::I32Load8U { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tmp = self.tmp(); + let value = &format!("value{tmp}"); + let ok = &format!("ok{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $value, $ok := i.module.Memory().ReadByte(uint32($operand + $offset)) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if !$ok { + panic($ERRORS_NEW("failed to read byte from memory")) + } + }; + results.push(Operand::SingleValue(value.into())); + } + Instruction::I32FromBool => { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + var $(&value) uint32 + if $operand { + $(&value) = 1 + } else { + $(&value) = 0 + } + } + results.push(Operand::SingleValue(value)) + } + Instruction::BoolFromI32 => { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $(&value) := $operand != 0 + } + results.push(Operand::SingleValue(value)) + } + Instruction::I32FromU32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_ENCODE_U32($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::U32FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_U32($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::PointerLoad { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tmp = self.tmp(); + let ptr = &format!("ptr{tmp}"); + let ok = &format!("ok{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $ptr, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if !$ok { + panic($ERRORS_NEW("failed to read pointer from memory")) + } + }; + results.push(Operand::SingleValue(ptr.into())); + } + Instruction::LengthLoad { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tmp = self.tmp(); + let len = &format!("len{tmp}"); + let ok = &format!("ok{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $len, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if !$ok { + panic($ERRORS_NEW("failed to read length from memory")) + } + }; + results.push(Operand::SingleValue(len.into())); + } + Instruction::I32Load { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tmp = self.tmp(); + let value = &format!("value{tmp}"); + let ok = &format!("ok{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $value, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if !$ok { + panic($ERRORS_NEW("failed to read i32 from memory")) + } + }; + results.push(Operand::SingleValue(value.into())); + } + Instruction::StringLift => { + let tmp = self.tmp(); + let buf = &format!("buf{tmp}"); + let ok = &format!("ok{tmp}"); + let str = &format!("str{tmp}"); + let ptr = &format!("ptr{tmp}"); + let len = &format!("len{tmp}"); + let ptr_op = &operands[0]; + let len_op = &operands[1]; + + quote_in! { self.body => + $['\r'] + $ptr := $WAZERO_API_DECODE_U32($ptr_op) + $len := $WAZERO_API_DECODE_U32($len_op) + $buf, $ok := mod.Memory().Read($ptr, $len) + if !$ok { + panic($ERRORS_NEW("failed to read bytes from memory")) + } + $str := string($buf) + }; + results.push(Operand::SingleValue(str.into())); + } + Instruction::ResultLift { + result: + Result_ { + ok: Some(typ), + err: Some(Type::String), + }, + .. + } => { + let (err_block, err_results) = self.pop_block(); + assert_eq!(err_results.len(), 1); + let err_op = &err_results[0]; + + let (ok_block, ok_results) = self.pop_block(); + assert_eq!(ok_results.len(), 1); + let ok_op = &ok_results[0]; + + let tmp = self.tmp(); + let value = &format!("value{tmp}"); + let err = &format!("err{tmp}"); + let typ = resolve_type(typ, resolve); + let tag = &operands[0]; + quote_in! { self.body => + $['\r'] + var $value $typ + var $err error + switch $tag { + case 0: + $ok_block + $value = $ok_op + case 1: + $err_block + $err = $ERRORS_NEW($err_op) + default: + $err = $ERRORS_NEW("invalid variant discriminant for expected") + } + }; + + results.push(Operand::MultiValue((value.into(), err.into()))); + } + Instruction::ResultLift { + result: + Result_ { + ok: None, + err: Some(Type::String), + }, + .. + } => { + let (err_block, err_results) = self.pop_block(); + assert_eq!(err_results.len(), 1); + let err_op = &err_results[0]; + + let (ok_block, ok_results) = self.pop_block(); + assert_eq!(ok_results.len(), 0); + + let tmp = self.tmp(); + let err = &format!("err{tmp}"); + let tag = &operands[0]; + quote_in! { self.body => + $['\r'] + var $err error + switch $tag { + case 0: + $ok_block + case 1: + $err_block + $err = $ERRORS_NEW($err_op) + default: + $err = $ERRORS_NEW("invalid variant discriminant for expected") + } + }; + + results.push(Operand::SingleValue(err.into())); + } + Instruction::ResultLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::Return { amt, .. } => { + for idx in 0..*amt { + let operand = &operands[idx]; + quote_in! { self.body => + $['\r'] + stack[$idx] = $operand + }; + } + } + Instruction::CallInterface { func, .. } => { + let ident = GoIdentifier::public(&func.name); + let tmp = self.tmp(); + let args = quote!($(for op in operands.iter() join (, ) => $op)); + let returns = match &func.result { + None => GoType::Nothing, + Some(typ) => resolve_type(typ, resolve), + }; + let value = &format!("value{tmp}"); + let err = &format!("err{tmp}"); + let ok = &format!("ok{tmp}"); + let param_name = self.param_name; + + quote_in! { self.body => + $['\r'] + $(match returns { + GoType::Nothing => $param_name.$ident(ctx, $args), + GoType::Bool | GoType::Uint32 | GoType::Interface | GoType::String | GoType::UserDefined(_) => $value := $param_name.$ident(ctx, $args), + GoType::Error => $err := $param_name.$ident(ctx, $args), + GoType::ValueOrError(_) => { + $value, $err := $param_name.$ident(ctx, $args) + } + GoType::ValueOrOk(_) => { + $value, $ok := $param_name.$ident(ctx, $args) + } + _ => $(comment(&["TODO(#9): handle return type"])) + }) + } + match returns { + GoType::Nothing => (), + GoType::Bool + | GoType::Uint32 + | GoType::Interface + | GoType::UserDefined(_) + | GoType::String => { + results.push(Operand::SingleValue(value.into())); + } + GoType::Error => { + results.push(Operand::SingleValue(err.into())); + } + GoType::ValueOrError(_) => { + results.push(Operand::MultiValue((value.into(), err.into()))); + } + GoType::ValueOrOk(_) => { + results.push(Operand::MultiValue((value.into(), ok.into()))) + } + _ => todo!("TODO(#9): handle return type - {returns:?}"), + } + } + Instruction::VariantPayloadName => { + results.push(Operand::SingleValue("variantPayload".into())); + } + Instruction::I32Const { val } => results.push(Operand::Literal(val.to_string())), + Instruction::I32Store8 { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tag = &operands[0]; + let ptr = &operands[1]; + if let Operand::Literal(byte) = tag { + quote_in! { self.body => + $['\r'] + mod.Memory().WriteByte($ptr+$offset, $byte) + } + } else { + let tmp = self.tmp(); + let byte = format!("byte{tmp}"); + + quote_in! { self.body => + $['\r'] + var $(&byte) uint8 + switch $tag { + case 0: + $(&byte) = 0 + case 1: + $(&byte) = 1 + default: + panic($ERRORS_NEW("invalid int8 value encountered")) + } + mod.Memory().WriteByte($ptr+$offset, $byte) + } + } + } + Instruction::I32Store { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tag = &operands[0]; + let ptr = &operands[1]; + + quote_in! { self.body => + $['\r'] + mod.Memory().WriteUint32Le($ptr+$offset, $tag) + } + } + Instruction::LengthStore { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let len = &operands[0]; + let ptr = &operands[1]; + quote_in! { self.body => + $['\r'] + mod.Memory().WriteUint32Le($ptr+$offset, uint32($len)) + } + } + Instruction::PointerStore { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let value = &operands[0]; + let ptr = &operands[1]; + quote_in! { self.body => + $['\r'] + mod.Memory().WriteUint32Le($ptr+$offset, uint32($value)) + } + } + Instruction::ResultLower { + result: + Result_ { + ok: Some(_), + err: Some(Type::String), + }, + .. + } => { + let (err_block, _) = self.pop_block(); + let (ok_block, _) = self.pop_block(); + let operand = &operands[0]; + let (ok, err) = match operand { + Operand::Literal(_) => { + panic!("impossible: expected Operand::MultiValue but got Operand::Literal") + } + Operand::SingleValue(_) => panic!( + "impossible: expected Operand::MultiValue but got Operand::SingleValue" + ), + Operand::MultiValue(bindings) => bindings, + }; + quote_in! { self.body => + $['\r'] + if $err != nil { + variantPayload := $err.Error() + $err_block + } else { + variantPayload := $ok + $ok_block + } + }; + } + Instruction::ResultLower { + result: + Result_ { + ok: None, + err: Some(Type::String), + }, + .. + } => { + let (err, _) = self.pop_block(); + let (ok, _) = self.pop_block(); + let err_result = &operands[0]; + quote_in! { self.body => + $['\r'] + if $err_result != nil { + variantPayload := $err_result.Error() + $err + } else { + $ok + } + }; + } + Instruction::ResultLower { .. } => todo!("implement instruction: {inst:?}"), + Instruction::OptionLift { payload, .. } => { + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, _) = self.blocks.pop().unwrap(); + let some_result = &some_results[0]; + + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let ok = &format!("ok{tmp}"); + let typ = resolve_type(payload, resolve); + let op = &operands[0]; + + quote_in! { self.body => + $['\r'] + var $result $typ + var $ok bool + if $op == 0 { + $none + $ok = false + } else { + $some + $ok = true + $result = $some_result + } + }; + + results.push(Operand::MultiValue((result.into(), ok.into()))); + } + Instruction::OptionLower { + results: result_types, + .. + } => { + let (mut some_block, some_results) = self.pop_block(); + let (mut none_block, none_results) = self.pop_block(); + + let tmp = self.tmp(); + + // If there are no result_types, then the payload will be a pointer, + // because that's how we represent optionals in Go. + let is_pointer = result_types.is_empty(); + + let mut vars: Tokens = Tokens::new(); + for i in 0..result_types.len() { + let variant = &format!("variant{tmp}_{i}"); + let typ = resolve_wasm_type(&result_types[i]); + results.push(Operand::SingleValue(variant.into())); + + quote_in! { vars => + $['\r'] + var $variant $typ + } + + let some_result = &some_results[i]; + let none_result = &none_results[i]; + quote_in! { some_block => + $['\r'] + $variant = $some_result + }; + quote_in! { none_block => + $['\r'] + $variant = $none_result + }; + } + + let operand = &operands[0]; + match operand { + Operand::Literal(_) => { + panic!("impossible: expected Operand::MultiValue but got Operand::Literal") + } + Operand::SingleValue(value) => { + quote_in! { self.body => + $['\r'] + $vars + if $REFLECT_VALUE_OF($value).IsZero() { + $none_block + } else { + variantPayload := $(if is_pointer => *)$value + $some_block + } + }; + } + Operand::MultiValue((value, ok)) => { + quote_in! { self.body => + $['\r'] + if $ok { + variantPayload := $value + $some_block + } else { + $none_block + } + }; + } + }; + } + Instruction::RecordLower { record, .. } => { + let tmp = self.tmp(); + let operand = &operands[0]; + for field in record.fields.iter() { + let struct_field = GoIdentifier::public(&field.name); + let var = &GoIdentifier::local(format!("{}{tmp}", &field.name)); + quote_in! { self.body => + $['\r'] + $var := $operand.$struct_field + } + results.push(Operand::SingleValue(var.into())) + } + } + Instruction::RecordLift { record, name, .. } => { + let tmp = self.tmp(); + let value = &format!("value{tmp}"); + let fields = record + .fields + .iter() + .zip(operands) + .map(|(field, op)| (GoIdentifier::public(&field.name), op)); + + quote_in! {self.body => + $['\r'] + $value := $(GoIdentifier::public(*name)){ + $(for (name, op) in fields join ($['\r']) => $name: $op,) + } + }; + results.push(Operand::SingleValue(value.into())) + } + Instruction::IterElem { .. } => results.push(Operand::SingleValue(iter_element.into())), + Instruction::IterBasePointer => results.push(Operand::SingleValue(iter_base.into())), + Instruction::ListLower { realloc: None, .. } => { + todo!("implement instruction: {inst:?}") + } + Instruction::ListLower { + element, + realloc: Some(realloc_name), + } => { + let (body, _) = self.pop_block(); + let tmp = self.tmp(); + let vec = &format!("vec{tmp}"); + let result = &format!("result{tmp}"); + let err = &format!("err{tmp}"); + let ptr = &format!("ptr{tmp}"); + let len = &format!("len{tmp}"); + let operand = &operands[0]; + let size = self.sizes.size(element).size_wasm32(); + let align = self.sizes.align(element).align_wasm32(); + + quote_in! { self.body => + $['\r'] + $vec := $operand + $len := uint64(len($vec)) + $result, $err := i.module.ExportedFunction($(quoted(*realloc_name))).Call(ctx, 0, 0, $align, $len * $size) + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + if $err != nil { + panic($err) + } + $ptr := $result[0] + for idx := uint64(0); idx < $len; idx++ { + $iter_element := $vec[idx] + $iter_base := uint32($ptr + uint64(idx) * uint64($size)) + $body + } + }; + results.push(Operand::SingleValue(ptr.into())); + results.push(Operand::SingleValue(len.into())); + } + Instruction::ListLift { element, .. } => { + let (body, body_results) = self.pop_block(); + let tmp = self.tmp(); + let size = self.sizes.size(element).size_wasm32(); + let len = &format!("len{tmp}"); + let base = &format!("base{tmp}"); + let result = &format!("result{tmp}"); + let idx = &format!("idx{tmp}"); + + let base_operand = &operands[0]; + let len_operand = &operands[1]; + let body_result = &body_results[0]; + + let typ = resolve_type(element, resolve); + + quote_in! { self.body => + $['\r'] + $base := $base_operand + $len := $len_operand + $result := make([]$typ, $len) + for $idx := uint32(0); $idx < $len; $idx++ { + base := $base + $idx * $size + $body + $result[$idx] = $body_result + } + } + results.push(Operand::SingleValue(result.into())); + } + Instruction::VariantLower { + variant, + results: result_types, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let tmp = self.tmp(); + let value = &operands[0]; + + for (i, typ) in result_types.iter().enumerate() { + let variant_item = &format!("variant{tmp}_{i}"); + let typ = resolve_wasm_type(typ); + quote_in! { self.body => + $['\r'] + var $variant_item $typ + } + results.push(Operand::SingleValue(variant_item.into())); + } + + let mut cases: Tokens = Tokens::new(); + for (case, (block, block_results)) in variant.cases.iter().zip(blocks) { + let mut assignments: Tokens = Tokens::new(); + for (i, result) in block_results.iter().enumerate() { + let variant_item = &format!("variant{tmp}_{i}"); + quote_in! { assignments => + $['\r'] + $variant_item = $result + }; + } + + let name = GoIdentifier::public(case.name.clone()); + quote_in! { cases => + $['\r'] + case $name: + $block + $assignments + } + } + + quote_in! { self.body => + $['\r'] + switch variantPayload := $value.(type) { + $cases + default: + $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) + panic($ERRORS_NEW("invalid variant type provided")) + } + } + } + Instruction::EnumLower { enum_, .. } => { + let value = &operands[0]; + let tmp = self.tmp(); + let enum_tmp = &format!("enum{tmp}"); + + let mut cases: Tokens = Tokens::new(); + for (i, case) in enum_.cases.iter().enumerate() { + let case_name = GoIdentifier::public(case.name.clone()); + quote_in! { cases => + $['\r'] + case $case_name: + $enum_tmp = $i + }; + } + + quote_in! { self.body => + $['\r'] + var $enum_tmp uint32 + switch $value { + $cases + default: + panic($ERRORS_NEW("invalid enum type provided")) + } + }; + + results.push(Operand::SingleValue(enum_tmp.to_string())); + } + Instruction::Bitcasts { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I32Load8S { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I32Load16U { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I32Load16S { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I64Load { .. } => todo!("implement instruction: {inst:?}"), + Instruction::F32Load { .. } => todo!("implement instruction: {inst:?}"), + Instruction::F64Load { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I32Store16 { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I64Store { .. } => todo!("implement instruction: {inst:?}"), + Instruction::F32Store { .. } => todo!("implement instruction: {inst:?}"), + Instruction::F64Store { .. } => todo!("implement instruction: {inst:?}"), + Instruction::I32FromChar => todo!("implement instruction: {inst:?}"), + Instruction::I64FromU64 => todo!("implement instruction: {inst:?}"), + Instruction::I64FromS64 => todo!("implement instruction: {inst:?}"), + Instruction::I32FromS32 => { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $(&value) := $WAZERO_API_ENCODE_I32($operand) + } + results.push(Operand::SingleValue(value)) + } + // All of these values should fit in Go's `int32` type which allows a safe cast + Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU8 + | Instruction::I32FromS8 => { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $(&value) := $WAZERO_API_ENCODE_I32(int32($operand)) + } + results.push(Operand::SingleValue(value)) + } + Instruction::CoreF32FromF32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_ENCODE_F32($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::CoreF64FromF64 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_ENCODE_F64($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + // TODO: Validate the Go cast truncates the upper bits in the I32 + Instruction::S8FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := int8($WAZERO_API_DECODE_I32($operand)) + }; + results.push(Operand::SingleValue(result.into())); + } + // TODO: Validate the Go cast truncates the upper bits in the I32 + Instruction::U8FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := uint8($WAZERO_API_DECODE_U32($operand)) + }; + results.push(Operand::SingleValue(result.into())); + } + // TODO: Validate the Go cast truncates the upper bits in the I32 + Instruction::S16FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := int16($WAZERO_API_DECODE_I32($operand)) + }; + results.push(Operand::SingleValue(result.into())); + } + // TODO: Validate the Go cast truncates the upper bits in the I32 + Instruction::U16FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := uint16($WAZERO_API_DECODE_U32($operand)) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::S32FromI32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_I32($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::S64FromI64 => todo!("implement instruction: {inst:?}"), + Instruction::U64FromI64 => todo!("implement instruction: {inst:?}"), + Instruction::CharFromI32 => todo!("implement instruction: {inst:?}"), + Instruction::F32FromCoreF32 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_F32($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::F64FromCoreF64 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_F64($operand) + }; + results.push(Operand::SingleValue(result.into())); + } + Instruction::TupleLower { .. } => todo!("implement instruction: {inst:?}"), + Instruction::TupleLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::FlagsLower { .. } => todo!("implement instruction: {inst:?}"), + Instruction::FlagsLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::VariantLift { .. } => { + todo!("implement instruction: {inst:?}") + } + Instruction::EnumLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::Malloc { .. } => todo!("implement instruction: {inst:?}"), + Instruction::HandleLower { .. } | Instruction::HandleLift { .. } => { + todo!("implement resources: {inst:?}") + } + Instruction::ListCanonLower { .. } | Instruction::ListCanonLift { .. } => { + unimplemented!("gravity doesn't represent lists as Canonical") + } + Instruction::GuestDeallocateString + | Instruction::GuestDeallocate { .. } + | Instruction::GuestDeallocateList { .. } + | Instruction::GuestDeallocateVariant { .. } => { + unimplemented!("gravity doesn't generate the Guest code") + } + Instruction::FutureLower { .. } => todo!("implement instruction: {inst:?}"), + Instruction::FutureLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::StreamLower { .. } => todo!("implement instruction: {inst:?}"), + Instruction::StreamLift { .. } => todo!("implement instruction: {inst:?}"), + Instruction::ErrorContextLower => todo!("implement instruction: {inst:?}"), + Instruction::ErrorContextLift => todo!("implement instruction: {inst:?}"), + Instruction::AsyncTaskReturn { .. } => todo!("implement instruction: {inst:?}"), + Instruction::DropHandle { .. } => todo!("implement instruction: {inst:?}"), + Instruction::Flush { amt } => { + for op in operands.iter().take(*amt) { + results.push(op.clone()); + } + } + } + } + + fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> Self::Operand { + unimplemented!("return_pointer") + } + + fn push_block(&mut self) { + let prev = mem::replace(&mut self.body, Tokens::new()); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().expect("should have body"); + let src = mem::replace(&mut self.body, to_restore); + self.blocks.push((src, mem::take(operands))); + } + + fn sizes(&self) -> &SizeAlign { + self.sizes + } + + fn is_list_canonical(&self, _resolve: &Resolve, _element: &Type) -> bool { + // Go slices are never directly in the Wasm Memory, so they are never "canonical" + false + } +} diff --git a/cmd/gravity/src/codegen/imports.rs b/cmd/gravity/src/codegen/imports.rs index 76a2d24..e94d03b 100644 --- a/cmd/gravity/src/codegen/imports.rs +++ b/cmd/gravity/src/codegen/imports.rs @@ -10,7 +10,7 @@ use wit_bindgen_core::{ use crate::{ codegen::{ - func::Func, + ImportedFunc, ir::{ AnalyzedFunction, AnalyzedImports, AnalyzedInterface, AnalyzedType, InterfaceMethod, Parameter, TypeDefinition, WitReturn, @@ -19,8 +19,8 @@ use crate::{ go::{ GoIdentifier, GoResult, GoType, imports::{ - CONTEXT_CONTEXT, GoImport, WAZERO_API_DECODE_U32, WAZERO_API_GO_MODULE_FUNC, - WAZERO_API_MODULE, WAZERO_API_VALUE_TYPE, + CONTEXT_CONTEXT, GoImport, WAZERO_API_GO_MODULE_FUNC, WAZERO_API_MODULE, + WAZERO_API_VALUE_TYPE, }, }, resolve_type, @@ -420,7 +420,7 @@ impl<'a> ImportCodeGenerator<'a> { let wasm_sig = self .resolve .wasm_signature(AbiVariant::GuestImport, &method.wit_function); - let mut f = Func::import(param_name, self.sizes); + let mut f = ImportedFunc::new(param_name, self.sizes); // Magic wit_bindgen_core::abi::call( @@ -433,7 +433,6 @@ impl<'a> ImportCodeGenerator<'a> { false, ); - let args = f.args().iter().enumerate(); let wasm_params = wasm_sig.params.iter().map(GoImport::from); let wasm_results = wasm_sig.results.iter().map(GoImport::from); @@ -445,7 +444,6 @@ impl<'a> ImportCodeGenerator<'a> { mod $WAZERO_API_MODULE, stack []uint64, ) { - $(for (idx, arg) in args join ($['\r']) => $arg := $WAZERO_API_DECODE_U32(stack[$idx])) $(f.body()) }), []$WAZERO_API_VALUE_TYPE{ diff --git a/cmd/gravity/src/codegen/mod.rs b/cmd/gravity/src/codegen/mod.rs index 3bb7433..04ae959 100644 --- a/cmd/gravity/src/codegen/mod.rs +++ b/cmd/gravity/src/codegen/mod.rs @@ -2,6 +2,7 @@ mod bindings; mod exports; mod factory; mod func; +mod imported_func; mod imports; mod ir; mod wasm; @@ -10,4 +11,5 @@ pub use bindings::*; pub use exports::ExportGenerator; pub use factory::FactoryGenerator; pub use func::Func; +pub use imported_func::ImportedFunc; pub use wasm::WasmData; diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index 1632089..b90dfa5 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -64,9 +64,11 @@ func NewBasicFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } @@ -87,9 +89,11 @@ func NewBasicFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } @@ -110,9 +114,11 @@ func NewBasicFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } @@ -133,9 +139,11 @@ func NewBasicFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } @@ -161,15 +169,17 @@ func NewBasicFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - arg2 := api.DecodeU32(stack[2]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } str0 := string(buf0) value1 := utils.Uppercase(ctx, str0) + arg2 := stack[2] memory2 := mod.Memory() realloc2 := mod.ExportedFunction("cabi_realloc") ptr2, len2, err2 := writeString(ctx, value1, memory2, realloc2) diff --git a/cmd/gravity/tests/cmd/iface-method-returns.stdout b/cmd/gravity/tests/cmd/iface-method-returns.stdout index 6e19377..4671547 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns.stdout @@ -47,8 +47,8 @@ func NewExampleFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) value0 := runtime.Os(ctx, ) + arg0 := stack[0] memory1 := mod.Memory() realloc1 := mod.ExportedFunction("cabi_realloc") ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) @@ -71,8 +71,8 @@ func NewExampleFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) value0 := runtime.Arch(ctx, ) + arg0 := stack[0] memory1 := mod.Memory() realloc1 := mod.ExportedFunction("cabi_realloc") ptr1, len1, err1 := writeString(ctx, value0, memory1, realloc1) @@ -112,9 +112,11 @@ func NewExampleFactory( mod api.Module, stack []uint64, ) { - arg0 := api.DecodeU32(stack[0]) - arg1 := api.DecodeU32(stack[1]) - buf0, ok0 := mod.Memory().Read(arg0, arg1) + arg0 := stack[0] + arg1 := stack[1] + ptr0 := api.DecodeU32(arg0) + len0 := api.DecodeU32(arg1) + buf0, ok0 := mod.Memory().Read(ptr0, len0) if !ok0 { panic(errors.New("failed to read bytes from memory")) } From e6aed2fa2451d473cb6ef9f633d3432c4f19f237 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:25:40 -0700 Subject: [PATCH 03/13] decode stack values when they are needed --- cmd/gravity/src/codegen/imported_func.rs | 33 +++++++++++++------ cmd/gravity/tests/cmd/basic.stdout | 6 ++-- .../tests/cmd/iface-method-returns.stdout | 12 ++++--- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs index bfb2a7a..3ae05c3 100644 --- a/cmd/gravity/src/codegen/imported_func.rs +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -395,28 +395,32 @@ impl Bindgen for ImportedFunc<'_> { Instruction::I32Store8 { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); + let tmp = self.tmp(); let tag = &operands[0]; - let ptr = &operands[1]; + let ptr = &format!("ptr{tmp}"); + let ptr_op = &operands[1]; if let Operand::Literal(byte) = tag { quote_in! { self.body => $['\r'] + $ptr := $WAZERO_API_DECODE_U32($ptr_op) mod.Memory().WriteByte($ptr+$offset, $byte) } } else { let tmp = self.tmp(); - let byte = format!("byte{tmp}"); + let byte = &format!("byte{tmp}"); quote_in! { self.body => $['\r'] - var $(&byte) uint8 + var $byte uint8 switch $tag { case 0: - $(&byte) = 0 + $byte = 0 case 1: - $(&byte) = 1 + $byte = 1 default: panic($ERRORS_NEW("invalid int8 value encountered")) } + $ptr := $WAZERO_API_DECODE_U32($ptr_op) mod.Memory().WriteByte($ptr+$offset, $byte) } } @@ -424,32 +428,41 @@ impl Bindgen for ImportedFunc<'_> { Instruction::I32Store { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); + let tmp = self.tmp(); let tag = &operands[0]; - let ptr = &operands[1]; + let ptr = &format!("ptr{tmp}"); + let ptr_op = &operands[1]; quote_in! { self.body => $['\r'] + $ptr := $WAZERO_API_DECODE_U32($ptr_op) mod.Memory().WriteUint32Le($ptr+$offset, $tag) } } Instruction::LengthStore { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); + let tmp = self.tmp(); let len = &operands[0]; - let ptr = &operands[1]; + let ptr = &format!("ptr{tmp}"); + let ptr_op = &operands[1]; quote_in! { self.body => $['\r'] - mod.Memory().WriteUint32Le($ptr+$offset, uint32($len)) + $ptr := $WAZERO_API_DECODE_U32($ptr_op) + mod.Memory().WriteUint32Le($ptr+$offset, $len) } } Instruction::PointerStore { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); + let tmp = self.tmp(); let value = &operands[0]; - let ptr = &operands[1]; + let ptr = &format!("ptr{tmp}"); + let ptr_op = &operands[1]; quote_in! { self.body => $['\r'] - mod.Memory().WriteUint32Le($ptr+$offset, uint32($value)) + $ptr := $WAZERO_API_DECODE_U32($ptr_op) + mod.Memory().WriteUint32Le($ptr+$offset, $value) } } Instruction::ResultLower { diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index b90dfa5..d242af7 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -186,8 +186,10 @@ func NewBasicFactory( if err2 != nil { panic(err2) } - mod.Memory().WriteUint32Le(arg2+4, uint32(len2)) - mod.Memory().WriteUint32Le(arg2+0, uint32(ptr2)) + ptr3 := api.DecodeU32(arg2) + mod.Memory().WriteUint32Le(ptr3+4, len2) + ptr4 := api.DecodeU32(arg2) + mod.Memory().WriteUint32Le(ptr4+0, ptr2) }), []api.ValueType{ api.ValueTypeI32, diff --git a/cmd/gravity/tests/cmd/iface-method-returns.stdout b/cmd/gravity/tests/cmd/iface-method-returns.stdout index 4671547..db942fe 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns.stdout @@ -55,8 +55,10 @@ func NewExampleFactory( if err1 != nil { panic(err1) } - mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) - mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) + ptr2 := api.DecodeU32(arg0) + mod.Memory().WriteUint32Le(ptr2+4, len1) + ptr3 := api.DecodeU32(arg0) + mod.Memory().WriteUint32Le(ptr3+0, ptr1) }), []api.ValueType{ api.ValueTypeI32, @@ -79,8 +81,10 @@ func NewExampleFactory( if err1 != nil { panic(err1) } - mod.Memory().WriteUint32Le(arg0+4, uint32(len1)) - mod.Memory().WriteUint32Le(arg0+0, uint32(ptr1)) + ptr2 := api.DecodeU32(arg0) + mod.Memory().WriteUint32Le(ptr2+4, len1) + ptr3 := api.DecodeU32(arg0) + mod.Memory().WriteUint32Le(ptr3+0, ptr1) }), []api.ValueType{ api.ValueTypeI32, From 16e1bd158ced9267d33e50ecc6673cc67909ec46 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:32:59 -0700 Subject: [PATCH 04/13] restore LengthStore and PointerStore casting --- cmd/gravity/src/codegen/imported_func.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs index 3ae05c3..35ac62c 100644 --- a/cmd/gravity/src/codegen/imported_func.rs +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -449,7 +449,7 @@ impl Bindgen for ImportedFunc<'_> { quote_in! { self.body => $['\r'] $ptr := $WAZERO_API_DECODE_U32($ptr_op) - mod.Memory().WriteUint32Le($ptr+$offset, $len) + mod.Memory().WriteUint32Le($ptr+$offset, uint32($len)) } } Instruction::PointerStore { offset } => { @@ -462,7 +462,7 @@ impl Bindgen for ImportedFunc<'_> { quote_in! { self.body => $['\r'] $ptr := $WAZERO_API_DECODE_U32($ptr_op) - mod.Memory().WriteUint32Le($ptr+$offset, $value) + mod.Memory().WriteUint32Le($ptr+$offset, uint32($value)) } } Instruction::ResultLower { From 6db3652c754b9a6db13e99a0bf3bda0e34710c0f Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:43:45 -0700 Subject: [PATCH 05/13] cast the tag to a uint32 in I32Store --- cmd/gravity/src/codegen/imported_func.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs index 35ac62c..ea1c8a8 100644 --- a/cmd/gravity/src/codegen/imported_func.rs +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -436,7 +436,7 @@ impl Bindgen for ImportedFunc<'_> { quote_in! { self.body => $['\r'] $ptr := $WAZERO_API_DECODE_U32($ptr_op) - mod.Memory().WriteUint32Le($ptr+$offset, $tag) + mod.Memory().WriteUint32Le($ptr+$offset, uint32($tag)) } } Instruction::LengthStore { offset } => { From a17e1fc7c9dabdd254758e5c0de099accd95ec97 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:45:30 -0700 Subject: [PATCH 06/13] enums and bools as uint64 --- cmd/gravity/src/codegen/imported_func.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs index ea1c8a8..eddf3ab 100644 --- a/cmd/gravity/src/codegen/imported_func.rs +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -144,7 +144,7 @@ impl Bindgen for ImportedFunc<'_> { let operand = &operands[0]; quote_in! { self.body => $['\r'] - var $(&value) uint32 + var $(&value) uint64 if $operand { $(&value) = 1 } else { @@ -777,7 +777,7 @@ impl Bindgen for ImportedFunc<'_> { quote_in! { self.body => $['\r'] - var $enum_tmp uint32 + var $enum_tmp uint64 switch $value { $cases default: From a9fa8dad49bba753a7b1ff10e3c9404d0a80593d Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:45:56 -0700 Subject: [PATCH 07/13] snapshot --- cmd/gravity/tests/cmd/basic.stdout | 4 ++-- cmd/gravity/tests/cmd/iface-method-returns.stdout | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index d242af7..6a6488e 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -187,9 +187,9 @@ func NewBasicFactory( panic(err2) } ptr3 := api.DecodeU32(arg2) - mod.Memory().WriteUint32Le(ptr3+4, len2) + mod.Memory().WriteUint32Le(ptr3+4, uint32(len2)) ptr4 := api.DecodeU32(arg2) - mod.Memory().WriteUint32Le(ptr4+0, ptr2) + mod.Memory().WriteUint32Le(ptr4+0, uint32(ptr2)) }), []api.ValueType{ api.ValueTypeI32, diff --git a/cmd/gravity/tests/cmd/iface-method-returns.stdout b/cmd/gravity/tests/cmd/iface-method-returns.stdout index db942fe..e72dd61 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns.stdout @@ -56,9 +56,9 @@ func NewExampleFactory( panic(err1) } ptr2 := api.DecodeU32(arg0) - mod.Memory().WriteUint32Le(ptr2+4, len1) + mod.Memory().WriteUint32Le(ptr2+4, uint32(len1)) ptr3 := api.DecodeU32(arg0) - mod.Memory().WriteUint32Le(ptr3+0, ptr1) + mod.Memory().WriteUint32Le(ptr3+0, uint32(ptr1)) }), []api.ValueType{ api.ValueTypeI32, @@ -82,9 +82,9 @@ func NewExampleFactory( panic(err1) } ptr2 := api.DecodeU32(arg0) - mod.Memory().WriteUint32Le(ptr2+4, len1) + mod.Memory().WriteUint32Le(ptr2+4, uint32(len1)) ptr3 := api.DecodeU32(arg0) - mod.Memory().WriteUint32Le(ptr3+0, ptr1) + mod.Memory().WriteUint32Le(ptr3+0, uint32(ptr1)) }), []api.ValueType{ api.ValueTypeI32, From 25f2cfd917c115bf46cb668910bfbd2bc997021b Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:46:29 -0700 Subject: [PATCH 08/13] update test --- cmd/gravity/src/codegen/imports.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gravity/src/codegen/imports.rs b/cmd/gravity/src/codegen/imports.rs index e94d03b..14e913d 100644 --- a/cmd/gravity/src/codegen/imports.rs +++ b/cmd/gravity/src/codegen/imports.rs @@ -568,7 +568,7 @@ mod tests { // Should have only one uint32 parameter (plus ctx and mod) let code_str = result.to_string().unwrap(); - assert!(code_str.contains("arg0 := api.DecodeU32(stack[0])")); + assert!(code_str.contains("arg0 := stack[0]")); assert!(!code_str.contains("arg1 uint32")); assert!(!code_str.contains("mod.Memory().Read")); // No string reading From f4d9a0e0192b02e5d37df69f17967a0fc91d8a01 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:53:29 -0700 Subject: [PATCH 09/13] lower option and results using uint64 --- cmd/gravity/src/codegen/imported_func.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/gravity/src/codegen/imported_func.rs b/cmd/gravity/src/codegen/imported_func.rs index eddf3ab..160891f 100644 --- a/cmd/gravity/src/codegen/imported_func.rs +++ b/cmd/gravity/src/codegen/imported_func.rs @@ -15,7 +15,7 @@ use crate::{ WAZERO_API_ENCODE_F64, WAZERO_API_ENCODE_I32, WAZERO_API_ENCODE_U32, }, }, - resolve_type, resolve_wasm_type, + resolve_type, }; pub struct ImportedFunc<'a> { @@ -561,12 +561,11 @@ impl Bindgen for ImportedFunc<'_> { let mut vars: Tokens = Tokens::new(); for i in 0..result_types.len() { let variant = &format!("variant{tmp}_{i}"); - let typ = resolve_wasm_type(&result_types[i]); results.push(Operand::SingleValue(variant.into())); quote_in! { vars => $['\r'] - var $variant $typ + var $variant uint64 } let some_result = &some_results[i]; @@ -720,12 +719,11 @@ impl Bindgen for ImportedFunc<'_> { let tmp = self.tmp(); let value = &operands[0]; - for (i, typ) in result_types.iter().enumerate() { + for i in 0..result_types.len() { let variant_item = &format!("variant{tmp}_{i}"); - let typ = resolve_wasm_type(typ); quote_in! { self.body => $['\r'] - var $variant_item $typ + var $variant_item uint64 } results.push(Operand::SingleValue(variant_item.into())); } From 384284578b61f0cfa0d4efad67de2febe6a6e813 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 11:59:51 -0700 Subject: [PATCH 10/13] same option and variant lowering for exports --- cmd/gravity/src/codegen/func.rs | 10 ++++------ cmd/gravity/src/lib.rs | 18 +----------------- cmd/gravity/tests/cmd/basic.stdout | 6 +++--- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index e259f1a..f82d474 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -15,7 +15,7 @@ use crate::{ WAZERO_API_ENCODE_F64, WAZERO_API_ENCODE_I32, WAZERO_API_ENCODE_U32, }, }, - resolve_type, resolve_wasm_type, + resolve_type, }; pub struct Func<'a> { @@ -666,12 +666,11 @@ impl Bindgen for Func<'_> { let mut vars: Tokens = Tokens::new(); for i in 0..result_types.len() { let variant = &format!("variant{tmp}_{i}"); - let typ = resolve_wasm_type(&result_types[i]); results.push(Operand::SingleValue(variant.into())); quote_in! { vars => $['\r'] - var $variant $typ + var $variant uint64 } let some_result = &some_results[i]; @@ -842,12 +841,11 @@ impl Bindgen for Func<'_> { let value = &operands[0]; let default = &format!("default{tmp}"); - for (i, typ) in result_types.iter().enumerate() { + for i in 0..result_types.len() { let variant_item = &format!("variant{tmp}_{i}"); - let typ = resolve_wasm_type(typ); quote_in! { self.body => $['\r'] - var $variant_item $typ + var $variant_item uint64 } results.push(Operand::SingleValue(variant_item.into())); } diff --git a/cmd/gravity/src/lib.rs b/cmd/gravity/src/lib.rs index 0b6cc2b..0866592 100644 --- a/cmd/gravity/src/lib.rs +++ b/cmd/gravity/src/lib.rs @@ -2,27 +2,11 @@ pub mod codegen; pub mod go; use crate::go::GoType; -use wit_bindgen_core::{ - abi::WasmType, - wit_parser::{Resolve, Result_, Type, TypeDef, TypeDefKind}, -}; +use wit_bindgen_core::wit_parser::{Resolve, Result_, Type, TypeDef, TypeDefKind}; // Temporary re-export while we migrate. pub use codegen::Func; -/// Resolves a Wasm type to a Go type. -pub fn resolve_wasm_type(typ: &WasmType) -> GoType { - match typ { - WasmType::I32 => GoType::Uint32, - WasmType::I64 => GoType::Uint64, - WasmType::F32 => GoType::Float32, - WasmType::F64 => GoType::Float64, - WasmType::Pointer => GoType::Uint64, - WasmType::PointerOrI64 => GoType::Uint64, - WasmType::Length => GoType::Uint64, - } -} - /// Resolves a WIT type to a Go type. /// /// # Panics diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index 6a6488e..4cfa9be 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -357,8 +357,8 @@ func (i *BasicInstance) OptionalPrimitive( b bool, ) (bool, bool) { arg0 := b - var variant1_0 uint32 - var variant1_1 uint32 + var variant1_0 uint64 + var variant1_1 uint64 if reflect.ValueOf(arg0).IsZero() { variant1_0 = 0 variant1_1 = 0 @@ -471,7 +471,7 @@ func (i *BasicInstance) OptionalString( s string, ) (string, bool) { arg0 := s - var variant1_0 uint32 + var variant1_0 uint64 var variant1_1 uint64 var variant1_2 uint64 if reflect.ValueOf(arg0).IsZero() { From 648cdb31a3ecd397a7d221c1296c92bb4a5d8745 Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 12:32:21 -0700 Subject: [PATCH 11/13] encode u32 after read from memory --- cmd/gravity/src/codegen/func.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index f82d474..6ebd5de 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -354,6 +354,7 @@ impl Bindgen for Func<'_> { let offset = offset.size_wasm32(); let tmp = self.tmp(); let value = &format!("value{tmp}"); + let encoded = &format!("encoded{tmp}"); let ok = &format!("ok{tmp}"); let default = &format!("default{tmp}"); let operand = &operands[0]; @@ -379,8 +380,9 @@ impl Bindgen for Func<'_> { } } }) + $encoded := $WAZERO_API_ENCODE_U32($value) }; - results.push(Operand::SingleValue(value.into())); + results.push(Operand::SingleValue(encoded.into())); } Instruction::StringLift => { let tmp = self.tmp(); From d373a19c569036f0faa86277d859af138697b32c Mon Sep 17 00:00:00 2001 From: Blaine Date: Mon, 5 Jan 2026 12:37:10 -0700 Subject: [PATCH 12/13] uint64 for enum and bools like imported --- cmd/gravity/src/codegen/func.rs | 4 ++-- cmd/gravity/tests/cmd/basic.stdout | 2 +- cmd/gravity/tests/cmd/instructions.stdout | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index 6ebd5de..f203765 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -244,7 +244,7 @@ impl Bindgen for Func<'_> { let operand = &operands[0]; quote_in! { self.body => $['\r'] - var $(&value) uint32 + var $(&value) uint64 if $operand { $(&value) = 1 } else { @@ -910,7 +910,7 @@ impl Bindgen for Func<'_> { quote_in! { self.body => $['\r'] - var $enum_tmp uint32 + var $enum_tmp uint64 switch $value { $cases default: diff --git a/cmd/gravity/tests/cmd/basic.stdout b/cmd/gravity/tests/cmd/basic.stdout index 4cfa9be..01f3c3a 100644 --- a/cmd/gravity/tests/cmd/basic.stdout +++ b/cmd/gravity/tests/cmd/basic.stdout @@ -364,7 +364,7 @@ func (i *BasicInstance) OptionalPrimitive( variant1_1 = 0 } else { variantPayload := arg0 - var value0 uint32 + var value0 uint64 if variantPayload { value0 = 1 } else { diff --git a/cmd/gravity/tests/cmd/instructions.stdout b/cmd/gravity/tests/cmd/instructions.stdout index 9dbf974..a004079 100644 --- a/cmd/gravity/tests/cmd/instructions.stdout +++ b/cmd/gravity/tests/cmd/instructions.stdout @@ -237,7 +237,7 @@ func (i *InstructionsInstance) EnumInput( val EnumValues, ) { arg0 := val - var enum0 uint32 + var enum0 uint64 switch arg0 { case One: enum0 = 0 From 2d9cc2326e90f077b9e7b6168de2a68756609c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B6Rei?= <212411920+arcjet-rei@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:58:46 -0700 Subject: [PATCH 13/13] fix: return import results as their Wasm value types A host import is registered with wazero's WithFunc, which takes the function's Wasm signature from its Go types: uint32 for i32, uint64 for i64, float32 for f32 and float64 for f64. The scalar lowering instructions produced the uint64 encoding api.Function.Call expects instead, so an import returning s32, s8, u8, s16, u16, f32 or f64 generated Go that did not compile, and one returning s64 or char made gravity panic. - I32FromS32, I32FromS16, I32FromU16, I32FromS8 and I32FromU8 emit uint32(x), and I64FromS64 emits uint64(x). Go's conversion sign-extends signed values, so uint64 of the result is the same value api.EncodeI32 produced when CallWasm passes it to an export. - CoreF32FromF32 and CoreF64FromF64 pass the float through for imports and keep api.EncodeF32/EncodeF64 for exports, since uint64(float) is a numeric conversion rather than a bit copy. - Implement S64FromI64, I32FromChar and CharFromI32, and resolve WIT char to Go rune. Lifting a char panics on a surrogate or a value above U+10FFFF, as the canonical ABI requires. The iface-method-returns-string example gains an import and export per scalar type, with Go tests that round-trip boundary values through the guest, alongside the get-u32 case from this pull request's earlier commits. Co-Authored-By: Claude Opus 5.5 --- cmd/gravity/src/codegen/func.rs | 96 ++++-- cmd/gravity/src/go/imports.rs | 1 + cmd/gravity/src/go/type.rs | 7 +- cmd/gravity/src/lib.rs | 5 +- .../cmd/iface-method-returns-string.stdout | 302 ++++++++++++++++++ cmd/gravity/tests/cmd/instructions.stdout | 10 +- .../example_test.go | 121 +++++++ .../iface-method-returns-string/src/lib.rs | 44 +++ .../wit/example.wit | 23 ++ 9 files changed, 573 insertions(+), 36 deletions(-) diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index b0fedd2..419656f 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -10,9 +10,9 @@ use crate::{ go::{ comment, imports::{ - ERRORS_NEW, WAZERO_API_DECODE_F32, WAZERO_API_DECODE_F64, WAZERO_API_DECODE_I32, - WAZERO_API_DECODE_U32, WAZERO_API_ENCODE_F32, WAZERO_API_ENCODE_F64, - WAZERO_API_ENCODE_I32, + ERRORS_NEW, UTF8_VALID_RUNE, WAZERO_API_DECODE_F32, WAZERO_API_DECODE_F64, + WAZERO_API_DECODE_I32, WAZERO_API_DECODE_U32, WAZERO_API_ENCODE_F32, + WAZERO_API_ENCODE_F64, }, GoIdentifier, GoResult, GoType, Operand, }, @@ -1183,7 +1183,16 @@ impl Bindgen for Func<'_> { $module_handle.Memory().WriteUint64Le($ptr+$offset, $tag) } } - Instruction::I32FromChar => todo!("implement instruction: {inst:?}"), + Instruction::I32FromChar => { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $(&value) := uint32($operand) + } + results.push(Operand::SingleValue(value)) + } Instruction::I64FromU64 => { // I64FromU64 is a no-op reinterpretation (same 64-bit value, // different signedness). Use uint64() identity cast — int64() @@ -1199,27 +1208,24 @@ impl Bindgen for Func<'_> { results.push(Operand::SingleValue(value.into())); } Instruction::I64FromS64 => { + // Go's int64 -> uint64 conversion keeps the two's-complement + // bits, which is the i64 the canonical ABI expects. let tmp = self.tmp(); let value = format!("value{tmp}"); let operand = &operands[0]; quote_in! { self.body => $['\r'] - $(&value) := $operand + $(&value) := uint64($operand) } results.push(Operand::SingleValue(value.into())); } - Instruction::I32FromS32 => { - let tmp = self.tmp(); - let value = format!("value{tmp}"); - let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - $(&value) := $WAZERO_API_ENCODE_I32($operand) - } - results.push(Operand::SingleValue(value)) - } - // All of these values should fit in Go's `int32` type which allows a safe cast - Instruction::I32FromU16 + // Go's integer conversion to uint32 sign-extends the signed types + // and zero-extends the unsigned ones, which is how the canonical + // ABI widens them to an i32. The result is the uint32 a host + // function returns, and uint64(result) equals api.EncodeI32 when + // CallWasm passes it to an export. + Instruction::I32FromS32 + | Instruction::I32FromU16 | Instruction::I32FromS16 | Instruction::I32FromU8 | Instruction::I32FromS8 => { @@ -1228,17 +1234,25 @@ impl Bindgen for Func<'_> { let operand = &operands[0]; quote_in! { self.body => $['\r'] - $(&value) := $WAZERO_API_ENCODE_I32(int32($operand)) + $(&value) := uint32($operand) } results.push(Operand::SingleValue(value)) } + // A host function returns float32 and float64 directly, while + // CallWasm needs the IEEE bits in a uint64. Instruction::CoreF32FromF32 => { let tmp = self.tmp(); let result = &format!("result{tmp}"); let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - $result := $WAZERO_API_ENCODE_F32($operand) + match self.direction { + Direction::Export => quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_ENCODE_F32($operand) + }, + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $result := $operand + }, }; results.push(Operand::SingleValue(result.into())); } @@ -1246,9 +1260,15 @@ impl Bindgen for Func<'_> { let tmp = self.tmp(); let result = &format!("result{tmp}"); let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - $result := $WAZERO_API_ENCODE_F64($operand) + match self.direction { + Direction::Export => quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_ENCODE_F64($operand) + }, + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $result := $operand + }, }; results.push(Operand::SingleValue(result.into())); } @@ -1306,7 +1326,16 @@ impl Bindgen for Func<'_> { }; results.push(Operand::SingleValue(result.into())); } - Instruction::S64FromI64 => todo!("implement instruction: {inst:?}"), + Instruction::S64FromI64 => { + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := int64($operand) + }; + results.push(Operand::SingleValue(result.into())); + } Instruction::U64FromI64 => { let tmp = self.tmp(); let value = format!("value{tmp}"); @@ -1317,7 +1346,22 @@ impl Bindgen for Func<'_> { } results.push(Operand::SingleValue(value.into())); } - Instruction::CharFromI32 => todo!("implement instruction: {inst:?}"), + Instruction::CharFromI32 => { + // The canonical ABI rejects surrogates and values above + // U+10FFFF when lifting a char. + let tmp = self.tmp(); + let result = &format!("result{tmp}"); + let operand = &operands[0]; + quote_in! { self.body => + $['\r'] + $result := rune($operand) + if !$UTF8_VALID_RUNE($result) { + $(comment(["TODO(#8): Return an error if the return type allows it"])) + panic($ERRORS_NEW("invalid char value encountered")) + } + }; + results.push(Operand::SingleValue(result.into())); + } Instruction::F32FromCoreF32 => { let tmp = self.tmp(); let result = &format!("result{tmp}"); diff --git a/cmd/gravity/src/go/imports.rs b/cmd/gravity/src/go/imports.rs index 53ea3f2..94522d5 100644 --- a/cmd/gravity/src/go/imports.rs +++ b/cmd/gravity/src/go/imports.rs @@ -37,3 +37,4 @@ pub static WAZERO_API_ENCODE_F64: GoImport = pub static WAZERO_API_DECODE_F64: GoImport = GoImport("github.com/tetratelabs/wazero/api", "DecodeF64"); pub static REFLECT_VALUE_OF: GoImport = GoImport("reflect", "ValueOf"); +pub static UTF8_VALID_RUNE: GoImport = GoImport("unicode/utf8", "ValidRune"); diff --git a/cmd/gravity/src/go/type.rs b/cmd/gravity/src/go/type.rs index 24e47e3..c52005a 100644 --- a/cmd/gravity/src/go/type.rs +++ b/cmd/gravity/src/go/type.rs @@ -30,6 +30,8 @@ pub enum GoType { Float32, /// 64-bit floating point Float64, + /// Unicode code point (`rune`, an alias of `int32`) + Rune, /// String type String, /// Error type (represents Result) @@ -90,7 +92,8 @@ impl GoType { | GoType::Int32 | GoType::Int64 | GoType::Float32 - | GoType::Float64 => false, + | GoType::Float64 + | GoType::Rune => false, // String and slices allocate memory and need cleanup GoType::String | GoType::Slice(_) => true, @@ -150,6 +153,7 @@ impl FormatInto for &GoType { GoType::Int64 => tokens.append(static_literal("int64")), GoType::Float32 => tokens.append(static_literal("float32")), GoType::Float64 => tokens.append(static_literal("float64")), + GoType::Rune => tokens.append(static_literal("rune")), GoType::String => tokens.append(static_literal("string")), GoType::Error => tokens.append(static_literal("error")), GoType::Interface => tokens.append(static_literal("interface{}")), @@ -211,6 +215,7 @@ mod tests { (GoType::Int64, "int64"), (GoType::Float32, "float32"), (GoType::Float64, "float64"), + (GoType::Rune, "rune"), (GoType::String, "string"), (GoType::Error, "error"), (GoType::Interface, "interface{}"), diff --git a/cmd/gravity/src/lib.rs b/cmd/gravity/src/lib.rs index 0b4654c..58d095c 100644 --- a/cmd/gravity/src/lib.rs +++ b/cmd/gravity/src/lib.rs @@ -127,10 +127,7 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType { Type::S64 => GoType::Int64, Type::F32 => GoType::Float32, Type::F64 => GoType::Float64, - Type::Char => { - // Is this a Go "rune"? - todo!("TODO(#6): resolve char type") - } + Type::Char => GoType::Rune, Type::String => GoType::String, Type::ErrorContext => todo!("TODO(#4): implement error context conversion"), diff --git a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout b/cmd/gravity/tests/cmd/iface-method-returns-string.stdout index f0c8a04..e88fafd 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns-string.stdout @@ -6,6 +6,7 @@ import "context" import "errors" import "github.com/tetratelabs/wazero" import "github.com/tetratelabs/wazero/api" +import "unicode/utf8" import _ "embed" @@ -19,6 +20,39 @@ type IExampleRuntime interface { Arch( ctx context.Context, ) string + GetU32( + ctx context.Context, + ) uint32 + GetU8( + ctx context.Context, + ) uint8 + GetS8( + ctx context.Context, + ) int8 + GetU16( + ctx context.Context, + ) uint16 + GetS16( + ctx context.Context, + ) int16 + GetS32( + ctx context.Context, + ) int32 + GetU64( + ctx context.Context, + ) uint64 + GetS64( + ctx context.Context, + ) int64 + GetF32( + ctx context.Context, + ) float32 + GetF64( + ctx context.Context, + ) float64 + GetChar( + ctx context.Context, + ) rune Puts( ctx context.Context, msg string, @@ -72,6 +106,116 @@ func NewExampleFactory( }). Export("arch"). NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetU32(ctx, ) + result1 := uint32(value0) + return result1 + }). + Export("get-u32"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetU8(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-u8"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetS8(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-s8"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetU16(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-u16"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetS16(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-s16"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetS32(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-s32"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint64{ + value0 := runtime.GetU64(ctx, ) + value1 := uint64(value0) + return value1 + }). + Export("get-u64"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint64{ + value0 := runtime.GetS64(ctx, ) + value1 := uint64(value0) + return value1 + }). + Export("get-s64"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) float32{ + value0 := runtime.GetF32(ctx, ) + result1 := value0 + return result1 + }). + Export("get-f32"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) float64{ + value0 := runtime.GetF64(ctx, ) + result1 := value0 + return result1 + }). + Export("get-f64"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + ) uint32{ + value0 := runtime.GetChar(ctx, ) + value1 := uint32(value0) + return value1 + }). + Export("get-char"). + NewFunctionBuilder(). WithFunc(func( ctx context.Context, mod api.Module, @@ -225,3 +369,161 @@ func (i *ExampleInstance) Hello( return value8, err8 } +func (i *ExampleInstance) CallGetU32( + ctx context.Context, +) uint32 { + raw0, err0 := i.module.ExportedFunction("call-get-u32").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := uint32(results0) + return result1 +} + +func (i *ExampleInstance) CallGetU8( + ctx context.Context, +) uint8 { + raw0, err0 := i.module.ExportedFunction("call-get-u8").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := uint8(api.DecodeU32(results0)) + return result1 +} + +func (i *ExampleInstance) CallGetS8( + ctx context.Context, +) int8 { + raw0, err0 := i.module.ExportedFunction("call-get-s8").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := int8(api.DecodeI32(results0)) + return result1 +} + +func (i *ExampleInstance) CallGetU16( + ctx context.Context, +) uint16 { + raw0, err0 := i.module.ExportedFunction("call-get-u16").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := uint16(api.DecodeU32(results0)) + return result1 +} + +func (i *ExampleInstance) CallGetS16( + ctx context.Context, +) int16 { + raw0, err0 := i.module.ExportedFunction("call-get-s16").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := int16(api.DecodeI32(results0)) + return result1 +} + +func (i *ExampleInstance) CallGetS32( + ctx context.Context, +) int32 { + raw0, err0 := i.module.ExportedFunction("call-get-s32").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := api.DecodeI32(results0) + return result1 +} + +func (i *ExampleInstance) CallGetU64( + ctx context.Context, +) uint64 { + raw0, err0 := i.module.ExportedFunction("call-get-u64").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + value1 := uint64(results0) + return value1 +} + +func (i *ExampleInstance) CallGetS64( + ctx context.Context, +) int64 { + raw0, err0 := i.module.ExportedFunction("call-get-s64").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := int64(results0) + return result1 +} + +func (i *ExampleInstance) CallGetF32( + ctx context.Context, +) float32 { + raw0, err0 := i.module.ExportedFunction("call-get-f32").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := api.DecodeF32(results0) + return result1 +} + +func (i *ExampleInstance) CallGetF64( + ctx context.Context, +) float64 { + raw0, err0 := i.module.ExportedFunction("call-get-f64").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := api.DecodeF64(results0) + return result1 +} + +func (i *ExampleInstance) CallGetChar( + ctx context.Context, +) rune { + raw0, err0 := i.module.ExportedFunction("call-get-char").Call(ctx, ) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + + results0 := raw0[0] + result1 := rune(results0) + if !utf8.ValidRune(result1) { + // TODO(#8): Return an error if the return type allows it + panic(errors.New("invalid char value encountered")) + } + return result1 +} + diff --git a/cmd/gravity/tests/cmd/instructions.stdout b/cmd/gravity/tests/cmd/instructions.stdout index 3766664..6cbe15c 100644 --- a/cmd/gravity/tests/cmd/instructions.stdout +++ b/cmd/gravity/tests/cmd/instructions.stdout @@ -120,7 +120,7 @@ func (i *InstructionsInstance) S8Roundtrip( val int8, ) int8 { arg0 := val - value0 := api.EncodeI32(int32(arg0)) + value0 := uint32(arg0) raw1, err1 := i.module.ExportedFunction("s8-roundtrip").Call(ctx, uint64(value0)) // The return type doesn't contain an error so we panic if one is encountered if err1 != nil { @@ -137,7 +137,7 @@ func (i *InstructionsInstance) U8Roundtrip( val uint8, ) uint8 { arg0 := val - value0 := api.EncodeI32(int32(arg0)) + value0 := uint32(arg0) raw1, err1 := i.module.ExportedFunction("u8-roundtrip").Call(ctx, uint64(value0)) // The return type doesn't contain an error so we panic if one is encountered if err1 != nil { @@ -154,7 +154,7 @@ func (i *InstructionsInstance) S16Roundtrip( val int16, ) int16 { arg0 := val - value0 := api.EncodeI32(int32(arg0)) + value0 := uint32(arg0) raw1, err1 := i.module.ExportedFunction("s16-roundtrip").Call(ctx, uint64(value0)) // The return type doesn't contain an error so we panic if one is encountered if err1 != nil { @@ -171,7 +171,7 @@ func (i *InstructionsInstance) U16Roundtrip( val uint16, ) uint16 { arg0 := val - value0 := api.EncodeI32(int32(arg0)) + value0 := uint32(arg0) raw1, err1 := i.module.ExportedFunction("u16-roundtrip").Call(ctx, uint64(value0)) // The return type doesn't contain an error so we panic if one is encountered if err1 != nil { @@ -188,7 +188,7 @@ func (i *InstructionsInstance) S32Roundtrip( val int32, ) int32 { arg0 := val - value0 := api.EncodeI32(arg0) + value0 := uint32(arg0) raw1, err1 := i.module.ExportedFunction("s32-roundtrip").Call(ctx, uint64(value0)) // The return type doesn't contain an error so we panic if one is encountered if err1 != nil { diff --git a/examples/iface-method-returns-string/example_test.go b/examples/iface-method-returns-string/example_test.go index 6e6a337..cd66ea2 100644 --- a/examples/iface-method-returns-string/example_test.go +++ b/examples/iface-method-returns-string/example_test.go @@ -3,16 +3,39 @@ package example import ( "context" "fmt" + "math" "runtime" "testing" ) type Runtime struct { msg string + + u8 uint8 + s8 int8 + u16 uint16 + s16 int16 + s32 int32 + u64 uint64 + s64 int64 + f32 float32 + f64 float64 + char rune } func (Runtime) Os(context.Context) string { return runtime.GOOS } func (Runtime) Arch(context.Context) string { return runtime.GOARCH } +func (Runtime) GetU32(context.Context) uint32 { return 42 } +func (r *Runtime) GetU8(context.Context) uint8 { return r.u8 } +func (r *Runtime) GetS8(context.Context) int8 { return r.s8 } +func (r *Runtime) GetU16(context.Context) uint16 { return r.u16 } +func (r *Runtime) GetS16(context.Context) int16 { return r.s16 } +func (r *Runtime) GetS32(context.Context) int32 { return r.s32 } +func (r *Runtime) GetU64(context.Context) uint64 { return r.u64 } +func (r *Runtime) GetS64(context.Context) int64 { return r.s64 } +func (r *Runtime) GetF32(context.Context) float32 { return r.f32 } +func (r *Runtime) GetF64(context.Context) float64 { return r.f64 } +func (r *Runtime) GetChar(context.Context) rune { return r.char } func (r *Runtime) Puts(_ context.Context, msg string) { r.msg = msg } func TestBasic(t *testing.T) { @@ -44,3 +67,101 @@ func TestBasic(t *testing.T) { t.Errorf("wanted: %s, but got: %s", wantPutsMsg, r.msg) } } + +func TestCallGetU32(t *testing.T) { + r := &Runtime{} + fac, err := NewExampleFactory(t.Context(), r) + if err != nil { + t.Fatal(err) + } + defer fac.Close(t.Context()) + + ins, err := fac.Instantiate(t.Context()) + if err != nil { + t.Fatal(err) + } + defer ins.Close(t.Context()) + + value := ins.CallGetU32(t.Context()) + + var want uint32 = 42 + if value != want { + t.Errorf("wanted: %d, but got: %d", want, value) + } +} + +// roundTrip sets each value as the host import's return value, calls the +// export that returns the import's result, and checks the value survives the +// trip through the guest. +func roundTrip[T comparable](t *testing.T, values []T, set func(*Runtime, T), call func(*ExampleInstance, context.Context) T) { + t.Helper() + r := &Runtime{} + fac, err := NewExampleFactory(t.Context(), r) + if err != nil { + t.Fatal(err) + } + defer fac.Close(t.Context()) + + ins, err := fac.Instantiate(t.Context()) + if err != nil { + t.Fatal(err) + } + defer ins.Close(t.Context()) + + for _, want := range values { + set(r, want) + if got := call(ins, t.Context()); got != want { + t.Errorf("wanted: %v, but got: %v", want, got) + } + } +} + +func TestImportReturnsU8(t *testing.T) { + roundTrip(t, []uint8{0, 1, 200, math.MaxUint8}, + func(r *Runtime, v uint8) { r.u8 = v }, (*ExampleInstance).CallGetU8) +} + +func TestImportReturnsS8(t *testing.T) { + roundTrip(t, []int8{math.MinInt8, -1, 0, 1, math.MaxInt8}, + func(r *Runtime, v int8) { r.s8 = v }, (*ExampleInstance).CallGetS8) +} + +func TestImportReturnsU16(t *testing.T) { + roundTrip(t, []uint16{0, 1, 65000, math.MaxUint16}, + func(r *Runtime, v uint16) { r.u16 = v }, (*ExampleInstance).CallGetU16) +} + +func TestImportReturnsS16(t *testing.T) { + roundTrip(t, []int16{math.MinInt16, -1, 0, 1, math.MaxInt16}, + func(r *Runtime, v int16) { r.s16 = v }, (*ExampleInstance).CallGetS16) +} + +func TestImportReturnsS32(t *testing.T) { + roundTrip(t, []int32{math.MinInt32, -42, -1, 0, 1, math.MaxInt32}, + func(r *Runtime, v int32) { r.s32 = v }, (*ExampleInstance).CallGetS32) +} + +func TestImportReturnsU64(t *testing.T) { + roundTrip(t, []uint64{0, 1, 1 << 40, math.MaxUint64}, + func(r *Runtime, v uint64) { r.u64 = v }, (*ExampleInstance).CallGetU64) +} + +func TestImportReturnsS64(t *testing.T) { + roundTrip(t, []int64{math.MinInt64, -(1 << 40), -1, 0, 1, math.MaxInt64}, + func(r *Runtime, v int64) { r.s64 = v }, (*ExampleInstance).CallGetS64) +} + +func TestImportReturnsF32(t *testing.T) { + roundTrip(t, []float32{0, 1.5, -2.25, math.SmallestNonzeroFloat32, math.MaxFloat32, float32(math.Inf(1)), float32(math.Inf(-1))}, + func(r *Runtime, v float32) { r.f32 = v }, (*ExampleInstance).CallGetF32) +} + +func TestImportReturnsF64(t *testing.T) { + roundTrip(t, []float64{0, 1.5, -2.25, math.SmallestNonzeroFloat64, math.MaxFloat64, math.Inf(1), math.Inf(-1)}, + func(r *Runtime, v float64) { r.f64 = v }, (*ExampleInstance).CallGetF64) +} + +func TestImportReturnsChar(t *testing.T) { + roundTrip(t, []rune{0, 'a', 'λ', '😀', 0xD7FF, 0xE000, 0x10FFFF}, + func(r *Runtime, v rune) { r.char = v }, (*ExampleInstance).CallGetChar) +} diff --git a/examples/iface-method-returns-string/src/lib.rs b/examples/iface-method-returns-string/src/lib.rs index b50bf52..96614b9 100644 --- a/examples/iface-method-returns-string/src/lib.rs +++ b/examples/iface-method-returns-string/src/lib.rs @@ -14,4 +14,48 @@ impl Guest for ExampleWorld { Ok("Hello, world!".into()) } + + fn call_get_u32() -> u32 { + runtime::get_u32() + } + + fn call_get_u8() -> u8 { + runtime::get_u8() + } + + fn call_get_s8() -> i8 { + runtime::get_s8() + } + + fn call_get_u16() -> u16 { + runtime::get_u16() + } + + fn call_get_s16() -> i16 { + runtime::get_s16() + } + + fn call_get_s32() -> i32 { + runtime::get_s32() + } + + fn call_get_u64() -> u64 { + runtime::get_u64() + } + + fn call_get_s64() -> i64 { + runtime::get_s64() + } + + fn call_get_f32() -> f32 { + runtime::get_f32() + } + + fn call_get_f64() -> f64 { + runtime::get_f64() + } + + fn call_get_char() -> char { + runtime::get_char() + } } diff --git a/examples/iface-method-returns-string/wit/example.wit b/examples/iface-method-returns-string/wit/example.wit index 5e82f65..bdf5e5c 100644 --- a/examples/iface-method-returns-string/wit/example.wit +++ b/examples/iface-method-returns-string/wit/example.wit @@ -4,6 +4,18 @@ interface runtime { os: func() -> string; arch: func() -> string; + get-u32: func() -> u32; + get-u8: func() -> u8; + get-s8: func() -> s8; + get-u16: func() -> u16; + get-s16: func() -> s16; + get-s32: func() -> s32; + get-u64: func() -> u64; + get-s64: func() -> s64; + get-f32: func() -> f32; + get-f64: func() -> f64; + get-char: func() -> char; + puts: func(msg: string); } @@ -11,4 +23,15 @@ world example { import runtime; export hello: func() -> result; + export call-get-u32: func() -> u32; + export call-get-u8: func() -> u8; + export call-get-s8: func() -> s8; + export call-get-u16: func() -> u16; + export call-get-s16: func() -> s16; + export call-get-s32: func() -> s32; + export call-get-u64: func() -> u64; + export call-get-s64: func() -> s64; + export call-get-f32: func() -> f32; + export call-get-f64: func() -> f64; + export call-get-char: func() -> char; }