Skip to content

Commit

Permalink
Adds wasm_exec for Go generated Wasm
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Cole <adrian@tetrate.io>
  • Loading branch information
Adrian Cole committed Aug 4, 2022
1 parent fe56fab commit 868ec94
Show file tree
Hide file tree
Showing 33 changed files with 2,985 additions and 2 deletions.
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ build.bench:
build.examples.as:
@cd ./examples/assemblyscript/testdata && npm install && npm run build

tinygo_sources := $(wildcard examples/*/testdata/*.go examples/*/*/testdata/*.go examples/*/testdata/*/*.go)
go_sources := examples/wasm_exec/testdata/cat.go
.PHONY: build.examples.go
build.examples.go: $(go_sources)
@for f in $^; do \
cd $$(dirname $$f); \
GOARCH=wasm GOOS=js go build -o $$(basename $$f | sed -e 's/\.go/\.wasm/') .; \
cd -; \
done

tinygo_sources := $(filter-out $(go_sources), $(wildcard examples/*/testdata/*.go examples/*/*/testdata/*.go examples/*/testdata/*/*.go))
.PHONY: build.examples.tinygo
build.examples.tinygo: $(tinygo_sources)
@for f in $^; do \
Expand Down
2 changes: 1 addition & 1 deletion RATIONALE.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ value (possibly `PWD`). Those unable to control the compiled code should only
use absolute paths in configuration.

See
* https://github.com/golang/go/blob/go1.19rc2/src/syscall/fs_js.go#L324
* https://github.com/golang/go/blob/go1.19/src/syscall/fs_js.go#L324
* https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117

### FdPrestatDirName
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ The following example projects can help you practice WebAssembly with wazero:
* [namespace](namespace) - how WebAssembly modules can import their own host module, such as "env".
* [replace-import](replace-import) - how to override a module name hard-coded in a WebAssembly module.
* [wasi](wasi) - how to use I/O in your WebAssembly modules using WASI (WebAssembly System Interface).
* [wasm_exec](wasm_exec) - how to use I/O in your WebAssembly modules compiled with `GOARCH=wasm GOOS=js`.

Please [open an issue](https://github.com/tetratelabs/wazero/issues/new) if you would like to see another example.
25 changes: 25 additions & 0 deletions examples/wasm_exec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## wasm_exec example

This example shows how to use I/O in your WebAssembly modules compiled with
`GOARCH=wasm GOOS=js`.

```bash
$ go run cat.go /test.txt
greet filesystem
```

### Compilation instructions

When `GOOS=js` and `GOARCH=wasm`, Go's compiler targets WebAssembly 1.0 Binary
format (%.wasm).

Ex.
```bash
$ cd testdata
$ GOOS=js GOARCH=wasm go build -o cat.wasm .
```

The operating system is "js", but more specifically it is [wasm_exec.js][1].
This package runs the `%.wasm` just like `wasm_exec.js` would.

[1]: https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js
68 changes: 68 additions & 0 deletions examples/wasm_exec/cat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"context"
"embed"
_ "embed"
"io/fs"
"log"
"os"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/sys"
"github.com/tetratelabs/wazero/wasm_exec"
)

// catFS is an embedded filesystem limited to test.txt
//
//go:embed testdata/test.txt
var catFS embed.FS

// catWasm was compiled the Go source testdata/cat.go
//
//go:embed testdata/cat.wasm
var catWasm []byte

// main writes an input file to stdout, just like `cat`.
//
// This is a basic introduction to the WebAssembly System Interface (WASI).
// See https://github.com/WebAssembly/WASI
func main() {
// Choose the context to use for function calls.
ctx := context.Background()

// Create a new WebAssembly Runtime.
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter().WithWasmCore2())
defer r.Close(ctx) // This closes everything this Runtime created.

// Compile the WebAssembly module using the default configuration.
compiled, err := r.CompileModule(ctx, catWasm, wazero.NewCompileConfig())
if err != nil {
log.Panicln(err)
}

we, err := wasm_exec.NewBuilder().Build(ctx, r)
if err != nil {
log.Panicln(err)
}

// Since wazero uses fs.FS, we can use standard libraries to do things like trim the leading path.
rooted, err := fs.Sub(catFS, "testdata")
if err != nil {
log.Panicln(err)
}

// Create a configuration for running main, overriding defaults (which discard stdout and has no file system).
config := wazero.NewModuleConfig().
WithFS(rooted).
WithStdout(os.Stdout).
WithStderr(os.Stderr).
WithArgs("cat", os.Args[1])

err = we.Run(ctx, compiled, config)
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
//log.Panicln(err)
} else if !ok {
log.Panicln(err)
}
}
17 changes: 17 additions & 0 deletions examples/wasm_exec/cat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 cat.go /test.txt
func Test_main(t *testing.T) {
stdout, stderr := maintester.TestMain(t, main, "cat", "/test.txt")
require.Equal(t, "", stderr)
require.Equal(t, "greet filesystem\n", stdout)
}
21 changes: 21 additions & 0 deletions examples/wasm_exec/testdata/cat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"io/ioutil"
"os"
)

// main is the same as wasi: "concatenate and print files."
func main() {
// Start at arg[1] because args[0] is the program name.
for i := 1; i < len(os.Args); i++ {
// Intentionally use ioutil.ReadFile instead of os.ReadFile for TinyGo.
bytes, err := ioutil.ReadFile(os.Args[i])
if err != nil {
os.Exit(1)
}

// Use write to avoid needing to worry about Windows newlines.
os.Stdout.Write(bytes)
}
}
Binary file added examples/wasm_exec/testdata/cat.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions examples/wasm_exec/testdata/sub/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
greet sub dir
1 change: 1 addition & 0 deletions examples/wasm_exec/testdata/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
greet filesystem
5 changes: 5 additions & 0 deletions internal/wasm/call_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ func (f *FunctionInstance) Call(ctx context.Context, params ...uint64) (ret []ui
return
}

// GlobalVal is an internal hack to get the lower 64 bits of a global.
func (m *CallContext) GlobalVal(idx Index) uint64 {
return m.module.Globals[idx].Val
}

// ExportedGlobal implements the same method as documented on api.Module.
func (m *CallContext) ExportedGlobal(name string) api.Global {
exp, err := m.module.getExport(name, ExternTypeGlobal)
Expand Down
31 changes: 31 additions & 0 deletions internal/wasm/host.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package wasm

import (
"errors"
"fmt"
"sort"
"strings"

"github.com/tetratelabs/wazero/internal/wasmdebug"
)

type ProxyFunc struct {
// Proxy must be a wasm func
Proxy *HostFunc
// Proxied should be a go func.
Proxied *HostFunc

// CallBodyPos is the position in Code.Body of the caller to replace the
// real funcIdx of the proxied.
CallBodyPos int
}

func (p *ProxyFunc) Name() string {
return p.Proxied.Name
}

// HostFunc is a function with an inlined type, used for NewHostModule.
// Any corresponding FunctionType will be reused or added to the Module.
type HostFunc struct {
Expand Down Expand Up @@ -156,6 +172,21 @@ func addFuncs(
if hf, ok := v.(*HostFunc); ok {
nameToFunc[hf.Name] = hf
funcNames = append(funcNames, hf.Name)
} else if pf, ok := v.(*ProxyFunc); ok {
proxiedIdx := len(funcNames)
if proxiedIdx > 255 {
return errors.New("TODO: proxied funcidx larger than one byte")
}
nameToFunc[pf.Proxied.Name] = pf.Proxied
funcNames = append(funcNames, pf.Proxied.Name)

proxyBody := make([]byte, len(pf.Proxy.Code.Body))
copy(proxyBody, pf.Proxy.Code.Body)
proxyBody[pf.CallBodyPos] = byte(proxiedIdx)
proxy := pf.Proxy.WithWasm(proxyBody)

nameToFunc[proxy.Name] = proxy
funcNames = append(funcNames, proxy.Name)
} else {
params, results, code, ftErr := parseGoFunc(v)
if ftErr != nil {
Expand Down
Loading

0 comments on commit 868ec94

Please sign in to comment.