From 0c03f4ebc058ef2dabe624d3c11405adb41112b5 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 19:02:26 -0700 Subject: [PATCH 1/2] fix: store and load every value type at its own width Values that pass through guest memory (record fields, list elements, a host import's result, an export's indirect parameters) were written and read with the wrong width or type: - F32Store and F64Store called WriteUint64Le. On the import side, where arcjet/gravity#141 made floats plain float32 and float64, the generated Go no longer compiled; on the export side, F32Store wrote 8 bytes for a 4-byte value and overwrote whatever followed it. Imports now use WriteFloat32Le/WriteFloat64Le and exports write the IEEE bits at the value's width. F32Load read 8 bytes as well; it now reads 4. - I32Store8 only accepted 0 and 1 and panicked otherwise, so a u8, s8, enum or option payload above 1 panicked. It now stores the low byte of any value. - I32Store16, I64Store, I32Load8S, I32Load16U and I32Load16S were todo!(), so u16, s16, u64, s64 and s8 values in memory crashed gravity. They are implemented, with the loads sharing a read_memory helper for the failed-read check. - Each store converts its operand explicitly, and the integer lifts use Go conversions instead of api.DecodeI32/DecodeU32, so they accept the uint64 CallWasm returns as well as the narrower values loads produce. The float lifts pass Go floats through on the import side. The new memory example passes a record of every scalar type (17 flattened values, so it always travels through memory) to an export, back from an export, to a host import and back from it, plus a 68-byte record whose trailing f32 sits directly before a string allocation. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 8 + cmd/gravity/src/codegen/func.rs | 254 ++-- .../cmd/iface-method-returns-string.stdout | 10 +- cmd/gravity/tests/cmd/instructions.stdout | 40 +- cmd/gravity/tests/cmd/memory.stderr | 0 cmd/gravity/tests/cmd/memory.stdout | 1093 +++++++++++++++++ cmd/gravity/tests/cmd/memory.toml | 2 + cmd/gravity/tests/cmd/records.stdout | 294 ++--- cmd/gravity/tests/cmd/regressions.stdout | 4 +- cmd/gravity/tests/cmd/variants.stdout | 40 +- examples/generate.go | 2 + examples/memory/Cargo.toml | 11 + examples/memory/memory_test.go | 108 ++ examples/memory/src/lib.rs | 23 + examples/memory/wit/memory.wit | 73 ++ 15 files changed, 1665 insertions(+), 297 deletions(-) create mode 100644 cmd/gravity/tests/cmd/memory.stderr create mode 100644 cmd/gravity/tests/cmd/memory.stdout create mode 100644 cmd/gravity/tests/cmd/memory.toml create mode 100644 examples/memory/Cargo.toml create mode 100644 examples/memory/memory_test.go create mode 100644 examples/memory/src/lib.rs create mode 100644 examples/memory/wit/memory.wit diff --git a/Cargo.lock b/Cargo.lock index 893d1ab..2a0cae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,14 @@ dependencies = [ "wit-component", ] +[[package]] +name = "example-memory" +version = "0.0.2" +dependencies = [ + "wit-bindgen", + "wit-component", +] + [[package]] name = "example-records" version = "0.0.2" diff --git a/cmd/gravity/src/codegen/func.rs b/cmd/gravity/src/codegen/func.rs index 419656f..38890c9 100644 --- a/cmd/gravity/src/codegen/func.rs +++ b/cmd/gravity/src/codegen/func.rs @@ -11,8 +11,7 @@ use crate::{ comment, imports::{ 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, + WAZERO_API_ENCODE_F32, WAZERO_API_ENCODE_F64, }, GoIdentifier, GoResult, GoType, Operand, }, @@ -112,6 +111,43 @@ impl<'a> Func<'a> { fn pop_block(&mut self) -> (Tokens, Vec) { self.blocks.pop().expect("should have block to pop") } + + /// Emits `valueN, okN := .Memory().(uint32(ptr + offset))` + /// followed by the check that handles a failed read for the function's + /// return type, and returns the name of the value variable. `what` names + /// the value in the error message. + fn read_memory(&mut self, read: &str, ptr: &Operand, offset: usize, what: &str) -> String { + let tmp = self.tmp(); + let value = format!("value{tmp}"); + let ok = &format!("ok{tmp}"); + let default = &format!("default{tmp}"); + let module_handle = self.module_handle(); + let message = format!("failed to read {what} from memory"); + quote_in! { self.body => + $['\r'] + $(&value), $ok := $module_handle.Memory().$read(uint32($ptr + $offset)) + $(match &self.result { + GoResult::Anon(GoType::ValueOrError(typ)) => { + if !$ok { + var $default $(typ.as_ref()) + return $default, $ERRORS_NEW($(quoted(&message))) + } + } + GoResult::Anon(GoType::Error) => { + if !$ok { + return $ERRORS_NEW($(quoted(&message))) + } + } + 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($(quoted(&message)))) + } + } + }) + }; + value + } } impl Bindgen for Func<'_> { @@ -643,28 +679,11 @@ impl Bindgen for Func<'_> { let offset = offset.size_wasm32(); let tag = &operands[0]; let ptr = &operands[1]; - if let Operand::Literal(byte) = tag { - quote_in! { self.body => - $['\r'] - $module_handle.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: - $(comment(["TODO(#8): Return an error if the return type allows it"])) - panic($ERRORS_NEW("invalid int8 value encountered")) - } - $module_handle.Memory().WriteByte($ptr+$offset, $byte) - } + // The canonical ABI stores the low 8 bits of the i32. This + // covers bool, u8, s8, enum and variant discriminants alike. + quote_in! { self.body => + $['\r'] + $module_handle.Memory().WriteByte($ptr+$offset, uint8($tag)) } } Instruction::I32Store { offset } => { @@ -674,7 +693,7 @@ impl Bindgen for Func<'_> { let ptr = &operands[1]; quote_in! { self.body => $['\r'] - $module_handle.Memory().WriteUint32Le($ptr+$offset, $tag) + $module_handle.Memory().WriteUint32Le($ptr+$offset, uint32($tag)) } } Instruction::LengthStore { offset } => { @@ -1059,9 +1078,20 @@ impl Bindgen for Func<'_> { 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:?}"), + // The lift that follows (S8FromI32, U16FromI32, S16FromI32) + // converts the raw bits to the signed or unsigned Go type. + Instruction::I32Load8S { offset } => { + // TODO(#58): Support additional ArchitectureSize + let value = + self.read_memory("ReadByte", &operands[0], offset.size_wasm32(), "byte"); + results.push(Operand::SingleValue(value)); + } + Instruction::I32Load16U { offset } | Instruction::I32Load16S { offset } => { + // TODO(#58): Support additional ArchitectureSize + let value = + self.read_memory("ReadUint16Le", &operands[0], offset.size_wasm32(), "i16"); + results.push(Operand::SingleValue(value)); + } Instruction::I64Load { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); @@ -1095,92 +1125,91 @@ impl Bindgen for Func<'_> { }; results.push(Operand::SingleValue(value.into())); } + // Imports lift floats from Go float values; exports lift them + // from the IEEE bits in a uint64, as CallWasm returns them, so an + // export widens the f32's 4 bytes to a uint64. Instruction::F32Load { 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 default = &format!("default{tmp}"); - let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - $value, $ok := $module_handle.Memory().ReadUint64Le(uint32($operand + $offset)) - $(match &self.result { - GoResult::Anon(GoType::ValueOrError(typ)) => { - if !$ok { - var $default $(typ.as_ref()) - return $default, $ERRORS_NEW("failed to read f32 from memory") - } - } - GoResult::Anon(GoType::Error) => { - if !$ok { - return $ERRORS_NEW("failed to read f32 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 f32 from memory")) - } - } - }) + let value = match self.direction { + Direction::Import { .. } => { + self.read_memory("ReadFloat32Le", &operands[0], offset, "f32") + } + Direction::Export => { + let value = self.read_memory("ReadUint32Le", &operands[0], offset, "f32"); + let tmp = self.tmp(); + let bits = format!("bits{tmp}"); + quote_in! { self.body => + $['\r'] + $(&bits) := uint64($(&value)) + }; + bits + } }; - results.push(Operand::SingleValue(value.into())); + results.push(Operand::SingleValue(value)); } Instruction::F64Load { 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 default = &format!("default{tmp}"); - let operand = &operands[0]; - quote_in! { self.body => - $['\r'] - $value, $ok := $module_handle.Memory().ReadUint64Le(uint32($operand + $offset)) - $(match &self.result { - GoResult::Anon(GoType::ValueOrError(typ)) => { - if !$ok { - var $default $(typ.as_ref()) - return $default, $ERRORS_NEW("failed to read f64 from memory") - } - } - GoResult::Anon(GoType::Error) => { - if !$ok { - return $ERRORS_NEW("failed to read f64 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 f64 from memory")) - } - } - }) + let read = match self.direction { + Direction::Import { .. } => "ReadFloat64Le", + Direction::Export => "ReadUint64Le", }; - results.push(Operand::SingleValue(value.into())); + let value = self.read_memory(read, &operands[0], offset, "f64"); + results.push(Operand::SingleValue(value)); } - Instruction::I32Store16 { .. } => todo!("implement instruction: {inst:?}"), - Instruction::I64Store { .. } => todo!("implement instruction: {inst:?}"), - Instruction::F32Store { offset } => { + Instruction::I32Store16 { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); let tag = &operands[0]; let ptr = &operands[1]; quote_in! { self.body => $['\r'] - $module_handle.Memory().WriteUint64Le($ptr+$offset, $tag) + $module_handle.Memory().WriteUint16Le($ptr+$offset, uint16($tag)) } } - Instruction::F64Store { offset } => { + Instruction::I64Store { offset } => { // TODO(#58): Support additional ArchitectureSize let offset = offset.size_wasm32(); let tag = &operands[0]; let ptr = &operands[1]; quote_in! { self.body => $['\r'] - $module_handle.Memory().WriteUint64Le($ptr+$offset, $tag) + $module_handle.Memory().WriteUint64Le($ptr+$offset, uint64($tag)) + } + } + // Imports lower floats to Go float values; exports lower them to + // the IEEE bits in a uint64 (api.EncodeF32/EncodeF64). + Instruction::F32Store { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tag = &operands[0]; + let ptr = &operands[1]; + match self.direction { + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $module_handle.Memory().WriteFloat32Le($ptr+$offset, $tag) + }, + Direction::Export => quote_in! { self.body => + $['\r'] + $module_handle.Memory().WriteUint32Le($ptr+$offset, uint32($tag)) + }, + } + } + Instruction::F64Store { offset } => { + // TODO(#58): Support additional ArchitectureSize + let offset = offset.size_wasm32(); + let tag = &operands[0]; + let ptr = &operands[1]; + match self.direction { + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $module_handle.Memory().WriteFloat64Le($ptr+$offset, $tag) + }, + Direction::Export => quote_in! { self.body => + $['\r'] + $module_handle.Memory().WriteUint64Le($ptr+$offset, $tag) + }, } } Instruction::I32FromChar => { @@ -1272,47 +1301,47 @@ impl Bindgen for Func<'_> { }; results.push(Operand::SingleValue(result.into())); } - // TODO: Validate the Go cast truncates the upper bits in the I32 + // Go's conversion keeps the low bits and reinterprets their sign, + // which is the canonical ABI's lift of an i32. It accepts the + // uint64 CallWasm returns, a uint32 host parameter, and the + // narrower values a load produces. 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)) + $result := int8($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)) + $result := uint8($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)) + $result := int16($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)) + $result := uint16($operand) }; results.push(Operand::SingleValue(result.into())); } @@ -1322,7 +1351,7 @@ impl Bindgen for Func<'_> { let operand = &operands[0]; quote_in! { self.body => $['\r'] - $result := $WAZERO_API_DECODE_I32($operand) + $result := int32($operand) }; results.push(Operand::SingleValue(result.into())); } @@ -1362,13 +1391,22 @@ impl Bindgen for Func<'_> { }; results.push(Operand::SingleValue(result.into())); } + // A host function receives float32 and float64 directly, and the + // float loads produce them; CallWasm returns the IEEE bits in a + // uint64. 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) + match self.direction { + Direction::Export => quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_F32($operand) + }, + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $result := $operand + }, }; results.push(Operand::SingleValue(result.into())); } @@ -1376,9 +1414,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_DECODE_F64($operand) + match self.direction { + Direction::Export => quote_in! { self.body => + $['\r'] + $result := $WAZERO_API_DECODE_F64($operand) + }, + Direction::Import { .. } => quote_in! { self.body => + $['\r'] + $result := $operand + }, }; results.push(Operand::SingleValue(result.into())); } diff --git a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout b/cmd/gravity/tests/cmd/iface-method-returns-string.stdout index e88fafd..25dc761 100644 --- a/cmd/gravity/tests/cmd/iface-method-returns-string.stdout +++ b/cmd/gravity/tests/cmd/iface-method-returns-string.stdout @@ -393,7 +393,7 @@ func (i *ExampleInstance) CallGetU8( } results0 := raw0[0] - result1 := uint8(api.DecodeU32(results0)) + result1 := uint8(results0) return result1 } @@ -407,7 +407,7 @@ func (i *ExampleInstance) CallGetS8( } results0 := raw0[0] - result1 := int8(api.DecodeI32(results0)) + result1 := int8(results0) return result1 } @@ -421,7 +421,7 @@ func (i *ExampleInstance) CallGetU16( } results0 := raw0[0] - result1 := uint16(api.DecodeU32(results0)) + result1 := uint16(results0) return result1 } @@ -435,7 +435,7 @@ func (i *ExampleInstance) CallGetS16( } results0 := raw0[0] - result1 := int16(api.DecodeI32(results0)) + result1 := int16(results0) return result1 } @@ -449,7 +449,7 @@ func (i *ExampleInstance) CallGetS32( } results0 := raw0[0] - result1 := api.DecodeI32(results0) + result1 := int32(results0) return result1 } diff --git a/cmd/gravity/tests/cmd/instructions.stdout b/cmd/gravity/tests/cmd/instructions.stdout index 6cbe15c..02e9465 100644 --- a/cmd/gravity/tests/cmd/instructions.stdout +++ b/cmd/gravity/tests/cmd/instructions.stdout @@ -128,7 +128,7 @@ func (i *InstructionsInstance) S8Roundtrip( } results1 := raw1[0] - result2 := int8(api.DecodeI32(results1)) + result2 := int8(results1) return result2 } @@ -145,7 +145,7 @@ func (i *InstructionsInstance) U8Roundtrip( } results1 := raw1[0] - result2 := uint8(api.DecodeU32(results1)) + result2 := uint8(results1) return result2 } @@ -162,7 +162,7 @@ func (i *InstructionsInstance) S16Roundtrip( } results1 := raw1[0] - result2 := int16(api.DecodeI32(results1)) + result2 := int16(results1) return result2 } @@ -179,7 +179,7 @@ func (i *InstructionsInstance) U16Roundtrip( } results1 := raw1[0] - result2 := uint16(api.DecodeU32(results1)) + result2 := uint16(results1) return result2 } @@ -196,7 +196,7 @@ func (i *InstructionsInstance) S32Roundtrip( } results1 := raw1[0] - result2 := api.DecodeI32(results1) + result2 := int32(results1) return result2 } @@ -303,35 +303,35 @@ func (i *InstructionsInstance) IndirectParams( o1 := arg0.O p1 := arg0.P result2 := uint32(a1) - i.module.Memory().WriteUint32Le(ptr0+0, result2) + i.module.Memory().WriteUint32Le(ptr0+0, uint32(result2)) result3 := uint32(b1) - i.module.Memory().WriteUint32Le(ptr0+4, result3) + i.module.Memory().WriteUint32Le(ptr0+4, uint32(result3)) result4 := uint32(c1) - i.module.Memory().WriteUint32Le(ptr0+8, result4) + i.module.Memory().WriteUint32Le(ptr0+8, uint32(result4)) result5 := uint32(d1) - i.module.Memory().WriteUint32Le(ptr0+12, result5) + i.module.Memory().WriteUint32Le(ptr0+12, uint32(result5)) result6 := uint32(e1) - i.module.Memory().WriteUint32Le(ptr0+16, result6) + i.module.Memory().WriteUint32Le(ptr0+16, uint32(result6)) result7 := uint32(f1) - i.module.Memory().WriteUint32Le(ptr0+20, result7) + i.module.Memory().WriteUint32Le(ptr0+20, uint32(result7)) result8 := uint32(g1) - i.module.Memory().WriteUint32Le(ptr0+24, result8) + i.module.Memory().WriteUint32Le(ptr0+24, uint32(result8)) result9 := uint32(h1) - i.module.Memory().WriteUint32Le(ptr0+28, result9) + i.module.Memory().WriteUint32Le(ptr0+28, uint32(result9)) result10 := uint32(i1) - i.module.Memory().WriteUint32Le(ptr0+32, result10) + i.module.Memory().WriteUint32Le(ptr0+32, uint32(result10)) result11 := uint32(j1) - i.module.Memory().WriteUint32Le(ptr0+36, result11) + i.module.Memory().WriteUint32Le(ptr0+36, uint32(result11)) result12 := uint32(k1) - i.module.Memory().WriteUint32Le(ptr0+40, result12) + i.module.Memory().WriteUint32Le(ptr0+40, uint32(result12)) result13 := uint32(l1) - i.module.Memory().WriteUint32Le(ptr0+44, result13) + i.module.Memory().WriteUint32Le(ptr0+44, uint32(result13)) result14 := uint32(m1) - i.module.Memory().WriteUint32Le(ptr0+48, result14) + i.module.Memory().WriteUint32Le(ptr0+48, uint32(result14)) result15 := uint32(n1) - i.module.Memory().WriteUint32Le(ptr0+52, result15) + i.module.Memory().WriteUint32Le(ptr0+52, uint32(result15)) result16 := uint32(o1) - i.module.Memory().WriteUint32Le(ptr0+56, result16) + i.module.Memory().WriteUint32Le(ptr0+56, uint32(result16)) memory17 := i.module.Memory() realloc17 := i.module.ExportedFunction("cabi_realloc") ptr17, len17, err17 := writeString(ctx, p1, memory17, realloc17) diff --git a/cmd/gravity/tests/cmd/memory.stderr b/cmd/gravity/tests/cmd/memory.stderr new file mode 100644 index 0000000..e69de29 diff --git a/cmd/gravity/tests/cmd/memory.stdout b/cmd/gravity/tests/cmd/memory.stdout new file mode 100644 index 0000000..0cbd7e2 --- /dev/null +++ b/cmd/gravity/tests/cmd/memory.stdout @@ -0,0 +1,1093 @@ +// Code generated by arcjet-gravity; DO NOT EDIT. + +package memory + +import "context" +import "errors" +import "github.com/tetratelabs/wazero" +import "github.com/tetratelabs/wazero/api" +import "unicode/utf8" + +import _ "embed" + +//go:embed memory.wasm +var wasmFileMemory []byte + +type IMemoryHost interface { + Echo( + ctx context.Context, + x Everything, + ) Everything +} + +type Color interface { + isColor() +} + +type color int + +func (color) isColor() {} + +const ( + Red color = iota + Green color = iota + Blue color = iota +) + +type Everything struct { + AU8 uint8 + AS8 int8 + AU16 uint16 + AS16 int16 + AU32 uint32 + AS32 int32 + AU64 uint64 + AS64 int64 + AF64 float64 + ABool bool + AChar rune + AColor Color + AOpt *uint8 + AStr string + AF32 float32 +} + +type Narrow struct { + N0 uint32 + N1 uint32 + N2 uint32 + N3 uint32 + N4 uint32 + N5 uint32 + N6 uint32 + N7 uint32 + N8 uint32 + N9 uint32 + N10 uint32 + N11 uint32 + N12 uint32 + N13 uint32 + S string + F float32 +} + +type MemoryFactory struct { + runtime wazero.Runtime + module wazero.CompiledModule +} + +func NewMemoryFactory( + ctx context.Context, + host IMemoryHost, +) (*MemoryFactory, error) { + wazeroRuntime := wazero.NewRuntime(ctx) + + _, err0 := wazeroRuntime.NewHostModuleBuilder("arcjet:memory/host"). + NewFunctionBuilder(). + WithFunc(func( + ctx context.Context, + mod api.Module, + arg0 uint32, + arg1 uint32, + ) { + value0, ok0 := mod.Memory().ReadByte(uint32(arg0 + 0)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok0 { + panic(errors.New("failed to read byte from memory")) + } + result1 := uint8(value0) + value2, ok2 := mod.Memory().ReadByte(uint32(arg0 + 1)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok2 { + panic(errors.New("failed to read byte from memory")) + } + result3 := int8(value2) + value4, ok4 := mod.Memory().ReadUint16Le(uint32(arg0 + 2)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok4 { + panic(errors.New("failed to read i16 from memory")) + } + result5 := uint16(value4) + value6, ok6 := mod.Memory().ReadUint16Le(uint32(arg0 + 4)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok6 { + panic(errors.New("failed to read i16 from memory")) + } + result7 := int16(value6) + value8, ok8 := mod.Memory().ReadUint32Le(uint32(arg0 + 8)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok8 { + panic(errors.New("failed to read i32 from memory")) + } + result9 := uint32(value8) + value10, ok10 := mod.Memory().ReadUint32Le(uint32(arg0 + 12)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok10 { + panic(errors.New("failed to read i32 from memory")) + } + result11 := int32(value10) + value12, ok12 := mod.Memory().ReadUint64Le(uint32(arg0 + 16)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok12 { + panic(errors.New("failed to read i64 from memory")) + } + value13 := uint64(value12) + value14, ok14 := mod.Memory().ReadUint64Le(uint32(arg0 + 24)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok14 { + panic(errors.New("failed to read i64 from memory")) + } + result15 := int64(value14) + value16, ok16 := mod.Memory().ReadFloat64Le(uint32(arg0 + 32)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok16 { + panic(errors.New("failed to read f64 from memory")) + } + result17 := value16 + value18, ok18 := mod.Memory().ReadByte(uint32(arg0 + 40)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok18 { + panic(errors.New("failed to read byte from memory")) + } + value19 := value18 != 0 + value20, ok20 := mod.Memory().ReadUint32Le(uint32(arg0 + 44)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok20 { + panic(errors.New("failed to read i32 from memory")) + } + result21 := rune(value20) + if !utf8.ValidRune(result21) { + // TODO(#8): Return an error if the return type allows it + panic(errors.New("invalid char value encountered")) + } + value22, ok22 := mod.Memory().ReadByte(uint32(arg0 + 48)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok22 { + panic(errors.New("failed to read byte from memory")) + } + var enum23 Color + switch value22 { + case 0: + enum23 = Red + case 1: + enum23 = Green + case 2: + enum23 = Blue + default: + // The return type doesn't contain an error so we panic if one is encountered + panic(errors.New("invalid color discriminant")) + } + value24, ok24 := mod.Memory().ReadByte(uint32(arg0 + 49)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok24 { + panic(errors.New("failed to read byte from memory")) + } + var result27 *uint8 + if value24 != 0 { + value25, ok25 := mod.Memory().ReadByte(uint32(arg0 + 50)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok25 { + panic(errors.New("failed to read byte from memory")) + } + result26 := uint8(value25) + someValue27 := result26 + result27 = &someValue27 + } + ptr28, ok28 := mod.Memory().ReadUint32Le(uint32(arg0 + 52)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok28 { + panic(errors.New("failed to read pointer from memory")) + } + len29, ok29 := mod.Memory().ReadUint32Le(uint32(arg0 + 56)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok29 { + panic(errors.New("failed to read length from memory")) + } + buf30, ok30 := mod.Memory().Read(ptr28, len29) + if !ok30 { + panic(errors.New("failed to read bytes from memory")) + } + str30 := string(buf30) + value31, ok31 := mod.Memory().ReadFloat32Le(uint32(arg0 + 60)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok31 { + panic(errors.New("failed to read f32 from memory")) + } + result32 := value31 + value33 := Everything{ + AU8: result1, + AS8: result3, + AU16: result5, + AS16: result7, + AU32: result9, + AS32: result11, + AU64: value13, + AS64: result15, + AF64: result17, + ABool: value19, + AChar: result21, + AColor: enum23, + AOpt: result27, + AStr: str30, + AF32: result32, + } + value34 := host.Echo(ctx, value33) + aU835 := value34.AU8 + aS835 := value34.AS8 + aU1635 := value34.AU16 + aS1635 := value34.AS16 + aU3235 := value34.AU32 + aS3235 := value34.AS32 + aU6435 := value34.AU64 + aS6435 := value34.AS64 + aF6435 := value34.AF64 + aBool35 := value34.ABool + aChar35 := value34.AChar + aColor35 := value34.AColor + aOpt35 := value34.AOpt + aStr35 := value34.AStr + aF3235 := value34.AF32 + value36 := uint32(aU835) + mod.Memory().WriteByte(arg1+0, uint8(value36)) + value37 := uint32(aS835) + mod.Memory().WriteByte(arg1+1, uint8(value37)) + value38 := uint32(aU1635) + mod.Memory().WriteUint16Le(arg1+2, uint16(value38)) + value39 := uint32(aS1635) + mod.Memory().WriteUint16Le(arg1+4, uint16(value39)) + result40 := uint32(aU3235) + mod.Memory().WriteUint32Le(arg1+8, uint32(result40)) + value41 := uint32(aS3235) + mod.Memory().WriteUint32Le(arg1+12, uint32(value41)) + value42 := uint64(aU6435) + mod.Memory().WriteUint64Le(arg1+16, uint64(value42)) + value43 := uint64(aS6435) + mod.Memory().WriteUint64Le(arg1+24, uint64(value43)) + result44 := aF6435 + mod.Memory().WriteFloat64Le(arg1+32, result44) + var value45 uint32 + if aBool35 { + value45 = 1 + } else { + value45 = 0 + } + mod.Memory().WriteByte(arg1+40, uint8(value45)) + value46 := uint32(aChar35) + mod.Memory().WriteUint32Le(arg1+44, uint32(value46)) + var enum47 uint32 + switch aColor35 { + case Red: + enum47 = 0 + case Green: + enum47 = 1 + case Blue: + enum47 = 2 + default: + panic(errors.New("invalid enum type provided")) + } + mod.Memory().WriteByte(arg1+48, uint8(enum47)) + if aOpt35 == nil { + mod.Memory().WriteByte(arg1+49, uint8(0)) + } else { + variantPayload := *aOpt35 + mod.Memory().WriteByte(arg1+49, uint8(1)) + value48 := uint32(variantPayload) + mod.Memory().WriteByte(arg1+50, uint8(value48)) + } + memory50 := mod.Memory() + realloc50 := mod.ExportedFunction("cabi_realloc") + ptr50, len50, err50 := writeString(ctx, aStr35, memory50, realloc50) + if err50 != nil { + panic(err50) + } + mod.Memory().WriteUint32Le(arg1+56, uint32(len50)) + mod.Memory().WriteUint32Le(arg1+52, uint32(ptr50)) + result51 := aF3235 + mod.Memory().WriteFloat32Le(arg1+60, result51) + }). + Export("echo"). + Instantiate(ctx) + if err0 != nil { + return nil, err0 + } + + // Compiling the module takes a LONG time, so we want to do it once and hold + // onto it with the Runtime + module, err := wazeroRuntime.CompileModule(ctx, wasmFileMemory) + if err != nil { + return nil, err + } + return &MemoryFactory{ + runtime: wazeroRuntime, + module: module, + }, nil +} + +func (f *MemoryFactory) Instantiate(ctx context.Context) (*MemoryInstance, error) { + if module, err := f.runtime.InstantiateModule(ctx, f.module, wazero.NewModuleConfig()); err != nil { + return nil, err + } else { + return &MemoryInstance{module}, nil + } +} + +func (f *MemoryFactory) Close(ctx context.Context) { + f.runtime.Close(ctx) +} + +type MemoryInstance struct { + module api.Module +} + +func (i *MemoryInstance) Close(ctx context.Context) error { + if err := i.module.Close(ctx); err != nil { + return err + } + + return nil +} + +// writeString will put a Go string into the Wasm memory following the Component +// Model calling conventions, such as allocating memory with the realloc function +func writeString( + ctx context.Context, + s string, + memory api.Memory, + realloc api.Function, +) (uint64, uint64, error) { + if len(s) == 0 { + return 1, 0, nil + } + + results, err := realloc.Call(ctx, 0, 0, 1, uint64(len(s))) + if err != nil { + return 1, 0, err + } + ptr := results[0] + ok := memory.Write(uint32(ptr), []byte(s)) + if !ok { + return 1, 0, errors.New("failed to write string to wasm memory") + } + return uint64(ptr), uint64(len(s)), nil +} + +func (i *MemoryInstance) RoundTrip( + ctx context.Context, + x Everything, +) Everything { + arg0 := x + // Allocate the area holding the indirectly passed parameters + result0, err0 := i.module.ExportedFunction("cabi_realloc").Call(ctx, 0, 0, 8, 64) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + ptr0 := uint32(result0[0]) + aU81 := arg0.AU8 + aS81 := arg0.AS8 + aU161 := arg0.AU16 + aS161 := arg0.AS16 + aU321 := arg0.AU32 + aS321 := arg0.AS32 + aU641 := arg0.AU64 + aS641 := arg0.AS64 + aF641 := arg0.AF64 + aBool1 := arg0.ABool + aChar1 := arg0.AChar + aColor1 := arg0.AColor + aOpt1 := arg0.AOpt + aStr1 := arg0.AStr + aF321 := arg0.AF32 + value2 := uint32(aU81) + i.module.Memory().WriteByte(ptr0+0, uint8(value2)) + value3 := uint32(aS81) + i.module.Memory().WriteByte(ptr0+1, uint8(value3)) + value4 := uint32(aU161) + i.module.Memory().WriteUint16Le(ptr0+2, uint16(value4)) + value5 := uint32(aS161) + i.module.Memory().WriteUint16Le(ptr0+4, uint16(value5)) + result6 := uint32(aU321) + i.module.Memory().WriteUint32Le(ptr0+8, uint32(result6)) + value7 := uint32(aS321) + i.module.Memory().WriteUint32Le(ptr0+12, uint32(value7)) + value8 := uint64(aU641) + i.module.Memory().WriteUint64Le(ptr0+16, uint64(value8)) + value9 := uint64(aS641) + i.module.Memory().WriteUint64Le(ptr0+24, uint64(value9)) + result10 := api.EncodeF64(aF641) + i.module.Memory().WriteUint64Le(ptr0+32, result10) + var value11 uint32 + if aBool1 { + value11 = 1 + } else { + value11 = 0 + } + i.module.Memory().WriteByte(ptr0+40, uint8(value11)) + value12 := uint32(aChar1) + i.module.Memory().WriteUint32Le(ptr0+44, uint32(value12)) + var enum13 uint32 + switch aColor1 { + case Red: + enum13 = 0 + case Green: + enum13 = 1 + case Blue: + enum13 = 2 + default: + panic(errors.New("invalid enum type provided")) + } + i.module.Memory().WriteByte(ptr0+48, uint8(enum13)) + if aOpt1 == nil { + i.module.Memory().WriteByte(ptr0+49, uint8(0)) + } else { + variantPayload := *aOpt1 + i.module.Memory().WriteByte(ptr0+49, uint8(1)) + value14 := uint32(variantPayload) + i.module.Memory().WriteByte(ptr0+50, uint8(value14)) + } + memory16 := i.module.Memory() + realloc16 := i.module.ExportedFunction("cabi_realloc") + ptr16, len16, err16 := writeString(ctx, aStr1, memory16, realloc16) + // The return type doesn't contain an error so we panic if one is encountered + if err16 != nil { + panic(err16) + } + i.module.Memory().WriteUint32Le(ptr0+56, uint32(len16)) + i.module.Memory().WriteUint32Le(ptr0+52, uint32(ptr16)) + result17 := api.EncodeF32(aF321) + i.module.Memory().WriteUint32Le(ptr0+60, uint32(result17)) + raw18, err18 := i.module.ExportedFunction("round-trip").Call(ctx, uint64(ptr0)) + // The return type doesn't contain an error so we panic if one is encountered + if err18 != nil { + panic(err18) + } + + // The cleanup via `cabi_post_*` cleans up the memory in the guest. By + // deferring this, we ensure that no memory is corrupted before the function + // is done accessing it. + defer func() { + if postFn := i.module.ExportedFunction("cabi_post_round-trip"); postFn != nil { + if _, err := postFn.Call(ctx, raw18...); err != nil { + // If we get an error during cleanup, something really bad is + // going on, so we panic. Also, you can't return the error from + // the `defer` + panic(errors.New("failed to cleanup")) + } + } + }() + + results18 := raw18[0] + value19, ok19 := i.module.Memory().ReadByte(uint32(results18 + 0)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok19 { + panic(errors.New("failed to read byte from memory")) + } + result20 := uint8(value19) + value21, ok21 := i.module.Memory().ReadByte(uint32(results18 + 1)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok21 { + panic(errors.New("failed to read byte from memory")) + } + result22 := int8(value21) + value23, ok23 := i.module.Memory().ReadUint16Le(uint32(results18 + 2)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok23 { + panic(errors.New("failed to read i16 from memory")) + } + result24 := uint16(value23) + value25, ok25 := i.module.Memory().ReadUint16Le(uint32(results18 + 4)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok25 { + panic(errors.New("failed to read i16 from memory")) + } + result26 := int16(value25) + value27, ok27 := i.module.Memory().ReadUint32Le(uint32(results18 + 8)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok27 { + panic(errors.New("failed to read i32 from memory")) + } + result28 := uint32(value27) + value29, ok29 := i.module.Memory().ReadUint32Le(uint32(results18 + 12)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok29 { + panic(errors.New("failed to read i32 from memory")) + } + result30 := int32(value29) + value31, ok31 := i.module.Memory().ReadUint64Le(uint32(results18 + 16)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok31 { + panic(errors.New("failed to read i64 from memory")) + } + value32 := uint64(value31) + value33, ok33 := i.module.Memory().ReadUint64Le(uint32(results18 + 24)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok33 { + panic(errors.New("failed to read i64 from memory")) + } + result34 := int64(value33) + value35, ok35 := i.module.Memory().ReadUint64Le(uint32(results18 + 32)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok35 { + panic(errors.New("failed to read f64 from memory")) + } + result36 := api.DecodeF64(value35) + value37, ok37 := i.module.Memory().ReadByte(uint32(results18 + 40)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok37 { + panic(errors.New("failed to read byte from memory")) + } + value38 := value37 != 0 + value39, ok39 := i.module.Memory().ReadUint32Le(uint32(results18 + 44)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok39 { + panic(errors.New("failed to read i32 from memory")) + } + result40 := rune(value39) + if !utf8.ValidRune(result40) { + // TODO(#8): Return an error if the return type allows it + panic(errors.New("invalid char value encountered")) + } + value41, ok41 := i.module.Memory().ReadByte(uint32(results18 + 48)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok41 { + panic(errors.New("failed to read byte from memory")) + } + var enum42 Color + switch value41 { + case 0: + enum42 = Red + case 1: + enum42 = Green + case 2: + enum42 = Blue + default: + // The return type doesn't contain an error so we panic if one is encountered + panic(errors.New("invalid color discriminant")) + } + value43, ok43 := i.module.Memory().ReadByte(uint32(results18 + 49)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok43 { + panic(errors.New("failed to read byte from memory")) + } + var result46 *uint8 + if value43 != 0 { + value44, ok44 := i.module.Memory().ReadByte(uint32(results18 + 50)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok44 { + panic(errors.New("failed to read byte from memory")) + } + result45 := uint8(value44) + someValue46 := result45 + result46 = &someValue46 + } + ptr47, ok47 := i.module.Memory().ReadUint32Le(uint32(results18 + 52)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok47 { + panic(errors.New("failed to read pointer from memory")) + } + len48, ok48 := i.module.Memory().ReadUint32Le(uint32(results18 + 56)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok48 { + panic(errors.New("failed to read length from memory")) + } + buf49, ok49 := i.module.Memory().Read(ptr47, len48) + // The return type doesn't contain an error so we panic if one is encountered + if !ok49 { + panic(errors.New("failed to read bytes from memory")) + } + str49 := string(buf49) + value50, ok50 := i.module.Memory().ReadUint32Le(uint32(results18 + 60)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok50 { + panic(errors.New("failed to read f32 from memory")) + } + bits51 := uint64(value50) + result52 := api.DecodeF32(bits51) + value53 := Everything{ + AU8: result20, + AS8: result22, + AU16: result24, + AS16: result26, + AU32: result28, + AS32: result30, + AU64: value32, + AS64: result34, + AF64: result36, + ABool: value38, + AChar: result40, + AColor: enum42, + AOpt: result46, + AStr: str49, + AF32: result52, + } + return value53 +} + +func (i *MemoryInstance) CallHostEcho( + ctx context.Context, + x Everything, +) Everything { + arg0 := x + // Allocate the area holding the indirectly passed parameters + result0, err0 := i.module.ExportedFunction("cabi_realloc").Call(ctx, 0, 0, 8, 64) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + ptr0 := uint32(result0[0]) + aU81 := arg0.AU8 + aS81 := arg0.AS8 + aU161 := arg0.AU16 + aS161 := arg0.AS16 + aU321 := arg0.AU32 + aS321 := arg0.AS32 + aU641 := arg0.AU64 + aS641 := arg0.AS64 + aF641 := arg0.AF64 + aBool1 := arg0.ABool + aChar1 := arg0.AChar + aColor1 := arg0.AColor + aOpt1 := arg0.AOpt + aStr1 := arg0.AStr + aF321 := arg0.AF32 + value2 := uint32(aU81) + i.module.Memory().WriteByte(ptr0+0, uint8(value2)) + value3 := uint32(aS81) + i.module.Memory().WriteByte(ptr0+1, uint8(value3)) + value4 := uint32(aU161) + i.module.Memory().WriteUint16Le(ptr0+2, uint16(value4)) + value5 := uint32(aS161) + i.module.Memory().WriteUint16Le(ptr0+4, uint16(value5)) + result6 := uint32(aU321) + i.module.Memory().WriteUint32Le(ptr0+8, uint32(result6)) + value7 := uint32(aS321) + i.module.Memory().WriteUint32Le(ptr0+12, uint32(value7)) + value8 := uint64(aU641) + i.module.Memory().WriteUint64Le(ptr0+16, uint64(value8)) + value9 := uint64(aS641) + i.module.Memory().WriteUint64Le(ptr0+24, uint64(value9)) + result10 := api.EncodeF64(aF641) + i.module.Memory().WriteUint64Le(ptr0+32, result10) + var value11 uint32 + if aBool1 { + value11 = 1 + } else { + value11 = 0 + } + i.module.Memory().WriteByte(ptr0+40, uint8(value11)) + value12 := uint32(aChar1) + i.module.Memory().WriteUint32Le(ptr0+44, uint32(value12)) + var enum13 uint32 + switch aColor1 { + case Red: + enum13 = 0 + case Green: + enum13 = 1 + case Blue: + enum13 = 2 + default: + panic(errors.New("invalid enum type provided")) + } + i.module.Memory().WriteByte(ptr0+48, uint8(enum13)) + if aOpt1 == nil { + i.module.Memory().WriteByte(ptr0+49, uint8(0)) + } else { + variantPayload := *aOpt1 + i.module.Memory().WriteByte(ptr0+49, uint8(1)) + value14 := uint32(variantPayload) + i.module.Memory().WriteByte(ptr0+50, uint8(value14)) + } + memory16 := i.module.Memory() + realloc16 := i.module.ExportedFunction("cabi_realloc") + ptr16, len16, err16 := writeString(ctx, aStr1, memory16, realloc16) + // The return type doesn't contain an error so we panic if one is encountered + if err16 != nil { + panic(err16) + } + i.module.Memory().WriteUint32Le(ptr0+56, uint32(len16)) + i.module.Memory().WriteUint32Le(ptr0+52, uint32(ptr16)) + result17 := api.EncodeF32(aF321) + i.module.Memory().WriteUint32Le(ptr0+60, uint32(result17)) + raw18, err18 := i.module.ExportedFunction("call-host-echo").Call(ctx, uint64(ptr0)) + // The return type doesn't contain an error so we panic if one is encountered + if err18 != nil { + panic(err18) + } + + // The cleanup via `cabi_post_*` cleans up the memory in the guest. By + // deferring this, we ensure that no memory is corrupted before the function + // is done accessing it. + defer func() { + if postFn := i.module.ExportedFunction("cabi_post_call-host-echo"); postFn != nil { + if _, err := postFn.Call(ctx, raw18...); err != nil { + // If we get an error during cleanup, something really bad is + // going on, so we panic. Also, you can't return the error from + // the `defer` + panic(errors.New("failed to cleanup")) + } + } + }() + + results18 := raw18[0] + value19, ok19 := i.module.Memory().ReadByte(uint32(results18 + 0)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok19 { + panic(errors.New("failed to read byte from memory")) + } + result20 := uint8(value19) + value21, ok21 := i.module.Memory().ReadByte(uint32(results18 + 1)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok21 { + panic(errors.New("failed to read byte from memory")) + } + result22 := int8(value21) + value23, ok23 := i.module.Memory().ReadUint16Le(uint32(results18 + 2)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok23 { + panic(errors.New("failed to read i16 from memory")) + } + result24 := uint16(value23) + value25, ok25 := i.module.Memory().ReadUint16Le(uint32(results18 + 4)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok25 { + panic(errors.New("failed to read i16 from memory")) + } + result26 := int16(value25) + value27, ok27 := i.module.Memory().ReadUint32Le(uint32(results18 + 8)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok27 { + panic(errors.New("failed to read i32 from memory")) + } + result28 := uint32(value27) + value29, ok29 := i.module.Memory().ReadUint32Le(uint32(results18 + 12)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok29 { + panic(errors.New("failed to read i32 from memory")) + } + result30 := int32(value29) + value31, ok31 := i.module.Memory().ReadUint64Le(uint32(results18 + 16)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok31 { + panic(errors.New("failed to read i64 from memory")) + } + value32 := uint64(value31) + value33, ok33 := i.module.Memory().ReadUint64Le(uint32(results18 + 24)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok33 { + panic(errors.New("failed to read i64 from memory")) + } + result34 := int64(value33) + value35, ok35 := i.module.Memory().ReadUint64Le(uint32(results18 + 32)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok35 { + panic(errors.New("failed to read f64 from memory")) + } + result36 := api.DecodeF64(value35) + value37, ok37 := i.module.Memory().ReadByte(uint32(results18 + 40)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok37 { + panic(errors.New("failed to read byte from memory")) + } + value38 := value37 != 0 + value39, ok39 := i.module.Memory().ReadUint32Le(uint32(results18 + 44)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok39 { + panic(errors.New("failed to read i32 from memory")) + } + result40 := rune(value39) + if !utf8.ValidRune(result40) { + // TODO(#8): Return an error if the return type allows it + panic(errors.New("invalid char value encountered")) + } + value41, ok41 := i.module.Memory().ReadByte(uint32(results18 + 48)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok41 { + panic(errors.New("failed to read byte from memory")) + } + var enum42 Color + switch value41 { + case 0: + enum42 = Red + case 1: + enum42 = Green + case 2: + enum42 = Blue + default: + // The return type doesn't contain an error so we panic if one is encountered + panic(errors.New("invalid color discriminant")) + } + value43, ok43 := i.module.Memory().ReadByte(uint32(results18 + 49)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok43 { + panic(errors.New("failed to read byte from memory")) + } + var result46 *uint8 + if value43 != 0 { + value44, ok44 := i.module.Memory().ReadByte(uint32(results18 + 50)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok44 { + panic(errors.New("failed to read byte from memory")) + } + result45 := uint8(value44) + someValue46 := result45 + result46 = &someValue46 + } + ptr47, ok47 := i.module.Memory().ReadUint32Le(uint32(results18 + 52)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok47 { + panic(errors.New("failed to read pointer from memory")) + } + len48, ok48 := i.module.Memory().ReadUint32Le(uint32(results18 + 56)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok48 { + panic(errors.New("failed to read length from memory")) + } + buf49, ok49 := i.module.Memory().Read(ptr47, len48) + // The return type doesn't contain an error so we panic if one is encountered + if !ok49 { + panic(errors.New("failed to read bytes from memory")) + } + str49 := string(buf49) + value50, ok50 := i.module.Memory().ReadUint32Le(uint32(results18 + 60)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok50 { + panic(errors.New("failed to read f32 from memory")) + } + bits51 := uint64(value50) + result52 := api.DecodeF32(bits51) + value53 := Everything{ + AU8: result20, + AS8: result22, + AU16: result24, + AS16: result26, + AU32: result28, + AS32: result30, + AU64: value32, + AS64: result34, + AF64: result36, + ABool: value38, + AChar: result40, + AColor: enum42, + AOpt: result46, + AStr: str49, + AF32: result52, + } + return value53 +} + +func (i *MemoryInstance) RoundTripNarrow( + ctx context.Context, + x Narrow, +) Narrow { + arg0 := x + // Allocate the area holding the indirectly passed parameters + result0, err0 := i.module.ExportedFunction("cabi_realloc").Call(ctx, 0, 0, 4, 68) + // The return type doesn't contain an error so we panic if one is encountered + if err0 != nil { + panic(err0) + } + ptr0 := uint32(result0[0]) + n01 := arg0.N0 + n11 := arg0.N1 + n21 := arg0.N2 + n31 := arg0.N3 + n41 := arg0.N4 + n51 := arg0.N5 + n61 := arg0.N6 + n71 := arg0.N7 + n81 := arg0.N8 + n91 := arg0.N9 + n101 := arg0.N10 + n111 := arg0.N11 + n121 := arg0.N12 + n131 := arg0.N13 + s1 := arg0.S + f1 := arg0.F + result2 := uint32(n01) + i.module.Memory().WriteUint32Le(ptr0+0, uint32(result2)) + result3 := uint32(n11) + i.module.Memory().WriteUint32Le(ptr0+4, uint32(result3)) + result4 := uint32(n21) + i.module.Memory().WriteUint32Le(ptr0+8, uint32(result4)) + result5 := uint32(n31) + i.module.Memory().WriteUint32Le(ptr0+12, uint32(result5)) + result6 := uint32(n41) + i.module.Memory().WriteUint32Le(ptr0+16, uint32(result6)) + result7 := uint32(n51) + i.module.Memory().WriteUint32Le(ptr0+20, uint32(result7)) + result8 := uint32(n61) + i.module.Memory().WriteUint32Le(ptr0+24, uint32(result8)) + result9 := uint32(n71) + i.module.Memory().WriteUint32Le(ptr0+28, uint32(result9)) + result10 := uint32(n81) + i.module.Memory().WriteUint32Le(ptr0+32, uint32(result10)) + result11 := uint32(n91) + i.module.Memory().WriteUint32Le(ptr0+36, uint32(result11)) + result12 := uint32(n101) + i.module.Memory().WriteUint32Le(ptr0+40, uint32(result12)) + result13 := uint32(n111) + i.module.Memory().WriteUint32Le(ptr0+44, uint32(result13)) + result14 := uint32(n121) + i.module.Memory().WriteUint32Le(ptr0+48, uint32(result14)) + result15 := uint32(n131) + i.module.Memory().WriteUint32Le(ptr0+52, uint32(result15)) + memory16 := i.module.Memory() + realloc16 := i.module.ExportedFunction("cabi_realloc") + ptr16, len16, err16 := writeString(ctx, s1, memory16, realloc16) + // The return type doesn't contain an error so we panic if one is encountered + if err16 != nil { + panic(err16) + } + i.module.Memory().WriteUint32Le(ptr0+60, uint32(len16)) + i.module.Memory().WriteUint32Le(ptr0+56, uint32(ptr16)) + result17 := api.EncodeF32(f1) + i.module.Memory().WriteUint32Le(ptr0+64, uint32(result17)) + raw18, err18 := i.module.ExportedFunction("round-trip-narrow").Call(ctx, uint64(ptr0)) + // The return type doesn't contain an error so we panic if one is encountered + if err18 != nil { + panic(err18) + } + + // The cleanup via `cabi_post_*` cleans up the memory in the guest. By + // deferring this, we ensure that no memory is corrupted before the function + // is done accessing it. + defer func() { + if postFn := i.module.ExportedFunction("cabi_post_round-trip-narrow"); postFn != nil { + if _, err := postFn.Call(ctx, raw18...); err != nil { + // If we get an error during cleanup, something really bad is + // going on, so we panic. Also, you can't return the error from + // the `defer` + panic(errors.New("failed to cleanup")) + } + } + }() + + results18 := raw18[0] + value19, ok19 := i.module.Memory().ReadUint32Le(uint32(results18 + 0)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok19 { + panic(errors.New("failed to read i32 from memory")) + } + result20 := uint32(value19) + value21, ok21 := i.module.Memory().ReadUint32Le(uint32(results18 + 4)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok21 { + panic(errors.New("failed to read i32 from memory")) + } + result22 := uint32(value21) + value23, ok23 := i.module.Memory().ReadUint32Le(uint32(results18 + 8)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok23 { + panic(errors.New("failed to read i32 from memory")) + } + result24 := uint32(value23) + value25, ok25 := i.module.Memory().ReadUint32Le(uint32(results18 + 12)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok25 { + panic(errors.New("failed to read i32 from memory")) + } + result26 := uint32(value25) + value27, ok27 := i.module.Memory().ReadUint32Le(uint32(results18 + 16)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok27 { + panic(errors.New("failed to read i32 from memory")) + } + result28 := uint32(value27) + value29, ok29 := i.module.Memory().ReadUint32Le(uint32(results18 + 20)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok29 { + panic(errors.New("failed to read i32 from memory")) + } + result30 := uint32(value29) + value31, ok31 := i.module.Memory().ReadUint32Le(uint32(results18 + 24)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok31 { + panic(errors.New("failed to read i32 from memory")) + } + result32 := uint32(value31) + value33, ok33 := i.module.Memory().ReadUint32Le(uint32(results18 + 28)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok33 { + panic(errors.New("failed to read i32 from memory")) + } + result34 := uint32(value33) + value35, ok35 := i.module.Memory().ReadUint32Le(uint32(results18 + 32)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok35 { + panic(errors.New("failed to read i32 from memory")) + } + result36 := uint32(value35) + value37, ok37 := i.module.Memory().ReadUint32Le(uint32(results18 + 36)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok37 { + panic(errors.New("failed to read i32 from memory")) + } + result38 := uint32(value37) + value39, ok39 := i.module.Memory().ReadUint32Le(uint32(results18 + 40)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok39 { + panic(errors.New("failed to read i32 from memory")) + } + result40 := uint32(value39) + value41, ok41 := i.module.Memory().ReadUint32Le(uint32(results18 + 44)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok41 { + panic(errors.New("failed to read i32 from memory")) + } + result42 := uint32(value41) + value43, ok43 := i.module.Memory().ReadUint32Le(uint32(results18 + 48)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok43 { + panic(errors.New("failed to read i32 from memory")) + } + result44 := uint32(value43) + value45, ok45 := i.module.Memory().ReadUint32Le(uint32(results18 + 52)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok45 { + panic(errors.New("failed to read i32 from memory")) + } + result46 := uint32(value45) + ptr47, ok47 := i.module.Memory().ReadUint32Le(uint32(results18 + 56)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok47 { + panic(errors.New("failed to read pointer from memory")) + } + len48, ok48 := i.module.Memory().ReadUint32Le(uint32(results18 + 60)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok48 { + panic(errors.New("failed to read length from memory")) + } + buf49, ok49 := i.module.Memory().Read(ptr47, len48) + // The return type doesn't contain an error so we panic if one is encountered + if !ok49 { + panic(errors.New("failed to read bytes from memory")) + } + str49 := string(buf49) + value50, ok50 := i.module.Memory().ReadUint32Le(uint32(results18 + 64)) + // The return type doesn't contain an error so we panic if one is encountered + if !ok50 { + panic(errors.New("failed to read f32 from memory")) + } + bits51 := uint64(value50) + result52 := api.DecodeF32(bits51) + value53 := Narrow{ + N0: result20, + N1: result22, + N2: result24, + N3: result26, + N4: result28, + N5: result30, + N6: result32, + N7: result34, + N8: result36, + N9: result38, + N10: result40, + N11: result42, + N12: result44, + N13: result46, + S: str49, + F: result52, + } + return value53 +} + diff --git a/cmd/gravity/tests/cmd/memory.toml b/cmd/gravity/tests/cmd/memory.toml new file mode 100644 index 0000000..f716650 --- /dev/null +++ b/cmd/gravity/tests/cmd/memory.toml @@ -0,0 +1,2 @@ +bin.name = "gravity" +args = "--world memory ../../target/wasm32-unknown-unknown/release/example_memory.wasm" diff --git a/cmd/gravity/tests/cmd/records.stdout b/cmd/gravity/tests/cmd/records.stdout index 7b114ff..ccbb1a6 100644 --- a/cmd/gravity/tests/cmd/records.stdout +++ b/cmd/gravity/tests/cmd/records.stdout @@ -127,7 +127,7 @@ func (i *RecordsInstance) ModifyFoo( e := vec7[idx] base := uint32(ptr7 + uint64(idx) * uint64(4)) result6 := api.EncodeF32(e) - i.module.Memory().WriteUint64Le(base+0, result6) + i.module.Memory().WriteUint32Le(base+0, uint32(result6)) } vec9 := vf640 len9 := uint64(len(vec9)) @@ -164,102 +164,104 @@ func (i *RecordsInstance) ModifyFoo( }() results10 := raw10[0] - value11, ok11 := i.module.Memory().ReadUint64Le(uint32(results10 + 0)) + value11, ok11 := i.module.Memory().ReadUint32Le(uint32(results10 + 0)) // The return type doesn't contain an error so we panic if one is encountered if !ok11 { panic(errors.New("failed to read f32 from memory")) } - result12 := api.DecodeF32(value11) - value13, ok13 := i.module.Memory().ReadUint64Le(uint32(results10 + 8)) + bits12 := uint64(value11) + result13 := api.DecodeF32(bits12) + value14, ok14 := i.module.Memory().ReadUint64Le(uint32(results10 + 8)) // The return type doesn't contain an error so we panic if one is encountered - if !ok13 { + if !ok14 { panic(errors.New("failed to read f64 from memory")) } - result14 := api.DecodeF64(value13) - value15, ok15 := i.module.Memory().ReadUint32Le(uint32(results10 + 16)) + result15 := api.DecodeF64(value14) + value16, ok16 := i.module.Memory().ReadUint32Le(uint32(results10 + 16)) // The return type doesn't contain an error so we panic if one is encountered - if !ok15 { + if !ok16 { panic(errors.New("failed to read i32 from memory")) } - result16 := uint32(value15) - value17, ok17 := i.module.Memory().ReadUint64Le(uint32(results10 + 24)) + result17 := uint32(value16) + value18, ok18 := i.module.Memory().ReadUint64Le(uint32(results10 + 24)) // The return type doesn't contain an error so we panic if one is encountered - if !ok17 { + if !ok18 { panic(errors.New("failed to read i64 from memory")) } - value18 := uint64(value17) - ptr19, ok19 := i.module.Memory().ReadUint32Le(uint32(results10 + 32)) + value19 := uint64(value18) + ptr20, ok20 := i.module.Memory().ReadUint32Le(uint32(results10 + 32)) // The return type doesn't contain an error so we panic if one is encountered - if !ok19 { + if !ok20 { panic(errors.New("failed to read pointer from memory")) } - len20, ok20 := i.module.Memory().ReadUint32Le(uint32(results10 + 36)) + len21, ok21 := i.module.Memory().ReadUint32Le(uint32(results10 + 36)) // The return type doesn't contain an error so we panic if one is encountered - if !ok20 { + if !ok21 { panic(errors.New("failed to read length from memory")) } - buf21, ok21 := i.module.Memory().Read(ptr19, len20) + buf22, ok22 := i.module.Memory().Read(ptr20, len21) // The return type doesn't contain an error so we panic if one is encountered - if !ok21 { + if !ok22 { panic(errors.New("failed to read bytes from memory")) } - str21 := string(buf21) - ptr22, ok22 := i.module.Memory().ReadUint32Le(uint32(results10 + 40)) + str22 := string(buf22) + ptr23, ok23 := i.module.Memory().ReadUint32Le(uint32(results10 + 40)) // The return type doesn't contain an error so we panic if one is encountered - if !ok22 { + if !ok23 { panic(errors.New("failed to read pointer from memory")) } - len23, ok23 := i.module.Memory().ReadUint32Le(uint32(results10 + 44)) + len24, ok24 := i.module.Memory().ReadUint32Le(uint32(results10 + 44)) // The return type doesn't contain an error so we panic if one is encountered - if !ok23 { + if !ok24 { panic(errors.New("failed to read length from memory")) } - base26 := ptr22 - len26 := len23 - result26 := make([]float32, len26) - for idx26 := uint32(0); idx26 < len26; idx26++ { - base := base26 + idx26 * 4 - value24, ok24 := i.module.Memory().ReadUint64Le(uint32(base + 0)) + base28 := ptr23 + len28 := len24 + result28 := make([]float32, len28) + for idx28 := uint32(0); idx28 < len28; idx28++ { + base := base28 + idx28 * 4 + value25, ok25 := i.module.Memory().ReadUint32Le(uint32(base + 0)) // The return type doesn't contain an error so we panic if one is encountered - if !ok24 { + if !ok25 { panic(errors.New("failed to read f32 from memory")) } - result25 := api.DecodeF32(value24) - result26[idx26] = result25 + bits26 := uint64(value25) + result27 := api.DecodeF32(bits26) + result28[idx28] = result27 } - ptr27, ok27 := i.module.Memory().ReadUint32Le(uint32(results10 + 48)) + ptr29, ok29 := i.module.Memory().ReadUint32Le(uint32(results10 + 48)) // The return type doesn't contain an error so we panic if one is encountered - if !ok27 { + if !ok29 { panic(errors.New("failed to read pointer from memory")) } - len28, ok28 := i.module.Memory().ReadUint32Le(uint32(results10 + 52)) + len30, ok30 := i.module.Memory().ReadUint32Le(uint32(results10 + 52)) // The return type doesn't contain an error so we panic if one is encountered - if !ok28 { + if !ok30 { panic(errors.New("failed to read length from memory")) } - base31 := ptr27 - len31 := len28 - result31 := make([]float64, len31) - for idx31 := uint32(0); idx31 < len31; idx31++ { - base := base31 + idx31 * 8 - value29, ok29 := i.module.Memory().ReadUint64Le(uint32(base + 0)) + base33 := ptr29 + len33 := len30 + result33 := make([]float64, len33) + for idx33 := uint32(0); idx33 < len33; idx33++ { + base := base33 + idx33 * 8 + value31, ok31 := i.module.Memory().ReadUint64Le(uint32(base + 0)) // The return type doesn't contain an error so we panic if one is encountered - if !ok29 { + if !ok31 { panic(errors.New("failed to read f64 from memory")) } - result30 := api.DecodeF64(value29) - result31[idx31] = result30 - } - value32 := Foo{ - Float32: result12, - Float64: result14, - Uint32: result16, - Uint64: value18, - S: str21, - Vf32: result26, - Vf64: result31, - } - return value32 + result32 := api.DecodeF64(value31) + result33[idx33] = result32 + } + value34 := Foo{ + Float32: result13, + Float64: result15, + Uint32: result17, + Uint64: value19, + S: str22, + Vf32: result28, + Vf64: result33, + } + return value34 } func (i *RecordsInstance) ModifyFooFallible( @@ -297,7 +299,7 @@ func (i *RecordsInstance) ModifyFooFallible( e := vec7[idx] base := uint32(ptr7 + uint64(idx) * uint64(4)) result6 := api.EncodeF32(e) - i.module.Memory().WriteUint64Le(base+0, result6) + i.module.Memory().WriteUint32Le(base+0, uint32(result6)) } vec9 := vf640 len9 := uint64(len(vec9)) @@ -339,127 +341,129 @@ func (i *RecordsInstance) ModifyFooFallible( var default11 Foo return default11, errors.New("failed to read byte from memory") } - var value37 Foo - var err37 error + var value39 Foo + var err39 error switch value11 { case 0: - value12, ok12 := i.module.Memory().ReadUint64Le(uint32(results10 + 8)) + value12, ok12 := i.module.Memory().ReadUint32Le(uint32(results10 + 8)) if !ok12 { var default12 Foo return default12, errors.New("failed to read f32 from memory") } - result13 := api.DecodeF32(value12) - value14, ok14 := i.module.Memory().ReadUint64Le(uint32(results10 + 16)) - if !ok14 { - var default14 Foo - return default14, errors.New("failed to read f64 from memory") - } - result15 := api.DecodeF64(value14) - value16, ok16 := i.module.Memory().ReadUint32Le(uint32(results10 + 24)) - if !ok16 { - var default16 Foo - return default16, errors.New("failed to read i32 from memory") + bits13 := uint64(value12) + result14 := api.DecodeF32(bits13) + value15, ok15 := i.module.Memory().ReadUint64Le(uint32(results10 + 16)) + if !ok15 { + var default15 Foo + return default15, errors.New("failed to read f64 from memory") } - result17 := uint32(value16) - value18, ok18 := i.module.Memory().ReadUint64Le(uint32(results10 + 32)) - if !ok18 { - var default18 Foo - return default18, errors.New("failed to read i64 from memory") + result16 := api.DecodeF64(value15) + value17, ok17 := i.module.Memory().ReadUint32Le(uint32(results10 + 24)) + if !ok17 { + var default17 Foo + return default17, errors.New("failed to read i32 from memory") } - value19 := uint64(value18) - ptr20, ok20 := i.module.Memory().ReadUint32Le(uint32(results10 + 40)) - if !ok20 { - var default20 Foo - return default20, errors.New("failed to read pointer from memory") + result18 := uint32(value17) + value19, ok19 := i.module.Memory().ReadUint64Le(uint32(results10 + 32)) + if !ok19 { + var default19 Foo + return default19, errors.New("failed to read i64 from memory") } - len21, ok21 := i.module.Memory().ReadUint32Le(uint32(results10 + 44)) + value20 := uint64(value19) + ptr21, ok21 := i.module.Memory().ReadUint32Le(uint32(results10 + 40)) if !ok21 { var default21 Foo - return default21, errors.New("failed to read length from memory") + return default21, errors.New("failed to read pointer from memory") } - buf22, ok22 := i.module.Memory().Read(ptr20, len21) + len22, ok22 := i.module.Memory().ReadUint32Le(uint32(results10 + 44)) if !ok22 { var default22 Foo - return default22, errors.New("failed to read bytes from memory") + return default22, errors.New("failed to read length from memory") } - str22 := string(buf22) - ptr23, ok23 := i.module.Memory().ReadUint32Le(uint32(results10 + 48)) + buf23, ok23 := i.module.Memory().Read(ptr21, len22) if !ok23 { var default23 Foo - return default23, errors.New("failed to read pointer from memory") + return default23, errors.New("failed to read bytes from memory") } - len24, ok24 := i.module.Memory().ReadUint32Le(uint32(results10 + 52)) + str23 := string(buf23) + ptr24, ok24 := i.module.Memory().ReadUint32Le(uint32(results10 + 48)) if !ok24 { var default24 Foo - return default24, errors.New("failed to read length from memory") + return default24, errors.New("failed to read pointer from memory") } - base27 := ptr23 - len27 := len24 - result27 := make([]float32, len27) - for idx27 := uint32(0); idx27 < len27; idx27++ { - base := base27 + idx27 * 4 - value25, ok25 := i.module.Memory().ReadUint64Le(uint32(base + 0)) - if !ok25 { - var default25 Foo - return default25, errors.New("failed to read f32 from memory") + len25, ok25 := i.module.Memory().ReadUint32Le(uint32(results10 + 52)) + if !ok25 { + var default25 Foo + return default25, errors.New("failed to read length from memory") + } + base29 := ptr24 + len29 := len25 + result29 := make([]float32, len29) + for idx29 := uint32(0); idx29 < len29; idx29++ { + base := base29 + idx29 * 4 + value26, ok26 := i.module.Memory().ReadUint32Le(uint32(base + 0)) + if !ok26 { + var default26 Foo + return default26, errors.New("failed to read f32 from memory") } - result26 := api.DecodeF32(value25) - result27[idx27] = result26 + bits27 := uint64(value26) + result28 := api.DecodeF32(bits27) + result29[idx29] = result28 } - ptr28, ok28 := i.module.Memory().ReadUint32Le(uint32(results10 + 56)) - if !ok28 { - var default28 Foo - return default28, errors.New("failed to read pointer from memory") + ptr30, ok30 := i.module.Memory().ReadUint32Le(uint32(results10 + 56)) + if !ok30 { + var default30 Foo + return default30, errors.New("failed to read pointer from memory") } - len29, ok29 := i.module.Memory().ReadUint32Le(uint32(results10 + 60)) - if !ok29 { - var default29 Foo - return default29, errors.New("failed to read length from memory") + len31, ok31 := i.module.Memory().ReadUint32Le(uint32(results10 + 60)) + if !ok31 { + var default31 Foo + return default31, errors.New("failed to read length from memory") } - base32 := ptr28 - len32 := len29 - result32 := make([]float64, len32) - for idx32 := uint32(0); idx32 < len32; idx32++ { - base := base32 + idx32 * 8 - value30, ok30 := i.module.Memory().ReadUint64Le(uint32(base + 0)) - if !ok30 { - var default30 Foo - return default30, errors.New("failed to read f64 from memory") + base34 := ptr30 + len34 := len31 + result34 := make([]float64, len34) + for idx34 := uint32(0); idx34 < len34; idx34++ { + base := base34 + idx34 * 8 + value32, ok32 := i.module.Memory().ReadUint64Le(uint32(base + 0)) + if !ok32 { + var default32 Foo + return default32, errors.New("failed to read f64 from memory") } - result31 := api.DecodeF64(value30) - result32[idx32] = result31 + result33 := api.DecodeF64(value32) + result34[idx34] = result33 } - value33 := Foo{ - Float32: result13, - Float64: result15, - Uint32: result17, - Uint64: value19, - S: str22, - Vf32: result27, - Vf64: result32, + value35 := Foo{ + Float32: result14, + Float64: result16, + Uint32: result18, + Uint64: value20, + S: str23, + Vf32: result29, + Vf64: result34, } - value37 = value33 + value39 = value35 case 1: - ptr34, ok34 := i.module.Memory().ReadUint32Le(uint32(results10 + 8)) - if !ok34 { - var default34 Foo - return default34, errors.New("failed to read pointer from memory") - } - len35, ok35 := i.module.Memory().ReadUint32Le(uint32(results10 + 12)) - if !ok35 { - var default35 Foo - return default35, errors.New("failed to read length from memory") - } - buf36, ok36 := i.module.Memory().Read(ptr34, len35) + ptr36, ok36 := i.module.Memory().ReadUint32Le(uint32(results10 + 8)) if !ok36 { var default36 Foo - return default36, errors.New("failed to read bytes from memory") + return default36, errors.New("failed to read pointer from memory") + } + len37, ok37 := i.module.Memory().ReadUint32Le(uint32(results10 + 12)) + if !ok37 { + var default37 Foo + return default37, errors.New("failed to read length from memory") + } + buf38, ok38 := i.module.Memory().Read(ptr36, len37) + if !ok38 { + var default38 Foo + return default38, errors.New("failed to read bytes from memory") } - str36 := string(buf36) - err37 = errors.New(str36) + str38 := string(buf38) + err39 = errors.New(str38) default: - err37 = errors.New("invalid variant discriminant for expected") + err39 = errors.New("invalid variant discriminant for expected") } - return value37, err37 + return value39, err39 } diff --git a/cmd/gravity/tests/cmd/regressions.stdout b/cmd/gravity/tests/cmd/regressions.stdout index de00d9f..4893f1b 100644 --- a/cmd/gravity/tests/cmd/regressions.stdout +++ b/cmd/gravity/tests/cmd/regressions.stdout @@ -249,10 +249,10 @@ func NewRegressionsFactory( str0 := string(buf0) value1 := ipSource.Lookup(ctx, str0) if value1 == nil { - mod.Memory().WriteByte(arg2+0, 0) + mod.Memory().WriteByte(arg2+0, uint8(0)) } else { variantPayload := *value1 - mod.Memory().WriteByte(arg2+0, 1) + mod.Memory().WriteByte(arg2+0, uint8(1)) memory2 := mod.Memory() realloc2 := mod.ExportedFunction("cabi_realloc") ptr2, len2, err2 := writeString(ctx, variantPayload, memory2, realloc2) diff --git a/cmd/gravity/tests/cmd/variants.stdout b/cmd/gravity/tests/cmd/variants.stdout index 3141048..92b6588 100644 --- a/cmd/gravity/tests/cmd/variants.stdout +++ b/cmd/gravity/tests/cmd/variants.stdout @@ -369,19 +369,19 @@ func (i *VariantsInstance) Choose( switch case2 := e.(type) { case EntityEmail: _ = case2 - i.module.Memory().WriteByte(base+0, 0) + i.module.Memory().WriteByte(base+0, uint8(0)) case EntityPhoneNumber: _ = case2 - i.module.Memory().WriteByte(base+0, 1) + i.module.Memory().WriteByte(base+0, uint8(1)) case EntityIpAddress: _ = case2 - i.module.Memory().WriteByte(base+0, 2) + i.module.Memory().WriteByte(base+0, uint8(2)) case EntityCreditCardNumber: _ = case2 - i.module.Memory().WriteByte(base+0, 3) + i.module.Memory().WriteByte(base+0, uint8(3)) case EntityCustom: variantPayload := case2.Value - i.module.Memory().WriteByte(base+0, 4) + i.module.Memory().WriteByte(base+0, uint8(4)) memory1 := i.module.Memory() realloc1 := i.module.ExportedFunction("cabi_realloc") ptr1, len1, err1 := writeString(ctx, variantPayload, memory1, realloc1) @@ -428,19 +428,19 @@ func (i *VariantsInstance) Choose( switch case8 := e.(type) { case EntityEmail: _ = case8 - i.module.Memory().WriteByte(base+0, 0) + i.module.Memory().WriteByte(base+0, uint8(0)) case EntityPhoneNumber: _ = case8 - i.module.Memory().WriteByte(base+0, 1) + i.module.Memory().WriteByte(base+0, uint8(1)) case EntityIpAddress: _ = case8 - i.module.Memory().WriteByte(base+0, 2) + i.module.Memory().WriteByte(base+0, uint8(2)) case EntityCreditCardNumber: _ = case8 - i.module.Memory().WriteByte(base+0, 3) + i.module.Memory().WriteByte(base+0, uint8(3)) case EntityCustom: variantPayload := case8.Value - i.module.Memory().WriteByte(base+0, 4) + i.module.Memory().WriteByte(base+0, uint8(4)) memory7 := i.module.Memory() realloc7 := i.module.ExportedFunction("cabi_realloc") ptr7, len7, err7 := writeString(ctx, variantPayload, memory7, realloc7) @@ -529,19 +529,19 @@ func (i *VariantsInstance) ChooseMany( switch case1 := e.(type) { case EntityEmail: _ = case1 - i.module.Memory().WriteByte(base+0, 0) + i.module.Memory().WriteByte(base+0, uint8(0)) case EntityPhoneNumber: _ = case1 - i.module.Memory().WriteByte(base+0, 1) + i.module.Memory().WriteByte(base+0, uint8(1)) case EntityIpAddress: _ = case1 - i.module.Memory().WriteByte(base+0, 2) + i.module.Memory().WriteByte(base+0, uint8(2)) case EntityCreditCardNumber: _ = case1 - i.module.Memory().WriteByte(base+0, 3) + i.module.Memory().WriteByte(base+0, uint8(3)) case EntityCustom: variantPayload := case1.Value - i.module.Memory().WriteByte(base+0, 4) + i.module.Memory().WriteByte(base+0, uint8(4)) memory0 := i.module.Memory() realloc0 := i.module.ExportedFunction("cabi_realloc") ptr0, len0, err0 := writeString(ctx, variantPayload, memory0, realloc0) @@ -575,19 +575,19 @@ func (i *VariantsInstance) ChooseMany( switch case4 := e.(type) { case EntityEmail: _ = case4 - i.module.Memory().WriteByte(base+0, 0) + i.module.Memory().WriteByte(base+0, uint8(0)) case EntityPhoneNumber: _ = case4 - i.module.Memory().WriteByte(base+0, 1) + i.module.Memory().WriteByte(base+0, uint8(1)) case EntityIpAddress: _ = case4 - i.module.Memory().WriteByte(base+0, 2) + i.module.Memory().WriteByte(base+0, uint8(2)) case EntityCreditCardNumber: _ = case4 - i.module.Memory().WriteByte(base+0, 3) + i.module.Memory().WriteByte(base+0, uint8(3)) case EntityCustom: variantPayload := case4.Value - i.module.Memory().WriteByte(base+0, 4) + i.module.Memory().WriteByte(base+0, uint8(4)) memory3 := i.module.Memory() realloc3 := i.module.ExportedFunction("cabi_realloc") ptr3, len3, err3 := writeString(ctx, variantPayload, memory3, realloc3) diff --git a/examples/generate.go b/examples/generate.go index 909c31a..fb5761c 100644 --- a/examples/generate.go +++ b/examples/generate.go @@ -2,6 +2,7 @@ package examples //go:generate cargo build -p example-basic --target wasm32-unknown-unknown --release //go:generate cargo build -p example-records --target wasm32-unknown-unknown --release +//go:generate cargo build -p example-memory --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-instructions --target wasm32-unknown-unknown --release //go:generate cargo build -p example-regressions --target wasm32-unknown-unknown --release @@ -9,6 +10,7 @@ package examples //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 records --output ./records/records.go ../target/wasm32-unknown-unknown/release/example_records.wasm +//go:generate cargo run --bin gravity -- --world memory --output ./memory/memory.go ../target/wasm32-unknown-unknown/release/example_memory.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 instructions --output ./instructions/bindings.go ../target/wasm32-unknown-unknown/release/example_instructions.wasm //go:generate cargo run --bin gravity -- --world regressions --output ./regressions/regressions.go ../target/wasm32-unknown-unknown/release/example_regressions.wasm diff --git a/examples/memory/Cargo.toml b/examples/memory/Cargo.toml new file mode 100644 index 0000000..7956dd9 --- /dev/null +++ b/examples/memory/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "example-memory" +version = "0.0.2" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "=0.62.0" +wit-component = "=0.259.0" diff --git a/examples/memory/memory_test.go b/examples/memory/memory_test.go new file mode 100644 index 0000000..feeae2c --- /dev/null +++ b/examples/memory/memory_test.go @@ -0,0 +1,108 @@ +package memory + +import ( + "context" + "math" + "reflect" + "testing" +) + +// Host records the record it receives and replies with a different one, so the +// test can tell the import's parameter (loaded from guest memory) from its +// result (stored into guest memory). +type Host struct { + got Everything + reply Everything +} + +func (h *Host) Echo(_ context.Context, x Everything) Everything { + h.got = x + return h.reply +} + +func ptr[T any](v T) *T { return &v } + +// Each value covers every field type at a boundary: minimums, maximums, and a +// mix of signs, non-ASCII text and a present option. +var cases = map[string]Everything{ + "min": { + AU8: 0, AS8: math.MinInt8, AU16: 0, AS16: math.MinInt16, + AU32: 0, AS32: math.MinInt32, AU64: 0, AS64: math.MinInt64, + AF64: -math.MaxFloat64, ABool: false, AChar: 0, AColor: Red, + AOpt: nil, AStr: "", AF32: -math.MaxFloat32, + }, + "max": { + AU8: math.MaxUint8, AS8: math.MaxInt8, AU16: math.MaxUint16, AS16: math.MaxInt16, + AU32: math.MaxUint32, AS32: math.MaxInt32, AU64: math.MaxUint64, AS64: math.MaxInt64, + AF64: math.MaxFloat64, ABool: true, AChar: 0x10FFFF, AColor: Blue, + AOpt: ptr[uint8](math.MaxUint8), AStr: "hello, world", AF32: math.MaxFloat32, + }, + "mixed": { + AU8: 200, AS8: -5, AU16: 65000, AS16: -30000, + AU32: 4_000_000_000, AS32: -42, AU64: 1 << 40, AS64: -(1 << 40), + AF64: -2.25, ABool: true, AChar: 'λ', AColor: Green, + AOpt: ptr[uint8](0), AStr: "λ😀", AF32: 1.5, + }, +} + +func newInstance(t *testing.T, h *Host) *MemoryInstance { + t.Helper() + fac, err := NewMemoryFactory(t.Context(), h) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { fac.Close(context.Background()) }) + + ins, err := fac.Instantiate(t.Context()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { ins.Close(context.Background()) }) + return ins +} + +func TestRoundTrip(t *testing.T) { + ins := newInstance(t, &Host{}) + for name, want := range cases { + t.Run(name, func(t *testing.T) { + if got := ins.RoundTrip(t.Context(), want); !reflect.DeepEqual(got, want) { + t.Errorf("wanted: %+v, but got: %+v", want, got) + } + }) + } +} + +func TestCallHostEcho(t *testing.T) { + for name, sent := range cases { + for replyName, reply := range cases { + t.Run(name+"/"+replyName, func(t *testing.T) { + h := &Host{reply: reply} + ins := newInstance(t, h) + got := ins.CallHostEcho(t.Context(), sent) + if !reflect.DeepEqual(h.got, sent) { + t.Errorf("host received: %+v, but wanted: %+v", h.got, sent) + } + if !reflect.DeepEqual(got, reply) { + t.Errorf("wanted reply: %+v, but got: %+v", reply, got) + } + }) + } + } +} + +// TestRoundTripNarrow calls the export repeatedly: a store past the end of the +// 68-byte parameter area damages the string buffer allocated after it, and the +// guest traps when it frees that buffer. +func TestRoundTripNarrow(t *testing.T) { + ins := newInstance(t, &Host{}) + for i := range 100 { + want := Narrow{ + N0: uint32(i), N1: 1, N2: 2, N3: 3, N4: 4, N5: 5, N6: 6, N7: 7, + N8: 8, N9: 9, N10: 10, N11: 11, N12: 12, N13: math.MaxUint32, + S: "a string allocated after the parameter area", F: -1.5, + } + if got := ins.RoundTripNarrow(t.Context(), want); got != want { + t.Fatalf("call %d: wanted: %+v, but got: %+v", i, want, got) + } + } +} diff --git a/examples/memory/src/lib.rs b/examples/memory/src/lib.rs new file mode 100644 index 0000000..33148a2 --- /dev/null +++ b/examples/memory/src/lib.rs @@ -0,0 +1,23 @@ +use arcjet::memory::host; + +wit_bindgen::generate!({ + world: "memory", +}); + +struct MemoryWorld; + +export!(MemoryWorld); + +impl Guest for MemoryWorld { + fn round_trip(x: Everything) -> Everything { + x + } + + fn call_host_echo(x: Everything) -> Everything { + host::echo(&x) + } + + fn round_trip_narrow(x: Narrow) -> Narrow { + x + } +} diff --git a/examples/memory/wit/memory.wit b/examples/memory/wit/memory.wit new file mode 100644 index 0000000..4598bdc --- /dev/null +++ b/examples/memory/wit/memory.wit @@ -0,0 +1,73 @@ +package arcjet:memory; + +interface host { + enum color { + red, + green, + blue, + } + + // 17 flattened values, one more than the canonical ABI passes directly, so + // every call below moves this record through guest memory. The f32 sits + // after the string so a store wider than 4 bytes overwrites the string's + // allocation. + record everything { + a-u8: u8, + a-s8: s8, + a-u16: u16, + a-s16: s16, + a-u32: u32, + a-s32: s32, + a-u64: u64, + a-s64: s64, + a-f64: f64, + a-bool: bool, + a-char: char, + a-color: color, + a-opt: option, + a-str: string, + a-f32: f32, + } + + // 4-byte aligned and 68 bytes long, so its indirect parameter area does + // not end on an 8-byte boundary. The string's buffer is allocated directly + // after the area, so writing more than 4 bytes for the trailing f32 + // corrupts the allocator's header for that buffer. + record narrow { + n0: u32, + n1: u32, + n2: u32, + n3: u32, + n4: u32, + n5: u32, + n6: u32, + n7: u32, + n8: u32, + n9: u32, + n10: u32, + n11: u32, + n12: u32, + n13: u32, + s: string, + f: f32, + } + + echo: func(x: everything) -> everything; +} + +world memory { + import host; + use host.{everything, narrow}; + + // The guest returns its argument: the host stores the record into the + // indirect parameter area and loads the result. + export round-trip: func(x: everything) -> everything; + + // The guest passes its argument to the host's echo and returns the reply: + // the host loads the import's parameter and stores its result. + export call-host-echo: func(x: everything) -> everything; + + // The guest returns its argument, which the host passed in an area whose + // size is not a multiple of 8. + export round-trip-narrow: func(x: narrow) -> narrow; +} From 0ccf22b8cda811879d6b1f5a1771c6712ea56e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B6Rei?= <212411920+arcjet-rei@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:21:35 -0700 Subject: [PATCH 2/2] test: round-trip IEEE special floats through memory Send +Inf, -Inf, NaN and -0 as the f32 and f64 fields through an export's result, a host import's parameter and a host import's result. The comparison treats any NaN as equal to any NaN, since reflect.DeepEqual cannot, and checks the sign so -0 is not mistaken for +0. Co-Authored-By: Claude Opus 5.5 --- examples/memory/memory_test.go | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/examples/memory/memory_test.go b/examples/memory/memory_test.go index feeae2c..1ff160f 100644 --- a/examples/memory/memory_test.go +++ b/examples/memory/memory_test.go @@ -106,3 +106,45 @@ func TestRoundTripNarrow(t *testing.T) { } } } + +// sameFloat reports whether got and want are the same IEEE value: both NaN, +// or equal with the same sign, so -0 and +0 are told apart. +func sameFloat(got, want float64) bool { + if math.IsNaN(want) { + return math.IsNaN(got) + } + return got == want && math.Signbit(got) == math.Signbit(want) +} + +// TestSpecialFloats sends the IEEE special values through every memory path: +// reflect.DeepEqual cannot compare NaN, which is never equal to itself. +func TestSpecialFloats(t *testing.T) { + specials := map[string]float64{ + "+Inf": math.Inf(1), + "-Inf": math.Inf(-1), + "NaN": math.NaN(), + "-0": math.Copysign(0, -1), + } + check := func(t *testing.T, where string, got Everything, want float64) { + t.Helper() + if !sameFloat(got.AF64, want) { + t.Errorf("%s: f64 wanted: %v, but got: %v", where, want, got.AF64) + } + if !sameFloat(float64(got.AF32), want) { + t.Errorf("%s: f32 wanted: %v, but got: %v", where, want, got.AF32) + } + } + for name, v := range specials { + t.Run(name, func(t *testing.T) { + x := cases["mixed"] + x.AF64, x.AF32 = v, float32(v) + h := &Host{reply: x} + ins := newInstance(t, h) + + check(t, "export result", ins.RoundTrip(t.Context(), x), v) + reply := ins.CallHostEcho(t.Context(), x) + check(t, "host parameter", h.got, v) + check(t, "host result", reply, v) + }) + } +}