examples(allocation): free memory after unmarshalling a result from the guest (#1390)
Signed-off-by: Luca Burgazzoli <lburgazzoli@gmail.com>
This commit is contained in:
@@ -12,6 +12,6 @@ go >> Hello, wazero!
|
|||||||
Under the covers, [greet.go](testdata/greet.go) does a few things of interest:
|
Under the covers, [greet.go](testdata/greet.go) does a few things of interest:
|
||||||
* Uses `unsafe.Pointer` to change a Go pointer to a numeric type.
|
* Uses `unsafe.Pointer` to change a Go pointer to a numeric type.
|
||||||
* Uses `reflect.StringHeader` to build back a string from a pointer, len pair.
|
* Uses `reflect.StringHeader` to build back a string from a pointer, len pair.
|
||||||
* Relies on TinyGo not eagerly freeing pointers returned.
|
* Relies on CGO to allocate memory used to pass data from TinyGo to host.
|
||||||
|
|
||||||
See https://wazero.io/languages/tinygo/ for more tips.
|
See https://wazero.io/languages/tinygo/ for more tips.
|
||||||
|
|||||||
@@ -90,9 +90,21 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
// Note: This pointer is still owned by TinyGo, so don't try to free it!
|
|
||||||
greetingPtr := uint32(ptrSize[0] >> 32)
|
greetingPtr := uint32(ptrSize[0] >> 32)
|
||||||
greetingSize := uint32(ptrSize[0])
|
greetingSize := uint32(ptrSize[0])
|
||||||
|
|
||||||
|
// This pointer is managed by TinyGo, but TinyGo is unaware of external usage.
|
||||||
|
// So, we have to free it when finished
|
||||||
|
if greetingPtr != 0 {
|
||||||
|
defer func() {
|
||||||
|
_, err := free.Call(ctx, uint64(greetingPtr))
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// The pointer is a linear memory offset, which is where we write the name.
|
// The pointer is a linear memory offset, which is where we write the name.
|
||||||
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
|
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
|
||||||
log.Panicf("Memory.Read(%d, %d) out of range of memory size %d",
|
log.Panicf("Memory.Read(%d, %d) out of range of memory size %d",
|
||||||
|
|||||||
17
examples/allocation/tinygo/testdata/greet.go
vendored
17
examples/allocation/tinygo/testdata/greet.go
vendored
@@ -1,5 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
// #include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -53,7 +56,7 @@ func _greet(ptr, size uint32) {
|
|||||||
func _greeting(ptr, size uint32) (ptrSize uint64) {
|
func _greeting(ptr, size uint32) (ptrSize uint64) {
|
||||||
name := ptrToString(ptr, size)
|
name := ptrToString(ptr, size)
|
||||||
g := greeting(name)
|
g := greeting(name)
|
||||||
ptr, size = stringToPtr(g)
|
ptr, size = stringToLeakedPtr(g)
|
||||||
return (uint64(ptr) << uint64(32)) | uint64(size)
|
return (uint64(ptr) << uint64(32)) | uint64(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,3 +80,15 @@ func stringToPtr(s string) (uint32, uint32) {
|
|||||||
unsafePtr := uintptr(unsafe.Pointer(ptr))
|
unsafePtr := uintptr(unsafe.Pointer(ptr))
|
||||||
return uint32(unsafePtr), uint32(len(buf))
|
return uint32(unsafePtr), uint32(len(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringToLeakedPtr 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 hence it must be freed by the host.
|
||||||
|
func stringToLeakedPtr(s string) (uint32, uint32) {
|
||||||
|
size := C.ulong(len(s))
|
||||||
|
ptr := unsafe.Pointer(C.malloc(size))
|
||||||
|
|
||||||
|
copy(unsafe.Slice((*byte)(ptr), size), []byte(s))
|
||||||
|
|
||||||
|
return uint32(uintptr(ptr)), uint32(len(s))
|
||||||
|
}
|
||||||
|
|||||||
BIN
examples/allocation/tinygo/testdata/greet.wasm
vendored
BIN
examples/allocation/tinygo/testdata/greet.wasm
vendored
Binary file not shown.
@@ -37,7 +37,7 @@ func init() {
|
|||||||
|
|
||||||
func allocationCall(m Module, _ int) error {
|
func allocationCall(m Module, _ int) error {
|
||||||
nameSize := uint32(len(allocationParam))
|
nameSize := uint32(len(allocationParam))
|
||||||
// Instead of an arbitrary memory offset, use Rust's allocator. Notice
|
// Instead of an arbitrary memory offset, use TinyGo's allocator. Notice
|
||||||
// there is nothing string-specific in this allocation function. The same
|
// there is nothing string-specific in this allocation function. The same
|
||||||
// function could be used to pass binary serialized data to Wasm.
|
// function could be used to pass binary serialized data to Wasm.
|
||||||
namePtr, err := m.CallI32_I32(testCtx, "malloc", nameSize)
|
namePtr, err := m.CallI32_I32(testCtx, "malloc", nameSize)
|
||||||
@@ -50,14 +50,19 @@ func allocationCall(m Module, _ int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we can call "greet", which reads the string we wrote to memory!
|
// Now, we can call "greeting", which reads the string we wrote to memory!
|
||||||
if err = m.CallI32I32_V(testCtx, "greet", namePtr, nameSize); err != nil {
|
fnErr := m.CallI32I32_V(testCtx, "greet", namePtr, nameSize)
|
||||||
|
if fnErr != nil {
|
||||||
|
return fnErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// This pointer was allocated by TinyGo, but owned by Go, So, we have to
|
||||||
|
// deallocate it when finished
|
||||||
|
if err := m.CallI32_V(testCtx, "free", namePtr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This pointer was allocated by Rust, but owned by Go, So, we have to
|
return nil
|
||||||
// deallocate it when finished
|
|
||||||
return m.CallI32_V(testCtx, "free", namePtr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunTestAllocation(t *testing.T, runtime func() Runtime) {
|
func RunTestAllocation(t *testing.T, runtime func() Runtime) {
|
||||||
|
|||||||
@@ -191,10 +191,9 @@ func (m *wazeroModule) CallI32_V(ctx context.Context, funcName string, param uin
|
|||||||
func (m *wazeroModule) CallI64_I64(ctx context.Context, funcName string, param uint64) (uint64, error) {
|
func (m *wazeroModule) CallI64_I64(ctx context.Context, funcName string, param uint64) (uint64, error) {
|
||||||
if results, err := m.funcs[funcName].Call(ctx, param); err != nil {
|
if results, err := m.funcs[funcName].Call(ctx, param); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if len(results) > 0 {
|
} else {
|
||||||
return results[0], nil
|
return results[0], nil
|
||||||
}
|
}
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *wazeroModule) WriteMemory(offset uint32, bytes []byte) error {
|
func (m *wazeroModule) WriteMemory(offset uint32, bytes []byte) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user