3
.github/workflows/examples.yaml
vendored
3
.github/workflows/examples.yaml
vendored
@@ -90,6 +90,9 @@ jobs:
|
|||||||
- name: Build Rust examples
|
- name: Build Rust examples
|
||||||
run: make build.examples.rust
|
run: make build.examples.rust
|
||||||
|
|
||||||
|
- name: Build Zig examples
|
||||||
|
run: make build.examples.zig
|
||||||
|
|
||||||
- name: Build Emscripten examples
|
- name: Build Emscripten examples
|
||||||
run: |
|
run: |
|
||||||
./emsdk/emsdk activate ${{env.EMSDK_VERSION}}
|
./emsdk/emsdk activate ${{env.EMSDK_VERSION}}
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,3 +31,6 @@ package-lock.json
|
|||||||
/coverage.txt
|
/coverage.txt
|
||||||
|
|
||||||
.vagrant
|
.vagrant
|
||||||
|
|
||||||
|
zig-cache/
|
||||||
|
zig-out/
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -46,6 +46,13 @@ build.bench:
|
|||||||
build.examples.as:
|
build.examples.as:
|
||||||
@cd ./examples/assemblyscript/testdata && npm install && npm run build
|
@cd ./examples/assemblyscript/testdata && npm install && npm run build
|
||||||
|
|
||||||
|
.PHONY: build.examples.zig
|
||||||
|
build.examples.zig: examples/allocation/zig/testdata/greet.wasm
|
||||||
|
|
||||||
|
%.wasm: %.zig
|
||||||
|
@(cd $(@D); zig build)
|
||||||
|
@mv $(@D)/zig-out/lib/$(@F) $(@D)
|
||||||
|
|
||||||
tinygo_sources := $(wildcard examples/*/testdata/*.go examples/*/*/testdata/*.go examples/*/testdata/*/*.go)
|
tinygo_sources := $(wildcard examples/*/testdata/*.go examples/*/*/testdata/*.go examples/*/testdata/*/*.go)
|
||||||
.PHONY: build.examples.tinygo
|
.PHONY: build.examples.tinygo
|
||||||
build.examples.tinygo: $(tinygo_sources)
|
build.examples.tinygo: $(tinygo_sources)
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ for binary serialization.
|
|||||||
|
|
||||||
* [Rust](rust) - Calls Wasm built with `cargo build --release --target wasm32-unknown-unknown`
|
* [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`
|
* [TinyGo](tinygo) - Calls Wasm built with `tinygo build -o X.wasm -scheduler=none --no-debug -target=wasi X.go`
|
||||||
|
* [Zig](zig) - Calls Wasm built with `zig build`
|
||||||
|
|
||||||
Note: Each of the above languages differ in both terms of exports and runtime
|
Note: Each of the above languages differ in both terms of exports and runtime
|
||||||
behavior around allocation, because there is no WebAssembly specification for
|
behavior around allocation, because there is no WebAssembly specification for
|
||||||
it. For example, TinyGo exports allocation functions while Rust does not. Also,
|
it. For example, TinyGo exports allocation functions while Rust and Zig don't.
|
||||||
Rust eagerly collects memory before returning from a Wasm function while TinyGo
|
Also, Rust eagerly collects memory before returning from a Wasm function while TinyGo
|
||||||
does not.
|
does not.
|
||||||
|
|
||||||
We still try to keep the examples as close to the same as possible, and
|
We still try to keep the examples as close to the same as possible, and
|
||||||
|
|||||||
20
examples/allocation/zig/README.md
Normal file
20
examples/allocation/zig/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## Zig allocation example
|
||||||
|
|
||||||
|
This example shows how to pass strings in and out of a Wasm function defined in Zig, built with `zig build`.
|
||||||
|
|
||||||
|
Ex.
|
||||||
|
```bash
|
||||||
|
$ go run greet.go wazero
|
||||||
|
wasm >> Hello, wazero!
|
||||||
|
go >> Hello, wazero!
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the covers, [greet.zig](testdata/greet.zig) does a few things of interest:
|
||||||
|
* Uses `@ptrToInt` to change a Zig pointer to a numeric type
|
||||||
|
* Uses `[*]u8` as an argument to take a pointer and slices it to build back a string
|
||||||
|
|
||||||
|
The Zig code exports "malloc" and "free", which we use for that purpose.
|
||||||
|
|
||||||
|
Note: This example uses `@panic()` rather than `unreachable` to handle errors
|
||||||
|
since `unreachable` emits a call to panic only in `Debug` and `ReleaseSafe` mode.
|
||||||
|
In `ReleaseFast` and `ReleaseSmall` mode, it would lead into undefined behavior.
|
||||||
124
examples/allocation/zig/greet.go
Normal file
124
examples/allocation/zig/greet.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// greetWasm was compiled using `zig build`
|
||||||
|
//
|
||||||
|
//go:embed testdata/greet.wasm
|
||||||
|
var greetWasm []byte
|
||||||
|
|
||||||
|
// main shows how to interact with a WebAssembly function that was compiled from Zig.
|
||||||
|
//
|
||||||
|
// See README.md for a full description.
|
||||||
|
func main() {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
// Choose the context to use for function calls.
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create a new WebAssembly Runtime.
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().
|
||||||
|
// Enable WebAssembly 2.0 support.
|
||||||
|
WithWasmCore2(),
|
||||||
|
)
|
||||||
|
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
|
|
||||||
|
// Instantiate a Go-defined module named "env" that exports a function to
|
||||||
|
// log to the console.
|
||||||
|
_, err := r.NewModuleBuilder("env").
|
||||||
|
ExportFunction("log", logString).
|
||||||
|
Instantiate(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate a WebAssembly module that imports the "log" function defined
|
||||||
|
// in "env" and exports "memory" and functions we'll use in this example.
|
||||||
|
compiled, err := r.CompileModule(ctx, greetWasm, wazero.NewCompileConfig())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithStdout(os.Stdout).WithStderr(os.Stderr))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get references to WebAssembly functions we'll use in this example.
|
||||||
|
greet := mod.ExportedFunction("greet")
|
||||||
|
greeting := mod.ExportedFunction("greeting")
|
||||||
|
|
||||||
|
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 Zig'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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
namePtr := results[0]
|
||||||
|
if namePtr == 0 {
|
||||||
|
return errors.New("malloc failed")
|
||||||
|
}
|
||||||
|
// We have to free this pointer when finished.
|
||||||
|
defer free.Call(ctx, namePtr, nameSize)
|
||||||
|
|
||||||
|
// The pointer is a linear memory offset, which is where we write the name.
|
||||||
|
if !mod.Memory().Write(ctx, uint32(namePtr), []byte(name)) {
|
||||||
|
return fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d",
|
||||||
|
namePtr, nameSize, mod.Memory().Size(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, we can call "greet", which reads the string we wrote to memory!
|
||||||
|
_, err = greet.Call(ctx, namePtr, nameSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we get the greeting message "greet" printed. This shows how to
|
||||||
|
// read-back something allocated by Zig.
|
||||||
|
ptrSize, err := greeting.Call(ctx, namePtr, nameSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ctx, greetingPtr, greetingSize); !ok {
|
||||||
|
return fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
||||||
|
greetingPtr, greetingSize, mod.Memory().Size(ctx))
|
||||||
|
} else {
|
||||||
|
fmt.Println("go >>", string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logString(ctx context.Context, m api.Module, offset, byteCount uint32) {
|
||||||
|
buf, ok := m.Memory().Read(ctx, offset, byteCount)
|
||||||
|
if !ok {
|
||||||
|
log.Panicf("Memory.Read(%d, %d) out of range", offset, byteCount)
|
||||||
|
}
|
||||||
|
fmt.Println(string(buf))
|
||||||
|
}
|
||||||
18
examples/allocation/zig/greet_test.go
Normal file
18
examples/allocation/zig/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)
|
||||||
|
}
|
||||||
13
examples/allocation/zig/testdata/build.zig
vendored
Normal file
13
examples/allocation/zig/testdata/build.zig
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const CrossTarget = std.zig.CrossTarget;
|
||||||
|
|
||||||
|
pub fn build(b: *std.build.Builder) void {
|
||||||
|
// Standard release options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||||
|
const mode = b.standardReleaseOptions();
|
||||||
|
|
||||||
|
const lib = b.addSharedLibrary("greet", "greet.zig", .unversioned);
|
||||||
|
lib.setTarget(CrossTarget{ .cpu_arch = .wasm32, .os_tag = .freestanding });
|
||||||
|
lib.setBuildMode(mode);
|
||||||
|
lib.install();
|
||||||
|
}
|
||||||
BIN
examples/allocation/zig/testdata/greet.wasm
vendored
Executable file
BIN
examples/allocation/zig/testdata/greet.wasm
vendored
Executable file
Binary file not shown.
65
examples/allocation/zig/testdata/greet.zig
vendored
Normal file
65
examples/allocation/zig/testdata/greet.zig
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const allocator = std.heap.page_allocator;
|
||||||
|
|
||||||
|
extern "env" fn log(ptr: [*]const u8, size: u32) void;
|
||||||
|
|
||||||
|
// _log prints a message to the console using log.
|
||||||
|
pub fn _log(message: []const u8) void {
|
||||||
|
log(message.ptr, message.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub export fn malloc(length: usize) ?[*]u8 {
|
||||||
|
const buff = allocator.alloc(u8, length) catch return null;
|
||||||
|
return buff.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub export fn free(buf: [*]u8, length: usize) void {
|
||||||
|
allocator.free(buf[0..length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _greeting(name: []const u8) ![]u8 {
|
||||||
|
return try std.fmt.allocPrint(
|
||||||
|
allocator,
|
||||||
|
"Hello, {s}!",
|
||||||
|
.{name},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// _greet prints a greeting to the console.
|
||||||
|
pub fn _greet(name: []const u8) !void {
|
||||||
|
const s = try std.fmt.allocPrint(
|
||||||
|
allocator,
|
||||||
|
"wasm >> {s}",
|
||||||
|
.{name},
|
||||||
|
);
|
||||||
|
_log(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// greet is a WebAssembly export that accepts a string pointer (linear memory offset) and calls greet.
|
||||||
|
pub export fn greet(message: [*]const u8, size: u32) void {
|
||||||
|
const name = _greeting(message[0..size]) catch |err| @panic(switch (err) {
|
||||||
|
error.OutOfMemory => "out of memory",
|
||||||
|
else => "unexpected error",
|
||||||
|
});
|
||||||
|
_greet(name) catch |err| @panic(switch (err) {
|
||||||
|
error.OutOfMemory => "out of memory",
|
||||||
|
else => "unexpected error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// greeting 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.
|
||||||
|
pub export fn greeting(message: [*]const u8, size: u32) u64 {
|
||||||
|
const g = _greeting(message[0..size]) catch return 0;
|
||||||
|
return stringToPtr(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringToPtr returns a pointer and size pair for the given string in a way
|
||||||
|
// compatible with WebAssembly numeric types.
|
||||||
|
pub fn stringToPtr(s: []const u8) u64 {
|
||||||
|
const p: u64 = @ptrToInt(s.ptr);
|
||||||
|
return p << 32 | s.len;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user