Adds function names to host functions and improves logging listener (#697)
This improves the experimental logging listener to show parameter name
and values like so:
```
--> ._start.command_export()
--> .__wasm_call_ctors()
--> .__wasilibc_initialize_environ()
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=1048572,result.environBufSize=1048568)
<== ESUCCESS
<-- ()
==> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=1048568)
<== ESUCCESS
--> .dlmalloc(2)
--> .sbrk(0)
<-- (1114112)
<-- (1060080)
--snip--
```
The convention `==>` implies it was a host function call
(def.IsHostFunction). This also improves the lifecycle by creating
listeners during compile. Finally, this backfills param names for
assemblyscript and wasi.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
18
api/wasm.go
18
api/wasm.go
@@ -83,12 +83,17 @@ const (
|
|||||||
ValueTypeF32 ValueType = 0x7d
|
ValueTypeF32 ValueType = 0x7d
|
||||||
// ValueTypeF64 is a 64-bit floating point number.
|
// ValueTypeF64 is a 64-bit floating point number.
|
||||||
ValueTypeF64 ValueType = 0x7c
|
ValueTypeF64 ValueType = 0x7c
|
||||||
|
|
||||||
// ValueTypeExternref is a externref type.
|
// ValueTypeExternref is a externref type.
|
||||||
//
|
//
|
||||||
// Note: in wazero, externref type value are opaque raw 64-bit pointers, and the ValueTypeExternref type
|
// Note: in wazero, externref type value are opaque raw 64-bit pointers,
|
||||||
// in the signature will be translated as uintptr in wazero's API level.
|
// and the ValueTypeExternref type in the signature will be translated as
|
||||||
// For example, the import function `(func (import "env" "f") (param externref) (result externref))` can be defined in Go as:
|
// uintptr in wazero's API level.
|
||||||
//
|
//
|
||||||
|
// For example, given the import function:
|
||||||
|
// (func (import "env" "f") (param externref) (result externref))
|
||||||
|
//
|
||||||
|
// This can be defined in Go as:
|
||||||
// r.NewModuleBuilder("env").ExportFunctions(map[string]interface{}{
|
// r.NewModuleBuilder("env").ExportFunctions(map[string]interface{}{
|
||||||
// "f": func(externref uintptr) (resultExternRef uintptr) { return },
|
// "f": func(externref uintptr) (resultExternRef uintptr) { return },
|
||||||
// })
|
// })
|
||||||
@@ -222,6 +227,13 @@ type FunctionDefinition interface {
|
|||||||
// is possible.
|
// is possible.
|
||||||
ExportNames() []string
|
ExportNames() []string
|
||||||
|
|
||||||
|
// IsHostFunction returns true if the function was implemented by the
|
||||||
|
// embedder (ex via wazero.ModuleBuilder) instead of a wasm binary.
|
||||||
|
//
|
||||||
|
// Note: Host functions can be non-deterministic or cause side effects.
|
||||||
|
// See https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0
|
||||||
|
IsHostFunction() bool
|
||||||
|
|
||||||
// ParamTypes are the possibly empty sequence of value types accepted by a
|
// ParamTypes are the possibly empty sequence of value types accepted by a
|
||||||
// function with this signature.
|
// function with this signature.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ import (
|
|||||||
// * To add more functions to the "env" module, use FunctionExporter.
|
// * To add more functions to the "env" module, use FunctionExporter.
|
||||||
// * To instantiate into another wazero.Namespace, use FunctionExporter.
|
// * To instantiate into another wazero.Namespace, use FunctionExporter.
|
||||||
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
||||||
return r.NewModuleBuilder("env").
|
builder := r.NewModuleBuilder("env")
|
||||||
ExportFunctions(NewFunctionExporter().ExportFunctions()).
|
NewFunctionExporter().ExportFunctions(builder)
|
||||||
Instantiate(ctx, r)
|
return builder.Instantiate(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionExporter configures the functions in the "env" module used by
|
// FunctionExporter configures the functions in the "env" module used by
|
||||||
@@ -70,7 +70,7 @@ type FunctionExporter interface {
|
|||||||
|
|
||||||
// ExportFunctions builds functions to export with a wazero.ModuleBuilder
|
// ExportFunctions builds functions to export with a wazero.ModuleBuilder
|
||||||
// named "env".
|
// named "env".
|
||||||
ExportFunctions() (nameToGoFunc map[string]interface{})
|
ExportFunctions(builder wazero.ModuleBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
|
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
|
||||||
@@ -113,13 +113,13 @@ func (e *functionExporter) WithTraceToStderr() FunctionExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExportFunctions implements FunctionExporter.ExportFunctions
|
// ExportFunctions implements FunctionExporter.ExportFunctions
|
||||||
func (e *functionExporter) ExportFunctions() (nameToGoFunc map[string]interface{}) {
|
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
|
||||||
env := &assemblyscript{abortMessageDisabled: e.abortMessageDisabled, traceMode: e.traceMode}
|
env := &assemblyscript{abortMessageDisabled: e.abortMessageDisabled, traceMode: e.traceMode}
|
||||||
return map[string]interface{}{
|
builder.ExportFunction("abort", env.abort, "~lib/builtins/abort",
|
||||||
"abort": env.abort,
|
"message", "fileName", "lineNumber", "columnNumber")
|
||||||
"trace": env.trace,
|
builder.ExportFunction("trace", env.trace, "~lib/builtins/trace",
|
||||||
"seed": env.seed,
|
"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4")
|
||||||
}
|
builder.ExportFunction("seed", env.seed, "~lib/builtins/seed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// assemblyScript includes "Special imports" only used In AssemblyScript when a
|
// assemblyScript includes "Special imports" only used In AssemblyScript when a
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ func Example_functionExporter() {
|
|||||||
ExportFunction("get_int", func() uint32 { return 1 })
|
ExportFunction("get_int", func() uint32 { return 1 })
|
||||||
|
|
||||||
// Now, add AssemblyScript special function imports into it.
|
// Now, add AssemblyScript special function imports into it.
|
||||||
envBuilder.ExportFunctions(assemblyscript.NewFunctionExporter().
|
assemblyscript.NewFunctionExporter().
|
||||||
WithAbortMessageDisabled().ExportFunctions())
|
WithAbortMessageDisabled().
|
||||||
|
ExportFunctions(envBuilder)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/iotest"
|
"testing/iotest"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero"
|
"github.com/tetratelabs/wazero"
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
. "github.com/tetratelabs/wazero/experimental"
|
||||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||||
"github.com/tetratelabs/wazero/internal/watzero"
|
"github.com/tetratelabs/wazero/internal/watzero"
|
||||||
"github.com/tetratelabs/wazero/sys"
|
"github.com/tetratelabs/wazero/sys"
|
||||||
@@ -60,32 +62,37 @@ func TestAbort(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var out, log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
// Set context to one that has an experimental listener
|
||||||
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
|
|
||||||
_, err := r.NewModuleBuilder("env").
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
ExportFunctions(tc.exporter.ExportFunctions()).
|
defer r.Close(ctx)
|
||||||
Instantiate(testCtx, r)
|
|
||||||
|
envBuilder := r.NewModuleBuilder("env")
|
||||||
|
tc.exporter.ExportFunctions(envBuilder)
|
||||||
|
_, err := envBuilder.Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
code, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
code, err := r.CompileModule(ctx, abortWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithStderr(out))
|
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStderr(&out))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
|
messageOff, filenameOff := writeAbortMessageAndFileName(ctx, t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
_, err = mod.ExportedFunction("abort").Call(ctx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
|
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
|
||||||
|
|
||||||
require.Equal(t, tc.expected, out.String())
|
require.Equal(t, tc.expected, out.String())
|
||||||
|
require.Equal(t, `==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
|
||||||
|
`, log.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,16 +102,23 @@ func TestAbort_Error(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
messageUTF16 []byte
|
messageUTF16 []byte
|
||||||
fileNameUTF16 []byte
|
fileNameUTF16 []byte
|
||||||
|
expectedLog string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bad message",
|
name: "bad message",
|
||||||
messageUTF16: encodeUTF16("message")[:5],
|
messageUTF16: encodeUTF16("message")[:5],
|
||||||
fileNameUTF16: encodeUTF16("filename"),
|
fileNameUTF16: encodeUTF16("filename"),
|
||||||
|
expectedLog: `==> env.~lib/builtins/abort(message=4,fileName=13,lineNumber=1,columnNumber=2)
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad filename",
|
name: "bad filename",
|
||||||
messageUTF16: encodeUTF16("message"),
|
messageUTF16: encodeUTF16("message"),
|
||||||
fileNameUTF16: encodeUTF16("filename")[:5],
|
fileNameUTF16: encodeUTF16("filename")[:5],
|
||||||
|
expectedLog: `==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,28 +126,33 @@ func TestAbort_Error(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var out, log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := Instantiate(testCtx, r)
|
// Set context to one that has an experimental listener
|
||||||
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
|
_, err := Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
compiled, err := r.CompileModule(ctx, abortWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(&out)
|
||||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out)
|
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
messageOff, filenameOff := writeAbortMessageAndFileName(ctx, t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
_, err = mod.ExportedFunction("abort").Call(ctx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
||||||
|
require.Equal(t, tc.expectedLog, log.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,48 +172,63 @@ var unreachableAfterAbort = `(module
|
|||||||
|
|
||||||
// TestAbort_UnreachableAfter ensures code that follows an abort isn't invoked.
|
// TestAbort_UnreachableAfter ensures code that follows an abort isn't invoked.
|
||||||
func TestAbort_UnreachableAfter(t *testing.T) {
|
func TestAbort_UnreachableAfter(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := r.NewModuleBuilder("env").
|
// Set context to one that has an experimental listener
|
||||||
// Disable the abort message as we are passing invalid memory offsets.
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
ExportFunctions(NewFunctionExporter().WithAbortMessageDisabled().ExportFunctions()).
|
|
||||||
Instantiate(testCtx, r)
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
|
envBuilder := r.NewModuleBuilder("env")
|
||||||
|
// Disable the abort message as we are passing invalid memory offsets.
|
||||||
|
NewFunctionExporter().WithAbortMessageDisabled().ExportFunctions(envBuilder)
|
||||||
|
_, err := envBuilder.Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort)
|
abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = r.InstantiateModuleFromBinary(testCtx, abortWasm)
|
_, err = r.InstantiateModuleFromBinary(ctx, abortWasm)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
|
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
|
||||||
|
require.Equal(t, `--> .main()
|
||||||
|
==> env.~lib/builtins/abort(message=0,fileName=0,lineNumber=0,columnNumber=0)
|
||||||
|
`, log.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSeed(t *testing.T) {
|
func TestSeed(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
// Set context to one that has an experimental listener
|
||||||
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
seed := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
seed := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
||||||
|
|
||||||
_, err := Instantiate(testCtx, r)
|
_, err := Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
code, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
code, err := r.CompileModule(ctx, seedWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithRandSource(bytes.NewReader(seed)))
|
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithRandSource(bytes.NewReader(seed)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
seedFn := mod.ExportedFunction("seed")
|
seedFn := mod.ExportedFunction("seed")
|
||||||
|
|
||||||
res, err := seedFn.Call(testCtx)
|
_, err = seedFn.Call(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// If this test doesn't break, the seed is deterministic.
|
// If this test doesn't break, the seed is deterministic.
|
||||||
require.Equal(t, uint64(506097522914230528), res[0])
|
require.Equal(t, `==> env.~lib/builtins/seed()
|
||||||
|
<== (7.949928895127363e-275)
|
||||||
|
`, log.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSeed_error(t *testing.T) {
|
func TestSeed_error(t *testing.T) {
|
||||||
@@ -208,14 +242,14 @@ func TestSeed_error(t *testing.T) {
|
|||||||
source: bytes.NewReader([]byte{0, 1}),
|
source: bytes.NewReader([]byte{0, 1}),
|
||||||
expectedErr: `error reading random seed: unexpected EOF (recovered by wazero)
|
expectedErr: `error reading random seed: unexpected EOF (recovered by wazero)
|
||||||
wasm stack trace:
|
wasm stack trace:
|
||||||
env.seed() f64`,
|
env.~lib/builtins/seed() f64`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error reading",
|
name: "error reading",
|
||||||
source: iotest.ErrReader(errors.New("ice cream")),
|
source: iotest.ErrReader(errors.New("ice cream")),
|
||||||
expectedErr: `error reading random seed: ice cream (recovered by wazero)
|
expectedErr: `error reading random seed: ice cream (recovered by wazero)
|
||||||
wasm stack trace:
|
wasm stack trace:
|
||||||
env.seed() f64`,
|
env.~lib/builtins/seed() f64`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,65 +257,84 @@ wasm stack trace:
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := Instantiate(testCtx, r)
|
// Set context to one that has an experimental listener
|
||||||
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
|
_, err := Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
compiled, err := r.CompileModule(ctx, seedWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("seed").Call(testCtx)
|
_, err = mod.ExportedFunction("seed").Call(ctx)
|
||||||
require.EqualError(t, err, tc.expectedErr)
|
require.EqualError(t, err, tc.expectedErr)
|
||||||
|
require.Equal(t, `==> env.~lib/builtins/seed()
|
||||||
|
`, log.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrace(t *testing.T) {
|
func TestTrace(t *testing.T) {
|
||||||
noArgs := []uint64{4, 0, 0, 0, 0, 0, 0}
|
noArgs := []uint64{4, 0, 0, 0, 0, 0, 0}
|
||||||
|
noArgsLog := `==> env.~lib/builtins/trace(message=4,nArgs=0,arg0=0,arg1=0,arg2=0,arg3=0,arg4=0)
|
||||||
|
<== ()
|
||||||
|
`
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
exporter FunctionExporter
|
exporter FunctionExporter
|
||||||
params []uint64
|
params []uint64
|
||||||
expected string
|
expected, expectedLog string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "disabled",
|
name: "disabled",
|
||||||
exporter: NewFunctionExporter(),
|
exporter: NewFunctionExporter(),
|
||||||
params: noArgs,
|
params: noArgs,
|
||||||
expected: "",
|
expected: "",
|
||||||
|
expectedLog: noArgsLog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ToStderr",
|
name: "ToStderr",
|
||||||
exporter: NewFunctionExporter().WithTraceToStderr(),
|
exporter: NewFunctionExporter().WithTraceToStderr(),
|
||||||
params: noArgs,
|
params: noArgs,
|
||||||
expected: "trace: hello\n",
|
expected: "trace: hello\n",
|
||||||
|
expectedLog: noArgsLog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ToStdout - no args",
|
name: "ToStdout - no args",
|
||||||
exporter: NewFunctionExporter().WithTraceToStdout(),
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: noArgs,
|
params: noArgs,
|
||||||
expected: "trace: hello\n",
|
expected: "trace: hello\n",
|
||||||
|
expectedLog: noArgsLog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ToStdout - one arg",
|
name: "ToStdout - one arg",
|
||||||
exporter: NewFunctionExporter().WithTraceToStdout(),
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
|
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
|
||||||
expected: "trace: hello 1\n",
|
expected: "trace: hello 1\n",
|
||||||
|
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=1,arg0=1,arg1=0,arg2=0,arg3=0,arg4=0)
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ToStdout - two args",
|
name: "ToStdout - two args",
|
||||||
exporter: NewFunctionExporter().WithTraceToStdout(),
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
|
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
|
||||||
expected: "trace: hello 1,2\n",
|
expected: "trace: hello 1,2\n",
|
||||||
|
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=2,arg0=1,arg1=2,arg2=0,arg3=0,arg4=0)
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ToStdout - five args",
|
name: "ToStdout - five args",
|
||||||
@@ -296,6 +349,9 @@ func TestTrace(t *testing.T) {
|
|||||||
api.EncodeF64(5),
|
api.EncodeF64(5),
|
||||||
},
|
},
|
||||||
expected: "trace: hello 1,2,3.3,4.4,5\n",
|
expected: "trace: hello 1,2,3.3,4.4,5\n",
|
||||||
|
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=5,arg0=1,arg1=2,arg2=3.3,arg3=4.4,arg4=5)
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,40 +359,45 @@ func TestTrace(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var out, log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := r.NewModuleBuilder("env").
|
// Set context to one that has an experimental listener
|
||||||
ExportFunctions(tc.exporter.ExportFunctions()).
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
Instantiate(testCtx, r)
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
|
envBuilder := r.NewModuleBuilder("env")
|
||||||
|
tc.exporter.ExportFunctions(envBuilder)
|
||||||
|
_, err := envBuilder.Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
code, err := r.CompileModule(ctx, traceWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
config := wazero.NewModuleConfig()
|
config := wazero.NewModuleConfig()
|
||||||
if tc.name == "ToStderr" {
|
if strings.Contains("ToStderr", tc.name) {
|
||||||
config.WithStderr(out)
|
config = config.WithStderr(&out)
|
||||||
} else {
|
} else {
|
||||||
config.WithStdout(out)
|
config = config.WithStdout(&out)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithStdout(out).WithStderr(out))
|
mod, err := r.InstantiateModule(ctx, code, config)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
message := encodeUTF16("hello")
|
message := encodeUTF16("hello")
|
||||||
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(message)))
|
ok := mod.Memory().WriteUint32Le(ctx, 0, uint32(len(message)))
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
ok = mod.Memory().Write(testCtx, uint32(4), message)
|
ok = mod.Memory().Write(ctx, uint32(4), message)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("trace").Call(testCtx, tc.params...)
|
_, err = mod.ExportedFunction("trace").Call(ctx, tc.params...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expected, out.String())
|
require.Equal(t, tc.expected, out.String())
|
||||||
|
require.Equal(t, tc.expectedLog, log.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,7 +415,7 @@ func TestTrace_error(t *testing.T) {
|
|||||||
out: &bytes.Buffer{},
|
out: &bytes.Buffer{},
|
||||||
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
||||||
wasm stack trace:
|
wasm stack trace:
|
||||||
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
env.~lib/builtins/trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error writing",
|
name: "error writing",
|
||||||
@@ -362,7 +423,7 @@ wasm stack trace:
|
|||||||
out: &errWriter{err: errors.New("ice cream")},
|
out: &errWriter{err: errors.New("ice cream")},
|
||||||
expectedErr: `ice cream (recovered by wazero)
|
expectedErr: `ice cream (recovered by wazero)
|
||||||
wasm stack trace:
|
wasm stack trace:
|
||||||
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
env.~lib/builtins/trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,31 +431,38 @@ wasm stack trace:
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
var log bytes.Buffer
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := r.NewModuleBuilder("env").
|
// Set context to one that has an experimental listener
|
||||||
ExportFunctions(NewFunctionExporter().WithTraceToStdout().ExportFunctions()).
|
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||||
Instantiate(testCtx, r)
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx)
|
||||||
|
|
||||||
|
envBuilder := r.NewModuleBuilder("env")
|
||||||
|
NewFunctionExporter().WithTraceToStdout().ExportFunctions(envBuilder)
|
||||||
|
_, err := envBuilder.Instantiate(ctx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
compiled, err := r.CompileModule(ctx, traceWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
|
ok := mod.Memory().WriteUint32Le(ctx, 0, uint32(len(tc.message)))
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
|
ok = mod.Memory().Write(ctx, uint32(4), tc.message)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0)
|
_, err = mod.ExportedFunction("trace").Call(ctx, 4, 0, 0, 0, 0, 0, 0)
|
||||||
require.EqualError(t, err, tc.expectedErr)
|
require.EqualError(t, err, tc.expectedErr)
|
||||||
|
require.Equal(t, `==> env.~lib/builtins/trace(message=4,nArgs=0,arg0=0,arg1=0,arg2=0,arg3=0,arg4=0)
|
||||||
|
`, log.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,45 +470,45 @@ wasm stack trace:
|
|||||||
func Test_readAssemblyScriptString(t *testing.T) {
|
func Test_readAssemblyScriptString(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
memory func(api.Memory)
|
memory func(context.Context, api.Memory)
|
||||||
offset int
|
offset int
|
||||||
expected, expectedErr string
|
expected, expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
memory: func(memory api.Memory) {
|
memory: func(ctx context.Context, memory api.Memory) {
|
||||||
memory.WriteUint32Le(testCtx, 0, 10)
|
memory.WriteUint32Le(ctx, 0, 10)
|
||||||
b := encodeUTF16("hello")
|
b := encodeUTF16("hello")
|
||||||
memory.Write(testCtx, 4, b)
|
memory.Write(ctx, 4, b)
|
||||||
},
|
},
|
||||||
offset: 4,
|
offset: 4,
|
||||||
expected: "hello",
|
expected: "hello",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "can't read size",
|
name: "can't read size",
|
||||||
memory: func(memory api.Memory) {
|
memory: func(ctx context.Context, memory api.Memory) {
|
||||||
b := encodeUTF16("hello")
|
b := encodeUTF16("hello")
|
||||||
memory.Write(testCtx, 0, b)
|
memory.Write(ctx, 0, b)
|
||||||
},
|
},
|
||||||
offset: 0, // will attempt to read size from offset -4
|
offset: 0, // will attempt to read size from offset -4
|
||||||
expectedErr: "Memory.ReadUint32Le(4294967292) out of range",
|
expectedErr: "Memory.ReadUint32Le(4294967292) out of range",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "odd size",
|
name: "odd size",
|
||||||
memory: func(memory api.Memory) {
|
memory: func(ctx context.Context, memory api.Memory) {
|
||||||
memory.WriteUint32Le(testCtx, 0, 9)
|
memory.WriteUint32Le(ctx, 0, 9)
|
||||||
b := encodeUTF16("hello")
|
b := encodeUTF16("hello")
|
||||||
memory.Write(testCtx, 4, b)
|
memory.Write(ctx, 4, b)
|
||||||
},
|
},
|
||||||
offset: 4,
|
offset: 4,
|
||||||
expectedErr: "read an odd number of bytes for utf-16 string: 9",
|
expectedErr: "read an odd number of bytes for utf-16 string: 9",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "can't read string",
|
name: "can't read string",
|
||||||
memory: func(memory api.Memory) {
|
memory: func(ctx context.Context, memory api.Memory) {
|
||||||
memory.WriteUint32Le(testCtx, 0, 10_000_000) // set size to too large value
|
memory.WriteUint32Le(ctx, 0, 10_000_000) // set size to too large value
|
||||||
b := encodeUTF16("hello")
|
b := encodeUTF16("hello")
|
||||||
memory.Write(testCtx, 4, b)
|
memory.Write(ctx, 4, b)
|
||||||
},
|
},
|
||||||
offset: 4,
|
offset: 4,
|
||||||
expectedErr: "Memory.Read(4, 10000000) out of range",
|
expectedErr: "Memory.Read(4, 10000000) out of range",
|
||||||
@@ -459,7 +527,7 @@ func Test_readAssemblyScriptString(t *testing.T) {
|
|||||||
Instantiate(testCtx, r)
|
Instantiate(testCtx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tc.memory(mod.Memory())
|
tc.memory(testCtx, mod.Memory())
|
||||||
|
|
||||||
s, err := readAssemblyScriptString(testCtx, mod, uint32(tc.offset))
|
s, err := readAssemblyScriptString(testCtx, mod, uint32(tc.offset))
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
@@ -472,20 +540,20 @@ func Test_readAssemblyScriptString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
func writeAbortMessageAndFileName(ctx context.Context, t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
||||||
off := 0
|
off := 0
|
||||||
ok := mem.WriteUint32Le(testCtx, uint32(off), uint32(len(messageUTF16)))
|
ok := mem.WriteUint32Le(ctx, uint32(off), uint32(len(messageUTF16)))
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
off += 4
|
off += 4
|
||||||
messageOff := off
|
messageOff := off
|
||||||
ok = mem.Write(testCtx, uint32(off), messageUTF16)
|
ok = mem.Write(ctx, uint32(off), messageUTF16)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
off += len(messageUTF16)
|
off += len(messageUTF16)
|
||||||
ok = mem.WriteUint32Le(testCtx, uint32(off), uint32(len(fileNameUTF16)))
|
ok = mem.WriteUint32Le(ctx, uint32(off), uint32(len(fileNameUTF16)))
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
off += 4
|
off += 4
|
||||||
filenameOff := off
|
filenameOff := off
|
||||||
ok = mem.Write(testCtx, uint32(off), fileNameUTF16)
|
ok = mem.Write(ctx, uint32(off), fileNameUTF16)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
return messageOff, filenameOff
|
return messageOff, filenameOff
|
||||||
}
|
}
|
||||||
|
|||||||
73
builder.go
73
builder.go
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/tetratelabs/wazero/internal/wasm"
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ModuleBuilder is a way to define a WebAssembly 1.0 (20191205) in Go.
|
// ModuleBuilder is a way to define a WebAssembly Module in Go.
|
||||||
//
|
//
|
||||||
// Ex. Below defines and instantiates a module named "env" with one function:
|
// Ex. Below defines and instantiates a module named "env" with one function:
|
||||||
//
|
//
|
||||||
@@ -25,7 +25,8 @@ import (
|
|||||||
// ExportFunction("hello", hello).
|
// ExportFunction("hello", hello).
|
||||||
// Instantiate(ctx, r)
|
// Instantiate(ctx, r)
|
||||||
//
|
//
|
||||||
// If the same module may be instantiated multiple times, it is more efficient to separate steps. Ex.
|
// If the same module may be instantiated multiple times, it is more efficient
|
||||||
|
// to separate steps. Ex.
|
||||||
//
|
//
|
||||||
// compiled, _ := r.NewModuleBuilder("env").
|
// compiled, _ := r.NewModuleBuilder("env").
|
||||||
// ExportFunction("get_random_string", getRandomString).
|
// ExportFunction("get_random_string", getRandomString).
|
||||||
@@ -37,9 +38,12 @@ import (
|
|||||||
//
|
//
|
||||||
// Notes
|
// Notes
|
||||||
//
|
//
|
||||||
// * ModuleBuilder is mutable. WithXXX functions return the same instance for chaining.
|
// * ModuleBuilder is mutable: each method returns the same instance for
|
||||||
// * WithXXX methods do not return errors, to allow chaining. Any validation errors are deferred until Build.
|
// chaining.
|
||||||
// * Insertion order is not retained. Anything defined by this builder is sorted lexicographically on Build.
|
// * methods do not return errors, to allow chaining. Any validation errors
|
||||||
|
// are deferred until Compile.
|
||||||
|
// * Insertion order is not retained. Anything defined by this builder is
|
||||||
|
// sorted lexicographically on Compile.
|
||||||
type ModuleBuilder interface {
|
type ModuleBuilder interface {
|
||||||
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.
|
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.
|
||||||
|
|
||||||
@@ -48,29 +52,45 @@ type ModuleBuilder interface {
|
|||||||
//
|
//
|
||||||
// Parameters
|
// Parameters
|
||||||
//
|
//
|
||||||
// * name - the name to export. Ex "random_get"
|
// * exportName - The name to export. Ex "random_get"
|
||||||
// * goFunc - the `func` to export.
|
// * goFunc - The `func` to export.
|
||||||
|
// * names - If present, the first is the api.FunctionDefinition name.
|
||||||
|
// If any follow, they must match the count of goFunc's parameters.
|
||||||
//
|
//
|
||||||
// Noting a context exception described later, all parameters or result types must match WebAssembly 1.0 (20191205) value
|
// Ex.
|
||||||
// types. This means uint32, uint64, float32 or float64. Up to one result can be returned.
|
// // Just export the function, and use "abort" in stack traces.
|
||||||
|
// builder.ExportFunction("abort", env.abort)
|
||||||
|
// // Ensure "~lib/builtins/abort" is used in stack traces.
|
||||||
|
// builder.ExportFunction("abort", env.abort, "~lib/builtins/abort")
|
||||||
|
// // Allow function listeners to know the param names for logging, etc.
|
||||||
|
// builder.ExportFunction("abort", env.abort, "~lib/builtins/abort",
|
||||||
|
// "message", "fileName", "lineNumber", "columnNumber")
|
||||||
|
//
|
||||||
|
// Valid Signature
|
||||||
|
//
|
||||||
|
// Noting a context exception described later, all parameters or result
|
||||||
|
// types must match WebAssembly 1.0 (20191205) value types. This means
|
||||||
|
// uint32, uint64, float32 or float64. Up to one result can be returned.
|
||||||
//
|
//
|
||||||
// Ex. This is a valid host function:
|
// Ex. This is a valid host function:
|
||||||
//
|
//
|
||||||
// addInts := func(x uint32, uint32) uint32 {
|
// addInts := func(x, y uint32) uint32 {
|
||||||
// return x + y
|
// return x + y
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Host functions may also have an initial parameter (param[0]) of type context.Context or api.Module.
|
// Host functions may also have an initial parameter (param[0]) of type
|
||||||
|
// context.Context or api.Module.
|
||||||
//
|
//
|
||||||
// Ex. This uses a Go Context:
|
// Ex. This uses a Go Context:
|
||||||
//
|
//
|
||||||
// addInts := func(ctx context.Context, x uint32, uint32) uint32 {
|
// addInts := func(ctx context.Context, x, y uint32) uint32 {
|
||||||
// // add a little extra if we put some in the context!
|
// // add a little extra if we put some in the context!
|
||||||
// return x + y + ctx.Value(extraKey).(uint32)
|
// return x + y + ctx.Value(extraKey).(uint32)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Ex. This uses an api.Module to reads the parameters from memory. This is important because there are only numeric
|
// Ex. This uses an api.Module to reads the parameters from memory. This is
|
||||||
// types in Wasm. The only way to share other data is via writing memory and sharing offsets.
|
// important because there are only numeric types in Wasm. The only way to
|
||||||
|
// share other data is via writing memory and sharing offsets.
|
||||||
//
|
//
|
||||||
// addInts := func(ctx context.Context, m api.Module, offset uint32) uint32 {
|
// addInts := func(ctx context.Context, m api.Module, offset uint32) uint32 {
|
||||||
// x, _ := m.Memory().ReadUint32Le(ctx, offset)
|
// x, _ := m.Memory().ReadUint32Le(ctx, offset)
|
||||||
@@ -78,16 +98,17 @@ type ModuleBuilder interface {
|
|||||||
// return x + y
|
// return x + y
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If both parameters exist, they must be in order at positions zero and one.
|
// If both parameters exist, they must be in order at positions 0 and 1.
|
||||||
//
|
//
|
||||||
// Ex. This uses propagates context properly when calling other functions exported in the api.Module:
|
// Ex. This uses propagates context properly when calling other functions
|
||||||
|
// exported in the api.Module:
|
||||||
// callRead := func(ctx context.Context, m api.Module, offset, byteCount uint32) uint32 {
|
// callRead := func(ctx context.Context, m api.Module, offset, byteCount uint32) uint32 {
|
||||||
// fn = m.ExportedFunction("__read")
|
// fn = m.ExportedFunction("__read")
|
||||||
// results, err := fn(ctx, offset, byteCount)
|
// results, err := fn(ctx, offset, byteCount)
|
||||||
// --snip--
|
// --snip--
|
||||||
//
|
//
|
||||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
|
||||||
ExportFunction(name string, goFunc interface{}) ModuleBuilder
|
ExportFunction(exportName string, goFunc interface{}, names ...string) ModuleBuilder
|
||||||
|
|
||||||
// ExportFunctions is a convenience that calls ExportFunction for each key/value in the provided map.
|
// ExportFunctions is a convenience that calls ExportFunction for each key/value in the provided map.
|
||||||
ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder
|
ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder
|
||||||
@@ -200,6 +221,7 @@ type moduleBuilder struct {
|
|||||||
r *runtime
|
r *runtime
|
||||||
moduleName string
|
moduleName string
|
||||||
nameToGoFunc map[string]interface{}
|
nameToGoFunc map[string]interface{}
|
||||||
|
funcToNames map[string][]string
|
||||||
nameToMemory map[string]*wasm.Memory
|
nameToMemory map[string]*wasm.Memory
|
||||||
nameToGlobal map[string]*wasm.Global
|
nameToGlobal map[string]*wasm.Global
|
||||||
}
|
}
|
||||||
@@ -210,14 +232,18 @@ func (r *runtime) NewModuleBuilder(moduleName string) ModuleBuilder {
|
|||||||
r: r,
|
r: r,
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
nameToGoFunc: map[string]interface{}{},
|
nameToGoFunc: map[string]interface{}{},
|
||||||
|
funcToNames: map[string][]string{},
|
||||||
nameToMemory: map[string]*wasm.Memory{},
|
nameToMemory: map[string]*wasm.Memory{},
|
||||||
nameToGlobal: map[string]*wasm.Global{},
|
nameToGlobal: map[string]*wasm.Global{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportFunction implements ModuleBuilder.ExportFunction
|
// ExportFunction implements ModuleBuilder.ExportFunction
|
||||||
func (b *moduleBuilder) ExportFunction(name string, goFunc interface{}) ModuleBuilder {
|
func (b *moduleBuilder) ExportFunction(exportName string, goFunc interface{}, names ...string) ModuleBuilder {
|
||||||
b.nameToGoFunc[name] = goFunc
|
b.nameToGoFunc[exportName] = goFunc
|
||||||
|
if len(names) > 0 {
|
||||||
|
b.funcToNames[exportName] = names
|
||||||
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,16 +321,21 @@ func (b *moduleBuilder) Compile(ctx context.Context, cConfig CompileConfig) (Com
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.nameToMemory, b.nameToGlobal, b.r.enabledFeatures)
|
module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.nameToMemory, b.nameToGlobal, b.r.enabledFeatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := &compiledModule{module: module, compiledEngine: b.r.store.Engine}
|
||||||
|
if c.listeners, err = buildListeners(ctx, b.r, module); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err = b.r.store.Engine.CompileModule(ctx, module); err != nil {
|
if err = b.r.store.Engine.CompileModule(ctx, module); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &compiledModule{module: module, compiledEngine: b.r.store.Engine}, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate implements ModuleBuilder.Instantiate
|
// Instantiate implements ModuleBuilder.Instantiate
|
||||||
|
|||||||
@@ -63,6 +63,26 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ExportFunction with names",
|
||||||
|
input: func(r Runtime) ModuleBuilder {
|
||||||
|
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32, "get", "x")
|
||||||
|
},
|
||||||
|
expected: &wasm.Module{
|
||||||
|
TypeSection: []*wasm.FunctionType{
|
||||||
|
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
|
||||||
|
},
|
||||||
|
FunctionSection: []wasm.Index{0},
|
||||||
|
HostFunctionSection: []*reflect.Value{&fnUint32_uint32},
|
||||||
|
ExportSection: []*wasm.Export{
|
||||||
|
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
|
||||||
|
},
|
||||||
|
NameSection: &wasm.NameSection{
|
||||||
|
FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}},
|
||||||
|
LocalNames: []*wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ExportFunction overwrites existing",
|
name: "ExportFunction overwrites existing",
|
||||||
input: func(r Runtime) ModuleBuilder {
|
input: func(r Runtime) ModuleBuilder {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
"github.com/tetratelabs/wazero/internal/engine/compiler"
|
"github.com/tetratelabs/wazero/internal/engine/compiler"
|
||||||
"github.com/tetratelabs/wazero/internal/engine/interpreter"
|
"github.com/tetratelabs/wazero/internal/engine/interpreter"
|
||||||
"github.com/tetratelabs/wazero/internal/platform"
|
"github.com/tetratelabs/wazero/internal/platform"
|
||||||
@@ -148,6 +149,7 @@ func NewRuntimeConfig() RuntimeConfig {
|
|||||||
|
|
||||||
type runtimeConfig struct {
|
type runtimeConfig struct {
|
||||||
enabledFeatures wasm.Features
|
enabledFeatures wasm.Features
|
||||||
|
isInterpreter bool
|
||||||
newEngine func(wasm.Features) wasm.Engine
|
newEngine func(wasm.Features) wasm.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +181,7 @@ func NewRuntimeConfigCompiler() RuntimeConfig {
|
|||||||
// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
|
// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
|
||||||
func NewRuntimeConfigInterpreter() RuntimeConfig {
|
func NewRuntimeConfigInterpreter() RuntimeConfig {
|
||||||
ret := engineLessConfig.clone()
|
ret := engineLessConfig.clone()
|
||||||
|
ret.isInterpreter = true
|
||||||
ret.newEngine = interpreter.NewEngine
|
ret.newEngine = interpreter.NewEngine
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -280,7 +283,8 @@ type compiledModule struct {
|
|||||||
module *wasm.Module
|
module *wasm.Module
|
||||||
// compiledEngine holds an engine on which `module` is compiled.
|
// compiledEngine holds an engine on which `module` is compiled.
|
||||||
compiledEngine wasm.Engine
|
compiledEngine wasm.Engine
|
||||||
|
// listeners are present if the code was compiled with a listener
|
||||||
|
listeners []experimental.FunctionListener
|
||||||
// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
|
// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
|
||||||
closeWithModule bool
|
closeWithModule bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
// Package experimental_test includes examples for experimental features. When these complete, they'll end up as real
|
// Package experimental_test includes examples for experimental features. When these complete, they'll end up as real
|
||||||
// examples in the /examples directory.
|
// examples in the /examples directory.
|
||||||
package experimental_test
|
package experimental_test
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||||
|
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||||
|
|||||||
@@ -18,13 +18,7 @@ type FunctionListenerFactoryKey struct{}
|
|||||||
type FunctionListenerFactory interface {
|
type FunctionListenerFactory interface {
|
||||||
// NewListener returns a FunctionListener for a defined function. If nil is
|
// NewListener returns a FunctionListener for a defined function. If nil is
|
||||||
// returned, no listener will be notified.
|
// returned, no listener will be notified.
|
||||||
//
|
NewListener(api.FunctionDefinition) FunctionListener
|
||||||
// Params
|
|
||||||
//
|
|
||||||
// * moduleName - the name this listener was instantiated with, and might
|
|
||||||
// vary for the same binary.
|
|
||||||
// * definition - metadata about this function parsed from its Wasm binary.
|
|
||||||
NewListener(moduleName string, definition api.FunctionDefinition) FunctionListener
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionListener can be registered for any function via
|
// FunctionListener can be registered for any function via
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package experimental_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero"
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
|
||||||
"github.com/tetratelabs/wazero/experimental"
|
|
||||||
"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// listenerWasm was generated by the following:
|
|
||||||
// cd testdata; wat2wasm --debug-names listener.wat
|
|
||||||
//go:embed testdata/listener.wasm
|
|
||||||
var listenerWasm []byte
|
|
||||||
|
|
||||||
// compile-time check to ensure loggerFactory implements experimental.FunctionListenerFactory.
|
|
||||||
var _ experimental.FunctionListenerFactory = &loggerFactory{}
|
|
||||||
|
|
||||||
// loggerFactory implements experimental.FunctionListenerFactory to log all function calls to the console.
|
|
||||||
type loggerFactory struct{}
|
|
||||||
|
|
||||||
// NewListener implements the same method as documented on experimental.FunctionListener.
|
|
||||||
func (f *loggerFactory) NewListener(moduleName string, fnd api.FunctionDefinition) experimental.FunctionListener {
|
|
||||||
return &logger{funcName: []byte(moduleName + "." + funcName(fnd))}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nestLevelKey holds state between logger.Before and logger.After to ensure call depth is reflected.
|
|
||||||
type nestLevelKey struct{}
|
|
||||||
|
|
||||||
// logger implements experimental.FunctionListener to log entrance and exit of each function call.
|
|
||||||
type logger struct{ funcName []byte }
|
|
||||||
|
|
||||||
// Before logs to stdout the module and function name, prefixed with '>>' and indented based on the call nesting level.
|
|
||||||
func (l *logger) Before(ctx context.Context, _ []uint64) context.Context {
|
|
||||||
nestLevel, _ := ctx.Value(nestLevelKey{}).(int)
|
|
||||||
|
|
||||||
l.writeIndented(os.Stdout, true, nestLevel+1)
|
|
||||||
|
|
||||||
// Increase the next nesting level.
|
|
||||||
return context.WithValue(ctx, nestLevelKey{}, nestLevel+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// After logs to stdout the module and function name, prefixed with '<<' and indented based on the call nesting level.
|
|
||||||
func (l *logger) After(ctx context.Context, _ error, _ []uint64) {
|
|
||||||
// Note: We use the nest level directly even though it is the "next" nesting level.
|
|
||||||
// This works because our indent of zero nesting is one tab.
|
|
||||||
l.writeIndented(os.Stdout, false, ctx.Value(nestLevelKey{}).(int))
|
|
||||||
}
|
|
||||||
|
|
||||||
// funcName returns the name in priority order: first export name, module-defined name, numeric index.
|
|
||||||
func funcName(fnd api.FunctionDefinition) string {
|
|
||||||
if len(fnd.ExportNames()) > 0 {
|
|
||||||
return fnd.ExportNames()[0]
|
|
||||||
}
|
|
||||||
if fnd.Name() != "" {
|
|
||||||
return fnd.Name()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%d]", fnd.Index())
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a very basic integration of listener. The main goal is to show how it is configured.
|
|
||||||
func Example_listener() {
|
|
||||||
// Set context to one that has an experimental listener
|
|
||||||
ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, &loggerFactory{})
|
|
||||||
|
|
||||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
|
||||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
|
||||||
|
|
||||||
if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the WebAssembly module using the default configuration.
|
|
||||||
code, err := r.CompileModule(ctx, listenerWasm, wazero.NewCompileConfig())
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStdout(os.Stdout))
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("rand").Call(ctx, 4)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should see the same function called twice: directly and indirectly.
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
//>> listener.rand
|
|
||||||
//>> wasi_snapshot_preview1.random_get
|
|
||||||
//<< wasi_snapshot_preview1.random_get
|
|
||||||
//>> wasi_snapshot_preview1.random_get
|
|
||||||
//<< wasi_snapshot_preview1.random_get
|
|
||||||
//<< listener.rand
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeIndented writes an indented message like this: ">>\t\t\t$indentLevel$funcName\n"
|
|
||||||
func (l *logger) writeIndented(writer io.Writer, before bool, indentLevel int) {
|
|
||||||
var message = make([]byte, 0, 2+indentLevel+len(l.funcName)+1)
|
|
||||||
if before {
|
|
||||||
message = append(message, '>', '>')
|
|
||||||
} else { // after
|
|
||||||
message = append(message, '<', '<')
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < indentLevel; i++ {
|
|
||||||
message = append(message, '\t')
|
|
||||||
}
|
|
||||||
message = append(message, l.funcName...)
|
|
||||||
message = append(message, '\n')
|
|
||||||
_, _ = writer.Write(message)
|
|
||||||
}
|
|
||||||
73
experimental/listener_test.go
Normal file
73
experimental/listener_test.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package experimental_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
. "github.com/tetratelabs/wazero/experimental"
|
||||||
|
"github.com/tetratelabs/wazero/internal/platform"
|
||||||
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||||
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
|
"github.com/tetratelabs/wazero/internal/wasm/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// compile-time check to ensure recorder implements FunctionListenerFactory
|
||||||
|
var _ FunctionListenerFactory = &recorder{}
|
||||||
|
|
||||||
|
type recorder struct {
|
||||||
|
m map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *recorder) NewListener(definition api.FunctionDefinition) FunctionListener {
|
||||||
|
r.m[definition.Name()] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionListenerFactory(t *testing.T) {
|
||||||
|
// Set context to one that has an experimental listener
|
||||||
|
factory := &recorder{map[string]struct{}{}}
|
||||||
|
ctx := context.WithValue(context.Background(), FunctionListenerFactoryKey{}, factory)
|
||||||
|
|
||||||
|
// Define a module with two functions
|
||||||
|
bin := binary.EncodeModule(&wasm.Module{
|
||||||
|
TypeSection: []*wasm.FunctionType{{}},
|
||||||
|
ImportSection: []*wasm.Import{{}},
|
||||||
|
FunctionSection: []wasm.Index{0, 0},
|
||||||
|
CodeSection: []*wasm.Code{
|
||||||
|
{Body: []byte{wasm.OpcodeEnd}}, {Body: []byte{wasm.OpcodeEnd}},
|
||||||
|
},
|
||||||
|
NameSection: &wasm.NameSection{
|
||||||
|
ModuleName: "test",
|
||||||
|
FunctionNames: wasm.NameMap{
|
||||||
|
{Index: 0, Name: "import"}, // should skip
|
||||||
|
{Index: 1, Name: "fn1"},
|
||||||
|
{Index: 2, Name: "fn2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if platform.CompilerSupported() {
|
||||||
|
t.Run("fails on compile if compiler", func(t *testing.T) {
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigCompiler())
|
||||||
|
defer r.Close(testCtx) // This closes everything this Runtime created.
|
||||||
|
_, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig())
|
||||||
|
require.EqualError(t, err,
|
||||||
|
"context includes a FunctionListenerFactoryKey, which is only supported in the interpreter")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
|
|
||||||
|
_, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Ensure each function was converted to a listener eagerly
|
||||||
|
require.Equal(t, map[string]struct{}{
|
||||||
|
"fn1": {},
|
||||||
|
"fn2": {},
|
||||||
|
}, factory.m)
|
||||||
|
}
|
||||||
155
experimental/log_listener.go
Normal file
155
experimental/log_listener.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package experimental
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLoggingListenerFactory implements FunctionListenerFactory to log all
|
||||||
|
// function calls to the writer.
|
||||||
|
func NewLoggingListenerFactory(writer io.Writer) FunctionListenerFactory {
|
||||||
|
return &loggingListenerFactory{writer}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggingListenerFactory struct{ writer io.Writer }
|
||||||
|
|
||||||
|
// NewListener implements the same method as documented on
|
||||||
|
// experimental.FunctionListener.
|
||||||
|
func (f *loggingListenerFactory) NewListener(fnd api.FunctionDefinition) FunctionListener {
|
||||||
|
return &loggingListener{writer: f.writer, fnd: fnd, isWasi: fnd.ModuleName() == "wasi_snapshot_preview1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nestLevelKey holds state between logger.Before and loggingListener.After to ensure
|
||||||
|
// call depth is reflected.
|
||||||
|
type nestLevelKey struct{}
|
||||||
|
|
||||||
|
// loggingListener implements experimental.FunctionListener to log entrance and exit
|
||||||
|
// of each function call.
|
||||||
|
type loggingListener struct {
|
||||||
|
writer io.Writer
|
||||||
|
fnd api.FunctionDefinition
|
||||||
|
isWasi bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before logs to stdout the module and function name, prefixed with '-->' and
|
||||||
|
// indented based on the call nesting level.
|
||||||
|
func (l *loggingListener) Before(ctx context.Context, vals []uint64) context.Context {
|
||||||
|
nestLevel, _ := ctx.Value(nestLevelKey{}).(int)
|
||||||
|
|
||||||
|
l.writeIndented(true, nil, vals, nestLevel+1)
|
||||||
|
|
||||||
|
// Increase the next nesting level.
|
||||||
|
return context.WithValue(ctx, nestLevelKey{}, nestLevel+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After logs to stdout the module and function name, prefixed with '<--' and
|
||||||
|
// indented based on the call nesting level.
|
||||||
|
func (l *loggingListener) After(ctx context.Context, err error, vals []uint64) {
|
||||||
|
// Note: We use the nest level directly even though it is the "next" nesting level.
|
||||||
|
// This works because our indent of zero nesting is one tab.
|
||||||
|
l.writeIndented(false, err, vals, ctx.Value(nestLevelKey{}).(int))
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeIndented writes an indented message like this: "-->\t\t\t$indentLevel$funcName\n"
|
||||||
|
func (l *loggingListener) writeIndented(before bool, err error, vals []uint64, indentLevel int) {
|
||||||
|
var message strings.Builder
|
||||||
|
for i := 1; i < indentLevel; i++ {
|
||||||
|
message.WriteByte('\t')
|
||||||
|
}
|
||||||
|
if before {
|
||||||
|
if l.fnd.IsHostFunction() {
|
||||||
|
message.WriteString("==> ")
|
||||||
|
} else {
|
||||||
|
message.WriteString("--> ")
|
||||||
|
}
|
||||||
|
l.writeFuncEnter(&message, vals)
|
||||||
|
} else { // after
|
||||||
|
if l.fnd.IsHostFunction() {
|
||||||
|
message.WriteString("<== ")
|
||||||
|
} else {
|
||||||
|
message.WriteString("<-- ")
|
||||||
|
}
|
||||||
|
l.writeFuncExit(&message, err, vals)
|
||||||
|
}
|
||||||
|
message.WriteByte('\n')
|
||||||
|
|
||||||
|
_, _ = l.writer.Write([]byte(message.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggingListener) writeFuncEnter(message *strings.Builder, vals []uint64) {
|
||||||
|
valLen := len(vals)
|
||||||
|
message.WriteString(l.fnd.DebugName())
|
||||||
|
message.WriteByte('(')
|
||||||
|
switch valLen {
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
i := l.writeParam(message, 0, vals)
|
||||||
|
for i < valLen {
|
||||||
|
message.WriteByte(',')
|
||||||
|
i = l.writeParam(message, i, vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggingListener) writeFuncExit(message *strings.Builder, err error, vals []uint64) {
|
||||||
|
if err != nil {
|
||||||
|
message.WriteString("error: ")
|
||||||
|
message.WriteString(err.Error())
|
||||||
|
return
|
||||||
|
} else if l.isWasi {
|
||||||
|
message.WriteString(wasi_snapshot_preview1.ErrnoName(uint32(vals[0])))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valLen := len(vals)
|
||||||
|
message.WriteByte('(')
|
||||||
|
switch valLen {
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
i := l.writeResult(message, 0, vals)
|
||||||
|
for i < valLen {
|
||||||
|
message.WriteByte(',')
|
||||||
|
i = l.writeResult(message, i, vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggingListener) writeResult(message *strings.Builder, i int, vals []uint64) int {
|
||||||
|
return l.writeVal(message, l.fnd.ResultTypes()[i], i, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggingListener) writeParam(message *strings.Builder, i int, vals []uint64) int {
|
||||||
|
if len(l.fnd.ParamNames()) > 0 {
|
||||||
|
message.WriteString(l.fnd.ParamNames()[i])
|
||||||
|
message.WriteByte('=')
|
||||||
|
}
|
||||||
|
return l.writeVal(message, l.fnd.ParamTypes()[i], i, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggingListener) writeVal(message *strings.Builder, t api.ValueType, i int, vals []uint64) int {
|
||||||
|
v := vals[i]
|
||||||
|
i++
|
||||||
|
switch t {
|
||||||
|
case api.ValueTypeI32:
|
||||||
|
message.WriteString(strconv.FormatUint(uint64(uint32(v)), 10))
|
||||||
|
case api.ValueTypeI64:
|
||||||
|
message.WriteString(strconv.FormatUint(v, 10))
|
||||||
|
case api.ValueTypeF32:
|
||||||
|
message.WriteString(strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32))
|
||||||
|
case api.ValueTypeF64:
|
||||||
|
message.WriteString(strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64))
|
||||||
|
case 0x7b: // wasm.ValueTypeV128
|
||||||
|
message.WriteString(fmt.Sprintf("%016x%016x", v, vals[i])) // fixed-width hex
|
||||||
|
i++
|
||||||
|
case api.ValueTypeExternref, 0x70: // wasm.ValueTypeFuncref
|
||||||
|
message.WriteString(fmt.Sprintf("%016x", v)) // fixed-width hex
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
56
experimental/log_listener_example_test.go
Normal file
56
experimental/log_listener_example_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package experimental_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
. "github.com/tetratelabs/wazero/experimental"
|
||||||
|
"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// listenerWasm was generated by the following:
|
||||||
|
// cd testdata; wat2wasm --debug-names listener.wat
|
||||||
|
//go:embed testdata/listener.wasm
|
||||||
|
var listenerWasm []byte
|
||||||
|
|
||||||
|
// This is a very basic integration of listener. The main goal is to show how it is configured.
|
||||||
|
func Example_newLoggingListenerFactory() {
|
||||||
|
// Set context to one that has an experimental listener
|
||||||
|
ctx := context.WithValue(context.Background(), FunctionListenerFactoryKey{}, NewLoggingListenerFactory(os.Stdout))
|
||||||
|
|
||||||
|
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||||
|
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
|
|
||||||
|
if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the WebAssembly module using the default configuration.
|
||||||
|
code, err := r.CompileModule(ctx, listenerWasm, wazero.NewCompileConfig())
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStdout(os.Stdout))
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mod.ExportedFunction("rand").Call(ctx, 4)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should see the same function called twice: directly and indirectly.
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
//--> listener.rand(len=4)
|
||||||
|
// ==> wasi_snapshot_preview1.random_get(buf=4,buf_len=4)
|
||||||
|
// <== ESUCCESS
|
||||||
|
// ==> wasi_snapshot_preview1.random_get(buf=8,buf_len=4)
|
||||||
|
// <== ESUCCESS
|
||||||
|
//<-- ()
|
||||||
|
}
|
||||||
336
experimental/log_listener_test.go
Normal file
336
experimental/log_listener_test.go
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
package experimental_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||||
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
|
"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_loggingListener(t *testing.T) {
|
||||||
|
wasiFuncName := "random_get"
|
||||||
|
wasiFuncType := &wasm.FunctionType{
|
||||||
|
Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32},
|
||||||
|
Results: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32},
|
||||||
|
}
|
||||||
|
wasiParamNames := []string{"buf", "buf_len"}
|
||||||
|
wasiParams := []uint64{0, 8}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
moduleName, funcName string
|
||||||
|
functype *wasm.FunctionType
|
||||||
|
isHostFunc bool
|
||||||
|
paramNames []string
|
||||||
|
params, results []uint64
|
||||||
|
err error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "v_v",
|
||||||
|
functype: &wasm.FunctionType{},
|
||||||
|
expected: `--> test.fn()
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error",
|
||||||
|
functype: &wasm.FunctionType{},
|
||||||
|
err: io.EOF,
|
||||||
|
expected: `--> test.fn()
|
||||||
|
<-- error: EOF
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host",
|
||||||
|
functype: &wasm.FunctionType{},
|
||||||
|
isHostFunc: true,
|
||||||
|
expected: `==> test.fn()
|
||||||
|
<== ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wasi",
|
||||||
|
functype: wasiFuncType,
|
||||||
|
moduleName: wasi_snapshot_preview1.ModuleName,
|
||||||
|
funcName: wasiFuncName,
|
||||||
|
paramNames: wasiParamNames,
|
||||||
|
isHostFunc: true,
|
||||||
|
params: wasiParams,
|
||||||
|
results: []uint64{uint64(wasi_snapshot_preview1.ErrnoSuccess)},
|
||||||
|
expected: `==> wasi_snapshot_preview1.random_get(buf=0,buf_len=8)
|
||||||
|
<== ESUCCESS
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wasi errno",
|
||||||
|
functype: wasiFuncType,
|
||||||
|
moduleName: wasi_snapshot_preview1.ModuleName,
|
||||||
|
funcName: wasiFuncName,
|
||||||
|
paramNames: wasiParamNames,
|
||||||
|
isHostFunc: true,
|
||||||
|
params: wasiParams,
|
||||||
|
results: []uint64{uint64(wasi_snapshot_preview1.ErrnoFault)},
|
||||||
|
expected: `==> wasi_snapshot_preview1.random_get(buf=0,buf_len=8)
|
||||||
|
<== EFAULT
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wasi error",
|
||||||
|
functype: wasiFuncType,
|
||||||
|
moduleName: wasi_snapshot_preview1.ModuleName,
|
||||||
|
funcName: wasiFuncName,
|
||||||
|
paramNames: wasiParamNames,
|
||||||
|
isHostFunc: true,
|
||||||
|
params: wasiParams,
|
||||||
|
err: io.EOF, // not possible as we coerce errors to numbers, but test anyway!
|
||||||
|
expected: `==> wasi_snapshot_preview1.random_get(buf=0,buf_len=8)
|
||||||
|
<== error: EOF
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "i32",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeI32}},
|
||||||
|
params: []uint64{math.MaxUint32},
|
||||||
|
expected: `--> test.fn(4294967295)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "i32 named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeI32}},
|
||||||
|
params: []uint64{math.MaxUint32},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=4294967295)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "i64",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeI64}},
|
||||||
|
params: []uint64{math.MaxUint64},
|
||||||
|
expected: `--> test.fn(18446744073709551615)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "i64 named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeI64}},
|
||||||
|
params: []uint64{math.MaxUint64},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=18446744073709551615)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "f32",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeF32}},
|
||||||
|
params: []uint64{api.EncodeF32(math.MaxFloat32)},
|
||||||
|
expected: `--> test.fn(3.4028235e+38)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "f32 named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeF32}},
|
||||||
|
params: []uint64{api.EncodeF32(math.MaxFloat32)},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=3.4028235e+38)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "f64",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeF64}},
|
||||||
|
params: []uint64{api.EncodeF64(math.MaxFloat64)},
|
||||||
|
expected: `--> test.fn(1.7976931348623157e+308)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "f64 named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeF64}},
|
||||||
|
params: []uint64{api.EncodeF64(math.MaxFloat64)},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=1.7976931348623157e+308)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "externref",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeExternref}},
|
||||||
|
params: []uint64{0},
|
||||||
|
expected: `--> test.fn(0000000000000000)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "externref named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{api.ValueTypeExternref}},
|
||||||
|
params: []uint64{0},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=0000000000000000)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v128",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{0x7b}},
|
||||||
|
params: []uint64{0, 1},
|
||||||
|
expected: `--> test.fn(00000000000000000000000000000001)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v128 named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{0x7b}},
|
||||||
|
params: []uint64{0, 1},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=00000000000000000000000000000001)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "funcref",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{0x70}},
|
||||||
|
params: []uint64{0},
|
||||||
|
expected: `--> test.fn(0000000000000000)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "funcref named",
|
||||||
|
functype: &wasm.FunctionType{Params: []api.ValueType{0x70}},
|
||||||
|
params: []uint64{0},
|
||||||
|
paramNames: []string{"x"},
|
||||||
|
expected: `--> test.fn(x=0000000000000000)
|
||||||
|
<-- ()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params, one result",
|
||||||
|
functype: &wasm.FunctionType{Results: []api.ValueType{api.ValueTypeI32}},
|
||||||
|
results: []uint64{math.MaxUint32},
|
||||||
|
expected: `--> test.fn()
|
||||||
|
<-- (4294967295)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one param, one result",
|
||||||
|
functype: &wasm.FunctionType{
|
||||||
|
Params: []api.ValueType{api.ValueTypeI32},
|
||||||
|
Results: []api.ValueType{api.ValueTypeF32},
|
||||||
|
},
|
||||||
|
params: []uint64{math.MaxUint32},
|
||||||
|
results: []uint64{api.EncodeF32(math.MaxFloat32)},
|
||||||
|
expected: `--> test.fn(4294967295)
|
||||||
|
<-- (3.4028235e+38)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two params, two results",
|
||||||
|
functype: &wasm.FunctionType{
|
||||||
|
Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI64},
|
||||||
|
Results: []api.ValueType{api.ValueTypeF32, api.ValueTypeF64},
|
||||||
|
},
|
||||||
|
params: []uint64{math.MaxUint32, math.MaxUint64},
|
||||||
|
results: []uint64{api.EncodeF32(math.MaxFloat32), api.EncodeF64(math.MaxFloat64)},
|
||||||
|
expected: `--> test.fn(4294967295,18446744073709551615)
|
||||||
|
<-- (3.4028235e+38,1.7976931348623157e+308)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two params, two results named",
|
||||||
|
functype: &wasm.FunctionType{
|
||||||
|
Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI64},
|
||||||
|
Results: []api.ValueType{api.ValueTypeF32, api.ValueTypeF64},
|
||||||
|
},
|
||||||
|
params: []uint64{math.MaxUint32, math.MaxUint64},
|
||||||
|
paramNames: []string{"x", "y"},
|
||||||
|
results: []uint64{api.EncodeF32(math.MaxFloat32), api.EncodeF64(math.MaxFloat64)},
|
||||||
|
expected: `--> test.fn(x=4294967295,y=18446744073709551615)
|
||||||
|
<-- (3.4028235e+38,1.7976931348623157e+308)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
lf := experimental.NewLoggingListenerFactory(out)
|
||||||
|
fnV := reflect.ValueOf(func() {})
|
||||||
|
for _, tt := range tests {
|
||||||
|
tc := tt
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.moduleName == "" {
|
||||||
|
tc.moduleName = "test"
|
||||||
|
}
|
||||||
|
if tc.funcName == "" {
|
||||||
|
tc.funcName = "fn"
|
||||||
|
}
|
||||||
|
m := &wasm.Module{
|
||||||
|
TypeSection: []*wasm.FunctionType{tc.functype},
|
||||||
|
FunctionSection: []wasm.Index{0},
|
||||||
|
NameSection: &wasm.NameSection{
|
||||||
|
ModuleName: tc.moduleName,
|
||||||
|
FunctionNames: wasm.NameMap{{Name: tc.funcName}},
|
||||||
|
LocalNames: wasm.IndirectNameMap{{NameMap: toNameMap(tc.paramNames)}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.isHostFunc {
|
||||||
|
m.HostFunctionSection = []*reflect.Value{&fnV}
|
||||||
|
}
|
||||||
|
m.BuildFunctionDefinitions()
|
||||||
|
l := lf.NewListener(m.FunctionDefinitionSection[0])
|
||||||
|
|
||||||
|
out.Reset()
|
||||||
|
ctx := l.Before(testCtx, tc.params)
|
||||||
|
l.After(ctx, tc.err, tc.results)
|
||||||
|
require.Equal(t, tc.expected, out.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNameMap(names []string) wasm.NameMap {
|
||||||
|
if len(names) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret wasm.NameMap
|
||||||
|
for i, n := range names {
|
||||||
|
ret = append(ret, &wasm.NameAssoc{Index: wasm.Index(i), Name: n})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_loggingListener_indentation(t *testing.T) {
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
lf := experimental.NewLoggingListenerFactory(out)
|
||||||
|
m := &wasm.Module{
|
||||||
|
TypeSection: []*wasm.FunctionType{{}},
|
||||||
|
FunctionSection: []wasm.Index{0, 0},
|
||||||
|
NameSection: &wasm.NameSection{
|
||||||
|
ModuleName: "test",
|
||||||
|
FunctionNames: wasm.NameMap{{Index: 0, Name: "fn1"}, {Index: 1, Name: "fn2"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m.BuildFunctionDefinitions()
|
||||||
|
l1 := lf.NewListener(m.FunctionDefinitionSection[0])
|
||||||
|
l2 := lf.NewListener(m.FunctionDefinitionSection[1])
|
||||||
|
|
||||||
|
ctx := l1.Before(testCtx, []uint64{})
|
||||||
|
ctx1 := l2.Before(ctx, []uint64{})
|
||||||
|
l2.After(ctx1, nil, []uint64{})
|
||||||
|
l1.After(ctx, nil, []uint64{})
|
||||||
|
require.Equal(t, `--> test.fn1()
|
||||||
|
--> test.fn2()
|
||||||
|
<-- ()
|
||||||
|
<-- ()
|
||||||
|
`, out.String())
|
||||||
|
|
||||||
|
}
|
||||||
@@ -197,7 +197,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
|
|||||||
// Trigger relocation of goroutine stack because at this point we have the majority of
|
// Trigger relocation of goroutine stack because at this point we have the majority of
|
||||||
// goroutine stack unused after recursive call.
|
// goroutine stack unused after recursive call.
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
}}, map[string]*wasm.Memory{}, map[string]*wasm.Global{}, enabledFeatures)
|
}}, nil, map[string]*wasm.Memory{}, map[string]*wasm.Global{}, enabledFeatures)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = s.Engine.CompileModule(testCtx, hm)
|
err = s.Engine.CompileModule(testCtx, hm)
|
||||||
|
|||||||
95
internal/wasi_snapshot_preview1/errno.go
Normal file
95
internal/wasi_snapshot_preview1/errno.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Package wasi_snapshot_preview1 is an internal helper to remove package
|
||||||
|
// cycles re-using errno
|
||||||
|
package wasi_snapshot_preview1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is not an error. Ex. Errno2big -> "E2BIG"
|
||||||
|
func ErrnoName(errno uint32) string {
|
||||||
|
if int(errno) < len(errnoToString) {
|
||||||
|
return errnoToString[errno]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("errno(%d)", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errnoToString = [...]string{
|
||||||
|
"ESUCCESS",
|
||||||
|
"E2BIG",
|
||||||
|
"EACCES",
|
||||||
|
"EADDRINUSE",
|
||||||
|
"EADDRNOTAVAIL",
|
||||||
|
"EAFNOSUPPORT",
|
||||||
|
"EAGAIN",
|
||||||
|
"EALREADY",
|
||||||
|
"EBADF",
|
||||||
|
"EBADMSG",
|
||||||
|
"EBUSY",
|
||||||
|
"ECANCELED",
|
||||||
|
"ECHILD",
|
||||||
|
"ECONNABORTED",
|
||||||
|
"ECONNREFUSED",
|
||||||
|
"ECONNRESET",
|
||||||
|
"EDEADLK",
|
||||||
|
"EDESTADDRREQ",
|
||||||
|
"EDOM",
|
||||||
|
"EDQUOT",
|
||||||
|
"EEXIST",
|
||||||
|
"EFAULT",
|
||||||
|
"EFBIG",
|
||||||
|
"EHOSTUNREACH",
|
||||||
|
"EIDRM",
|
||||||
|
"EILSEQ",
|
||||||
|
"EINPROGRESS",
|
||||||
|
"EINTR",
|
||||||
|
"EINVAL",
|
||||||
|
"EIO",
|
||||||
|
"EISCONN",
|
||||||
|
"EISDIR",
|
||||||
|
"ELOOP",
|
||||||
|
"EMFILE",
|
||||||
|
"EMLINK",
|
||||||
|
"EMSGSIZE",
|
||||||
|
"EMULTIHOP",
|
||||||
|
"ENAMETOOLONG",
|
||||||
|
"ENETDOWN",
|
||||||
|
"ENETRESET",
|
||||||
|
"ENETUNREACH",
|
||||||
|
"ENFILE",
|
||||||
|
"ENOBUFS",
|
||||||
|
"ENODEV",
|
||||||
|
"ENOENT",
|
||||||
|
"ENOEXEC",
|
||||||
|
"ENOLCK",
|
||||||
|
"ENOLINK",
|
||||||
|
"ENOMEM",
|
||||||
|
"ENOMSG",
|
||||||
|
"ENOPROTOOPT",
|
||||||
|
"ENOSPC",
|
||||||
|
"ENOSYS",
|
||||||
|
"ENOTCONN",
|
||||||
|
"ENOTDIR",
|
||||||
|
"ENOTEMPTY",
|
||||||
|
"ENOTRECOVERABLE",
|
||||||
|
"ENOTSOCK",
|
||||||
|
"ENOTSUP",
|
||||||
|
"ENOTTY",
|
||||||
|
"ENXIO",
|
||||||
|
"EOVERFLOW",
|
||||||
|
"EOWNERDEAD",
|
||||||
|
"EPERM",
|
||||||
|
"EPIPE",
|
||||||
|
"EPROTO",
|
||||||
|
"EPROTONOSUPPORT",
|
||||||
|
"EPROTOTYPE",
|
||||||
|
"ERANGE",
|
||||||
|
"EROFS",
|
||||||
|
"ESPIPE",
|
||||||
|
"ESRCH",
|
||||||
|
"ESTALE",
|
||||||
|
"ETIMEDOUT",
|
||||||
|
"ETXTBSY",
|
||||||
|
"EXDEV",
|
||||||
|
"ENOTCAPABLE",
|
||||||
|
}
|
||||||
@@ -61,10 +61,13 @@ func (m *Module) BuildFunctionDefinitions() {
|
|||||||
importFuncIdx++
|
importFuncIdx++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At the moment, a module can either be solely wasm or host functions.
|
||||||
|
isHostFunction := m.HostFunctionSection != nil
|
||||||
for codeIndex, typeIndex := range m.FunctionSection {
|
for codeIndex, typeIndex := range m.FunctionSection {
|
||||||
m.FunctionDefinitionSection = append(m.FunctionDefinitionSection, &FunctionDefinition{
|
m.FunctionDefinitionSection = append(m.FunctionDefinitionSection, &FunctionDefinition{
|
||||||
index: Index(codeIndex) + importCount,
|
index: Index(codeIndex) + importCount,
|
||||||
funcType: m.TypeSection[typeIndex],
|
funcType: m.TypeSection[typeIndex],
|
||||||
|
isHostFunction: isHostFunction,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,14 +102,15 @@ func (m *Module) BuildFunctionDefinitions() {
|
|||||||
|
|
||||||
// FunctionDefinition implements api.FunctionDefinition
|
// FunctionDefinition implements api.FunctionDefinition
|
||||||
type FunctionDefinition struct {
|
type FunctionDefinition struct {
|
||||||
moduleName string
|
moduleName string
|
||||||
index Index
|
index Index
|
||||||
name string
|
name string
|
||||||
debugName string
|
debugName string
|
||||||
funcType *FunctionType
|
isHostFunction bool
|
||||||
importDesc *[2]string
|
funcType *FunctionType
|
||||||
exportNames []string
|
importDesc *[2]string
|
||||||
paramNames []string
|
exportNames []string
|
||||||
|
paramNames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModuleName implements the same method as documented on api.FunctionDefinition.
|
// ModuleName implements the same method as documented on api.FunctionDefinition.
|
||||||
@@ -142,6 +146,11 @@ func (f *FunctionDefinition) ExportNames() []string {
|
|||||||
return f.exportNames
|
return f.exportNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsHostFunction implements the same method as documented on api.FunctionDefinition.
|
||||||
|
func (f *FunctionDefinition) IsHostFunction() bool {
|
||||||
|
return f.isHostFunction
|
||||||
|
}
|
||||||
|
|
||||||
// ParamNames implements the same method as documented on api.FunctionDefinition.
|
// ParamNames implements the same method as documented on api.FunctionDefinition.
|
||||||
func (f *FunctionDefinition) ParamNames() []string {
|
func (f *FunctionDefinition) ParamNames() []string {
|
||||||
return f.paramNames
|
return f.paramNames
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package wasm
|
package wasm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
|
|
||||||
func TestModule_BuildFunctionDefinitions(t *testing.T) {
|
func TestModule_BuildFunctionDefinitions(t *testing.T) {
|
||||||
nopCode := &Code{nil, []byte{OpcodeEnd}}
|
nopCode := &Code{nil, []byte{OpcodeEnd}}
|
||||||
|
fnV := reflect.ValueOf(func() {})
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
m *Module
|
m *Module
|
||||||
@@ -26,6 +28,22 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
|
|||||||
GlobalSection: []*Global{{}},
|
GlobalSection: []*Global{{}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "host func",
|
||||||
|
m: &Module{
|
||||||
|
TypeSection: []*FunctionType{v_v},
|
||||||
|
FunctionSection: []Index{0},
|
||||||
|
HostFunctionSection: []*reflect.Value{&fnV},
|
||||||
|
},
|
||||||
|
expected: []*FunctionDefinition{
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
debugName: ".$0",
|
||||||
|
isHostFunction: true,
|
||||||
|
funcType: v_v,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "without imports",
|
name: "without imports",
|
||||||
m: &Module{
|
m: &Module{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
func NewHostModule(
|
func NewHostModule(
|
||||||
moduleName string,
|
moduleName string,
|
||||||
nameToGoFunc map[string]interface{},
|
nameToGoFunc map[string]interface{},
|
||||||
|
funcToNames map[string][]string,
|
||||||
nameToMemory map[string]*Memory,
|
nameToMemory map[string]*Memory,
|
||||||
nameToGlobal map[string]*Global,
|
nameToGlobal map[string]*Global,
|
||||||
enabledFeatures Features,
|
enabledFeatures Features,
|
||||||
@@ -33,11 +34,12 @@ func NewHostModule(
|
|||||||
|
|
||||||
// Check name collision as exports cannot collide on names, regardless of type.
|
// Check name collision as exports cannot collide on names, regardless of type.
|
||||||
for name := range nameToGoFunc {
|
for name := range nameToGoFunc {
|
||||||
|
// manually generate the error message as we don't have debug names yet.
|
||||||
if _, ok := nameToMemory[name]; ok {
|
if _, ok := nameToMemory[name]; ok {
|
||||||
return nil, fmt.Errorf("func[%s] exports the same name as a memory", name)
|
return nil, fmt.Errorf("func[%s.%s] exports the same name as a memory", moduleName, name)
|
||||||
}
|
}
|
||||||
if _, ok := nameToGlobal[name]; ok {
|
if _, ok := nameToGlobal[name]; ok {
|
||||||
return nil, fmt.Errorf("func[%s] exports the same name as a global", name)
|
return nil, fmt.Errorf("func[%s.%s] exports the same name as a global", moduleName, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name := range nameToMemory {
|
for name := range nameToMemory {
|
||||||
@@ -47,7 +49,7 @@ func NewHostModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if funcCount > 0 {
|
if funcCount > 0 {
|
||||||
if err = addFuncs(m, nameToGoFunc, enabledFeatures); err != nil {
|
if err = addFuncs(m, nameToGoFunc, funcToNames, enabledFeatures); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +78,12 @@ func (m *Module) IsHostModule() bool {
|
|||||||
return len(m.HostFunctionSection) > 0
|
return len(m.HostFunctionSection) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFuncs(m *Module, nameToGoFunc map[string]interface{}, enabledFeatures Features) error {
|
func addFuncs(
|
||||||
|
m *Module,
|
||||||
|
nameToGoFunc map[string]interface{},
|
||||||
|
funcToNames map[string][]string,
|
||||||
|
enabledFeatures Features,
|
||||||
|
) error {
|
||||||
funcCount := uint32(len(nameToGoFunc))
|
funcCount := uint32(len(nameToGoFunc))
|
||||||
funcNames := make([]string, 0, funcCount)
|
funcNames := make([]string, 0, funcCount)
|
||||||
if m.NameSection == nil {
|
if m.NameSection == nil {
|
||||||
@@ -95,26 +102,32 @@ func addFuncs(m *Module, nameToGoFunc map[string]interface{}, enabledFeatures Fe
|
|||||||
sort.Strings(funcNames)
|
sort.Strings(funcNames)
|
||||||
|
|
||||||
for idx := Index(0); idx < funcCount; idx++ {
|
for idx := Index(0); idx < funcCount; idx++ {
|
||||||
name := funcNames[idx]
|
exportName := funcNames[idx]
|
||||||
fn := reflect.ValueOf(nameToGoFunc[name])
|
debugName := wasmdebug.FuncName(moduleName, exportName, idx)
|
||||||
|
fn := reflect.ValueOf(nameToGoFunc[exportName])
|
||||||
_, functionType, err := getFunctionType(&fn, enabledFeatures)
|
_, functionType, err := getFunctionType(&fn, enabledFeatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("func[%s] %w", name, err)
|
return fmt.Errorf("func[%s] %w", debugName, err)
|
||||||
|
}
|
||||||
|
names := funcToNames[exportName]
|
||||||
|
namesLen := len(names)
|
||||||
|
if namesLen > 1 && namesLen-1 != len(functionType.Params) {
|
||||||
|
return fmt.Errorf("func[%s] has %d params, but %d param names", debugName, namesLen-1, len(functionType.Params))
|
||||||
}
|
}
|
||||||
|
|
||||||
m.FunctionSection = append(m.FunctionSection, m.maybeAddType(functionType))
|
m.FunctionSection = append(m.FunctionSection, m.maybeAddType(functionType))
|
||||||
m.HostFunctionSection = append(m.HostFunctionSection, &fn)
|
m.HostFunctionSection = append(m.HostFunctionSection, &fn)
|
||||||
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: name, Index: idx})
|
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: exportName, Index: idx})
|
||||||
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: name})
|
if namesLen > 0 {
|
||||||
m.FunctionDefinitionSection = append(m.FunctionDefinitionSection, &FunctionDefinition{
|
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: names[0]})
|
||||||
moduleName: moduleName,
|
localNames := &NameMapAssoc{Index: idx}
|
||||||
index: idx,
|
for i, n := range names[1:] {
|
||||||
name: name,
|
localNames.NameMap = append(localNames.NameMap, &NameAssoc{Index: Index(i), Name: n})
|
||||||
debugName: wasmdebug.FuncName(moduleName, name, idx),
|
}
|
||||||
funcType: functionType,
|
m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames)
|
||||||
exportNames: []string{name},
|
} else {
|
||||||
paramNames: nil, // TODO
|
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: exportName})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,13 +181,7 @@ func TestNewHostModule(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
m, e := NewHostModule(
|
m, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil, tc.nameToMemory, tc.nameToGlobal, Features20191205|FeatureMultiValue)
|
||||||
tc.moduleName,
|
|
||||||
tc.nameToGoFunc,
|
|
||||||
tc.nameToMemory,
|
|
||||||
tc.nameToGlobal,
|
|
||||||
Features20191205|FeatureMultiValue,
|
|
||||||
)
|
|
||||||
require.NoError(t, e)
|
require.NoError(t, e)
|
||||||
requireHostModuleEquals(t, tc.expected, m)
|
requireHostModuleEquals(t, tc.expected, m)
|
||||||
})
|
})
|
||||||
@@ -227,19 +221,19 @@ func TestNewHostModule_Errors(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "not a function",
|
name: "not a function",
|
||||||
nameToGoFunc: map[string]interface{}{"fn": t},
|
nameToGoFunc: map[string]interface{}{"fn": t},
|
||||||
expectedErr: "func[fn] kind != func: ptr",
|
expectedErr: "func[.fn] kind != func: ptr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "function has multiple results",
|
name: "function has multiple results",
|
||||||
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }},
|
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }},
|
||||||
nameToMemory: map[string]*Memory{"mem": {Min: 1, Max: 1}},
|
nameToMemory: map[string]*Memory{"mem": {Min: 1, Max: 1}},
|
||||||
expectedErr: "func[fn] multiple result types invalid as feature \"multi-value\" is disabled",
|
expectedErr: "func[.fn] multiple result types invalid as feature \"multi-value\" is disabled",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "func collides on memory name",
|
name: "func collides on memory name",
|
||||||
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
|
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
|
||||||
nameToMemory: map[string]*Memory{"fn": {Min: 1, Max: 1}},
|
nameToMemory: map[string]*Memory{"fn": {Min: 1, Max: 1}},
|
||||||
expectedErr: "func[fn] exports the same name as a memory",
|
expectedErr: "func[.fn] exports the same name as a memory",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple memories",
|
name: "multiple memories",
|
||||||
@@ -255,7 +249,7 @@ func TestNewHostModule_Errors(t *testing.T) {
|
|||||||
name: "func collides on global name",
|
name: "func collides on global name",
|
||||||
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
|
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
|
||||||
nameToGlobal: map[string]*Global{"fn": {}},
|
nameToGlobal: map[string]*Global{"fn": {}},
|
||||||
expectedErr: "func[fn] exports the same name as a global",
|
expectedErr: "func[.fn] exports the same name as a global",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +257,7 @@ func TestNewHostModule_Errors(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
_, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.nameToMemory, tc.nameToGlobal, Features20191205)
|
_, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil, tc.nameToMemory, tc.nameToGlobal, Features20191205)
|
||||||
require.EqualError(t, e, tc.expectedErr)
|
require.EqualError(t, e, tc.expectedErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*Glo
|
|||||||
// * This relies on data generated by Module.BuildFunctionDefinitions.
|
// * This relies on data generated by Module.BuildFunctionDefinitions.
|
||||||
// * This is exported for tests that don't call Instantiate, notably only
|
// * This is exported for tests that don't call Instantiate, notably only
|
||||||
// enginetest.go.
|
// enginetest.go.
|
||||||
func (m *ModuleInstance) BuildFunctions(mod *Module, fnlf experimental.FunctionListenerFactory) (fns []*FunctionInstance) {
|
func (m *ModuleInstance) BuildFunctions(mod *Module, listeners []experimental.FunctionListener) (fns []*FunctionInstance) {
|
||||||
fns = make([]*FunctionInstance, 0, len(mod.FunctionDefinitionSection))
|
fns = make([]*FunctionInstance, 0, len(mod.FunctionDefinitionSection))
|
||||||
if mod.IsHostModule() {
|
if mod.IsHostModule() {
|
||||||
for i := range mod.HostFunctionSection {
|
for i := range mod.HostFunctionSection {
|
||||||
@@ -615,8 +615,8 @@ func (m *ModuleInstance) BuildFunctions(mod *Module, fnlf experimental.FunctionL
|
|||||||
f.Idx = d.index
|
f.Idx = d.index
|
||||||
f.Type = d.funcType
|
f.Type = d.funcType
|
||||||
f.definition = d
|
f.definition = d
|
||||||
if fnlf != nil {
|
if listeners != nil {
|
||||||
f.FunctionListener = fnlf.NewListener(m.Name, d)
|
f.FunctionListener = listeners[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ func (s *Store) Instantiate(
|
|||||||
module *Module,
|
module *Module,
|
||||||
name string,
|
name string,
|
||||||
sys *internalsys.Context,
|
sys *internalsys.Context,
|
||||||
functionListenerFactory experimentalapi.FunctionListenerFactory,
|
listeners []experimentalapi.FunctionListener,
|
||||||
) (*CallContext, error) {
|
) (*CallContext, error) {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
@@ -329,7 +329,7 @@ func (s *Store) Instantiate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate the module and add it to the namespace so that other modules can import it.
|
// Instantiate the module and add it to the namespace so that other modules can import it.
|
||||||
if callCtx, err := s.instantiate(ctx, ns, module, name, sys, functionListenerFactory, importedModules); err != nil {
|
if callCtx, err := s.instantiate(ctx, ns, module, name, sys, listeners, importedModules); err != nil {
|
||||||
ns.deleteModule(name)
|
ns.deleteModule(name)
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
@@ -346,7 +346,7 @@ func (s *Store) instantiate(
|
|||||||
module *Module,
|
module *Module,
|
||||||
name string,
|
name string,
|
||||||
sysCtx *internalsys.Context,
|
sysCtx *internalsys.Context,
|
||||||
functionListenerFactory experimentalapi.FunctionListenerFactory,
|
listeners []experimentalapi.FunctionListener,
|
||||||
modules map[string]*ModuleInstance,
|
modules map[string]*ModuleInstance,
|
||||||
) (*CallContext, error) {
|
) (*CallContext, error) {
|
||||||
typeIDs, err := s.getFunctionTypeIDs(module.TypeSection)
|
typeIDs, err := s.getFunctionTypeIDs(module.TypeSection)
|
||||||
@@ -376,7 +376,7 @@ func (s *Store) instantiate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &ModuleInstance{Name: name, TypeIDs: typeIDs}
|
m := &ModuleInstance{Name: name, TypeIDs: typeIDs}
|
||||||
functions := m.BuildFunctions(module, functionListenerFactory)
|
functions := m.BuildFunctions(module, listeners)
|
||||||
|
|
||||||
// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
|
// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
|
||||||
m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
|
m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
|
||||||
|
|||||||
@@ -91,13 +91,7 @@ func TestModuleInstance_Memory(t *testing.T) {
|
|||||||
|
|
||||||
func TestStore_Instantiate(t *testing.T) {
|
func TestStore_Instantiate(t *testing.T) {
|
||||||
s, ns := newStore()
|
s, ns := newStore()
|
||||||
m, err := NewHostModule(
|
m, err := NewHostModule("", map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, Features20191205)
|
||||||
"",
|
|
||||||
map[string]interface{}{"fn": func(api.Module) {}},
|
|
||||||
map[string]*Memory{},
|
|
||||||
map[string]*Global{},
|
|
||||||
Features20191205,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sysCtx := sys.DefaultContext(nil)
|
sysCtx := sys.DefaultContext(nil)
|
||||||
@@ -175,13 +169,7 @@ func TestStore_CloseWithExitCode(t *testing.T) {
|
|||||||
func TestStore_hammer(t *testing.T) {
|
func TestStore_hammer(t *testing.T) {
|
||||||
const importedModuleName = "imported"
|
const importedModuleName = "imported"
|
||||||
|
|
||||||
m, err := NewHostModule(
|
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, Features20191205)
|
||||||
importedModuleName,
|
|
||||||
map[string]interface{}{"fn": func(api.Module) {}},
|
|
||||||
map[string]*Memory{},
|
|
||||||
map[string]*Global{},
|
|
||||||
Features20191205,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s, ns := newStore()
|
s, ns := newStore()
|
||||||
@@ -235,13 +223,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
|||||||
const importedModuleName = "imported"
|
const importedModuleName = "imported"
|
||||||
const importingModuleName = "test"
|
const importingModuleName = "test"
|
||||||
|
|
||||||
m, err := NewHostModule(
|
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, Features20191205)
|
||||||
importedModuleName,
|
|
||||||
map[string]interface{}{"fn": func(api.Module) {}},
|
|
||||||
map[string]*Memory{},
|
|
||||||
map[string]*Global{},
|
|
||||||
Features20191205,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("Fails if module name already in use", func(t *testing.T) {
|
t.Run("Fails if module name already in use", func(t *testing.T) {
|
||||||
@@ -332,13 +314,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCallContext_ExportedFunction(t *testing.T) {
|
func TestCallContext_ExportedFunction(t *testing.T) {
|
||||||
host, err := NewHostModule(
|
host, err := NewHostModule("host", map[string]interface{}{"host_fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, Features20191205)
|
||||||
"host",
|
|
||||||
map[string]interface{}{"host_fn": func(api.Module) {}},
|
|
||||||
map[string]*Memory{},
|
|
||||||
map[string]*Global{},
|
|
||||||
Features20191205,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s, ns := newStore()
|
s, ns := newStore()
|
||||||
|
|||||||
10
namespace.go
10
namespace.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
experimentalapi "github.com/tetratelabs/wazero/experimental"
|
|
||||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||||
"github.com/tetratelabs/wazero/internal/wasm"
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
"github.com/tetratelabs/wazero/sys"
|
"github.com/tetratelabs/wazero/sys"
|
||||||
@@ -80,15 +79,8 @@ func (ns *namespace) InstantiateModule(
|
|||||||
name = code.module.NameSection.ModuleName
|
name = code.module.NameSection.ModuleName
|
||||||
}
|
}
|
||||||
|
|
||||||
var functionListenerFactory experimentalapi.FunctionListenerFactory
|
|
||||||
if ctx != nil { // Test to see if internal code are using an experimental feature.
|
|
||||||
if fnlf := ctx.Value(experimentalapi.FunctionListenerFactoryKey{}); fnlf != nil {
|
|
||||||
functionListenerFactory = fnlf.(experimentalapi.FunctionListenerFactory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the module in the appropriate namespace.
|
// Instantiate the module in the appropriate namespace.
|
||||||
mod, err = ns.store.Instantiate(ctx, ns.ns, code.module, name, sysCtx, functionListenerFactory)
|
mod, err = ns.store.Instantiate(ctx, ns.ns, code.module, name, sysCtx, code.listeners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If there was an error, don't leak the compiled module.
|
// If there was an error, don't leak the compiled module.
|
||||||
if code.closeWithModule {
|
if code.closeWithModule {
|
||||||
|
|||||||
36
runtime.go
36
runtime.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
experimentalapi "github.com/tetratelabs/wazero/experimental"
|
||||||
"github.com/tetratelabs/wazero/internal/wasm"
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
|
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
|
||||||
)
|
)
|
||||||
@@ -130,6 +131,7 @@ func NewRuntimeWithConfig(rConfig RuntimeConfig) Runtime {
|
|||||||
store: store,
|
store: store,
|
||||||
ns: &namespace{store: store, ns: ns},
|
ns: &namespace{store: store, ns: ns},
|
||||||
enabledFeatures: config.enabledFeatures,
|
enabledFeatures: config.enabledFeatures,
|
||||||
|
isInterpreter: config.isInterpreter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,15 +140,13 @@ type runtime struct {
|
|||||||
store *wasm.Store
|
store *wasm.Store
|
||||||
ns *namespace
|
ns *namespace
|
||||||
enabledFeatures wasm.Features
|
enabledFeatures wasm.Features
|
||||||
|
isInterpreter bool
|
||||||
compiledModules []*compiledModule
|
compiledModules []*compiledModule
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNamespace implements Runtime.NewNamespace.
|
// NewNamespace implements Runtime.NewNamespace.
|
||||||
func (r *runtime) NewNamespace(ctx context.Context) Namespace {
|
func (r *runtime) NewNamespace(ctx context.Context) Namespace {
|
||||||
return &namespace{
|
return &namespace{store: r.store, ns: r.store.NewNamespace(ctx)}
|
||||||
store: r.store,
|
|
||||||
ns: r.store.NewNamespace(ctx),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module implements Namespace.Module embedded by Runtime.
|
// Module implements Namespace.Module embedded by Runtime.
|
||||||
@@ -179,15 +179,41 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte, cConfig Comp
|
|||||||
// Now that the module is validated, cache the function definitions.
|
// Now that the module is validated, cache the function definitions.
|
||||||
internal.BuildFunctionDefinitions()
|
internal.BuildFunctionDefinitions()
|
||||||
|
|
||||||
|
c := &compiledModule{module: internal, compiledEngine: r.store.Engine}
|
||||||
|
|
||||||
|
if c.listeners, err = buildListeners(ctx, r, internal); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err = r.store.Engine.CompileModule(ctx, internal); err != nil {
|
if err = r.store.Engine.CompileModule(ctx, internal); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &compiledModule{module: internal, compiledEngine: r.store.Engine}
|
|
||||||
r.compiledModules = append(r.compiledModules, c)
|
r.compiledModules = append(r.compiledModules, c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildListeners(ctx context.Context, r *runtime, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Test to see if internal code are using an experimental feature.
|
||||||
|
fnlf := ctx.Value(experimentalapi.FunctionListenerFactoryKey{})
|
||||||
|
if fnlf == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if !r.isInterpreter {
|
||||||
|
return nil, errors.New("context includes a FunctionListenerFactoryKey, which is only supported in the interpreter")
|
||||||
|
}
|
||||||
|
factory := fnlf.(experimentalapi.FunctionListenerFactory)
|
||||||
|
importCount := internal.ImportFuncCount()
|
||||||
|
listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection))
|
||||||
|
for i := 0; i < len(listeners); i++ {
|
||||||
|
listeners[i] = factory.NewListener(internal.FunctionDefinitionSection[uint32(i)+importCount])
|
||||||
|
}
|
||||||
|
return listeners, nil
|
||||||
|
}
|
||||||
|
|
||||||
// InstantiateModuleFromBinary implements Runtime.InstantiateModuleFromBinary
|
// InstantiateModuleFromBinary implements Runtime.InstantiateModuleFromBinary
|
||||||
func (r *runtime) InstantiateModuleFromBinary(ctx context.Context, binary []byte) (api.Module, error) {
|
func (r *runtime) InstantiateModuleFromBinary(ctx context.Context, binary []byte) (api.Module, error) {
|
||||||
if compiled, err := r.CompileModule(ctx, binary, NewCompileConfig()); err != nil {
|
if compiled, err := r.CompileModule(ctx, binary, NewCompileConfig()); err != nil {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package wasi_snapshot_preview1
|
package wasi_snapshot_preview1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
internalwasi "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errno are the error codes returned by WASI functions.
|
// Errno are the error codes returned by WASI functions.
|
||||||
@@ -17,10 +17,7 @@ type Errno = uint32 // neither uint16 nor an alias for parity with wasm.ValueTyp
|
|||||||
|
|
||||||
// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is not an error. Ex. Errno2big -> "E2BIG"
|
// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is not an error. Ex. Errno2big -> "E2BIG"
|
||||||
func ErrnoName(errno Errno) string {
|
func ErrnoName(errno Errno) string {
|
||||||
if int(errno) < len(errnoToString) {
|
return internalwasi.ErrnoName(errno)
|
||||||
return errnoToString[errno]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("errno(%d)", errno)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Below prefers POSIX symbol names over WASI ones, even if the docs are from WASI.
|
// Note: Below prefers POSIX symbol names over WASI ones, even if the docs are from WASI.
|
||||||
@@ -182,83 +179,3 @@ const (
|
|||||||
// ErrnoNotcapable Extension: Capabilities insufficient.
|
// ErrnoNotcapable Extension: Capabilities insufficient.
|
||||||
ErrnoNotcapable
|
ErrnoNotcapable
|
||||||
)
|
)
|
||||||
|
|
||||||
var errnoToString = [...]string{
|
|
||||||
ErrnoSuccess: "ESUCCESS",
|
|
||||||
Errno2big: "E2BIG",
|
|
||||||
ErrnoAcces: "EACCES",
|
|
||||||
ErrnoAddrinuse: "EADDRINUSE",
|
|
||||||
ErrnoAddrnotavail: "EADDRNOTAVAIL",
|
|
||||||
ErrnoAfnosupport: "EAFNOSUPPORT",
|
|
||||||
ErrnoAgain: "EAGAIN",
|
|
||||||
ErrnoAlready: "EALREADY",
|
|
||||||
ErrnoBadf: "EBADF",
|
|
||||||
ErrnoBadmsg: "EBADMSG",
|
|
||||||
ErrnoBusy: "EBUSY",
|
|
||||||
ErrnoCanceled: "ECANCELED",
|
|
||||||
ErrnoChild: "ECHILD",
|
|
||||||
ErrnoConnaborted: "ECONNABORTED",
|
|
||||||
ErrnoConnrefused: "ECONNREFUSED",
|
|
||||||
ErrnoConnreset: "ECONNRESET",
|
|
||||||
ErrnoDeadlk: "EDEADLK",
|
|
||||||
ErrnoDestaddrreq: "EDESTADDRREQ",
|
|
||||||
ErrnoDom: "EDOM",
|
|
||||||
ErrnoDquot: "EDQUOT",
|
|
||||||
ErrnoExist: "EEXIST",
|
|
||||||
ErrnoFault: "EFAULT",
|
|
||||||
ErrnoFbig: "EFBIG",
|
|
||||||
ErrnoHostunreach: "EHOSTUNREACH",
|
|
||||||
ErrnoIdrm: "EIDRM",
|
|
||||||
ErrnoIlseq: "EILSEQ",
|
|
||||||
ErrnoInprogress: "EINPROGRESS",
|
|
||||||
ErrnoIntr: "EINTR",
|
|
||||||
ErrnoInval: "EINVAL",
|
|
||||||
ErrnoIo: "EIO",
|
|
||||||
ErrnoIsconn: "EISCONN",
|
|
||||||
ErrnoIsdir: "EISDIR",
|
|
||||||
ErrnoLoop: "ELOOP",
|
|
||||||
ErrnoMfile: "EMFILE",
|
|
||||||
ErrnoMlink: "EMLINK",
|
|
||||||
ErrnoMsgsize: "EMSGSIZE",
|
|
||||||
ErrnoMultihop: "EMULTIHOP",
|
|
||||||
ErrnoNametoolong: "ENAMETOOLONG",
|
|
||||||
ErrnoNetdown: "ENETDOWN",
|
|
||||||
ErrnoNetreset: "ENETRESET",
|
|
||||||
ErrnoNetunreach: "ENETUNREACH",
|
|
||||||
ErrnoNfile: "ENFILE",
|
|
||||||
ErrnoNobufs: "ENOBUFS",
|
|
||||||
ErrnoNodev: "ENODEV",
|
|
||||||
ErrnoNoent: "ENOENT",
|
|
||||||
ErrnoNoexec: "ENOEXEC",
|
|
||||||
ErrnoNolck: "ENOLCK",
|
|
||||||
ErrnoNolink: "ENOLINK",
|
|
||||||
ErrnoNomem: "ENOMEM",
|
|
||||||
ErrnoNomsg: "ENOMSG",
|
|
||||||
ErrnoNoprotoopt: "ENOPROTOOPT",
|
|
||||||
ErrnoNospc: "ENOSPC",
|
|
||||||
ErrnoNosys: "ENOSYS",
|
|
||||||
ErrnoNotconn: "ENOTCONN",
|
|
||||||
ErrnoNotdir: "ENOTDIR",
|
|
||||||
ErrnoNotempty: "ENOTEMPTY",
|
|
||||||
ErrnoNotrecoverable: "ENOTRECOVERABLE",
|
|
||||||
ErrnoNotsock: "ENOTSOCK",
|
|
||||||
ErrnoNotsup: "ENOTSUP",
|
|
||||||
ErrnoNotty: "ENOTTY",
|
|
||||||
ErrnoNxio: "ENXIO",
|
|
||||||
ErrnoOverflow: "EOVERFLOW",
|
|
||||||
ErrnoOwnerdead: "EOWNERDEAD",
|
|
||||||
ErrnoPerm: "EPERM",
|
|
||||||
ErrnoPipe: "EPIPE",
|
|
||||||
ErrnoProto: "EPROTO",
|
|
||||||
ErrnoProtonosupport: "EPROTONOSUPPORT",
|
|
||||||
ErrnoPrototype: "EPROTOTYPE",
|
|
||||||
ErrnoRange: "ERANGE",
|
|
||||||
ErrnoRofs: "EROFS",
|
|
||||||
ErrnoSpipe: "ESPIPE",
|
|
||||||
ErrnoSrch: "ESRCH",
|
|
||||||
ErrnoStale: "ESTALE",
|
|
||||||
ErrnoTimedout: "ETIMEDOUT",
|
|
||||||
ErrnoTxtbsy: "ETXTBSY",
|
|
||||||
ErrnoXdev: "EXDEV",
|
|
||||||
ErrnoNotcapable: "ENOTCAPABLE",
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// Package wasi_snapshot_preview1 contains Go-defined functions to access system calls, such as opening a file, similar
|
// Package wasi_snapshot_preview1 contains Go-defined functions to access
|
||||||
// to Go's x/sys package. These are accessible from WebAssembly-defined functions via importing ModuleName.
|
// system calls, such as opening a file, similar to Go's x/sys package. These
|
||||||
// All WASI functions return a single Errno result, which is ErrnoSuccess on success.
|
// are accessible from WebAssembly-defined functions via importing ModuleName.
|
||||||
|
// All WASI functions return a single Errno result: ErrnoSuccess on success.
|
||||||
//
|
//
|
||||||
// Ex. If your source (%.wasm binary) includes an import "wasi_snapshot_preview1", call Instantiate
|
// Ex. Call Instantiate before instantiating any wasm binary that imports
|
||||||
// prior to instantiating it. Otherwise, it will error due to missing imports.
|
// "wasi_snapshot_preview1", Otherwise, it will error due to missing imports.
|
||||||
// ctx := context.Background()
|
// ctx := context.Background()
|
||||||
// r := wazero.NewRuntime()
|
// r := wazero.NewRuntime()
|
||||||
// defer r.Close(ctx) // This closes everything this Runtime created.
|
// defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
@@ -31,7 +32,8 @@ import (
|
|||||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
||||||
const ModuleName = "wasi_snapshot_preview1"
|
const ModuleName = "wasi_snapshot_preview1"
|
||||||
|
|
||||||
// Instantiate instantiates the ModuleName module into the runtime default namespace.
|
// Instantiate instantiates the ModuleName module into the runtime default
|
||||||
|
// namespace.
|
||||||
//
|
//
|
||||||
// Notes
|
// Notes
|
||||||
//
|
//
|
||||||
@@ -44,12 +46,13 @@ func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
|||||||
// Builder configures the ModuleName module for later use via Compile or Instantiate.
|
// Builder configures the ModuleName module for later use via Compile or Instantiate.
|
||||||
type Builder interface {
|
type Builder interface {
|
||||||
|
|
||||||
// Compile compiles the ModuleName module that can instantiated in any namespace (wazero.Namespace).
|
// Compile compiles the ModuleName module that can instantiated in any
|
||||||
|
// namespace (wazero.Namespace).
|
||||||
//
|
//
|
||||||
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
||||||
Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error)
|
Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error)
|
||||||
|
|
||||||
// Instantiate instantiates the ModuleName module into the provided namespace.
|
// Instantiate instantiates the ModuleName module into the given namespace.
|
||||||
//
|
//
|
||||||
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
||||||
Instantiate(context.Context, wazero.Namespace) (api.Closer, error)
|
Instantiate(context.Context, wazero.Namespace) (api.Closer, error)
|
||||||
@@ -64,7 +67,9 @@ type builder struct{ r wazero.Runtime }
|
|||||||
|
|
||||||
// moduleBuilder returns a new wazero.ModuleBuilder for ModuleName
|
// moduleBuilder returns a new wazero.ModuleBuilder for ModuleName
|
||||||
func (b *builder) moduleBuilder() wazero.ModuleBuilder {
|
func (b *builder) moduleBuilder() wazero.ModuleBuilder {
|
||||||
return b.r.NewModuleBuilder(ModuleName).ExportFunctions(wasiFunctions())
|
builder := b.r.NewModuleBuilder(ModuleName)
|
||||||
|
exportFunctions(builder)
|
||||||
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile implements Builder.Compile
|
// Compile implements Builder.Compile
|
||||||
@@ -437,59 +442,102 @@ const (
|
|||||||
// See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
|
// See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
|
||||||
type wasi struct{}
|
type wasi struct{}
|
||||||
|
|
||||||
// wasiFunctions returns all go functions that implement wasi.
|
// exportFunctions adds all go functions that implement wasi.
|
||||||
// These should be exported in the module named ModuleName.
|
// These should be exported in the module named ModuleName.
|
||||||
func wasiFunctions() map[string]interface{} {
|
func exportFunctions(builder wazero.ModuleBuilder) {
|
||||||
a := &wasi{}
|
a := &wasi{}
|
||||||
// Note: these are ordered per spec for consistency even if the resulting map can't guarantee that.
|
// Note: these are ordered per spec for consistency even if the resulting map can't guarantee that.
|
||||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
|
||||||
return map[string]interface{}{
|
builder.ExportFunction(functionArgsGet, a.ArgsGet,
|
||||||
functionArgsGet: a.ArgsGet,
|
functionArgsGet, "argv", "argv_buf")
|
||||||
functionArgsSizesGet: a.ArgsSizesGet,
|
builder.ExportFunction(functionArgsSizesGet, a.ArgsSizesGet,
|
||||||
functionEnvironGet: a.EnvironGet,
|
functionArgsSizesGet, "result.argc", "result.argv_buf_size")
|
||||||
functionEnvironSizesGet: a.EnvironSizesGet,
|
builder.ExportFunction(functionEnvironGet, a.EnvironGet,
|
||||||
functionClockResGet: a.ClockResGet,
|
functionEnvironGet, "environ", "environ_buf")
|
||||||
functionClockTimeGet: a.ClockTimeGet,
|
builder.ExportFunction(functionEnvironSizesGet, a.EnvironSizesGet,
|
||||||
functionFdAdvise: a.FdAdvise,
|
functionEnvironSizesGet, "result.environc", "result.environBufSize")
|
||||||
functionFdAllocate: a.FdAllocate,
|
builder.ExportFunction(functionClockResGet, a.ClockResGet,
|
||||||
functionFdClose: a.FdClose,
|
functionClockResGet, "id", "result.resolution")
|
||||||
functionFdDatasync: a.FdDatasync,
|
builder.ExportFunction(functionClockTimeGet, a.ClockTimeGet,
|
||||||
functionFdFdstatGet: a.FdFdstatGet,
|
functionClockTimeGet, "id", "precision", "result.timestamp")
|
||||||
functionFdFdstatSetFlags: a.FdFdstatSetFlags,
|
builder.ExportFunction(functionFdAdvise, a.FdAdvise,
|
||||||
functionFdFdstatSetRights: a.FdFdstatSetRights,
|
functionFdAdvise, "fd", "offset", "len", "result.advice")
|
||||||
functionFdFilestatGet: a.FdFilestatGet,
|
builder.ExportFunction(functionFdAllocate, a.FdAllocate,
|
||||||
functionFdFilestatSetSize: a.FdFilestatSetSize,
|
functionFdAllocate, "fd", "offset", "len")
|
||||||
functionFdFilestatSetTimes: a.FdFilestatSetTimes,
|
builder.ExportFunction(functionFdClose, a.FdClose,
|
||||||
functionFdPread: a.FdPread,
|
functionFdClose, "fd")
|
||||||
functionFdPrestatGet: a.FdPrestatGet,
|
builder.ExportFunction(functionFdDatasync, a.FdDatasync,
|
||||||
functionFdPrestatDirName: a.FdPrestatDirName,
|
functionFdDatasync, "fd")
|
||||||
functionFdPwrite: a.FdPwrite,
|
builder.ExportFunction(functionFdFdstatGet, a.FdFdstatGet,
|
||||||
functionFdRead: a.FdRead,
|
functionFdFdstatGet, "fd", "result.stat")
|
||||||
functionFdReaddir: a.FdReaddir,
|
builder.ExportFunction(functionFdFdstatSetFlags, a.FdFdstatSetFlags,
|
||||||
functionFdRenumber: a.FdRenumber,
|
functionFdFdstatSetFlags, "fd", "flags")
|
||||||
functionFdSeek: a.FdSeek,
|
builder.ExportFunction(functionFdFdstatSetRights, a.FdFdstatSetRights,
|
||||||
functionFdSync: a.FdSync,
|
functionFdFdstatSetRights, "fd", "fs_rights_base", "fs_rights_inheriting")
|
||||||
functionFdTell: a.FdTell,
|
builder.ExportFunction(functionFdFilestatGet, a.FdFilestatGet,
|
||||||
functionFdWrite: a.FdWrite,
|
functionFdFilestatGet, "fd", "result.buf")
|
||||||
functionPathCreateDirectory: a.PathCreateDirectory,
|
builder.ExportFunction(functionFdFilestatSetSize, a.FdFilestatSetSize,
|
||||||
functionPathFilestatGet: a.PathFilestatGet,
|
functionFdFilestatSetSize, "fd", "size")
|
||||||
functionPathFilestatSetTimes: a.PathFilestatSetTimes,
|
builder.ExportFunction(functionFdFilestatSetTimes, a.FdFilestatSetTimes,
|
||||||
functionPathLink: a.PathLink,
|
functionFdFilestatSetTimes, "fd", "atim", "mtim", "fst_flags")
|
||||||
functionPathOpen: a.PathOpen,
|
builder.ExportFunction(functionFdPread, a.FdPread,
|
||||||
functionPathReadlink: a.PathReadlink,
|
functionFdPread, "fd", "iovs", "iovs_len", "offset", "result.nread")
|
||||||
functionPathRemoveDirectory: a.PathRemoveDirectory,
|
builder.ExportFunction(functionFdPrestatGet, a.FdPrestatGet,
|
||||||
functionPathRename: a.PathRename,
|
functionFdPrestatGet, "fd", "result.prestat")
|
||||||
functionPathSymlink: a.PathSymlink,
|
builder.ExportFunction(functionFdPrestatDirName, a.FdPrestatDirName,
|
||||||
functionPathUnlinkFile: a.PathUnlinkFile,
|
functionFdPrestatDirName, "fd", "path", "path_len")
|
||||||
functionPollOneoff: a.PollOneoff,
|
builder.ExportFunction(functionFdPwrite, a.FdPwrite,
|
||||||
functionProcExit: a.ProcExit,
|
functionFdPwrite, "fd", "iovs", "iovs_len", "offset", "result.nwritten")
|
||||||
functionProcRaise: a.ProcRaise,
|
builder.ExportFunction(functionFdRead, a.FdRead,
|
||||||
functionSchedYield: a.SchedYield,
|
functionFdRead, "fd", "iovs", "iovs_len", "result.size")
|
||||||
functionRandomGet: a.RandomGet,
|
builder.ExportFunction(functionFdReaddir, a.FdReaddir,
|
||||||
functionSockRecv: a.SockRecv,
|
functionFdReaddir, "fd", "buf", "buf_len", "cookie", "result.bufused")
|
||||||
functionSockSend: a.SockSend,
|
builder.ExportFunction(functionFdRenumber, a.FdRenumber,
|
||||||
functionSockShutdown: a.SockShutdown,
|
functionFdRenumber, "fd", "to")
|
||||||
}
|
builder.ExportFunction(functionFdSeek, a.FdSeek,
|
||||||
|
functionFdSeek, "fd", "offset", "whence", "result.newoffset")
|
||||||
|
builder.ExportFunction(functionFdSync, a.FdSync,
|
||||||
|
functionFdSync, "fd")
|
||||||
|
builder.ExportFunction(functionFdTell, a.FdTell,
|
||||||
|
functionFdTell, "fd", "result.offset")
|
||||||
|
builder.ExportFunction(functionFdWrite, a.FdWrite,
|
||||||
|
functionFdWrite, "fd", "iovs", "iovs_len", "result.size")
|
||||||
|
builder.ExportFunction(functionPathCreateDirectory, a.PathCreateDirectory,
|
||||||
|
functionPathCreateDirectory, "fd", "path", "path_len")
|
||||||
|
builder.ExportFunction(functionPathFilestatGet, a.PathFilestatGet,
|
||||||
|
functionPathFilestatGet, "fd", "flags", "path", "path_len", "result.buf")
|
||||||
|
builder.ExportFunction(functionPathFilestatSetTimes, a.PathFilestatSetTimes,
|
||||||
|
functionPathFilestatSetTimes, "fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags")
|
||||||
|
builder.ExportFunction(functionPathLink, a.PathLink,
|
||||||
|
functionPathLink, "old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len")
|
||||||
|
builder.ExportFunction(functionPathOpen, a.PathOpen,
|
||||||
|
functionPathOpen, "fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd")
|
||||||
|
builder.ExportFunction(functionPathReadlink, a.PathReadlink,
|
||||||
|
functionPathReadlink, "fd", "path", "path_len", "buf", "buf_len", "result.bufused")
|
||||||
|
builder.ExportFunction(functionPathRemoveDirectory, a.PathRemoveDirectory,
|
||||||
|
functionPathRemoveDirectory, "fd", "path", "path_len")
|
||||||
|
builder.ExportFunction(functionPathRename, a.PathRename,
|
||||||
|
functionPathRename, "fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len")
|
||||||
|
builder.ExportFunction(functionPathSymlink, a.PathSymlink,
|
||||||
|
functionPathSymlink, "old_path", "old_path_len", "fd", "new_path", "new_path_len")
|
||||||
|
builder.ExportFunction(functionPathUnlinkFile, a.PathUnlinkFile,
|
||||||
|
functionPathUnlinkFile, "fd", "path", "path_len")
|
||||||
|
builder.ExportFunction(functionPollOneoff, a.PollOneoff,
|
||||||
|
functionPollOneoff, "in", "out", "nsubscriptions", "result.nevents")
|
||||||
|
builder.ExportFunction(functionProcExit, a.ProcExit,
|
||||||
|
functionProcExit, "rval")
|
||||||
|
builder.ExportFunction(functionProcRaise, a.ProcRaise,
|
||||||
|
functionProcRaise, "sig")
|
||||||
|
builder.ExportFunction(functionSchedYield, a.SchedYield,
|
||||||
|
functionSchedYield)
|
||||||
|
builder.ExportFunction(functionRandomGet, a.RandomGet,
|
||||||
|
functionRandomGet, "buf", "buf_len")
|
||||||
|
builder.ExportFunction(functionSockRecv, a.SockRecv,
|
||||||
|
functionSockRecv, "fd", "ri_data", "ri_data_count", "ri_flags", "result.ro_datalen", "result.ro_flags")
|
||||||
|
builder.ExportFunction(functionSockSend, a.SockSend,
|
||||||
|
functionSockSend, "fd", "si_data", "si_data_count", "si_flags", "result.so_datalen")
|
||||||
|
builder.ExportFunction(functionSockShutdown, a.SockShutdown,
|
||||||
|
functionSockShutdown, "fd", "how")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArgsGet is the WASI function that reads command-line argument data (WithArgs).
|
// ArgsGet is the WASI function that reads command-line argument data (WithArgs).
|
||||||
|
|||||||
Reference in New Issue
Block a user