Removes integration_test/vs (#2270)
vs was introduced to bench various things including the tests against other runtimes. However, the cases are not representative, and also some are just unable to build plus some are simply failing. This removes the entire vs directory, and reduces the maintenance burden. Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
25
.github/workflows/commit.yaml
vendored
25
.github/workflows/commit.yaml
vendored
@@ -128,9 +128,8 @@ jobs:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Build test binaries
|
||||
# Exclude benchmarks as we don't run those in Docker
|
||||
run: |
|
||||
go list -f '{{.Dir}}' ./... | egrep -v '(bench|vs|spectest)' | xargs -Ipkg go test pkg -c -o pkg.test
|
||||
go list -f '{{.Dir}}' ./... | egrep -v '(vs|spectest)' | xargs -Ipkg go test pkg -c -o pkg.test
|
||||
go build -o wazerocli ./cmd/wazero
|
||||
env:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
@@ -167,28 +166,6 @@ jobs:
|
||||
- run: tinygo build ./cmd/wazero
|
||||
- run: tinygo build -size short -target pico -stack-size=8kb ./cmd/wazero
|
||||
|
||||
bench:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
# Unlike the other CGO libraries, WasmEdge requires offline installation.
|
||||
- name: Install WasmEdge
|
||||
run: |
|
||||
wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -p /usr/local -v ${WASMEDGE_VERSION}
|
||||
# The version here is coupled to internal/integration_test/go.mod, but it
|
||||
# isn't always the same as sometimes the Go layer has a broken release.
|
||||
env:
|
||||
WASMEDGE_VERSION: 0.12.1
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- run: make bench
|
||||
|
||||
# This ensures that internal/integration_test/fuzz is runnable, and is not intended to
|
||||
# run full-length fuzzing while trying to find low-hanging frontend bugs.
|
||||
fuzz:
|
||||
|
||||
4
.github/workflows/examples.yaml
vendored
4
.github/workflows/examples.yaml
vendored
@@ -104,9 +104,5 @@ jobs:
|
||||
source ./emsdk/emsdk_env.sh
|
||||
make build.examples.emscripten
|
||||
|
||||
- name: Build bench cases
|
||||
run: make build.bench
|
||||
if: matrix.go-version != '1.20' # fails with TinyGo v0.32.0
|
||||
|
||||
- name: Run example tests
|
||||
run: make test.examples
|
||||
|
||||
16
Makefile
16
Makefile
@@ -20,22 +20,6 @@ main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,
|
||||
|
||||
go_test_options ?= -timeout 300s
|
||||
|
||||
ensureCompilerFastest := -ldflags '-X github.com/tetratelabs/wazero/internal/integration_test/vs.ensureCompilerFastest=true'
|
||||
.PHONY: bench
|
||||
bench:
|
||||
@go build ./internal/integration_test/bench/...
|
||||
@# Don't use -test.benchmem as it isn't accurate when comparing against CGO libs
|
||||
@for d in vs/time vs/wasmedge vs/wasmtime ; do \
|
||||
cd ./internal/integration_test/$$d ; \
|
||||
go test -bench=. . -tags='wasmedge' $(ensureCompilerFastest) ; \
|
||||
cd - ;\
|
||||
done
|
||||
|
||||
bench_testdata_dir := internal/integration_test/bench/testdata
|
||||
.PHONY: build.bench
|
||||
build.bench:
|
||||
@tinygo build -o $(bench_testdata_dir)/case.wasm -scheduler=none --no-debug -target=wasi $(bench_testdata_dir)/case.go
|
||||
|
||||
.PHONY: test.examples
|
||||
test.examples:
|
||||
@go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/wasi_snapshot_preview1/example/...
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
//go:embed internal/integration_test/vs/testdata/fac.wasm
|
||||
//go:embed testdata/fac.wasm
|
||||
var facWasm []byte
|
||||
|
||||
//go:embed internal/integration_test/vs/testdata/mem_grow.wasm
|
||||
//go:embed testdata/mem_grow.wasm
|
||||
var memGrowWasm []byte
|
||||
|
||||
func TestCompilationCache(t *testing.T) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# VS
|
||||
|
||||
This directory contains tests which compare against other runtimes. As all
|
||||
known alternatives use CGO, this contains its own [go.mod](go.mod), as
|
||||
otherwise project dependencies are tainted and multi-platform tests more
|
||||
difficult to manage.
|
||||
|
||||
Examples of portability issues besides CGO
|
||||
* Wasmtime can only be used in amd64
|
||||
* Wasmer doesn't link on Windows
|
||||
@@ -1,187 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||
|
||||
// ensureCompilerFastest is overridable via ldflags. e.g.
|
||||
//
|
||||
// -ldflags '-X github.com/tetratelabs/wazero/internal/integration_test/vs.ensureCompilerFastest=true'
|
||||
var ensureCompilerFastest = "false"
|
||||
|
||||
const compilerRuntime = "wazero-compiler"
|
||||
|
||||
// runTestBenchmark_Call_CompilerFastest ensures that Compiler is the fastest engine for function invocations.
|
||||
// This is disabled by default, and can be run with -ldflags '-X github.com/tetratelabs/wazero/vs.ensureCompilerFastest=true'.
|
||||
func runTestBenchmark_Call_CompilerFastest(t *testing.T, rtCfg *RuntimeConfig, name string, call func(Module, int) error, vsRuntime Runtime) {
|
||||
if ensureCompilerFastest != "true" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
type benchResult struct {
|
||||
name string
|
||||
nsOp float64
|
||||
}
|
||||
|
||||
results := make([]benchResult, 0, 2)
|
||||
// Add the result for Compiler
|
||||
compilerNsOp := runCallBenchmark(NewWazeroCompilerRuntime(), rtCfg, call)
|
||||
results = append(results, benchResult{name: compilerRuntime, nsOp: compilerNsOp})
|
||||
|
||||
// Add a result for the runtime we're comparing against
|
||||
vsNsOp := runCallBenchmark(vsRuntime, rtCfg, call)
|
||||
results = append(results, benchResult{name: vsRuntime.Name(), nsOp: vsNsOp})
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].nsOp < results[j].nsOp
|
||||
})
|
||||
|
||||
// Print results before deciding if this failed
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "Benchmark%s/Call-16\n", name)
|
||||
for _, result := range results {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%.2f\tns/op\n", result.name, result.nsOp)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
// Fail if compiler wasn't fastest!
|
||||
require.Equal(t, compilerRuntime, results[0].name, "%s is faster than %s. "+
|
||||
"Run with ensureCompilerFastest=false instead to see the detailed result",
|
||||
results[0].name, compilerRuntime)
|
||||
}
|
||||
|
||||
func runCallBenchmark(rt Runtime, rtCfg *RuntimeConfig, call func(Module, int) error) float64 {
|
||||
result := testing.Benchmark(func(b *testing.B) {
|
||||
benchmarkCall(b, rt, rtCfg, call)
|
||||
})
|
||||
// https://github.com/golang/go/blob/go1.20/src/testing/benchmark.go#L428-L432
|
||||
nsOp := float64(result.T.Nanoseconds()) / float64(result.N)
|
||||
return nsOp
|
||||
}
|
||||
|
||||
func benchmark(b *testing.B, runtime func() Runtime, rtCfg *RuntimeConfig, call func(Module, int) error) {
|
||||
rt := runtime()
|
||||
b.Run("Compile", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
benchmarkCompile(b, rt, rtCfg)
|
||||
})
|
||||
b.Run("Instantiate", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
benchmarkInstantiate(b, rt, rtCfg)
|
||||
})
|
||||
|
||||
// Don't burn CPU when this is already going to be called in runTestBenchmark_Call_CompilerFastest
|
||||
if call != nil && (ensureCompilerFastest != "true" || rt.Name() == compilerRuntime) {
|
||||
b.Run("Call", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
benchmarkCall(b, rt, rtCfg, call)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkCompile(b *testing.B, rt Runtime, rtCfg *RuntimeConfig) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := rt.Compile(testCtx, rtCfg); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := rt.Close(testCtx); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkInstantiate(b *testing.B, rt Runtime, rtCfg *RuntimeConfig) {
|
||||
// Compile outside the benchmark loop
|
||||
if err := rt.Compile(testCtx, rtCfg); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rt.Close(testCtx)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod, err := rt.Instantiate(testCtx, rtCfg)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
err = mod.Close(testCtx)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkCall(b *testing.B, rt Runtime, rtCfg *RuntimeConfig, call func(Module, int) error) {
|
||||
// Initialize outside the benchmark loop
|
||||
if err := rt.Compile(testCtx, rtCfg); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rt.Close(testCtx)
|
||||
mod, err := rt.Instantiate(testCtx, rtCfg)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mod.Close(testCtx)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := call(mod, i); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCall(t *testing.T, runtime func() Runtime, rtCfg *RuntimeConfig, testCall func(*testing.T, Module, int, int)) {
|
||||
rt := runtime()
|
||||
err := rt.Compile(testCtx, rtCfg)
|
||||
require.NoError(t, err)
|
||||
defer rt.Close(testCtx)
|
||||
|
||||
// Ensure the module can be re-instantiated times, even if not all runtimes allow renaming.
|
||||
for i := 0; i < 10; i++ {
|
||||
m, err := rt.Instantiate(testCtx, rtCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Large loop in test is only to show the function is stable (ex doesn't leak or crash on Nth use).
|
||||
for j := 0; j < 1000; j++ {
|
||||
testCall(t, m, i, j)
|
||||
}
|
||||
|
||||
require.NoError(t, m.Close(testCtx))
|
||||
}
|
||||
}
|
||||
|
||||
func testInstantiate(t *testing.T, runtime func() Runtime, rtCfg *RuntimeConfig) {
|
||||
rt := runtime()
|
||||
err := rt.Compile(testCtx, rtCfg)
|
||||
require.NoError(t, err)
|
||||
defer rt.Close(testCtx)
|
||||
|
||||
// Ensure the module can be re-instantiated times, even if not all runtimes allow renaming.
|
||||
for i := 0; i < 10; i++ {
|
||||
m, err := rt.Instantiate(testCtx, rtCfg)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, m.Close(testCtx))
|
||||
}
|
||||
}
|
||||
|
||||
func readRelativeFile(relativePath string) []byte {
|
||||
// We can't resolve relative paths as init() is called from each of its subdirs
|
||||
_, source, _, _ := runtime.Caller(1) // 1 as this utility is in a different source than the caller.
|
||||
realPath := path.Join(path.Dir(source), relativePath)
|
||||
bytes, err := os.ReadFile(realPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
var (
|
||||
// allocationWasm is compiled from ../../../examples/allocation/tinygo/testdata/src/greet.go
|
||||
// We can't use go:embed as it is outside this directory. Copying it isn't ideal due to size and drift.
|
||||
allocationWasmPath = "../../../examples/allocation/tinygo/testdata/greet.wasm"
|
||||
allocationWasm []byte
|
||||
allocationParam = "wazero"
|
||||
allocationResult = []byte("wasm >> Hello, wazero!")
|
||||
allocationConfig *RuntimeConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
allocationWasm = readRelativeFile(allocationWasmPath)
|
||||
allocationConfig = &RuntimeConfig{
|
||||
ModuleName: "greet",
|
||||
ModuleWasm: allocationWasm,
|
||||
FuncNames: []string{"malloc", "free", "greet"},
|
||||
NeedsWASI: true, // Needed for TinyGo
|
||||
LogFn: func(buf []byte) error {
|
||||
if !bytes.Equal(allocationResult, buf) {
|
||||
return fmt.Errorf("expected %q, but was %q", allocationResult, buf)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func allocationCall(m Module, _ int) error {
|
||||
nameSize := uint32(len(allocationParam))
|
||||
// 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.
|
||||
namePtr, err := m.CallI32_I32(testCtx, "malloc", nameSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the name.
|
||||
if err = m.WriteMemory(namePtr, []byte(allocationParam)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now, we can call "greeting", which reads the string we wrote to memory!
|
||||
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 nil
|
||||
}
|
||||
|
||||
func RunTestAllocation(t *testing.T, runtime func() Runtime) {
|
||||
testCall(t, runtime, allocationConfig, testAllocationCall)
|
||||
}
|
||||
|
||||
func testAllocationCall(t *testing.T, m Module, instantiation, iteration int) {
|
||||
err := allocationCall(m, iteration)
|
||||
require.NoError(t, err, "instantiation[%d] iteration[%d] failed: %v", instantiation, iteration, err)
|
||||
}
|
||||
|
||||
func RunTestBenchmarkAllocation_Call_CompilerFastest(t *testing.T, vsRuntime Runtime) {
|
||||
runTestBenchmark_Call_CompilerFastest(t, allocationConfig, "Allocation", allocationCall, vsRuntime)
|
||||
}
|
||||
|
||||
func RunBenchmarkAllocation(b *testing.B, runtime func() Runtime) {
|
||||
benchmark(b, runtime, allocationConfig, allocationCall)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
var (
|
||||
// catFS is an embedded filesystem limited to test.txt
|
||||
//go:embed testdata/fac.wasm
|
||||
factorialWasm []byte
|
||||
factorialParam = uint64(30)
|
||||
factorialResult = uint64(9682165104862298112)
|
||||
factorialConfig *RuntimeConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
factorialConfig = &RuntimeConfig{
|
||||
ModuleName: "math",
|
||||
ModuleWasm: factorialWasm,
|
||||
FuncNames: []string{"fac-ssa"},
|
||||
}
|
||||
}
|
||||
|
||||
func factorialCall(m Module, _ int) error {
|
||||
_, err := m.CallI64_I64(testCtx, "fac-ssa", factorialParam)
|
||||
return err
|
||||
}
|
||||
|
||||
func RunTestFactorial(t *testing.T, runtime func() Runtime) {
|
||||
testCall(t, runtime, factorialConfig, testFactorialCall)
|
||||
}
|
||||
|
||||
func testFactorialCall(t *testing.T, m Module, instantiation, iteration int) {
|
||||
res, err := m.CallI64_I64(testCtx, "fac-ssa", factorialParam)
|
||||
require.NoError(t, err, "instantiation[%d] iteration[%d] failed", instantiation, iteration)
|
||||
require.Equal(t, factorialResult, res)
|
||||
}
|
||||
|
||||
func RunTestBenchmarkFactorial_Call_CompilerFastest(t *testing.T, vsRuntime Runtime) {
|
||||
runTestBenchmark_Call_CompilerFastest(t, factorialConfig, "Factorial", factorialCall, vsRuntime)
|
||||
}
|
||||
|
||||
func RunBenchmarkFactorial(b *testing.B, runtime func() Runtime) {
|
||||
benchmark(b, runtime, factorialConfig, factorialCall)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/hostcall.wasm
|
||||
hostCallWasm []byte
|
||||
hostCallConfig *RuntimeConfig
|
||||
hostCallFunction = "call_host_func"
|
||||
hostCallParam = uint64(12345)
|
||||
)
|
||||
|
||||
func init() {
|
||||
hostCallConfig = &RuntimeConfig{
|
||||
ModuleName: "hostcall",
|
||||
ModuleWasm: hostCallWasm,
|
||||
FuncNames: []string{hostCallFunction},
|
||||
EnvFReturnValue: 0xffff,
|
||||
}
|
||||
}
|
||||
|
||||
func RunTestHostCall(t *testing.T, runtime func() Runtime) {
|
||||
testCall(t, runtime, hostCallConfig, testHostCall)
|
||||
}
|
||||
|
||||
func testHostCall(t *testing.T, m Module, instantiation, iteration int) {
|
||||
res, err := m.CallI64_I64(testCtx, hostCallFunction, hostCallParam)
|
||||
require.NoError(t, err, "instantiation[%d] iteration[%d] failed", instantiation, iteration)
|
||||
require.Equal(t, hostCallConfig.EnvFReturnValue, res)
|
||||
}
|
||||
|
||||
func RunTestBenchmarkHostCall_CompilerFastest(t *testing.T, vsRuntime Runtime) {
|
||||
runTestBenchmark_Call_CompilerFastest(t, hostCallConfig, "HostCall_CrossBoundary", hostCall, vsRuntime)
|
||||
}
|
||||
|
||||
func RunBenchmarkHostCall(b *testing.B, runtime func() Runtime) {
|
||||
benchmark(b, runtime, hostCallConfig, hostCall)
|
||||
}
|
||||
|
||||
func hostCall(m Module, _ int) error {
|
||||
_, err := m.CallI64_I64(testCtx, hostCallFunction, hostCallParam)
|
||||
return err
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
const (
|
||||
i32 = "i32"
|
||||
i32ValueMemoryOffset = 32
|
||||
i64 = "i64"
|
||||
i64ValueMemoryOffset = 64
|
||||
inWasmIteration = 100
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/memory.wasm
|
||||
memoryWasm []byte
|
||||
memoryConfig *RuntimeConfig
|
||||
memoryFunctions = []string{i32, i64}
|
||||
)
|
||||
|
||||
func init() {
|
||||
memoryConfig = &RuntimeConfig{
|
||||
ModuleName: "memory",
|
||||
ModuleWasm: memoryWasm,
|
||||
FuncNames: memoryFunctions,
|
||||
NeedsMemoryExport: true,
|
||||
}
|
||||
}
|
||||
|
||||
func RunTestMemory(t *testing.T, runtime func() Runtime) {
|
||||
t.Run(i32, func(t *testing.T) {
|
||||
testCall(t, runtime, memoryConfig, func(t *testing.T, m Module, instantiation int, iteration int) {
|
||||
buf := m.Memory()
|
||||
binary.LittleEndian.PutUint32(buf[i32ValueMemoryOffset:], inWasmIteration)
|
||||
err := m.CallV_V(testCtx, i32)
|
||||
require.NoError(t, err)
|
||||
if 0 != binary.LittleEndian.Uint32(buf[i32ValueMemoryOffset:]) {
|
||||
panic(fmt.Sprintf("BUG at iteration %d: %d", iteration, binary.LittleEndian.Uint32(buf[i32ValueMemoryOffset:])))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run(i64, func(t *testing.T) {
|
||||
testCall(t, runtime, memoryConfig, func(t *testing.T, m Module, instantiation int, iteration int) {
|
||||
buf := m.Memory()
|
||||
binary.LittleEndian.PutUint64(buf[i64ValueMemoryOffset:], inWasmIteration)
|
||||
err := m.CallV_V(testCtx, i64)
|
||||
require.NoError(t, err)
|
||||
if 0 != binary.LittleEndian.Uint64(buf[i64ValueMemoryOffset:]) {
|
||||
panic(fmt.Sprintf("BUG at iteration %d: %d", iteration, binary.LittleEndian.Uint64(buf[i32ValueMemoryOffset:])))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func RunTestBenchmarkMemory_CompilerFastest(t *testing.T, vsRuntime Runtime) {
|
||||
t.Run(i32, func(t *testing.T) {
|
||||
runTestBenchmark_Call_CompilerFastest(t, memoryConfig, "/memory.i32", memoryI32, vsRuntime)
|
||||
})
|
||||
t.Run(i64, func(t *testing.T) {
|
||||
runTestBenchmark_Call_CompilerFastest(t, memoryConfig, "/memory.i64", memoryI64, vsRuntime)
|
||||
})
|
||||
}
|
||||
|
||||
func RunBenchmarkMemory(b *testing.B, runtime func() Runtime) {
|
||||
b.Run(i32, func(b *testing.B) {
|
||||
benchmark(b, runtime, memoryConfig, memoryI32)
|
||||
})
|
||||
b.Run(i64, func(b *testing.B) {
|
||||
benchmark(b, runtime, memoryConfig, memoryI64)
|
||||
})
|
||||
}
|
||||
|
||||
func memoryI32(m Module, iteration int) (err error) {
|
||||
buf := m.Memory()
|
||||
binary.LittleEndian.PutUint32(buf[i32ValueMemoryOffset:], inWasmIteration)
|
||||
err = m.CallV_V(testCtx, i32)
|
||||
if 0 != binary.LittleEndian.Uint32(buf[i32ValueMemoryOffset:]) {
|
||||
panic(fmt.Sprintf("BUG at iteration %d", iteration))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func memoryI64(m Module, iteration int) (err error) {
|
||||
buf := m.Memory()
|
||||
binary.LittleEndian.PutUint64(buf[i64ValueMemoryOffset:], inWasmIteration)
|
||||
err = m.CallV_V(testCtx, i64)
|
||||
if 0 != binary.LittleEndian.Uint64(buf[i64ValueMemoryOffset:]) {
|
||||
panic(fmt.Sprintf("BUG at iteration %d", iteration))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
// shorthashWasm is a wasi binary which runs the same wasm function inside
|
||||
// a loop. See https://github.com/tetratelabs/wazero/issues/947
|
||||
//
|
||||
// Taken from https://github.com/jedisct1/webassembly-benchmarks/tree/master/2022-12/wasm
|
||||
//go:embed testdata/shorthash.wasm
|
||||
shorthashWasm []byte
|
||||
shorthashConfig *RuntimeConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
shorthashConfig = &RuntimeConfig{
|
||||
ModuleName: "shorthash",
|
||||
ModuleWasm: shorthashWasm,
|
||||
NeedsWASI: true, // runs as a _start function
|
||||
}
|
||||
}
|
||||
|
||||
func RunTestShorthash(t *testing.T, runtime func() Runtime) {
|
||||
// not testCall as there are no exported functions except _start
|
||||
testInstantiate(t, runtime, shorthashConfig)
|
||||
}
|
||||
|
||||
func RunBenchmarkShorthash(b *testing.B, runtime func() Runtime) {
|
||||
benchmark(b, runtime, shorthashConfig, nil)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
var runtime = vs.NewWazeroCompilerRuntime
|
||||
|
||||
func TestAllocation(t *testing.T) {
|
||||
vs.RunTestAllocation(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkAllocation(b *testing.B) {
|
||||
vs.RunBenchmarkAllocation(b, runtime)
|
||||
}
|
||||
|
||||
func TestFactorial(t *testing.T) {
|
||||
vs.RunTestFactorial(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkFactorial(b *testing.B) {
|
||||
vs.RunBenchmarkFactorial(b, runtime)
|
||||
}
|
||||
|
||||
func TestHostCall(t *testing.T) {
|
||||
vs.RunTestHostCall(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkHostCall(b *testing.B) {
|
||||
vs.RunBenchmarkHostCall(b, runtime)
|
||||
}
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
vs.RunTestMemory(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkMemory(b *testing.B) {
|
||||
vs.RunBenchmarkMemory(b, runtime)
|
||||
}
|
||||
|
||||
func TestShorthash(t *testing.T) {
|
||||
vs.RunTestShorthash(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkShorthash(b *testing.B) {
|
||||
vs.RunBenchmarkShorthash(b, runtime)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
var runtime = vs.NewWazeroInterpreterRuntime
|
||||
|
||||
func TestAllocation(t *testing.T) {
|
||||
vs.RunTestAllocation(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkAllocation(b *testing.B) {
|
||||
vs.RunBenchmarkAllocation(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkAllocation_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkAllocation_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestFactorial(t *testing.T) {
|
||||
vs.RunTestFactorial(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkFactorial(b *testing.B) {
|
||||
vs.RunBenchmarkFactorial(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkFactorial_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkFactorial_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestHostCall(t *testing.T) {
|
||||
vs.RunTestHostCall(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkHostCall(b *testing.B) {
|
||||
vs.RunBenchmarkHostCall(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkHostCall_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkHostCall_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
vs.RunTestMemory(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkMemory(b *testing.B) {
|
||||
vs.RunBenchmarkMemory(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkMemory_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkMemory_CompilerFastest(t, runtime())
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package vs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
type RuntimeConfig struct {
|
||||
Name string
|
||||
ModuleName string
|
||||
ModuleWasm []byte
|
||||
FuncNames []string
|
||||
NeedsWASI bool
|
||||
NeedsMemoryExport bool
|
||||
// LogFn requires the implementation to export a function "env.log" which accepts i32i32_v.
|
||||
// The implementation invoke this with a byte slice allocated from the offset, length pair.
|
||||
// This function simulates a host function that logs a message.
|
||||
LogFn func([]byte) error
|
||||
// EnvFReturnValue is set to non-zero if we want the runtime to instantiate "env" module with the function "f"
|
||||
// which accepts one i64 value and returns the EnvFReturnValue as i64. This is mutually exclusive to LogFn.
|
||||
EnvFReturnValue uint64
|
||||
}
|
||||
|
||||
type Runtime interface {
|
||||
Name() string
|
||||
Compile(context.Context, *RuntimeConfig) error
|
||||
Instantiate(context.Context, *RuntimeConfig) (Module, error)
|
||||
Close(context.Context) error
|
||||
}
|
||||
|
||||
type Module interface {
|
||||
CallI32_I32(ctx context.Context, funcName string, param uint32) (uint32, error)
|
||||
CallI32I32_V(ctx context.Context, funcName string, x, y uint32) error
|
||||
CallI32_V(ctx context.Context, funcName string, param uint32) error
|
||||
CallV_V(ctx context.Context, funcName string) error
|
||||
CallI64_I64(ctx context.Context, funcName string, param uint64) (uint64, error)
|
||||
WriteMemory(offset uint32, bytes []byte) error
|
||||
Memory() []byte
|
||||
Close(context.Context) error
|
||||
}
|
||||
|
||||
func NewWazeroInterpreterRuntime() Runtime {
|
||||
return newWazeroRuntime("wazero-interpreter", wazero.NewRuntimeConfigInterpreter())
|
||||
}
|
||||
|
||||
func NewWazeroCompilerRuntime() Runtime {
|
||||
return newWazeroRuntime(compilerRuntime, wazero.NewRuntimeConfigCompiler())
|
||||
}
|
||||
|
||||
func newWazeroRuntime(name string, config wazero.RuntimeConfig) *wazeroRuntime {
|
||||
return &wazeroRuntime{name: name, config: config}
|
||||
}
|
||||
|
||||
type wazeroRuntime struct {
|
||||
name string
|
||||
config wazero.RuntimeConfig
|
||||
runtime wazero.Runtime
|
||||
logFn func([]byte) error
|
||||
env, compiled wazero.CompiledModule
|
||||
}
|
||||
|
||||
type wazeroModule struct {
|
||||
wasi api.Closer
|
||||
env, mod api.Module
|
||||
funcs map[string]api.Function
|
||||
}
|
||||
|
||||
func (r *wazeroRuntime) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (m *wazeroModule) Memory() []byte {
|
||||
return m.mod.Memory().(*wasm.MemoryInstance).Buffer
|
||||
}
|
||||
|
||||
func (r *wazeroRuntime) log(_ context.Context, mod api.Module, stack []uint64) {
|
||||
offset, byteCount := uint32(stack[0]), uint32(stack[1])
|
||||
|
||||
buf, ok := mod.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
panic("out of memory reading log buffer")
|
||||
}
|
||||
if err := r.logFn(buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *wazeroRuntime) Compile(ctx context.Context, cfg *RuntimeConfig) (err error) {
|
||||
r.runtime = wazero.NewRuntimeWithConfig(ctx, r.config)
|
||||
if cfg.LogFn != nil {
|
||||
r.logFn = cfg.LogFn
|
||||
if r.env, err = r.runtime.NewHostModuleBuilder("env").
|
||||
NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(r.log), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).
|
||||
Export("log").
|
||||
Compile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cfg.EnvFReturnValue != 0 {
|
||||
if r.env, err = r.runtime.NewHostModuleBuilder("env").
|
||||
NewFunctionBuilder().
|
||||
WithGoFunction(api.GoFunc(func(ctx context.Context, stack []uint64) {
|
||||
stack[0] = cfg.EnvFReturnValue
|
||||
}), []api.ValueType{api.ValueTypeI64}, []api.ValueType{api.ValueTypeI64}).
|
||||
Export("f").
|
||||
Compile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.compiled, err = r.runtime.CompileModule(ctx, cfg.ModuleWasm)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wazeroRuntime) Instantiate(ctx context.Context, cfg *RuntimeConfig) (mod Module, err error) {
|
||||
wazeroCfg := wazero.NewModuleConfig().WithName(cfg.ModuleName)
|
||||
m := &wazeroModule{funcs: map[string]api.Function{}}
|
||||
|
||||
// Instantiate WASI, if configured.
|
||||
if cfg.NeedsWASI {
|
||||
if m.wasi, err = wasi_snapshot_preview1.Instantiate(ctx, r.runtime); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the host module, "env", if configured.
|
||||
if env := r.env; env != nil {
|
||||
if m.env, err = r.runtime.InstantiateModule(ctx, env, wazero.NewModuleConfig()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the module.
|
||||
if m.mod, err = r.runtime.InstantiateModule(ctx, r.compiled, wazeroCfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure function exports exist.
|
||||
for _, funcName := range cfg.FuncNames {
|
||||
if fn := m.mod.ExportedFunction(funcName); fn == nil {
|
||||
return nil, fmt.Errorf("%s is not an exported function", funcName)
|
||||
} else {
|
||||
m.funcs[funcName] = fn
|
||||
}
|
||||
}
|
||||
mod = m
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wazeroRuntime) Close(ctx context.Context) (err error) {
|
||||
if compiled := r.compiled; compiled != nil {
|
||||
err = compiled.Close(ctx)
|
||||
}
|
||||
r.compiled = nil
|
||||
if env := r.env; env != nil {
|
||||
err = env.Close(ctx)
|
||||
}
|
||||
r.env = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wazeroModule) CallV_V(ctx context.Context, funcName string) (err error) {
|
||||
_, err = m.funcs[funcName].Call(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wazeroModule) CallI32_I32(ctx context.Context, funcName string, param uint32) (uint32, error) {
|
||||
if results, err := m.funcs[funcName].Call(ctx, uint64(param)); err != nil {
|
||||
return 0, err
|
||||
} else if len(results) > 0 {
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *wazeroModule) CallI32I32_V(ctx context.Context, funcName string, x, y uint32) (err error) {
|
||||
_, err = m.funcs[funcName].Call(ctx, uint64(x), uint64(y))
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wazeroModule) CallI32_V(ctx context.Context, funcName string, param uint32) (err error) {
|
||||
_, err = m.funcs[funcName].Call(ctx, uint64(param))
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
return 0, err
|
||||
} else {
|
||||
return results[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wazeroModule) WriteMemory(offset uint32, bytes []byte) error {
|
||||
if !m.mod.Memory().Write(offset, bytes) {
|
||||
return errors.New("out of memory writing name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *wazeroModule) Close(ctx context.Context) (err error) {
|
||||
if mod := m.mod; mod != nil {
|
||||
err = mod.Close(ctx)
|
||||
}
|
||||
m.mod = nil
|
||||
if env := m.env; env != nil {
|
||||
err = env.Close(ctx)
|
||||
}
|
||||
m.env = nil
|
||||
if wasi := m.wasi; wasi != nil {
|
||||
err = wasi.Close(ctx)
|
||||
}
|
||||
m.wasi = nil
|
||||
return
|
||||
}
|
||||
BIN
internal/integration_test/vs/testdata/hostcall.wasm
vendored
BIN
internal/integration_test/vs/testdata/hostcall.wasm
vendored
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
(module
|
||||
;; env.f must be a host function for benchmarks on the cost of host calls which cross the Wasm<>Go boundary.
|
||||
(func $host_func (import "env" "f") (param i64) (result i64))
|
||||
;; call_host_func calls "env.f" and returns the resut as-is.
|
||||
(func (export "call_host_func") (param i64) (result i64)
|
||||
local.get 0
|
||||
call $host_func
|
||||
)
|
||||
)
|
||||
BIN
internal/integration_test/vs/testdata/mem_grow.wasm
vendored
BIN
internal/integration_test/vs/testdata/mem_grow.wasm
vendored
Binary file not shown.
BIN
internal/integration_test/vs/testdata/memory.wasm
vendored
BIN
internal/integration_test/vs/testdata/memory.wasm
vendored
Binary file not shown.
31
internal/integration_test/vs/testdata/memory.wat
vendored
31
internal/integration_test/vs/testdata/memory.wat
vendored
@@ -1,31 +0,0 @@
|
||||
(module
|
||||
(memory (export "memory") 1)
|
||||
|
||||
;; Load the i32 value at the offset 32, decrement it, and store it back at the same position
|
||||
;; until the value becomes zero.
|
||||
(func (export "i32")
|
||||
(loop
|
||||
i32.const 32
|
||||
(i32.load align=1 (i32.const 32))
|
||||
i32.const 1
|
||||
i32.sub
|
||||
i32.store
|
||||
(br_if 1 (i32.eqz (i32.load align=1 (i32.const 32)))) ;; exit.
|
||||
(br 0) ;; continue loop.
|
||||
)
|
||||
)
|
||||
|
||||
;; Load the i64 value at the offset 64, decrement it, and store it back at the same position
|
||||
;; until the value becomes zero.
|
||||
(func (export "i64")
|
||||
(loop
|
||||
i32.const 64
|
||||
(i64.load align=1 (i32.const 64))
|
||||
i64.const 1
|
||||
i64.sub
|
||||
i64.store
|
||||
(br_if 1 (i64.eqz (i64.load align=1 (i32.const 64)))) ;; exit.
|
||||
(br 0) ;; continue loop.
|
||||
)
|
||||
)
|
||||
)
|
||||
BIN
internal/integration_test/vs/testdata/shorthash.wasm
vendored
BIN
internal/integration_test/vs/testdata/shorthash.wasm
vendored
Binary file not shown.
@@ -1,7 +0,0 @@
|
||||
module github.com/tetratelabs/wazero/internal/integration_test/vs/clock
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/sys v0.1.0
|
||||
|
||||
replace github.com/tetratelabs/wazero => ../../../..
|
||||
@@ -1,2 +0,0 @@
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1,57 +0,0 @@
|
||||
// Package time benchmarks shows ways to implementing the platform clock more
|
||||
// efficiently. As long as CGO is available, all platforms can use
|
||||
// `runtime.nanotime` to more efficiently implement sys.Nanotime vs using
|
||||
// time.Since or x/sys.
|
||||
//
|
||||
// While results would be more impressive, this doesn't show how to use
|
||||
// `runtime.walltime` to avoid the double-performance vs using time.Now. The
|
||||
// corresponding function only exists in darwin, so prevents this benchmark
|
||||
// from running on other platforms.
|
||||
package time
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
//go:linkname nanotime runtime.nanotime
|
||||
func nanotime() int64
|
||||
|
||||
func BenchmarkClock(b *testing.B) {
|
||||
b.Run("time.Now", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = time.Now()
|
||||
}
|
||||
})
|
||||
b.Run("ClockGettime(CLOCK_REALTIME)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var now unix.Timespec
|
||||
if err := unix.ClockGettime(unix.CLOCK_REALTIME, &now); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
base := time.Now()
|
||||
b.Run("time.Since", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = time.Since(base)
|
||||
}
|
||||
})
|
||||
b.Run("runtime.nanotime", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = nanotime()
|
||||
}
|
||||
})
|
||||
b.Run("ClockGettime(CLOCK_MONOTONIC)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var tick unix.Timespec
|
||||
if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &tick); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
module github.com/tetratelabs/wazero/internal/integration_test/vs/wasmedge
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/second-state/WasmEdge-go v0.12.1
|
||||
github.com/tetratelabs/wazero v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/tetratelabs/wazero => ../../../..
|
||||
@@ -1,2 +0,0 @@
|
||||
github.com/second-state/WasmEdge-go v0.12.1 h1:rIRiRF35+8CtcYTsiW4KfFNIh3faRb3LoG2c7cCqQyI=
|
||||
github.com/second-state/WasmEdge-go v0.12.1/go.mod h1:HyBf9hVj1sRAjklsjc1Yvs9b5RcmthPG9z99dY78TKg=
|
||||
@@ -1,200 +0,0 @@
|
||||
//go:build cgo && !windows && wasmedge
|
||||
|
||||
// Note: WasmEdge depends on manual installation of a shared library.
|
||||
// e.g. wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | \
|
||||
// sudo bash -s -- -p /usr/local -e none -v ${WASMEDGE_VERSION}
|
||||
|
||||
package wasmedge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/second-state/WasmEdge-go/wasmedge"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
func newWasmedgeRuntime() vs.Runtime {
|
||||
return &wasmedgeRuntime{}
|
||||
}
|
||||
|
||||
type wasmedgeRuntime struct {
|
||||
conf *wasmedge.Configure
|
||||
logFn func([]byte) error
|
||||
}
|
||||
|
||||
type wasmedgeModule struct {
|
||||
store *wasmedge.Store
|
||||
vm *wasmedge.VM
|
||||
env *wasmedge.Module
|
||||
}
|
||||
|
||||
func (r *wasmedgeRuntime) Name() string {
|
||||
return "wasmedge"
|
||||
}
|
||||
|
||||
func (r *wasmedgeRuntime) log(_ interface{}, callframe *wasmedge.CallingFrame, params []interface{}) ([]interface{}, wasmedge.Result) {
|
||||
offset := params[0].(int32)
|
||||
byteCount := params[1].(int32)
|
||||
mem := callframe.GetMemoryByIndex(0)
|
||||
buf, err := mem.GetData(uint(offset), uint(byteCount))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return nil, wasmedge.Result_Fail
|
||||
}
|
||||
if err = r.logFn(buf); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
panic(err)
|
||||
}
|
||||
return nil, wasmedge.Result_Success
|
||||
}
|
||||
|
||||
func (r *wasmedgeRuntime) Compile(_ context.Context, cfg *vs.RuntimeConfig) (err error) {
|
||||
if cfg.NeedsWASI {
|
||||
r.conf = wasmedge.NewConfigure(wasmedge.WASI)
|
||||
} else {
|
||||
r.conf = wasmedge.NewConfigure()
|
||||
}
|
||||
// We can't re-use a store because "module name conflict" occurs even after releasing a VM
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wasmedgeRuntime) Instantiate(_ context.Context, cfg *vs.RuntimeConfig) (mod vs.Module, err error) {
|
||||
m := &wasmedgeModule{store: wasmedge.NewStore()}
|
||||
m.vm = wasmedge.NewVMWithConfigAndStore(r.conf, m.store)
|
||||
|
||||
// Instantiate WASI, if configured.
|
||||
if cfg.NeedsWASI {
|
||||
wasi := m.vm.GetImportModule(wasmedge.WASI)
|
||||
wasi.InitWasi(nil, nil, nil)
|
||||
}
|
||||
|
||||
// Instantiate the host module, "env", if configured.
|
||||
if cfg.LogFn != nil {
|
||||
r.logFn = cfg.LogFn
|
||||
m.env = wasmedge.NewModule("env")
|
||||
logType := wasmedge.NewFunctionType([]wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32}, nil)
|
||||
m.env.AddFunction("log", wasmedge.NewFunction(logType, r.log, nil, 0))
|
||||
if err = m.vm.RegisterModule(m.env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if cfg.EnvFReturnValue != 0 {
|
||||
m.env = wasmedge.NewModule("env")
|
||||
fType := wasmedge.NewFunctionType([]wasmedge.ValType{wasmedge.ValType_I64}, []wasmedge.ValType{wasmedge.ValType_I64})
|
||||
m.env.AddFunction("f", wasmedge.NewFunction(fType, func(interface{}, *wasmedge.CallingFrame, []interface{}) ([]interface{}, wasmedge.Result) {
|
||||
return []interface{}{int64(cfg.EnvFReturnValue)}, wasmedge.Result_Success
|
||||
}, nil, 0))
|
||||
if err = m.vm.RegisterModule(m.env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the module.
|
||||
if err = m.vm.RegisterWasmBuffer(cfg.ModuleName, cfg.ModuleWasm); err != nil {
|
||||
return
|
||||
}
|
||||
if err = m.vm.LoadWasmBuffer(cfg.ModuleWasm); err != nil {
|
||||
return
|
||||
}
|
||||
if err = m.vm.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = m.vm.Instantiate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If WASI is needed, we have to go back and invoke the _start function.
|
||||
if cfg.NeedsWASI {
|
||||
if _, err = m.vm.Execute("_start"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure function exports exist.
|
||||
for _, funcName := range cfg.FuncNames {
|
||||
if fn := m.vm.GetFunctionType(funcName); fn == nil {
|
||||
err = fmt.Errorf("%s is not an exported function", funcName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mod = m
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wasmedgeRuntime) Close(context.Context) error {
|
||||
if conf := r.conf; conf != nil {
|
||||
conf.Release()
|
||||
}
|
||||
r.conf = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) Memory() []byte {
|
||||
mod := m.vm.GetActiveModule()
|
||||
mem := mod.FindMemory("memory")
|
||||
d, err := mem.GetData(0, mem.GetPageSize()*65536)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) CallI32_I32(_ context.Context, funcName string, param uint32) (uint32, error) {
|
||||
if result, err := m.vm.Execute(funcName, int32(param)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return uint32(result[0].(int32)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) CallI32I32_V(_ context.Context, funcName string, x, y uint32) (err error) {
|
||||
_, err = m.vm.Execute(funcName, int32(x), int32(y))
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) CallV_V(_ context.Context, funcName string) (err error) {
|
||||
_, err = m.vm.Execute(funcName)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) CallI32_V(_ context.Context, funcName string, param uint32) (err error) {
|
||||
_, err = m.vm.Execute(funcName, int32(param))
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) CallI64_I64(_ context.Context, funcName string, param uint64) (uint64, error) {
|
||||
if result, err := m.vm.Execute(funcName, int64(param)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return uint64(result[0].(int64)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) WriteMemory(offset uint32, bytes []byte) error {
|
||||
mod := m.vm.GetActiveModule()
|
||||
mem := mod.FindMemory("memory")
|
||||
if unsafeSlice, err := mem.GetData(uint(offset), uint(len(bytes))); err != nil {
|
||||
return err
|
||||
} else {
|
||||
copy(unsafeSlice, bytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *wasmedgeModule) Close(context.Context) error {
|
||||
if env := m.env; env != nil {
|
||||
env.Release()
|
||||
}
|
||||
if vm := m.vm; vm != nil {
|
||||
vm.Release()
|
||||
}
|
||||
m.vm = nil
|
||||
if store := m.store; store != nil {
|
||||
store.Release()
|
||||
}
|
||||
m.store = nil
|
||||
return nil
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//go:build cgo && wasmedge
|
||||
|
||||
// wasmedge depends on manual installation of a shared library, so is guarded by a flag by default.
|
||||
|
||||
package wasmedge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
var runtime = newWasmedgeRuntime
|
||||
|
||||
func TestAllocation(t *testing.T) {
|
||||
vs.RunTestAllocation(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkAllocation(b *testing.B) {
|
||||
vs.RunBenchmarkAllocation(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkAllocation_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkAllocation_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestFactorial(t *testing.T) {
|
||||
vs.RunTestFactorial(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkFactorial(b *testing.B) {
|
||||
vs.RunBenchmarkFactorial(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkFactorial_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkFactorial_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestHostCall(t *testing.T) {
|
||||
vs.RunTestHostCall(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkHostCall(b *testing.B) {
|
||||
vs.RunBenchmarkHostCall(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkHostCall_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkHostCall_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
vs.RunTestMemory(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkMemory(b *testing.B) {
|
||||
vs.RunBenchmarkMemory(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkMemory_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkMemory_CompilerFastest(t, runtime())
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
module github.com/tetratelabs/wazero/internal/integration_test/vs/wasmtime
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/bytecodealliance/wasmtime-go/v9 v9.0.0
|
||||
github.com/tetratelabs/wazero v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/tetratelabs/wazero => ../../../..
|
||||
@@ -1,6 +0,0 @@
|
||||
github.com/bytecodealliance/wasmtime-go/v9 v9.0.0 h1:lkyiPbbo++bSmDyJVxDQwxxaiu3LOFVm0iBHnTS1W5A=
|
||||
github.com/bytecodealliance/wasmtime-go/v9 v9.0.0/go.mod h1:zpOxt1j5vj44AzXZVhS4H+hr39vMk4hDlyC42kGksbU=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -1,205 +0,0 @@
|
||||
//go:build cgo
|
||||
|
||||
package wasmtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
wt "github.com/bytecodealliance/wasmtime-go/v9"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
func newWasmtimeRuntime() vs.Runtime {
|
||||
return &wasmtimeRuntime{}
|
||||
}
|
||||
|
||||
type wasmtimeRuntime struct {
|
||||
engine *wt.Engine
|
||||
module *wt.Module
|
||||
}
|
||||
|
||||
type wasmtimeModule struct {
|
||||
store *wt.Store
|
||||
// instance is here because there's no close/destroy function. The only thing is garbage collection.
|
||||
instance *wt.Instance
|
||||
funcs map[string]*wt.Func
|
||||
logFn func([]byte) error
|
||||
mem *wt.Memory
|
||||
}
|
||||
|
||||
func (r *wasmtimeRuntime) Name() string {
|
||||
return "wasmtime"
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) log(_ *wt.Caller, args []wt.Val) ([]wt.Val, *wt.Trap) {
|
||||
unsafeSlice := m.mem.UnsafeData(m.store)
|
||||
offset := args[0].I32()
|
||||
byteCount := args[1].I32()
|
||||
if err := m.logFn(unsafeSlice[offset : offset+byteCount]); err != nil {
|
||||
return nil, wt.NewTrap(err.Error())
|
||||
}
|
||||
return []wt.Val{}, nil
|
||||
}
|
||||
|
||||
func (r *wasmtimeRuntime) Compile(_ context.Context, cfg *vs.RuntimeConfig) (err error) {
|
||||
r.engine = wt.NewEngine()
|
||||
if r.module, err = wt.NewModule(r.engine, cfg.ModuleWasm); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wasmtimeRuntime) Instantiate(_ context.Context, cfg *vs.RuntimeConfig) (mod vs.Module, err error) {
|
||||
wm := &wasmtimeModule{funcs: map[string]*wt.Func{}}
|
||||
|
||||
// We can't reuse a store because even if we call close, re-instantiating too many times leads to:
|
||||
// >> resource limit exceeded: instance count too high at 10001
|
||||
wm.store = wt.NewStore(r.engine)
|
||||
linker := wt.NewLinker(wm.store.Engine)
|
||||
|
||||
// Instantiate WASI, if configured.
|
||||
if cfg.NeedsWASI {
|
||||
if err = linker.DefineWasi(); err != nil {
|
||||
return
|
||||
}
|
||||
config := wt.NewWasiConfig() // defaults to toss stdout
|
||||
config.InheritStderr() // see errors
|
||||
wm.store.SetWasi(config)
|
||||
}
|
||||
|
||||
// Instantiate the host module, "env", if configured.
|
||||
if cfg.LogFn != nil {
|
||||
wm.logFn = cfg.LogFn
|
||||
if err = linker.Define(wm.store, "env", "log", wt.NewFunc(
|
||||
wm.store,
|
||||
wt.NewFuncType(
|
||||
[]*wt.ValType{
|
||||
wt.NewValType(wt.KindI32),
|
||||
wt.NewValType(wt.KindI32),
|
||||
},
|
||||
[]*wt.ValType{},
|
||||
),
|
||||
wm.log,
|
||||
)); err != nil {
|
||||
return
|
||||
}
|
||||
} else if cfg.EnvFReturnValue != 0 {
|
||||
ret := []wt.Val{wt.ValI64(int64(cfg.EnvFReturnValue))}
|
||||
if err = linker.Define(wm.store, "env", "f", wt.NewFunc(
|
||||
wm.store,
|
||||
wt.NewFuncType(
|
||||
[]*wt.ValType{
|
||||
wt.NewValType(wt.KindI64),
|
||||
},
|
||||
[]*wt.ValType{wt.NewValType(wt.KindI64)},
|
||||
),
|
||||
func(_ *wt.Caller, args []wt.Val) ([]wt.Val, *wt.Trap) {
|
||||
return ret, nil
|
||||
},
|
||||
)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set the module name.
|
||||
if err = linker.DefineModule(wm.store, cfg.ModuleName, r.module); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Instantiate the module.
|
||||
instance, instantiateErr := linker.Instantiate(wm.store, r.module)
|
||||
if instantiateErr != nil {
|
||||
err = instantiateErr
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.LogFn != nil || cfg.NeedsMemoryExport {
|
||||
// Wasmtime does not allow a host function parameter for memory, so you have to manually propagate it.
|
||||
if wm.mem = instance.GetExport(wm.store, "memory").Memory(); wm.mem == nil {
|
||||
err = fmt.Errorf(`"memory" not exported`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If WASI is needed, we have to go back and invoke the _start function.
|
||||
if cfg.NeedsWASI {
|
||||
start := instance.GetFunc(wm.store, "_start")
|
||||
if _, err = start.Call(wm.store); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure function exports exist.
|
||||
for _, funcName := range cfg.FuncNames {
|
||||
if fn := instance.GetFunc(wm.store, funcName); fn == nil {
|
||||
err = fmt.Errorf("%s is not an exported function", funcName)
|
||||
return
|
||||
} else {
|
||||
wm.funcs[funcName] = fn
|
||||
}
|
||||
}
|
||||
|
||||
mod = wm
|
||||
return
|
||||
}
|
||||
|
||||
func (r *wasmtimeRuntime) Close(context.Context) error {
|
||||
r.module = nil
|
||||
r.engine = nil
|
||||
return nil // wt only closes via finalizer
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) Memory() []byte {
|
||||
return m.mem.UnsafeData(m.store)
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) CallI32_I32(_ context.Context, funcName string, param uint32) (uint32, error) {
|
||||
fn := m.funcs[funcName]
|
||||
if result, err := fn.Call(m.store, int32(param)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return uint32(result.(int32)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) CallI32I32_V(_ context.Context, funcName string, x, y uint32) (err error) {
|
||||
fn := m.funcs[funcName]
|
||||
_, err = fn.Call(m.store, int32(x), int32(y))
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) CallV_V(_ context.Context, funcName string) (err error) {
|
||||
fn := m.funcs[funcName]
|
||||
_, err = fn.Call(m.store)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) CallI32_V(_ context.Context, funcName string, param uint32) (err error) {
|
||||
fn := m.funcs[funcName]
|
||||
_, err = fn.Call(m.store, int32(param))
|
||||
return
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) CallI64_I64(_ context.Context, funcName string, param uint64) (uint64, error) {
|
||||
fn := m.funcs[funcName]
|
||||
if result, err := fn.Call(m.store, int64(param)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return uint64(result.(int64)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) WriteMemory(offset uint32, bytes []byte) error {
|
||||
unsafeSlice := m.mem.UnsafeData(m.store)
|
||||
copy(unsafeSlice[offset:], bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *wasmtimeModule) Close(context.Context) error {
|
||||
m.store = nil
|
||||
m.instance = nil
|
||||
m.funcs = nil
|
||||
return nil // wt only closes via finalizer
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
//go:build cgo
|
||||
|
||||
package wasmtime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/vs"
|
||||
)
|
||||
|
||||
var runtime = newWasmtimeRuntime
|
||||
|
||||
func TestAllocation(t *testing.T) {
|
||||
vs.RunTestAllocation(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkAllocation(b *testing.B) {
|
||||
vs.RunBenchmarkAllocation(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkAllocation_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkAllocation_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestFactorial(t *testing.T) {
|
||||
vs.RunTestFactorial(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkFactorial(b *testing.B) {
|
||||
vs.RunBenchmarkFactorial(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkFactorial_Call_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkFactorial_Call_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestHostCall(t *testing.T) {
|
||||
vs.RunTestHostCall(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkHostCall(b *testing.B) {
|
||||
vs.RunBenchmarkHostCall(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkHostCall_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkHostCall_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
vs.RunTestMemory(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkMemory(b *testing.B) {
|
||||
vs.RunBenchmarkMemory(b, runtime)
|
||||
}
|
||||
|
||||
func TestBenchmarkMemory_CompilerFastest(t *testing.T) {
|
||||
vs.RunTestBenchmarkMemory_CompilerFastest(t, runtime())
|
||||
}
|
||||
|
||||
func TestShorthash(t *testing.T) {
|
||||
vs.RunTestShorthash(t, runtime)
|
||||
}
|
||||
|
||||
func BenchmarkShorthash(b *testing.B) {
|
||||
vs.RunBenchmarkShorthash(b, runtime)
|
||||
}
|
||||
@@ -27,4 +27,4 @@
|
||||
(drop) (return)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
BIN
testdata/mem_grow.wasm
vendored
Normal file
BIN
testdata/mem_grow.wasm
vendored
Normal file
Binary file not shown.
@@ -11,4 +11,4 @@
|
||||
return
|
||||
)
|
||||
(start $main)
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user