-
Notifications
You must be signed in to change notification settings - Fork 267
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples(allocation): free memory after unmarshalling a result from t…
…he guest (cgo, tinymem, bench)
- Loading branch information
1 parent
28814da
commit 14d3752
Showing
13 changed files
with
501 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
## TinyGo allocation example | ||
|
||
This example shows how to pass strings in and out of a Wasm function defined | ||
in TinyGo, built with `tinygo build -o greet.wasm -scheduler=none -target=wasi greet.go` | ||
|
||
```bash | ||
$ go run greet.go cgo wazero | ||
cgo: wazero! | ||
``` | ||
|
||
```bash | ||
$ go run greet.go tinymem wazero | ||
tinymem: wazero! | ||
``` | ||
|
||
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.Slice` to build back a string from a pointer, len pair. | ||
* Uses CGO to allocate and free memory if `cgo` is passed as first argument. | ||
* Uses [TinyMem](https://github.com/tetratelabs/tinymem) alike to allocate and free memory if `tinymem` is passed as first argument. | ||
|
||
See https://wazero.io/languages/tinygo/ for more tips. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"github.com/tetratelabs/wazero" | ||
"github.com/tetratelabs/wazero/api" | ||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" | ||
) | ||
|
||
// greetWasm was compiled using `tinygo build -o greet.wasm -scheduler=none --no-debug -target=wasi greet.go` | ||
// | ||
//go:embed testdata/greet.wasm | ||
var greetWasm []byte | ||
|
||
// main shows how to interact with a WebAssembly function that was compiled | ||
// from TinyGo. | ||
// | ||
// See README.md for a full description. | ||
func main() { | ||
// Choose the context to use for function calls. | ||
ctx := context.Background() | ||
|
||
// Create a new WebAssembly Runtime. | ||
r := wazero.NewRuntime(ctx) | ||
defer r.Close(ctx) // This closes everything this Runtime created. | ||
|
||
// Note: testdata/greet.go doesn't use WASI, but TinyGo needs it to | ||
// implement functions such as panic. | ||
wasi_snapshot_preview1.MustInstantiate(ctx, r) | ||
|
||
// Instantiate a WebAssembly module that imports the "log" function defined | ||
// in "env" and exports "memory" and functions we'll use in this example. | ||
mod, err := r.InstantiateWithConfig(ctx, greetWasm, wazero.NewModuleConfig().WithStdout(os.Stdout)) | ||
if err != nil { | ||
log.Panicln(err) | ||
} | ||
|
||
var free api.Function | ||
var malloc api.Function | ||
var greeting api.Function | ||
|
||
switch os.Args[1] { | ||
case "cgo": | ||
// These are undocumented, but exported. See tinygo-org/tinygo#2788 | ||
malloc = mod.ExportedFunction("malloc") | ||
free = mod.ExportedFunction("free") | ||
greeting = mod.ExportedFunction("greeting_cgo") | ||
case "tinymem": | ||
malloc = mod.ExportedFunction("malloc_tinymem") | ||
free = mod.ExportedFunction("free_tinymem") | ||
greeting = mod.ExportedFunction("greeting_tinymem") | ||
default: | ||
log.Panicf("unsupported allocation mode %s", os.Args[1]) | ||
} | ||
|
||
// Let's use the argument to this main function in Wasm. | ||
name := os.Args[2] | ||
nameSize := uint64(len(name)) | ||
|
||
// Instead of an arbitrary memory offset, use TinyGo'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. | ||
results, err := malloc.Call(ctx, nameSize) | ||
if err != nil { | ||
log.Panicln(err) | ||
} | ||
namePtr := results[0] | ||
|
||
// This pointer is managed by TinyGo, but TinyGo is unaware of external usage. | ||
// So, we have to free it when finished | ||
defer func() { | ||
_, err := free.Call(ctx, namePtr) | ||
if err != nil { | ||
log.Panicln(err) | ||
} | ||
}() | ||
|
||
// The pointer is a linear memory offset, which is where we write the name. | ||
if !mod.Memory().Write(uint32(namePtr), []byte(name)) { | ||
log.Panicf("Memory.Write(%d, %d) out of range of memory size %d", | ||
namePtr, nameSize, mod.Memory().Size()) | ||
} | ||
|
||
// Finally, we get the greeting message "greet" printed. This shows how to | ||
// read-back something allocated by TinyGo. | ||
ptrSize, err := greeting.Call(ctx, namePtr, nameSize) | ||
if err != nil { | ||
log.Panicln(err) | ||
} | ||
|
||
greetingPtr := uint32(ptrSize[0] >> 32) | ||
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. | ||
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok { | ||
log.Panicf("Memory.Read(%d, %d) out of range of memory size %d", | ||
greetingPtr, greetingSize, mod.Memory().Size()) | ||
} else { | ||
fmt.Println(string(bytes)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/tetratelabs/wazero/internal/testing/maintester" | ||
"github.com/tetratelabs/wazero/internal/testing/require" | ||
) | ||
|
||
// Test_main ensures the following will work: | ||
// | ||
// go run greet.go wazero | ||
func Test_main(t *testing.T) { | ||
stdout, _ := maintester.TestMain(t, main, "greet", "wazero") | ||
require.Equal(t, `wasm >> Hello, wazero! | ||
go >> Hello, wazero! | ||
`, stdout) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"unsafe" | ||
) | ||
|
||
// #include <stdlib.h> | ||
import "C" | ||
|
||
// main is required for TinyGo to compile to Wasm. | ||
func main() {} | ||
|
||
// greeting gets a greeting for the name. | ||
func greeting(name string) string { | ||
return fmt.Sprint(name, "!") | ||
} | ||
|
||
// ptrToString returns a string from WebAssembly compatible numeric types | ||
// representing its pointer and length. | ||
func ptrToString(ptr uint32, size uint32) string { | ||
// Get a slice view of the underlying bytes in the stream. | ||
s := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size) | ||
return *(*string)(unsafe.Pointer(&s)) | ||
} | ||
|
||
// | ||
// CGO memory management | ||
// | ||
|
||
// stringToPtr_cgo 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_cgo(s string) (uint32, uint32) { | ||
if len(s) == 0 { | ||
return 0, 0 | ||
} | ||
|
||
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)) | ||
} | ||
|
||
// _greeting_cgo is a WebAssembly export that accepts a string pointer (linear memory | ||
// offset) and returns a pointer/size pair packed into a uint64. | ||
// | ||
// Note: | ||
// - This uses an uint64 instead of two result values for compatibility with | ||
// WebAssembly 1.0. | ||
// - The pointer returned by the function must be freed by the host using builtin free | ||
// as it has been allocated using CGO malloc | ||
// | ||
//export greeting_cgo | ||
func _greeting_cgo(ptr, size uint32) (ptrSize uint64) { | ||
name := ptrToString(ptr, size) | ||
g := greeting("cgo: " + name) | ||
ptr, size = stringToPtr_cgo(g) | ||
return (uint64(ptr) << uint64(32)) | uint64(size) | ||
} | ||
|
||
// | ||
// TinyMem alike memory management (https://github.com/tetratelabs/tinymem) | ||
// | ||
|
||
var alivePointers = map[uintptr]interface{}{} | ||
|
||
// stringToPtr_tinymem 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_tinymem(s string) (uint32, uint32) { | ||
|
||
ptr := _malloc_tinymem(uint32(len(s))) | ||
unsafePtr := unsafe.Pointer(ptr) | ||
size := len(s) | ||
|
||
copy(unsafe.Slice((*byte)(unsafePtr), size), []byte(s)) | ||
|
||
return uint32(ptr), uint32(len(s)) | ||
} | ||
|
||
// _greeting_tinymem is a WebAssembly export that accepts a string pointer (linear memory | ||
// offset) and returns a pointer/size pair packed into a uint64. | ||
// | ||
// Note: | ||
// - This uses an uint64 instead of two result values for compatibility with | ||
// WebAssembly 1.0. | ||
// - The pointer returned by the function must be freed by the host by using free_tinymem | ||
// as it has been kept alive in a local map | ||
// | ||
//export greeting_tinymem | ||
func _greeting_tinymem(ptr, size uint32) (ptrSize uint64) { | ||
name := ptrToString(ptr, size) | ||
g := greeting("tinymem: " + name) | ||
ptr, size = stringToPtr_tinymem(g) | ||
return (uint64(ptr) << uint64(32)) | uint64(size) | ||
} | ||
|
||
// malloc_tinymem is a WebAssembly export that allocates a pointer (linear memory offset) | ||
// that can be used for the given size in bytes. | ||
// | ||
// Note: This is an ownership transfer, which means the caller must call free | ||
// when finished. | ||
// | ||
//export malloc_tinymem | ||
func _malloc_tinymem(size uint32) uintptr { | ||
buf := make([]byte, size) | ||
ptr := &buf[0] | ||
unsafePtr := uintptr(unsafe.Pointer(ptr)) | ||
alivePointers[unsafePtr] = buf | ||
|
||
return unsafePtr | ||
} | ||
|
||
// free_tinymem frees a uintptr returned by keepaliveBuf or allocate, allowing it | ||
// to be garbage collected. | ||
// | ||
//export free_tinymem | ||
func _free_tinymem(ptr uint32) { | ||
delete(alivePointers, uintptr(ptr)) | ||
} |
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
-371 Bytes
(99%)
imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.wasm
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.