diff --git a/examples/allocation/tinygo/greet.go b/examples/allocation/tinygo/greet.go index 14d4d882d54..482ddc4028d 100644 --- a/examples/allocation/tinygo/greet.go +++ b/examples/allocation/tinygo/greet.go @@ -55,6 +55,7 @@ func main() { // These are undocumented, but exported. See tinygo-org/tinygo#2788 malloc := mod.ExportedFunction("malloc") free := mod.ExportedFunction("free") + deallocate := mod.ExportedFunction("deallocate") // Let's use the argument to this main function in Wasm. name := os.Args[1] @@ -96,7 +97,7 @@ func main() { // We don't need the memory after deserialization: make sure it is freed. if greetingPtr != 0 { - defer free.Call(ctx, uint64(greetingPtr)) + defer deallocate.Call(ctx, uint64(greetingPtr)) } // The pointer is a linear memory offset, which is where we write the name. diff --git a/examples/allocation/tinygo/testdata/greet.go b/examples/allocation/tinygo/testdata/greet.go index cfc460a027e..0ec27bb1a1d 100644 --- a/examples/allocation/tinygo/testdata/greet.go +++ b/examples/allocation/tinygo/testdata/greet.go @@ -72,18 +72,25 @@ func ptrToString(ptr uint32, size uint32) string { })) } +var alivePointers = map[uintptr]interface{}{} + // stringToPtr returns a pointer and size pair for the given string in a way // compatible with WebAssembly numeric types. The pointer is not automatically // managed by tinygo but must be freed by the host. func stringToPtr(s string) (uint32, uint32) { - if len(s) == 0 { - return 0, 0 - } + buf := []byte(s) + ptr := &buf[0] + unsafePtr := uintptr(unsafe.Pointer(ptr)) - size := C.ulong(len(s)) - ptr := unsafe.Pointer(C.malloc(size)) + alivePointers[unsafePtr] = buf - copy(unsafe.Slice((*byte)(ptr), size), []byte(s)) + return uint32(unsafePtr), uint32(len(buf)) +} - return uint32(uintptr(ptr)), uint32(len(s)) +// deallocate frees a uintptr returned by keepaliveBuf or allocate, allowing it +// to be garbage collected. +// +//export deallocate +func _deallocate(ptr uint32) { + delete(alivePointers, uintptr(ptr)) } diff --git a/examples/allocation/tinygo/testdata/greet.wasm b/examples/allocation/tinygo/testdata/greet.wasm index bd91540c7de..ce7c8b4f04b 100755 Binary files a/examples/allocation/tinygo/testdata/greet.wasm and b/examples/allocation/tinygo/testdata/greet.wasm differ diff --git a/internal/integration_test/vs/bench_allocation.go b/internal/integration_test/vs/bench_allocation.go index 921343f0a43..e4d44acd745 100644 --- a/internal/integration_test/vs/bench_allocation.go +++ b/internal/integration_test/vs/bench_allocation.go @@ -37,7 +37,7 @@ func init() { func allocationCall(m Module, _ int) error { nameSize := uint32(len(allocationParam)) - // Instead of an arbitrary memory offset, use Rust's allocator. Notice + // Instead of an arbitrary memory offset, use guest's allocator. Notice // there is nothing string-specific in this allocation function. The same // function could be used to pass binary serialized data to Wasm. namePtr, err := m.CallI32_I32(testCtx, "malloc", nameSize) @@ -56,15 +56,15 @@ func allocationCall(m Module, _ int) error { return fnErr } - // This pointer was allocated by Rust, but owned by Go, So, we have to + // This pointer was allocated by guest, but owned by Go, So, we have to // deallocate it when finished if err := m.CallI32_V(testCtx, "free", namePtr); err != nil { return err } - // This pointer was allocated with malloc. So, we have to deallocate it - // when finished - if err := m.CallI32_V(testCtx, "free", uint32(ptrSize>>32)); err != nil { + // deallocate removes a uintptr from a map which stores a reference to the + // buffer ptrSize points to, allowing it to be garbage collected. + if err := m.CallI32_V(testCtx, "deallocate", uint32(ptrSize>>32)); err != nil { return err }