Adds allocation examples in Rust and TinyGo (#475)
Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
The following example projects can help you practice WebAssembly with wazero:
|
||||
|
||||
* [allocation](allocation) - how to pass strings in and out of WebAssembly functions defined in Rust or TinyGo.
|
||||
* [basic](basic) - how to use both WebAssembly and Go-defined functions.
|
||||
* [import-go](import-go) - how to define, import and call a Go-defined function from a WebAssembly-defined function.
|
||||
* [multiple-results](multiple-results) - how to return more than one result from WebAssembly or Go-defined functions.
|
||||
|
||||
25
examples/allocation/README.md
Normal file
25
examples/allocation/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## Allocation examples
|
||||
|
||||
The examples in this directory deal with memory allocation concerns in
|
||||
WebAssembly, e.g. How to pass strings in and out of WebAssembly functions.
|
||||
|
||||
```bash
|
||||
$ go run greet.go wazero
|
||||
wasm >> Hello, wazero!
|
||||
go >> Hello, wazero!
|
||||
```
|
||||
|
||||
While the below examples use strings, they are written in a way that would work
|
||||
for binary serialization.
|
||||
|
||||
* [Rust](rust) - Calls Wasm built with `cargo build --release --target wasm32-unknown-unknown`
|
||||
* [TinyGo](tinygo) - Calls Wasm built with `tinygo build -o X.wasm -scheduler=none --no-debug -target=wasi X.go`
|
||||
|
||||
Note: Each of the above languages differ in both terms of exports and runtime
|
||||
behavior around allocation, because there is no WebAssembly specification for
|
||||
it. For example, TinyGo exports allocation functions while Rust does not. Also,
|
||||
Rust eagerly collects memory before returning from a Wasm function while TinyGo
|
||||
does not.
|
||||
|
||||
We still try to keep the examples as close to the same as possible, and
|
||||
highlight things to be aware of in the respective source and README files.
|
||||
1
examples/allocation/rust/.gitignore
vendored
Normal file
1
examples/allocation/rust/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
greet
|
||||
23
examples/allocation/rust/README.md
Normal file
23
examples/allocation/rust/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
## Rust allocation example
|
||||
|
||||
This example shows how to pass strings in and out of a Wasm function defined
|
||||
in Rust, built with `cargo build --release --target wasm32-unknown-unknown`
|
||||
|
||||
Ex.
|
||||
```bash
|
||||
$ go run greet.go wazero
|
||||
Hello, wazero!
|
||||
```
|
||||
|
||||
Under the covers, [lib.rs](testdata/src/lib.rs) does a few things of interest:
|
||||
* Uses a WebAssembly-tuned memory allocator: [wee_alloc](https://github.com/rustwasm/wee_alloc).
|
||||
* Exports wrapper functions to allocate and deallocate memory.
|
||||
* Uses `&str` instead of CString (NUL-terminated strings).
|
||||
* Uses `std::mem::forget` to prevent Rust from eagerly freeing pointers returned.
|
||||
|
||||
Note: We chose to not use CString because it keeps the example similar to how
|
||||
you would track memory for arbitrary blobs. We also watched function signatures
|
||||
carefully as Rust compiles different WebAssembly signatures depending on the
|
||||
input type. All of this is Rust-specific, and wazero isn't a Rust project, but
|
||||
we hope this gets you started. For next steps, consider reading the
|
||||
[Rust and WebAssembly book](https://rustwasm.github.io/docs/book/).
|
||||
108
examples/allocation/rust/greet.go
Normal file
108
examples/allocation/rust/greet.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// greetWasm was compiled using `cargo build --release --target wasm32-unknown-unknown`
|
||||
//go:embed testdata/greet.wasm
|
||||
var greetWasm []byte
|
||||
|
||||
// main shows how to interact with a WebAssembly function that was compiled
|
||||
// from Rust.
|
||||
//
|
||||
// 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()
|
||||
|
||||
// Instantiate a module named "env" that exports a function to log a string
|
||||
// to the console.
|
||||
env, err := r.NewModuleBuilder("env").
|
||||
ExportFunction("log", logString).
|
||||
Instantiate(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer env.Close()
|
||||
|
||||
// Instantiate a module named "greet" that imports the "log" function
|
||||
// defined in "env".
|
||||
mod, err := r.InstantiateModuleFromCode(ctx, greetWasm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer mod.Close()
|
||||
|
||||
// Get a references to functions we'll use in this example.
|
||||
greet := mod.ExportedFunction("greet")
|
||||
greeting := mod.ExportedFunction("greeting")
|
||||
allocate := mod.ExportedFunction("allocate")
|
||||
deallocate := mod.ExportedFunction("deallocate")
|
||||
|
||||
// Let's use the argument to this main function in Wasm.
|
||||
name := os.Args[1]
|
||||
nameSize := uint64(len(name))
|
||||
|
||||
// Instead of an arbitrary memory offset, use Rust'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 := allocate.Call(ctx, nameSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
namePtr := results[0]
|
||||
// This pointer was allocated by Rust, but owned by Go, So, we have to
|
||||
// deallocate it when finished
|
||||
defer deallocate.Call(ctx, namePtr, nameSize)
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the name.
|
||||
if !mod.Memory().Write(uint32(namePtr), []byte(name)) {
|
||||
log.Fatalf("Memory.Write(%d, %d) out of range of memory size %d",
|
||||
namePtr, nameSize, mod.Memory().Size())
|
||||
}
|
||||
|
||||
// Now, we can call "greet", which reads the string we wrote to memory!
|
||||
_, err = greet.Call(ctx, namePtr, nameSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Finally, we get the greeting message "greet" printed. This shows how to
|
||||
// read-back something allocated by Rust.
|
||||
ptrSize, err := greeting.Call(ctx, namePtr, nameSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
greetingPtr := uint32(ptrSize[0] >> 32)
|
||||
greetingSize := uint32(ptrSize[0])
|
||||
// This pointer was allocated by Rust, but owned by Go, So, we have to
|
||||
// deallocate it when finished
|
||||
defer deallocate.Call(ctx, uint64(greetingPtr), uint64(greetingSize))
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the name.
|
||||
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
|
||||
log.Fatalf("Memory.Read(%d, %d) out of range of memory size %d",
|
||||
greetingPtr, greetingSize, mod.Memory().Size())
|
||||
} else {
|
||||
fmt.Println("go >>", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
func logString(m api.Module, offset, byteCount uint32) {
|
||||
buf, ok := m.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
log.Fatalf("Memory.Read(%d, %d) out of range", offset, byteCount)
|
||||
}
|
||||
fmt.Println(string(buf))
|
||||
}
|
||||
18
examples/allocation/rust/greet_test.go
Normal file
18
examples/allocation/rust/greet_test.go
Normal file
@@ -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)
|
||||
}
|
||||
2
examples/allocation/rust/testdata/.gitignore
vendored
Normal file
2
examples/allocation/rust/testdata/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
21
examples/allocation/rust/testdata/Cargo.toml
vendored
Normal file
21
examples/allocation/rust/testdata/Cargo.toml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "greet"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
# cdylib builds a a %.wasm file with `cargo build --release --target wasm32-unknown-unknown`
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
# wee_aloc is a WebAssembly optimized allocator, which is needed to use non-numeric types like strings.
|
||||
# See https://docs.rs/wee_alloc/latest/wee_alloc/
|
||||
wee_alloc = "0.4.5"
|
||||
|
||||
# Below settings dramatically reduce wasm output size
|
||||
# See https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-sizewasm-opt -Oz -o
|
||||
# See https://doc.rust-lang.org/cargo/reference/profiles.html#codegen-units
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
BIN
examples/allocation/rust/testdata/greet.wasm
vendored
Executable file
BIN
examples/allocation/rust/testdata/greet.wasm
vendored
Executable file
Binary file not shown.
127
examples/allocation/rust/testdata/src/lib.rs
vendored
Normal file
127
examples/allocation/rust/testdata/src/lib.rs
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
extern crate alloc;
|
||||
extern crate core;
|
||||
extern crate wee_alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use std::slice;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
/// Prints a greeting to the console using [`log`].
|
||||
fn greet(name: &String) {
|
||||
log(&["wasm >> ", &greeting(name)].concat());
|
||||
}
|
||||
|
||||
/// Gets a greeting for the name.
|
||||
fn greeting(name: &String) -> String {
|
||||
return ["Hello, ", &name, "!"].concat();
|
||||
}
|
||||
|
||||
/// Logs a message to the console using [`_log`].
|
||||
fn log(message: &String) {
|
||||
unsafe {
|
||||
let (ptr, len) = string_to_ptr(message);
|
||||
_log(ptr, len);
|
||||
}
|
||||
}
|
||||
|
||||
#[link(wasm_import_module = "env")]
|
||||
extern "C" {
|
||||
/// WebAssembly import which prints a string (linear memory offset,
|
||||
/// byteCount) to the console.
|
||||
///
|
||||
/// Note: This is not an ownership transfer: Rust still owns the pointer
|
||||
/// and ensures it isn't deallocated during this call.
|
||||
#[link_name = "log"]
|
||||
fn _log(ptr: u32, size: u32);
|
||||
}
|
||||
|
||||
/// WebAssembly export that accepts a string (linear memory offset, byteCount)
|
||||
/// and calls [`greet`].
|
||||
///
|
||||
/// Note: The input parameters were returned by [`allocate`]. This is not an
|
||||
/// ownership transfer, so the inputs can be reused after this call.
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", target_os = "unknown"),
|
||||
export_name = "greet"
|
||||
)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn _greet(ptr: u32, len: u32) {
|
||||
greet(&ptr_to_string(ptr, len));
|
||||
}
|
||||
|
||||
/// WebAssembly export that accepts a string (linear memory offset, byteCount)
|
||||
/// and returns a pointer/size pair packed into a u64.
|
||||
///
|
||||
/// Note: The return value is leaked to the caller, so it must call
|
||||
/// [`deallocate`] when finished.
|
||||
/// Note: This uses a u64 instead of two result values for compatibility with
|
||||
/// WebAssembly 1.0.
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", target_os = "unknown"),
|
||||
export_name = "greeting"
|
||||
)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn _greeting(ptr: u32, len: u32) -> u64 {
|
||||
let name = &ptr_to_string(ptr, len);
|
||||
let g = greeting(name);
|
||||
let (ptr, len) = string_to_ptr(&g);
|
||||
// Note: This changes ownership of the pointer to the external caller. If
|
||||
// we didn't call forget, the caller would read back a corrupt value. Since
|
||||
// we call forget, the caller must deallocate externally to prevent leaks.
|
||||
std::mem::forget(g);
|
||||
return ((ptr as u64) << 32) | len as u64;
|
||||
}
|
||||
|
||||
/// Returns a string from WebAssembly compatible numeric types representing
|
||||
/// its pointer and length.
|
||||
unsafe fn ptr_to_string(ptr: u32, len: u32) -> String {
|
||||
let slice = slice::from_raw_parts_mut(ptr as *mut u8, len as usize);
|
||||
let utf8 = std::str::from_utf8_unchecked_mut(slice);
|
||||
return String::from(utf8);
|
||||
}
|
||||
|
||||
/// Returns a pointer and size pair for the given string in a way that is
|
||||
/// compatible with WebAssembly numeric types.
|
||||
///
|
||||
/// Note: This doesn't change the ownership of the String. To intentionally
|
||||
/// leak it, use [`std::mem::forget`] on the input after calling this.
|
||||
unsafe fn string_to_ptr(s: &String) -> (u32, u32) {
|
||||
return (s.as_ptr() as u32, s.len() as u32);
|
||||
}
|
||||
|
||||
/// Set the global allocator to the WebAssembly optimized one.
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
/// WebAssembly export that allocates a pointer (linear memory offset) that can
|
||||
/// be used for a string.
|
||||
///
|
||||
/// This is an ownership transfer, which means the caller must call
|
||||
/// [`deallocate`] when finished.
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", target_os = "unknown"),
|
||||
export_name = "allocate"
|
||||
)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn allocate(size: u32) -> *mut u8 {
|
||||
// Allocate the amount of bytes needed.
|
||||
let vec: Vec<MaybeUninit<u8>> = Vec::with_capacity(size as usize);
|
||||
|
||||
// into_raw leaks the memory to the caller.
|
||||
Box::into_raw(vec.into_boxed_slice()) as *mut u8
|
||||
}
|
||||
|
||||
|
||||
/// WebAssembly export that deallocates a pointer of the given size (linear
|
||||
/// memory offset, byteCount) allocated by [`allocate`].
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", target_os = "unknown"),
|
||||
export_name = "deallocate"
|
||||
)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn deallocate(ptr: u32, size: u32) {
|
||||
unsafe {
|
||||
// Retake the pointer which allows its memory to be freed.
|
||||
let _ = Vec::from_raw_parts(ptr as *mut u8, 0, size as usize);
|
||||
}
|
||||
}
|
||||
1
examples/allocation/tinygo/.gitignore
vendored
Normal file
1
examples/allocation/tinygo/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
greet
|
||||
28
examples/allocation/tinygo/README.md
Normal file
28
examples/allocation/tinygo/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## 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`
|
||||
|
||||
Ex.
|
||||
```bash
|
||||
$ go run greet.go wazero
|
||||
wasm >> Hello, wazero!
|
||||
go >> Hello, 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 `reflect.StringHeader` to build back a string from a pointer, len pair.
|
||||
* Relies on TinyGo not eagerly freeing pointers returned.
|
||||
|
||||
Go does not export allocation functions, but when TinyGo generates WebAssembly,
|
||||
it exports "malloc" and "free", which we use for that purpose. These are not
|
||||
documented, so not necessarily a best practice. See the following issues for
|
||||
updates:
|
||||
* WebAssembly exports for allocation: https://github.com/tinygo-org/tinygo/issues/2788
|
||||
* Memory ownership of TinyGo allocated pointers: https://github.com/tinygo-org/tinygo/issues/2787
|
||||
|
||||
Note: While folks here are familiar with TinyGo, wazero isn't a TinyGo project.
|
||||
We hope this gets you started. For next steps, consider reading the
|
||||
[TinyGo Using WebAssembly Guide](https://tinygo.org/docs/guides/webassembly/)
|
||||
or joining the [#TinyGo channel on the Gophers Slack](https://github.com/tinygo-org/tinygo#getting-help).
|
||||
115
examples/allocation/tinygo/greet.go
Normal file
115
examples/allocation/tinygo/greet.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
// 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()
|
||||
|
||||
// Instantiate a module named "env" that exports a function to log a string
|
||||
// to the console.
|
||||
env, err := r.NewModuleBuilder("env").
|
||||
ExportFunction("log", logString).
|
||||
Instantiate(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer env.Close()
|
||||
|
||||
// Note: testdata/greet.go doesn't use WASI, but TinyGo needs it to
|
||||
// implement functions such as panic.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(ctx, r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer wm.Close()
|
||||
|
||||
// Instantiate a module named "greet" that imports the "log" function
|
||||
// defined in "env".
|
||||
mod, err := r.InstantiateModuleFromCode(ctx, greetWasm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer mod.Close()
|
||||
|
||||
// Get a references to functions we'll use in this example.
|
||||
greet := mod.ExportedFunction("greet")
|
||||
greeting := mod.ExportedFunction("greeting")
|
||||
// These are undocumented, but exported. See tinygo-org/tinygo#2788
|
||||
malloc := mod.ExportedFunction("malloc")
|
||||
free := mod.ExportedFunction("free")
|
||||
|
||||
// Let's use the argument to this main function in Wasm.
|
||||
name := os.Args[1]
|
||||
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.Fatal(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 free.Call(ctx, namePtr)
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the name.
|
||||
if !mod.Memory().Write(uint32(namePtr), []byte(name)) {
|
||||
log.Fatalf("Memory.Write(%d, %d) out of range of memory size %d",
|
||||
namePtr, nameSize, mod.Memory().Size())
|
||||
}
|
||||
|
||||
// Now, we can call "greet", which reads the string we wrote to memory!
|
||||
_, err = greet.Call(ctx, namePtr, nameSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 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.Fatal(err)
|
||||
}
|
||||
// Note: This pointer is still owned by TinyGo, so don't try to free it!
|
||||
greetingPtr := uint32(ptrSize[0] >> 32)
|
||||
greetingSize := uint32(ptrSize[0])
|
||||
// The pointer is a linear memory offset, which is where we write the name.
|
||||
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
|
||||
log.Fatalf("Memory.Read(%d, %d) out of range of memory size %d",
|
||||
greetingPtr, greetingSize, mod.Memory().Size())
|
||||
} else {
|
||||
fmt.Println("go >>", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
func logString(m api.Module, offset, byteCount uint32) {
|
||||
buf, ok := m.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
log.Fatalf("Memory.Read(%d, %d) out of range", offset, byteCount)
|
||||
}
|
||||
fmt.Println(string(buf))
|
||||
}
|
||||
18
examples/allocation/tinygo/greet_test.go
Normal file
18
examples/allocation/tinygo/greet_test.go
Normal file
@@ -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)
|
||||
}
|
||||
75
examples/allocation/tinygo/testdata/greet.go
vendored
Normal file
75
examples/allocation/tinygo/testdata/greet.go
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// main is required for TinyGo to compile to Wasm.
|
||||
func main() {}
|
||||
|
||||
// greet prints a greeting to the console.
|
||||
func greet(name string) {
|
||||
log(fmt.Sprint("wasm >> ", greeting(name)))
|
||||
}
|
||||
|
||||
// log a message to the console using _log.
|
||||
func log(message string) {
|
||||
ptr, size := stringToPtr(message)
|
||||
_log(ptr, size)
|
||||
}
|
||||
|
||||
// _log is a WebAssembly import which prints a string (linear memory offset,
|
||||
// byteCount) to the console.
|
||||
//
|
||||
// Note: In TinyGo "//export" on a func is actually an import!
|
||||
//go:wasm-module env
|
||||
//export log
|
||||
func _log(ptr uint32, size uint32)
|
||||
|
||||
// greeting gets a greeting for the name.
|
||||
func greeting(name string) string {
|
||||
return fmt.Sprint("Hello, ", name, "!")
|
||||
}
|
||||
|
||||
// _greet is a WebAssembly export that accepts a string pointer (linear
|
||||
// memory offset) and calls greet.
|
||||
//export greet
|
||||
func _greet(ptr, size uint32) {
|
||||
name := ptrToString(ptr, size)
|
||||
greet(name)
|
||||
}
|
||||
|
||||
// _greet 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 a uint64 instead of two result values for compatibility
|
||||
// with WebAssembly 1.0.
|
||||
//export greeting
|
||||
func _greeting(ptr, size uint32) (ptrSize uint64) {
|
||||
name := ptrToString(ptr, size)
|
||||
g := greeting(name)
|
||||
ptr, size = stringToPtr(g)
|
||||
return (uint64(ptr) << uint64(32)) | uint64(size)
|
||||
}
|
||||
|
||||
// ptrToString returns a string from WebAssembly compatible numeric
|
||||
// types representing its pointer and length.
|
||||
func ptrToString(ptr uint32, size uint32) (ret string) {
|
||||
// Here, we want to get a string represented by the ptr and size. If we
|
||||
// wanted a []byte, we'd use reflect.SliceHeader instead.
|
||||
strHdr := (*reflect.StringHeader)(unsafe.Pointer(&ret))
|
||||
strHdr.Data = uintptr(ptr)
|
||||
strHdr.Len = uintptr(size)
|
||||
return
|
||||
}
|
||||
|
||||
// stringToPtr returns a pointer and size pair for the given string
|
||||
// in a way that is compatible with WebAssembly numeric types.
|
||||
func stringToPtr(s string) (uint32, uint32) {
|
||||
buf := []byte(s)
|
||||
ptr := &buf[0]
|
||||
unsafePtr := uintptr(unsafe.Pointer(ptr))
|
||||
return uint32(unsafePtr), uint32(len(buf))
|
||||
}
|
||||
BIN
examples/allocation/tinygo/testdata/greet.wasm
vendored
Executable file
BIN
examples/allocation/tinygo/testdata/greet.wasm
vendored
Executable file
Binary file not shown.
@@ -12,5 +12,5 @@ import (
|
||||
// go run cat.go ./test.txt
|
||||
func Test_main(t *testing.T) {
|
||||
stdout, _ := maintester.TestMain(t, main, "cat", "./test.txt")
|
||||
require.Equal(t, "hello filesystem\n", stdout)
|
||||
require.Equal(t, "greet filesystem\n", stdout)
|
||||
}
|
||||
|
||||
2
examples/wasi/testdata/test.txt
vendored
2
examples/wasi/testdata/test.txt
vendored
@@ -1 +1 @@
|
||||
hello filesystem
|
||||
greet filesystem
|
||||
|
||||
Reference in New Issue
Block a user