Refactors host function tests to stub with wasm (#710)
This refactors host functions with no-op or constant returns to be implemented with wasm instead of the host function bridge. This allows better performance. This also breaks up and makes WASI tests consistent, in a way that shows parameter name drifts easier. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -18,7 +18,9 @@
|
||||
// these functions.
|
||||
//
|
||||
// See wasi_snapshot_preview1.Instantiate and
|
||||
// https://www.assemblyscript.org/concepts.html#special-imports
|
||||
// * https://www.assemblyscript.org/concepts.html#special-imports
|
||||
// * https://www.assemblyscript.org/concepts.html#targeting-wasi
|
||||
// * https://www.assemblyscript.org/compiler.html#compiler-options
|
||||
package assemblyscript
|
||||
|
||||
import (
|
||||
@@ -114,24 +116,26 @@ func (e *functionExporter) WithTraceToStderr() FunctionExporter {
|
||||
|
||||
// ExportFunctions implements FunctionExporter.ExportFunctions
|
||||
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
|
||||
env := &assemblyscript{abortMessageDisabled: e.abortMessageDisabled, traceMode: e.traceMode}
|
||||
builder.ExportFunction("abort", env.abort, "~lib/builtins/abort",
|
||||
var abortFn fnAbort
|
||||
if e.abortMessageDisabled {
|
||||
abortFn = abort
|
||||
} else {
|
||||
abortFn = abortWithMessage
|
||||
}
|
||||
var traceFn interface{}
|
||||
switch e.traceMode {
|
||||
case traceDisabled:
|
||||
traceFn = traceNoop
|
||||
case traceStdout:
|
||||
traceFn = traceToStdout
|
||||
case traceStderr:
|
||||
traceFn = traceToStderr
|
||||
}
|
||||
builder.ExportFunction("abort", abortFn, "~lib/builtins/abort",
|
||||
"message", "fileName", "lineNumber", "columnNumber")
|
||||
builder.ExportFunction("trace", env.trace, "~lib/builtins/trace",
|
||||
builder.ExportFunction("trace", traceFn, "~lib/builtins/trace",
|
||||
"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
|
||||
// user didn't add `import "wasi"` to their entry file.
|
||||
//
|
||||
// See https://www.assemblyscript.org/concepts.html#special-imports
|
||||
// See https://www.assemblyscript.org/concepts.html#targeting-wasi
|
||||
// See https://www.assemblyscript.org/compiler.html#compiler-options
|
||||
// See https://github.com/AssemblyScript/assemblyscript/issues/1562
|
||||
type assemblyscript struct {
|
||||
abortMessageDisabled bool
|
||||
traceMode traceMode
|
||||
builder.ExportFunction("seed", seed, "~lib/builtins/seed")
|
||||
}
|
||||
|
||||
// abort is called on unrecoverable errors. This is typically present in Wasm
|
||||
@@ -146,27 +150,25 @@ type assemblyscript struct {
|
||||
// (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
//
|
||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L18
|
||||
func (a *assemblyscript) abort(
|
||||
ctx context.Context,
|
||||
mod api.Module,
|
||||
message uint32,
|
||||
fileName uint32,
|
||||
lineNumber uint32,
|
||||
columnNumber uint32,
|
||||
) {
|
||||
if !a.abortMessageDisabled {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
msg, err := readAssemblyScriptString(ctx, mod, message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fn, err := readAssemblyScriptString(ctx, mod, fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
|
||||
}
|
||||
type fnAbort func(
|
||||
ctx context.Context, mod api.Module, message, fileName, lineNumber, columnNumber uint32,
|
||||
)
|
||||
|
||||
// abortWithMessage implements fnAbort
|
||||
func abortWithMessage(
|
||||
ctx context.Context, mod api.Module, message, fileName, lineNumber, columnNumber uint32,
|
||||
) {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
msg := requireAssemblyScriptString(ctx, mod, "message", message)
|
||||
fn := requireAssemblyScriptString(ctx, mod, "fileName", fileName)
|
||||
_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
|
||||
abort(ctx, mod, message, fileName, lineNumber, columnNumber)
|
||||
}
|
||||
|
||||
// abortWithMessage implements fnAbort ignoring the message.
|
||||
func abort(
|
||||
ctx context.Context, mod api.Module, message, fileName, lineNumber, columnNumber uint32,
|
||||
) {
|
||||
// AssemblyScript expects the exit code to be 255
|
||||
// See https://github.com/AssemblyScript/assemblyscript/blob/v0.20.13/tests/compiler/wasi/abort.js#L14
|
||||
exitCode := uint32(255)
|
||||
@@ -178,7 +180,27 @@ func (a *assemblyscript) abort(
|
||||
panic(sys.NewExitError(mod.Name(), exitCode))
|
||||
}
|
||||
|
||||
// trace implements the same named function in AssemblyScript. Ex.
|
||||
// traceNoop implements trace in wasm to avoid host call overhead on no-op.
|
||||
var traceNoop = &wasm.Func{
|
||||
Type: wasm.MustFunctionType(traceToStdout),
|
||||
Code: &wasm.Code{Body: []byte{wasm.OpcodeEnd}},
|
||||
}
|
||||
|
||||
// traceToStdout implements trace to the configured Stdout.
|
||||
func traceToStdout(
|
||||
ctx context.Context, mod api.Module, message uint32, nArgs uint32, arg0, arg1, arg2, arg3, arg4 float64,
|
||||
) {
|
||||
traceTo(ctx, mod, message, nArgs, arg0, arg1, arg2, arg3, arg4, mod.(*wasm.CallContext).Sys.Stdout())
|
||||
}
|
||||
|
||||
// traceToStdout implements trace to the configured Stderr.
|
||||
func traceToStderr(
|
||||
ctx context.Context, mod api.Module, message uint32, nArgs uint32, arg0, arg1, arg2, arg3, arg4 float64,
|
||||
) {
|
||||
traceTo(ctx, mod, message, nArgs, arg0, arg1, arg2, arg3, arg4, mod.(*wasm.CallContext).Sys.Stderr())
|
||||
}
|
||||
|
||||
// traceTo implements the function "trace" in AssemblyScript. Ex.
|
||||
// trace('Hello World!')
|
||||
//
|
||||
// Here's the import in a user's module that ends up using this, in WebAssembly
|
||||
@@ -186,26 +208,13 @@ func (a *assemblyscript) abort(
|
||||
// (import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
||||
//
|
||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L61
|
||||
func (a *assemblyscript) trace(
|
||||
func traceTo(
|
||||
ctx context.Context, mod api.Module, message uint32, nArgs uint32, arg0, arg1, arg2, arg3, arg4 float64,
|
||||
writer io.Writer,
|
||||
) {
|
||||
var writer io.Writer
|
||||
switch a.traceMode {
|
||||
case traceDisabled:
|
||||
return
|
||||
case traceStdout:
|
||||
writer = mod.(*wasm.CallContext).Sys.Stdout()
|
||||
case traceStderr:
|
||||
writer = mod.(*wasm.CallContext).Sys.Stderr()
|
||||
}
|
||||
|
||||
msg, err := readAssemblyScriptString(ctx, mod, message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var ret strings.Builder
|
||||
ret.WriteString("trace: ")
|
||||
ret.WriteString(msg)
|
||||
ret.WriteString(requireAssemblyScriptString(ctx, mod, "message", message))
|
||||
if nArgs >= 1 {
|
||||
ret.WriteString(" ")
|
||||
ret.WriteString(formatFloat(arg0))
|
||||
@@ -227,8 +236,7 @@ func (a *assemblyscript) trace(
|
||||
ret.WriteString(formatFloat(arg4))
|
||||
}
|
||||
ret.WriteByte('\n')
|
||||
_, err = writer.Write([]byte(ret.String()))
|
||||
if err != nil {
|
||||
if _, err := writer.Write([]byte(ret.String())); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -245,7 +253,7 @@ func formatFloat(f float64) string {
|
||||
// (import "env" "seed" (func $~lib/builtins/seed (result f64)))
|
||||
//
|
||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L111
|
||||
func (a *assemblyscript) seed(mod api.Module) float64 {
|
||||
func seed(mod api.Module) float64 {
|
||||
randSource := mod.(*wasm.CallContext).Sys.RandSource()
|
||||
v, err := ieee754.DecodeFloat64(randSource)
|
||||
if err != nil {
|
||||
@@ -254,21 +262,21 @@ func (a *assemblyscript) seed(mod api.Module) float64 {
|
||||
return v
|
||||
}
|
||||
|
||||
// readAssemblyScriptString reads a UTF-16 string created by AssemblyScript.
|
||||
func readAssemblyScriptString(ctx context.Context, mod api.Module, offset uint32) (string, error) {
|
||||
// requireAssemblyScriptString reads a UTF-16 string created by AssemblyScript.
|
||||
func requireAssemblyScriptString(ctx context.Context, mod api.Module, fieldName string, offset uint32) string {
|
||||
// Length is four bytes before pointer.
|
||||
byteCount, ok := mod.Memory().ReadUint32Le(ctx, offset-4)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Memory.ReadUint32Le(%d) out of range", offset-4)
|
||||
panic(fmt.Errorf("out of memory reading %s", fieldName))
|
||||
}
|
||||
if byteCount%2 != 0 {
|
||||
return "", fmt.Errorf("read an odd number of bytes for utf-16 string: %d", byteCount)
|
||||
panic(fmt.Errorf("invalid UTF-16 reading %s", fieldName))
|
||||
}
|
||||
buf, ok := mod.Memory().Read(ctx, offset, byteCount)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Memory.Read(%d, %d) out of range", offset, byteCount)
|
||||
panic(fmt.Errorf("out of memory reading %s", fieldName))
|
||||
}
|
||||
return decodeUTF16(buf), nil
|
||||
return decodeUTF16(buf)
|
||||
}
|
||||
|
||||
func decodeUTF16(b []byte) string {
|
||||
|
||||
@@ -19,40 +19,29 @@ import (
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
var abortWat = `(module
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
(memory 1 1)
|
||||
(export "abort" (func 0))
|
||||
)`
|
||||
|
||||
var seedWat = `(module
|
||||
(import "env" "seed" (func $~lib/builtins/seed (result f64)))
|
||||
(memory 1 1)
|
||||
(export "seed" (func 0))
|
||||
)`
|
||||
|
||||
var traceWat = `(module
|
||||
(import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
||||
(memory 1 1)
|
||||
(export "trace" (func 0))
|
||||
)`
|
||||
|
||||
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||
|
||||
func TestAbort(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(&stderr))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
abortFn fnAbort
|
||||
exporter FunctionExporter
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "enabled",
|
||||
abortFn: abortWithMessage,
|
||||
exporter: NewFunctionExporter(),
|
||||
expected: "message at filename:1:2\n",
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
abortFn: abort,
|
||||
exporter: NewFunctionExporter().WithAbortMessageDisabled(),
|
||||
expected: "",
|
||||
},
|
||||
@@ -62,42 +51,26 @@ func TestAbort(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var out, log bytes.Buffer
|
||||
defer stderr.Reset()
|
||||
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
|
||||
|
||||
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)
|
||||
|
||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||
require.NoError(t, err)
|
||||
|
||||
code, err := r.CompileModule(ctx, abortWasm, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStderr(&out))
|
||||
require.NoError(t, err)
|
||||
|
||||
messageOff, filenameOff := writeAbortMessageAndFileName(ctx, t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
|
||||
|
||||
_, err = mod.ExportedFunction("abort").Call(ctx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
||||
err := require.CapturePanic(func() {
|
||||
tc.abortFn(testCtx, mod, messageOff, filenameOff, 1, 2)
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
|
||||
|
||||
require.Equal(t, tc.expected, out.String())
|
||||
require.Equal(t, `==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
|
||||
`, log.String())
|
||||
require.Equal(t, tc.expected, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbort_Error(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(&stderr))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
messageUTF16 []byte
|
||||
@@ -126,109 +99,25 @@ func TestAbort_Error(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var out, log bytes.Buffer
|
||||
defer stderr.Reset()
|
||||
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||
defer r.Close(ctx)
|
||||
|
||||
_, err := Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, err := r.CompileModule(ctx, abortWasm, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(&out)
|
||||
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||
require.NoError(t, err)
|
||||
|
||||
messageOff, filenameOff := writeAbortMessageAndFileName(ctx, t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
||||
|
||||
_, err = mod.ExportedFunction("abort").Call(ctx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
// Since abort panics, any opcodes afterwards cannot be reached.
|
||||
_ = require.CapturePanic(func() {
|
||||
abortWithMessage(testCtx, mod, messageOff, filenameOff, 1, 2)
|
||||
})
|
||||
require.Equal(t, "", stderr.String()) // nothing output if strings fail to read.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var unreachableAfterAbort = `(module
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
(func $main
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
call $~lib/builtins/abort
|
||||
unreachable ;; If abort doesn't panic, this code is reached.
|
||||
)
|
||||
(start $main)
|
||||
)`
|
||||
|
||||
// TestAbort_UnreachableAfter ensures code that follows an abort isn't invoked.
|
||||
func TestAbort_UnreachableAfter(t *testing.T) {
|
||||
var log bytes.Buffer
|
||||
|
||||
// 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)
|
||||
|
||||
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)
|
||||
|
||||
abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModuleFromBinary(ctx, abortWasm)
|
||||
require.Error(t, err)
|
||||
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) {
|
||||
var log bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().
|
||||
WithRandSource(bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7})))
|
||||
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}
|
||||
|
||||
_, err := Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
||||
require.NoError(t, err)
|
||||
|
||||
code, err := r.CompileModule(ctx, seedWasm, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithRandSource(bytes.NewReader(seed)))
|
||||
require.NoError(t, err)
|
||||
|
||||
seedFn := mod.ExportedFunction("seed")
|
||||
|
||||
_, err = seedFn.Call(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// If this test doesn't break, the seed is deterministic.
|
||||
require.Equal(t, `==> env.~lib/builtins/seed()
|
||||
<== (7.949928895127363e-275)
|
||||
`, log.String())
|
||||
require.Equal(t, 7.949928895127363e-275, seed(mod))
|
||||
}
|
||||
|
||||
func TestSeed_error(t *testing.T) {
|
||||
@@ -240,16 +129,12 @@ func TestSeed_error(t *testing.T) {
|
||||
{
|
||||
name: "not 8 bytes",
|
||||
source: bytes.NewReader([]byte{0, 1}),
|
||||
expectedErr: `error reading random seed: unexpected EOF (recovered by wazero)
|
||||
wasm stack trace:
|
||||
env.~lib/builtins/seed() f64`,
|
||||
expectedErr: `error reading random seed: unexpected EOF`,
|
||||
},
|
||||
{
|
||||
name: "error reading",
|
||||
source: iotest.ErrReader(errors.New("ice cream")),
|
||||
expectedErr: `error reading random seed: ice cream (recovered by wazero)
|
||||
wasm stack trace:
|
||||
env.~lib/builtins/seed() f64`,
|
||||
expectedErr: `error reading random seed: ice cream`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -257,36 +142,17 @@ wasm stack trace:
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var log bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().WithRandSource(tc.source))
|
||||
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)
|
||||
|
||||
_, err := Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, err := r.CompileModule(ctx, seedWasm, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
||||
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = mod.ExportedFunction("seed").Call(ctx)
|
||||
err := require.CapturePanic(func() { seed(mod) })
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
require.Equal(t, `==> env.~lib/builtins/seed()
|
||||
`, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrace(t *testing.T) {
|
||||
// TestFunctionExporter_Trace ensures the trace output is according to configuration.
|
||||
func TestFunctionExporter_Trace(t *testing.T) {
|
||||
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)
|
||||
<== ()
|
||||
@@ -302,7 +168,8 @@ func TestTrace(t *testing.T) {
|
||||
exporter: NewFunctionExporter(),
|
||||
params: noArgs,
|
||||
expected: "",
|
||||
expectedLog: noArgsLog,
|
||||
// expect no host call since it is disabled. ==> is host and --> is wasm.
|
||||
expectedLog: strings.ReplaceAll(noArgsLog, "==", "--"),
|
||||
},
|
||||
{
|
||||
name: "ToStderr",
|
||||
@@ -359,10 +226,10 @@ func TestTrace(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var out, log bytes.Buffer
|
||||
var stderr, functionLog bytes.Buffer
|
||||
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&log))
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&functionLog))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||
defer r.Close(ctx)
|
||||
@@ -372,7 +239,11 @@ func TestTrace(t *testing.T) {
|
||||
_, err := envBuilder.Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||
traceWasm, err := watzero.Wat2Wasm(`(module
|
||||
(import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
||||
(memory 1 1)
|
||||
(export "trace" (func 0))
|
||||
)`)
|
||||
require.NoError(t, err)
|
||||
|
||||
code, err := r.CompileModule(ctx, traceWasm, wazero.NewCompileConfig())
|
||||
@@ -380,9 +251,9 @@ func TestTrace(t *testing.T) {
|
||||
|
||||
config := wazero.NewModuleConfig()
|
||||
if strings.Contains("ToStderr", tc.name) {
|
||||
config = config.WithStderr(&out)
|
||||
config = config.WithStderr(&stderr)
|
||||
} else {
|
||||
config = config.WithStdout(&out)
|
||||
config = config.WithStdout(&stderr)
|
||||
}
|
||||
|
||||
mod, err := r.InstantiateModule(ctx, code, config)
|
||||
@@ -396,8 +267,8 @@ func TestTrace(t *testing.T) {
|
||||
|
||||
_, err = mod.ExportedFunction("trace").Call(ctx, tc.params...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, out.String())
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
require.Equal(t, tc.expected, stderr.String())
|
||||
require.Equal(t, tc.expectedLog, functionLog.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -406,24 +277,20 @@ func TestTrace_error(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message []byte
|
||||
out io.Writer
|
||||
stderr io.Writer
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "not 8 bytes",
|
||||
message: encodeUTF16("hello")[:5],
|
||||
out: &bytes.Buffer{},
|
||||
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
||||
wasm stack trace:
|
||||
env.~lib/builtins/trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||
stderr: &bytes.Buffer{},
|
||||
expectedErr: `invalid UTF-16 reading message`,
|
||||
},
|
||||
{
|
||||
name: "error writing",
|
||||
message: encodeUTF16("hello"),
|
||||
out: &errWriter{err: errors.New("ice cream")},
|
||||
expectedErr: `ice cream (recovered by wazero)
|
||||
wasm stack trace:
|
||||
env.~lib/builtins/trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||
stderr: &errWriter{err: errors.New("ice cream")},
|
||||
expectedErr: `ice cream`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -431,43 +298,27 @@ wasm stack trace:
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var log bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(tc.stderr))
|
||||
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)
|
||||
|
||||
envBuilder := r.NewModuleBuilder("env")
|
||||
NewFunctionExporter().WithTraceToStdout().ExportFunctions(envBuilder)
|
||||
_, err := envBuilder.Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, err := r.CompileModule(ctx, traceWasm, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
||||
mod, err := r.InstantiateModule(ctx, compiled, exporter)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok := mod.Memory().WriteUint32Le(ctx, 0, uint32(len(tc.message)))
|
||||
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
|
||||
require.True(t, ok)
|
||||
ok = mod.Memory().Write(ctx, uint32(4), tc.message)
|
||||
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = mod.ExportedFunction("trace").Call(ctx, 4, 0, 0, 0, 0, 0, 0)
|
||||
err := require.CapturePanic(func() {
|
||||
traceToStderr(testCtx, mod, 4, 0, 0, 0, 0, 0, 0)
|
||||
})
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_readAssemblyScriptString(t *testing.T) {
|
||||
func Test_requireAssemblyScriptString(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(&stderr))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
memory func(context.Context, api.Memory)
|
||||
@@ -476,42 +327,42 @@ func Test_readAssemblyScriptString(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
memory: func(ctx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(ctx, 0, 10)
|
||||
memory: func(testCtx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(testCtx, 0, 10)
|
||||
b := encodeUTF16("hello")
|
||||
memory.Write(ctx, 4, b)
|
||||
memory.Write(testCtx, 4, b)
|
||||
},
|
||||
offset: 4,
|
||||
expected: "hello",
|
||||
},
|
||||
{
|
||||
name: "can't read size",
|
||||
memory: func(ctx context.Context, memory api.Memory) {
|
||||
memory: func(testCtx context.Context, memory api.Memory) {
|
||||
b := encodeUTF16("hello")
|
||||
memory.Write(ctx, 0, b)
|
||||
memory.Write(testCtx, 0, b)
|
||||
},
|
||||
offset: 0, // will attempt to read size from offset -4
|
||||
expectedErr: "Memory.ReadUint32Le(4294967292) out of range",
|
||||
expectedErr: "out of memory reading message",
|
||||
},
|
||||
{
|
||||
name: "odd size",
|
||||
memory: func(ctx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(ctx, 0, 9)
|
||||
memory: func(testCtx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(testCtx, 0, 9)
|
||||
b := encodeUTF16("hello")
|
||||
memory.Write(ctx, 4, b)
|
||||
memory.Write(testCtx, 4, b)
|
||||
},
|
||||
offset: 4,
|
||||
expectedErr: "read an odd number of bytes for utf-16 string: 9",
|
||||
expectedErr: "invalid UTF-16 reading message",
|
||||
},
|
||||
{
|
||||
name: "can't read string",
|
||||
memory: func(ctx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(ctx, 0, 10_000_000) // set size to too large value
|
||||
memory: func(testCtx context.Context, memory api.Memory) {
|
||||
memory.WriteUint32Le(testCtx, 0, 10_000_000) // set size to too large value
|
||||
b := encodeUTF16("hello")
|
||||
memory.Write(ctx, 4, b)
|
||||
memory.Write(testCtx, 4, b)
|
||||
},
|
||||
offset: 4,
|
||||
expectedErr: "Memory.Read(4, 10000000) out of range",
|
||||
expectedErr: "out of memory reading message",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -519,41 +370,35 @@ func Test_readAssemblyScriptString(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
defer r.Close(testCtx)
|
||||
|
||||
mod, err := r.NewModuleBuilder("mod").
|
||||
ExportMemory("memory", 1).
|
||||
Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
tc.memory(testCtx, mod.Memory())
|
||||
|
||||
s, err := readAssemblyScriptString(testCtx, mod, uint32(tc.offset))
|
||||
if tc.expectedErr != "" {
|
||||
err := require.CapturePanic(func() {
|
||||
_ = requireAssemblyScriptString(testCtx, mod, "message", uint32(tc.offset))
|
||||
})
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
s := requireAssemblyScriptString(testCtx, mod, "message", uint32(tc.offset))
|
||||
require.Equal(t, tc.expected, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeAbortMessageAndFileName(ctx context.Context, t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
||||
off := 0
|
||||
ok := mem.WriteUint32Le(ctx, uint32(off), uint32(len(messageUTF16)))
|
||||
func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (uint32, uint32) {
|
||||
off := uint32(0)
|
||||
ok := mem.WriteUint32Le(testCtx, off, uint32(len(messageUTF16)))
|
||||
require.True(t, ok)
|
||||
off += 4
|
||||
messageOff := off
|
||||
ok = mem.Write(ctx, uint32(off), messageUTF16)
|
||||
ok = mem.Write(testCtx, off, messageUTF16)
|
||||
require.True(t, ok)
|
||||
off += len(messageUTF16)
|
||||
ok = mem.WriteUint32Le(ctx, uint32(off), uint32(len(fileNameUTF16)))
|
||||
off += uint32(len(messageUTF16))
|
||||
ok = mem.WriteUint32Le(testCtx, off, uint32(len(fileNameUTF16)))
|
||||
require.True(t, ok)
|
||||
off += 4
|
||||
filenameOff := off
|
||||
ok = mem.Write(ctx, uint32(off), fileNameUTF16)
|
||||
ok = mem.Write(testCtx, off, fileNameUTF16)
|
||||
require.True(t, ok)
|
||||
return messageOff, filenameOff
|
||||
}
|
||||
@@ -575,3 +420,16 @@ type errWriter struct {
|
||||
func (w *errWriter) Write([]byte) (int, error) {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
func requireModule(t *testing.T, config wazero.ModuleConfig) (api.Module, api.Closer) {
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
compiled, err := r.NewModuleBuilder(t.Name()).
|
||||
ExportMemoryWithMax("memory", 1, 1).
|
||||
Compile(testCtx, wazero.NewCompileConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
||||
require.NoError(t, err)
|
||||
return mod, r
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// Instantiate instantiates the "env" module used by Emscripten into the
|
||||
@@ -50,14 +51,10 @@ type functionExporter struct{}
|
||||
|
||||
// ExportFunctions implements FunctionExporter.ExportFunctions
|
||||
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
|
||||
env := &emscripten{}
|
||||
builder.ExportFunction("emscripten_notify_memory_growth", env.emscriptenNotifyMemoryGrowth,
|
||||
builder.ExportFunction("emscripten_notify_memory_growth", emscriptenNotifyMemoryGrowth,
|
||||
"emscripten_notify_memory_growth", "memory_index")
|
||||
}
|
||||
|
||||
// emscripten implements common imports used by standalone wasm.
|
||||
type emscripten struct{}
|
||||
|
||||
// emscriptenNotifyMemoryGrowth is called when wasm is compiled with
|
||||
// `-s ALLOW_MEMORY_GROWTH` and a "memory.grow" instruction succeeded.
|
||||
// The memoryIndex parameter will be zero until "multi-memory" is implemented.
|
||||
@@ -73,4 +70,7 @@ type emscripten struct{}
|
||||
//
|
||||
// See https://github.com/emscripten-core/emscripten/blob/3.1.16/system/lib/standalone/standalone.c#L118
|
||||
// and https://emscripten.org/docs/api_reference/emscripten.h.html#abi-functions
|
||||
func (a *emscripten) emscriptenNotifyMemoryGrowth(uint32) {}
|
||||
var emscriptenNotifyMemoryGrowth = &wasm.Func{
|
||||
Type: &wasm.FunctionType{Params: []wasm.ValueType{wasm.ValueTypeI32}},
|
||||
Code: &wasm.Code{Body: []byte{wasm.OpcodeEnd}},
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var growWasm []byte
|
||||
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||
|
||||
// TestGrow is an integration test until we have an Emscripten example.
|
||||
func TestGrow(t *testing.T) {
|
||||
var log bytes.Buffer
|
||||
|
||||
@@ -40,6 +41,6 @@ func TestGrow(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.Zero(t, err.(*sys.ExitError).ExitCode())
|
||||
|
||||
// We expect the memory no-op memory growth hook to be invoked.
|
||||
require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)")
|
||||
// We expect the memory no-op memory growth hook to be invoked as wasm.
|
||||
require.Contains(t, log.String(), "--> env.emscripten_notify_memory_growth(memory_index=0)")
|
||||
}
|
||||
|
||||
@@ -261,8 +261,8 @@ func Test_loggingListener(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
lf := experimental.NewLoggingListenerFactory(out)
|
||||
var out bytes.Buffer
|
||||
lf := experimental.NewLoggingListenerFactory(&out)
|
||||
fnV := reflect.ValueOf(func() {})
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/testing/enginetest"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
@@ -22,12 +23,17 @@ var et = &engineTester{}
|
||||
// engineTester implements enginetest.EngineTester.
|
||||
type engineTester struct{}
|
||||
|
||||
// NewEngine implements enginetest.EngineTester NewEngine.
|
||||
// ListenerFactory implements the same method as documented on enginetest.EngineTester.
|
||||
func (e *engineTester) ListenerFactory() experimental.FunctionListenerFactory {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEngine implements the same method as documented on enginetest.EngineTester.
|
||||
func (e *engineTester) NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return newEngine(enabledFeatures)
|
||||
}
|
||||
|
||||
// InitTables implements enginetest.EngineTester InitTables.
|
||||
// InitTables implements the same method as documented on enginetest.EngineTester.
|
||||
func (e engineTester) InitTables(me wasm.ModuleEngine, tableIndexToLen map[wasm.Index]int, tableInits []wasm.TableInitEntry) [][]wasm.Reference {
|
||||
references := make([][]wasm.Reference, len(tableIndexToLen))
|
||||
for tableIndex, l := range tableIndexToLen {
|
||||
@@ -44,7 +50,7 @@ func (e engineTester) InitTables(me wasm.ModuleEngine, tableIndexToLen map[wasm.
|
||||
return references
|
||||
}
|
||||
|
||||
// CompiledFunctionPointerValue implements enginetest.EngineTester CompiledFunctionPointerValue.
|
||||
// CompiledFunctionPointerValue implements the same method as documented on enginetest.EngineTester.
|
||||
func (e engineTester) CompiledFunctionPointerValue(me wasm.ModuleEngine, funcIndex wasm.Index) uint64 {
|
||||
internal := me.(*moduleEngine)
|
||||
return uint64(uintptr(unsafe.Pointer(internal.functions[funcIndex])))
|
||||
@@ -55,7 +61,7 @@ func TestCompiler_Engine_NewModuleEngine(t *testing.T) {
|
||||
enginetest.RunTestEngine_NewModuleEngine(t, et)
|
||||
}
|
||||
|
||||
func TestInterpreter_Engine_InitializeFuncrefGlobals(t *testing.T) {
|
||||
func TestCompiler_Engine_InitializeFuncrefGlobals(t *testing.T) {
|
||||
enginetest.RunTestEngine_InitializeFuncrefGlobals(t, et)
|
||||
}
|
||||
|
||||
|
||||
@@ -111,18 +111,13 @@ func (ce *callEngine) popValue() (v uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
// peekValues peeks api.ValueType values from the stack and returns them in reverse order.
|
||||
// peekValues peeks api.ValueType values from the stack and returns them.
|
||||
func (ce *callEngine) peekValues(count int) []uint64 {
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
stackLen := len(ce.stack)
|
||||
peeked := ce.stack[stackLen-count : stackLen]
|
||||
values := make([]uint64, 0, count)
|
||||
for i := count - 1; i >= 0; i-- {
|
||||
values = append(values, peeked[i])
|
||||
}
|
||||
return values
|
||||
return ce.stack[stackLen-count : stackLen]
|
||||
}
|
||||
|
||||
func (ce *callEngine) drop(r *wazeroir.InclusiveRange) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/buildoptions"
|
||||
"github.com/tetratelabs/wazero/internal/testing/enginetest"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
@@ -24,7 +26,7 @@ func TestInterpreter_peekValues(t *testing.T) {
|
||||
|
||||
ce.stack = []uint64{5, 4, 3, 2, 1}
|
||||
require.Nil(t, ce.peekValues(0))
|
||||
require.Equal(t, []uint64{1, 2}, ce.peekValues(2))
|
||||
require.Equal(t, []uint64{2, 1}, ce.peekValues(2))
|
||||
}
|
||||
|
||||
func TestInterpreter_CallEngine_PushFrame(t *testing.T) {
|
||||
@@ -62,10 +64,16 @@ func TestInterpreter_CallEngine_PushFrame_StackOverflow(t *testing.T) {
|
||||
|
||||
// et is used for tests defined in the enginetest package.
|
||||
var et = &engineTester{}
|
||||
var functionLog bytes.Buffer
|
||||
var listenerFactory = experimental.NewLoggingListenerFactory(&functionLog)
|
||||
|
||||
// engineTester implements enginetest.EngineTester.
|
||||
type engineTester struct{}
|
||||
|
||||
func (e engineTester) ListenerFactory() experimental.FunctionListenerFactory {
|
||||
return listenerFactory
|
||||
}
|
||||
|
||||
// NewEngine implements enginetest.EngineTester NewEngine.
|
||||
func (e engineTester) NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return NewEngine(enabledFeatures)
|
||||
@@ -107,15 +115,96 @@ func TestInterpreter_Engine_NewModuleEngine_InitTable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInterpreter_ModuleEngine_Call(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
enginetest.RunTestModuleEngine_Call(t, et)
|
||||
require.Equal(t, `--> .$0(1,2)
|
||||
<-- (1,2)
|
||||
`, functionLog.String())
|
||||
}
|
||||
|
||||
func TestInterpreter_ModuleEngine_Call_HostFn(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
enginetest.RunTestModuleEngine_Call_HostFn(t, et)
|
||||
require.Equal(t, `==> .$0(3)
|
||||
<== (3)
|
||||
--> imported.wasm_div_by(1)
|
||||
<-- (1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->host_div_by(1)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
`, functionLog.String())
|
||||
}
|
||||
|
||||
func TestInterpreter_ModuleEngine_Call_Errors(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
enginetest.RunTestModuleEngine_Call_Errors(t, et)
|
||||
|
||||
// TODO: Currently, the listener doesn't get notified on errors as they are
|
||||
// implemented with panic. This means the end hooks aren't make resulting
|
||||
// in dangling logs like this:
|
||||
// ==> host.host_div_by(4294967295)
|
||||
// instead of seeing a return like
|
||||
// <== DivByZero
|
||||
require.Equal(t, `==> host.host_div_by(1)
|
||||
<== (1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
--> imported.wasm_div_by(1)
|
||||
<-- (1)
|
||||
--> imported.wasm_div_by(1)
|
||||
<-- (1)
|
||||
--> imported.wasm_div_by(0)
|
||||
--> imported.wasm_div_by(1)
|
||||
<-- (1)
|
||||
==> host.host_div_by(4294967295)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
==> host.host_div_by(0)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
--> imported.call->host_div_by(4294967295)
|
||||
==> host.host_div_by(4294967295)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->host_div_by(0)
|
||||
--> imported.call->host_div_by(0)
|
||||
==> host.host_div_by(0)
|
||||
--> importing.call_import->call->host_div_by(1)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->host_div_by(4294967295)
|
||||
--> imported.call->host_div_by(4294967295)
|
||||
==> host.host_div_by(4294967295)
|
||||
--> importing.call_import->call->host_div_by(1)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->host_div_by(0)
|
||||
--> imported.call->host_div_by(0)
|
||||
==> host.host_div_by(0)
|
||||
--> importing.call_import->call->host_div_by(1)
|
||||
--> imported.call->host_div_by(1)
|
||||
==> host.host_div_by(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
`, functionLog.String())
|
||||
}
|
||||
|
||||
func TestInterpreter_ModuleEngine_Memory(t *testing.T) {
|
||||
|
||||
@@ -14,6 +14,16 @@ import (
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func TestContext_FS(t *testing.T) {
|
||||
sysCtx := DefaultContext(testfs.FS{})
|
||||
|
||||
require.Equal(t, NewFSContext(testfs.FS{}), sysCtx.FS(testCtx))
|
||||
|
||||
// can override to something else
|
||||
fsc := NewFSContext(testfs.FS{"foo": &testfs.File{}})
|
||||
require.Equal(t, fsc, sysCtx.FS(context.WithValue(testCtx, FSKey{}, fsc)))
|
||||
}
|
||||
|
||||
func TestDefaultSysContext(t *testing.T) {
|
||||
sysCtx, err := NewContext(
|
||||
0, // max
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -37,9 +38,13 @@ var (
|
||||
|
||||
type EngineTester interface {
|
||||
NewEngine(enabledFeatures wasm.Features) wasm.Engine
|
||||
|
||||
ListenerFactory() experimental.FunctionListenerFactory
|
||||
|
||||
// InitTables returns expected table contents ([]wasm.Reference) per table.
|
||||
InitTables(me wasm.ModuleEngine, tableIndexToLen map[wasm.Index]int,
|
||||
tableInits []wasm.TableInitEntry) [][]wasm.Reference
|
||||
|
||||
// CompiledFunctionPointerValue returns the opaque compiledFunction's pointer for the `funcIndex`.
|
||||
CompiledFunctionPointerValue(tme wasm.ModuleEngine, funcIndex wasm.Index) uint64
|
||||
}
|
||||
@@ -81,7 +86,7 @@ func RunTestEngine_InitializeFuncrefGlobals(t *testing.T, et EngineTester) {
|
||||
|
||||
// To use the function, we first need to add it to a module.
|
||||
instance := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
fns := instance.BuildFunctions(m, nil)
|
||||
fns := instance.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
me, err := e.NewModuleEngine(t.Name(), m, nil, fns, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -106,14 +111,24 @@ func RunTestEngine_InitializeFuncrefGlobals(t *testing.T, et EngineTester) {
|
||||
}
|
||||
|
||||
func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
e := et.NewEngine(wasm.Features20220419)
|
||||
|
||||
// Define a basic function which defines one parameter. This is used to test results when incorrect arity is used.
|
||||
// Define a basic function which defines two parameters and two results.
|
||||
// This is used to test results when incorrect arity is used.
|
||||
i64 := wasm.ValueTypeI64
|
||||
m := &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, ParamNumInUint64: 1, ResultNumInUint64: 1}},
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
{
|
||||
Params: []wasm.ValueType{i64, i64},
|
||||
Results: []wasm.ValueType{i64, i64},
|
||||
ParamNumInUint64: 2,
|
||||
ResultNumInUint64: 2,
|
||||
},
|
||||
},
|
||||
FunctionSection: []uint32{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}, LocalTypes: []wasm.ValueType{wasm.ValueTypeI64}}},
|
||||
CodeSection: []*wasm.Code{
|
||||
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeEnd}},
|
||||
},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, m)
|
||||
@@ -121,7 +136,7 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
|
||||
// To use the function, we first need to add it to a module.
|
||||
module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
module.Functions = module.BuildFunctions(m, nil)
|
||||
module.Functions = module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
|
||||
// Compile the module
|
||||
me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions, nil, nil)
|
||||
@@ -130,18 +145,18 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
|
||||
// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
|
||||
fn := module.Functions[0]
|
||||
results, err := me.Call(testCtx, module.CallCtx, fn, 3)
|
||||
results, err := me.Call(testCtx, module.CallCtx, fn, 1, 2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), results[0])
|
||||
require.Equal(t, []uint64{1, 2}, results)
|
||||
|
||||
t.Run("errs when not enough parameters", func(t *testing.T) {
|
||||
_, err := me.Call(testCtx, module.CallCtx, fn)
|
||||
require.EqualError(t, err, "expected 1 params, but passed 0")
|
||||
require.EqualError(t, err, "expected 2 params, but passed 0")
|
||||
})
|
||||
|
||||
t.Run("errs when too many parameters", func(t *testing.T) {
|
||||
_, err := me.Call(testCtx, module.CallCtx, fn, 1, 2)
|
||||
require.EqualError(t, err, "expected 1 params, but passed 2")
|
||||
_, err := me.Call(testCtx, module.CallCtx, fn, 1, 2, 3)
|
||||
require.EqualError(t, err, "expected 2 params, but passed 3")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -186,7 +201,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
require.NoError(t, err)
|
||||
|
||||
module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
fns := module.BuildFunctions(m, nil)
|
||||
fns := module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
|
||||
var func1, func2 = uint32(2), uint32(1)
|
||||
tableInits := []wasm.TableInitEntry{
|
||||
@@ -221,7 +236,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
require.NoError(t, err)
|
||||
|
||||
imported := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importedFunctions := imported.BuildFunctions(importedModule, nil)
|
||||
importedFunctions := imported.BuildFunctions(importedModule, buildListeners(et.ListenerFactory(), importedModule))
|
||||
|
||||
// Imported functions are compiled before the importing module is instantiated.
|
||||
importedMe, err := e.NewModuleEngine(t.Name(), importedModule, nil, importedFunctions, nil, nil)
|
||||
@@ -245,7 +260,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
}
|
||||
|
||||
importing := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
fns := importing.BuildFunctions(importingModule, nil)
|
||||
fns := importing.BuildFunctions(importingModule, buildListeners(et.ListenerFactory(), importingModule))
|
||||
|
||||
importingMe, err := e.NewModuleEngine(t.Name(), importingModule, importedFunctions, fns, tables, tableInits)
|
||||
require.NoError(t, err)
|
||||
@@ -272,7 +287,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
err := e.CompileModule(testCtx, importedModule)
|
||||
require.NoError(t, err)
|
||||
imported := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importedFunctions := imported.BuildFunctions(importedModule, nil)
|
||||
importedFunctions := imported.BuildFunctions(importedModule, buildListeners(et.ListenerFactory(), importedModule))
|
||||
|
||||
// Imported functions are compiled before the importing module is instantiated.
|
||||
importedMe, err := e.NewModuleEngine(t.Name(), importedModule, nil, importedFunctions, nil, nil)
|
||||
@@ -292,7 +307,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
require.NoError(t, err)
|
||||
|
||||
importing := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
fns := importing.BuildFunctions(importingModule, nil)
|
||||
fns := importing.BuildFunctions(importingModule, buildListeners(et.ListenerFactory(), importingModule))
|
||||
|
||||
var func1, func2 = uint32(0), uint32(4)
|
||||
tableInits := []wasm.TableInitEntry{
|
||||
@@ -341,7 +356,7 @@ func runTestModuleEngine_Call_HostFn_ModuleContext(t *testing.T, et EngineTester
|
||||
_, ns := wasm.NewStore(features, e)
|
||||
modCtx := wasm.NewCallContext(ns, module, nil)
|
||||
|
||||
fns := module.BuildFunctions(m, nil)
|
||||
fns := module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
me, err := e.NewModuleEngine(t.Name(), m, nil, fns, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -359,7 +374,7 @@ func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
|
||||
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
host, imported, importing, close := setupCallTests(t, e)
|
||||
host, imported, importing, close := setupCallTests(t, e, et.ListenerFactory())
|
||||
defer close()
|
||||
|
||||
// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
|
||||
@@ -405,7 +420,7 @@ func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
|
||||
func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
host, imported, importing, close := setupCallTests(t, e)
|
||||
host, imported, importing, close := setupCallTests(t, e, et.ListenerFactory())
|
||||
defer close()
|
||||
|
||||
tests := []struct {
|
||||
@@ -591,7 +606,7 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) {
|
||||
var memory api.Memory = module.Memory
|
||||
|
||||
// To use functions, we need to instantiate them (associate them with a ModuleInstance).
|
||||
module.Functions = module.BuildFunctions(m, nil)
|
||||
module.Functions = module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
module.BuildExports(m.ExportSection)
|
||||
grow, init := module.Functions[0], module.Functions[1]
|
||||
|
||||
@@ -666,7 +681,7 @@ func divBy(d uint32) uint32 {
|
||||
return 1 / d // go panics if d == 0
|
||||
}
|
||||
|
||||
func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, *wasm.ModuleInstance, *wasm.ModuleInstance, func()) {
|
||||
func setupCallTests(t *testing.T, e wasm.Engine, fnlf experimental.FunctionListenerFactory) (*wasm.ModuleInstance, *wasm.ModuleInstance, *wasm.ModuleInstance, func()) {
|
||||
i32 := wasm.ValueTypeI32
|
||||
ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}
|
||||
|
||||
@@ -686,7 +701,7 @@ func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, *wasm.Mo
|
||||
err := e.CompileModule(testCtx, hostModule)
|
||||
require.NoError(t, err)
|
||||
host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
host.Functions = host.BuildFunctions(hostModule, nil)
|
||||
host.Functions = host.BuildFunctions(hostModule, buildListeners(fnlf, hostModule))
|
||||
host.BuildExports(hostModule.ExportSection)
|
||||
hostFn := host.Exports[hostFnName].Function
|
||||
|
||||
@@ -721,7 +736,7 @@ func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, *wasm.Mo
|
||||
require.NoError(t, err)
|
||||
|
||||
imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importedFunctions := imported.BuildFunctions(importedModule, nil)
|
||||
importedFunctions := imported.BuildFunctions(importedModule, buildListeners(fnlf, importedModule))
|
||||
imported.Functions = append([]*wasm.FunctionInstance{hostFn}, importedFunctions...)
|
||||
imported.BuildExports(importedModule.ExportSection)
|
||||
callHostFn := imported.Exports[callHostFnName].Function
|
||||
@@ -754,7 +769,7 @@ func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, *wasm.Mo
|
||||
|
||||
// Add the exported function.
|
||||
importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importingFunctions := importing.BuildFunctions(importingModule, nil)
|
||||
importingFunctions := importing.BuildFunctions(importingModule, buildListeners(fnlf, importingModule))
|
||||
importing.Functions = append([]*wasm.FunctionInstance{callHostFn}, importingFunctions...)
|
||||
importing.BuildExports(importingModule.ExportSection)
|
||||
|
||||
@@ -782,3 +797,15 @@ func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) {
|
||||
// callEngineModuleContextModuleInstanceAddressOffset
|
||||
module.CallCtx = wasm.NewCallContext(nil, module, nil)
|
||||
}
|
||||
|
||||
func buildListeners(factory experimental.FunctionListenerFactory, m *wasm.Module) []experimental.FunctionListener {
|
||||
if factory == nil || len(m.FunctionSection) == 0 {
|
||||
return nil
|
||||
}
|
||||
listeners := make([]experimental.FunctionListener, len(m.FunctionSection))
|
||||
importCount := m.ImportFuncCount()
|
||||
for i := 0; i < len(listeners); i++ {
|
||||
listeners[i] = factory.NewListener(m.FunctionDefinitionSection[uint32(i)+importCount])
|
||||
}
|
||||
return listeners
|
||||
}
|
||||
|
||||
@@ -148,8 +148,19 @@ func newModuleVal(m api.Module) reflect.Value {
|
||||
return val
|
||||
}
|
||||
|
||||
// MustFunctionType returns the function type corresponding to the function
|
||||
// signature or panics if invalid.
|
||||
func MustFunctionType(fn interface{}) *FunctionType {
|
||||
fnV := reflect.ValueOf(fn)
|
||||
_, ft, err := getFunctionType(&fnV)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ft
|
||||
}
|
||||
|
||||
// getFunctionType returns the function type corresponding to the function signature or errs if invalid.
|
||||
func getFunctionType(fn *reflect.Value, enabledFeatures Features) (fk FunctionKind, ft *FunctionType, err error) {
|
||||
func getFunctionType(fn *reflect.Value) (fk FunctionKind, ft *FunctionType, err error) {
|
||||
p := fn.Type()
|
||||
|
||||
if fn.Kind() != reflect.Func {
|
||||
@@ -169,14 +180,6 @@ func getFunctionType(fn *reflect.Value, enabledFeatures Features) (fk FunctionKi
|
||||
|
||||
rCount := p.NumOut()
|
||||
|
||||
if rCount > 1 {
|
||||
// Guard >1.0 feature multi-value
|
||||
if err = enabledFeatures.Require(FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ft = &FunctionType{Params: make([]ValueType, p.NumIn()-pOffset), Results: make([]ValueType, rCount)}
|
||||
ft.CacheNumInUint64()
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestGetFunctionType(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rVal := reflect.ValueOf(tc.inputFunc)
|
||||
fk, ft, err := getFunctionType(&rVal, Features20191205|FeatureMultiValue)
|
||||
fk, ft, err := getFunctionType(&rVal)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedKind, fk)
|
||||
require.Equal(t, tc.expectedType, ft)
|
||||
@@ -122,11 +122,6 @@ func TestGetFunctionTypeErrors(t *testing.T) {
|
||||
input: func() error { return nil },
|
||||
expectedErr: "result[0] is an error, which is unsupported",
|
||||
},
|
||||
{
|
||||
name: "multiple results - multi-value not enabled",
|
||||
input: func() (uint64, uint32) { return 0, 0 },
|
||||
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "multiple context types",
|
||||
input: func(api.Module, context.Context) error { return nil },
|
||||
@@ -149,7 +144,7 @@ func TestGetFunctionTypeErrors(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rVal := reflect.ValueOf(tc.input)
|
||||
_, _, err := getFunctionType(&rVal, Features20191205)
|
||||
_, _, err := getFunctionType(&rVal)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
@@ -254,7 +249,7 @@ func TestPopGoFuncParams(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
goFunc := reflect.ValueOf(tc.inputFunc)
|
||||
fk, _, err := getFunctionType(&goFunc, Features20220419)
|
||||
fk, _, err := getFunctionType(&goFunc)
|
||||
require.NoError(t, err)
|
||||
|
||||
vals := PopGoFuncParams(&FunctionInstance{Kind: fk, GoFunc: &goFunc}, (&stack{stackVals}).pop)
|
||||
@@ -407,7 +402,7 @@ func TestCallGoFunc(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
goFunc := reflect.ValueOf(tc.inputFunc)
|
||||
fk, _, err := getFunctionType(&goFunc, Features20220419)
|
||||
fk, _, err := getFunctionType(&goFunc)
|
||||
require.NoError(t, err)
|
||||
|
||||
results := CallGoFunc(testCtx, callCtx, &FunctionInstance{Kind: fk, GoFunc: &goFunc}, tc.inputParams)
|
||||
|
||||
@@ -9,6 +9,16 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
)
|
||||
|
||||
// Func is a function with an inlined type, typically used for NewHostModule.
|
||||
type Func struct {
|
||||
// Type is the equivalent function in the SectionIDType.
|
||||
// This will resolve to an existing or new element.
|
||||
Type *FunctionType
|
||||
|
||||
// Code is the equivalent function in the SectionIDCode.
|
||||
Code *Code
|
||||
}
|
||||
|
||||
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
|
||||
func NewHostModule(
|
||||
moduleName string,
|
||||
@@ -79,7 +89,7 @@ func addFuncs(
|
||||
nameToGoFunc map[string]interface{},
|
||||
funcToNames map[string][]string,
|
||||
enabledFeatures Features,
|
||||
) error {
|
||||
) (err error) {
|
||||
funcCount := uint32(len(nameToGoFunc))
|
||||
funcNames := make([]string, 0, funcCount)
|
||||
if m.NameSection == nil {
|
||||
@@ -100,19 +110,36 @@ func addFuncs(
|
||||
for idx := Index(0); idx < funcCount; idx++ {
|
||||
exportName := funcNames[idx]
|
||||
debugName := wasmdebug.FuncName(moduleName, exportName, idx)
|
||||
fn := reflect.ValueOf(nameToGoFunc[exportName])
|
||||
_, functionType, err := getFunctionType(&fn, enabledFeatures)
|
||||
|
||||
gf := nameToGoFunc[exportName]
|
||||
var ft *FunctionType
|
||||
if hf, ok := gf.(*Func); ok {
|
||||
ft = hf.Type
|
||||
m.CodeSection = append(m.CodeSection, hf.Code)
|
||||
} else {
|
||||
fn := reflect.ValueOf(gf)
|
||||
_, ft, err = getFunctionType(&fn)
|
||||
if err != nil {
|
||||
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.CodeSection = append(m.CodeSection, &Code{GoFunc: &fn})
|
||||
}
|
||||
|
||||
m.FunctionSection = append(m.FunctionSection, m.maybeAddType(ft))
|
||||
|
||||
names := funcToNames[exportName]
|
||||
namesLen := len(names)
|
||||
if namesLen > 1 && namesLen-1 != len(ft.Params) {
|
||||
return fmt.Errorf("func[%s] has %d params, but %d param names", debugName, namesLen-1, len(ft.Params))
|
||||
}
|
||||
if len(ft.Results) > 1 {
|
||||
// Guard >1.0 feature multi-value
|
||||
if err = enabledFeatures.Require(FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("func[%s] multiple result types invalid as %v", debugName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m.FunctionSection = append(m.FunctionSection, m.maybeAddType(functionType))
|
||||
m.CodeSection = append(m.CodeSection, &Code{GoFunc: &fn})
|
||||
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: exportName, Index: idx})
|
||||
if namesLen > 0 {
|
||||
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: names[0]})
|
||||
|
||||
@@ -248,7 +248,7 @@ const (
|
||||
// OpcodeRefFunc pushes a funcref value whose index equals the immediate to this opcode.
|
||||
// This is defined in the reference-types proposal, but necessary for FeatureBulkMemoryOperations as well.
|
||||
//
|
||||
// Currently only supported in the constant expression in element segments.
|
||||
// Currently, this is only supported in the constant expression in element segments.
|
||||
OpcodeRefFunc = 0xd2
|
||||
|
||||
// Below are toggled with FeatureSignExtensionOps
|
||||
|
||||
92
wasi_snapshot_preview1/args.go
Normal file
92
wasi_snapshot_preview1/args.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
functionArgsGet = "args_get"
|
||||
functionArgsSizesGet = "args_sizes_get"
|
||||
)
|
||||
|
||||
// argsGet is the WASI function named functionArgsGet that reads command-line
|
||||
// argument data.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * argv: offset to begin writing argument offsets in uint32 little-endian
|
||||
// encoding to api.Memory
|
||||
// * argsSizesGet result argc * 4 bytes are written to this offset
|
||||
// * argvBuf: offset to write the null terminated arguments to api.Memory
|
||||
// * argsSizesGet result argv_buf_size bytes are written to this offset
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if argsSizesGet wrote argc=2 and argvBufSize=5 for arguments:
|
||||
// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
|
||||
// to api.Memory:
|
||||
//
|
||||
// argvBufSize uint32le uint32le
|
||||
// +----------------+ +--------+ +--------+
|
||||
// | | | | | |
|
||||
// []byte{?, 'a', 0, 'b', 'c', 0, ?, 1, 0, 0, 0, 3, 0, 0, 0, ?}
|
||||
// argvBuf --^ ^ ^
|
||||
// argv --| |
|
||||
// offset that begins "a" --+ |
|
||||
// offset that begins "bc" --+
|
||||
//
|
||||
// See argsSizesGet
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func argsGet(ctx context.Context, mod api.Module, argv, argvBuf uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
return writeOffsetsAndNullTerminatedValues(ctx, mod.Memory(), sysCtx.Args(), argv, argvBuf)
|
||||
}
|
||||
|
||||
// argsSizesGet is the WASI function named functionArgsSizesGet that reads
|
||||
// command-line argument sizes.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * resultArgc: offset to write the argument count to api.Memory
|
||||
// * resultArgvBufSize: offset to write the null-terminated argument length to
|
||||
// api.Memory
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if args are "a", "bc" and parameters resultArgc=1 and
|
||||
// resultArgvBufSize=6, this function writes the below to api.Memory:
|
||||
//
|
||||
// uint32le uint32le
|
||||
// +--------+ +--------+
|
||||
// | | | |
|
||||
// []byte{?, 2, 0, 0, 0, ?, 5, 0, 0, 0, ?}
|
||||
// resultArgc --^ ^
|
||||
// 2 args --+ |
|
||||
// resultArgvBufSize --|
|
||||
// len([]byte{'a',0,'b',c',0}) --+
|
||||
//
|
||||
// See argsGet
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func argsSizesGet(ctx context.Context, mod api.Module, resultArgc, resultArgvBufSize uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
mem := mod.Memory()
|
||||
|
||||
if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sysCtx.Args()))) {
|
||||
return ErrnoFault
|
||||
}
|
||||
if !mem.WriteUint32Le(ctx, resultArgvBufSize, sysCtx.ArgsSize()) {
|
||||
return ErrnoFault
|
||||
}
|
||||
return ErrnoSuccess
|
||||
}
|
||||
191
wasi_snapshot_preview1/args_test.go
Normal file
191
wasi_snapshot_preview1/args_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func Test_argsGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
argv := uint32(7) // arbitrary offset
|
||||
argvBuf := uint32(1) // arbitrary offset
|
||||
expectedMemory := []byte{
|
||||
'?', // argvBuf is after this
|
||||
'a', 0, 'b', 'c', 0, // null terminated "a", "bc"
|
||||
'?', // argv is after this
|
||||
1, 0, 0, 0, // little endian-encoded offset of "a"
|
||||
3, 0, 0, 0, // little endian-encoded offset of "bc"
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
// Invoke argsGet and check the memory side effects.
|
||||
requireErrno(t, ErrnoSuccess, mod, functionArgsGet, uint64(argv), uint64(argvBuf))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.args_get(argv=7,argv_buf=1)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
}
|
||||
|
||||
func Test_argsGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
validAddress := uint32(0) // arbitrary valid address as arguments to args_get. We chose 0 here.
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
argv, argvBuf uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "out-of-memory argv",
|
||||
argv: memorySize,
|
||||
argvBuf: validAddress,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_get(argv=65536,argv_buf=0)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "out-of-memory argvBuf",
|
||||
argv: validAddress,
|
||||
argvBuf: memorySize,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_get(argv=0,argv_buf=65536)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "argv exceeds the maximum valid address by 1",
|
||||
// 4*argCount is the size of the result of the pointers to args, 4 is the size of uint32
|
||||
argv: memorySize - 4*2 + 1,
|
||||
argvBuf: validAddress,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_get(argv=65529,argv_buf=0)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "argvBuf exceeds the maximum valid address by 1",
|
||||
argv: validAddress,
|
||||
// "a", "bc" size = size of "a0bc0" = 5
|
||||
argvBuf: memorySize - 5 + 1,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_get(argv=0,argv_buf=65532)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
requireErrno(t, ErrnoFault, mod, functionArgsGet, uint64(tc.argv), uint64(tc.argvBuf))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_argsSizesGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
resultArgc := uint32(1) // arbitrary offset
|
||||
resultArgvBufSize := uint32(6) // arbitrary offset
|
||||
expectedMemory := []byte{
|
||||
'?', // resultArgc is after this
|
||||
0x2, 0x0, 0x0, 0x0, // little endian-encoded arg count
|
||||
'?', // resultArgvBufSize is after this
|
||||
0x5, 0x0, 0x0, 0x0, // little endian-encoded size of null terminated strings
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
// Invoke argsSizesGet and check the memory side effects.
|
||||
requireErrno(t, ErrnoSuccess, mod, functionArgsSizesGet, uint64(resultArgc), uint64(resultArgvBufSize))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.args_sizes_get(result.argc=1,result.argv_buf_size=6)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
}
|
||||
|
||||
func Test_argsSizesGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here.
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
argc, argvBufSize uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "out-of-memory argc",
|
||||
argc: memorySize,
|
||||
argvBufSize: validAddress,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_sizes_get(result.argc=65536,result.argv_buf_size=0)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "out-of-memory argvBufSize",
|
||||
argc: validAddress,
|
||||
argvBufSize: memorySize,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_buf_size=65536)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "argc exceeds the maximum valid address by 1",
|
||||
argc: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args
|
||||
argvBufSize: validAddress,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_sizes_get(result.argc=65533,result.argv_buf_size=0)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "argvBufSize exceeds the maximum valid size by 1",
|
||||
argc: validAddress,
|
||||
argvBufSize: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_buf_size=65533)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
requireErrno(t, ErrnoFault, mod, functionArgsSizesGet, uint64(tc.argc), uint64(tc.argvBufSize))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,42 +9,40 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// functionClockResGet returns the resolution of a clock.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
|
||||
functionClockResGet = "clock_res_get"
|
||||
|
||||
// importClockResGet is the WebAssembly 1.0 Text format import of functionClockResGet.
|
||||
importClockResGet = `(import "wasi_snapshot_preview1" "clock_res_get"
|
||||
(func $wasi.clock_res_get (param $id i32) (param $result.resolution i32) (result (;errno;) i32)))`
|
||||
|
||||
// functionClockTimeGet returns the time value of a clock.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
|
||||
functionClockTimeGet = "clock_time_get"
|
||||
|
||||
// importClockTimeGet is the WebAssembly 1.0 Text format import of functionClockTimeGet.
|
||||
importClockTimeGet = `(import "wasi_snapshot_preview1" "clock_time_get"
|
||||
(func $wasi.clock_time_get (param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32)))`
|
||||
)
|
||||
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32
|
||||
const (
|
||||
// clockIDRealtime is the clock ID named "realtime" associated with sys.Walltime
|
||||
// clockIDRealtime is the name ID named "realtime" like sys.Walltime
|
||||
clockIDRealtime = iota
|
||||
// clockIDMonotonic is the clock ID named "monotonic" with sys.Nanotime
|
||||
// clockIDMonotonic is the name ID named "monotonic" like sys.Nanotime
|
||||
clockIDMonotonic
|
||||
// clockIDProcessCputime is the unsupported clock ID named "process_cputime_id"
|
||||
// clockIDProcessCputime is the unsupported "process_cputime_id"
|
||||
clockIDProcessCputime
|
||||
// clockIDThreadCputime is the unsupported clock ID named "thread_cputime_id"
|
||||
// clockIDThreadCputime is the unsupported "thread_cputime_id"
|
||||
clockIDThreadCputime
|
||||
)
|
||||
|
||||
// ClockResGet is the WASI function named functionClockResGet that returns the resolution of time values returned by ClockTimeGet.
|
||||
// clockResGet is the WASI function named functionClockResGet that returns the
|
||||
// resolution of time values returned by clockTimeGet.
|
||||
//
|
||||
// * id - The clock id for which to return the time.
|
||||
// * resultResolution - the offset to write the resolution to mod.Memory
|
||||
// * the resolution is an uint64 little-endian encoding.
|
||||
// Parameters
|
||||
//
|
||||
// For example, if the resolution is 100ns, this function writes the below to `mod.Memory`:
|
||||
// * id: clock ID to use
|
||||
// * resultResolution: offset to write the resolution to api.Memory
|
||||
// * the resolution is an uint64 little-endian encoding
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoNotsup: the clock ID is not supported.
|
||||
// * ErrnoInval: the clock ID is invalid.
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if the resolution is 100ns, this function writes the below to
|
||||
// api.Memory:
|
||||
//
|
||||
// uint64le
|
||||
// +-------------------------------------+
|
||||
@@ -52,11 +50,10 @@ const (
|
||||
// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?}
|
||||
// resultResolution --^
|
||||
//
|
||||
// Note: importClockResGet shows this signature in the WebAssembly 1.0 Text Format.
|
||||
// Note: This is similar to `clock_getres` in POSIX.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
|
||||
// See https://linux.die.net/man/3/clock_getres
|
||||
func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno {
|
||||
func clockResGet(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
|
||||
var resolution uint64 // ns
|
||||
@@ -66,8 +63,9 @@ func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resul
|
||||
case clockIDMonotonic:
|
||||
resolution = uint64(sysCtx.NanotimeResolution())
|
||||
case clockIDProcessCputime, clockIDThreadCputime:
|
||||
// Similar to many other runtimes, we only support realtime and monotonic clocks. Other types
|
||||
// are slated to be removed from the next version of WASI.
|
||||
// Similar to many other runtimes, we only support realtime and
|
||||
// monotonic clocks. Other types are slated to be removed from the next
|
||||
// version of WASI.
|
||||
return ErrnoNotsup
|
||||
default:
|
||||
return ErrnoInval
|
||||
@@ -78,15 +76,27 @@ func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resul
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// ClockTimeGet is the WASI function named functionClockTimeGet that returns the time value of a clock (time.Now).
|
||||
// clockTimeGet is the WASI function named functionClockTimeGet that returns
|
||||
// the time value of a name (time.Now).
|
||||
//
|
||||
// * id - The clock id for which to return the time.
|
||||
// * precision - The maximum lag (exclusive) that the returned time value may have, compared to its actual value.
|
||||
// * resultTimestamp - the offset to write the timestamp to mod.Memory
|
||||
// * the timestamp is epoch nanoseconds encoded as a uint64 little-endian encoding.
|
||||
// Parameters
|
||||
//
|
||||
// For example, if time.Now returned exactly midnight UTC 2022-01-01 (1640995200000000000), and
|
||||
// parameters resultTimestamp=1, this function writes the below to `mod.Memory`:
|
||||
// * id: clock ID to use
|
||||
// * precision: maximum lag (exclusive) that the returned time value may have,
|
||||
// compared to its actual value
|
||||
// * resultTimestamp: offset to write the timestamp to api.Memory
|
||||
// * the timestamp is epoch nanos encoded as a little-endian uint64
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoNotsup: the clock ID is not supported.
|
||||
// * ErrnoInval: the clock ID is invalid.
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if time.Now returned exactly midnight UTC 2022-01-01
|
||||
// (1640995200000000000), and parameters resultTimestamp=1, this function
|
||||
// writes the below to api.Memory:
|
||||
//
|
||||
// uint64le
|
||||
// +------------------------------------------+
|
||||
@@ -94,11 +104,10 @@ func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resul
|
||||
// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?}
|
||||
// resultTimestamp --^
|
||||
//
|
||||
// Note: importClockTimeGet shows this signature in the WebAssembly 1.0 Text Format.
|
||||
// Note: This is similar to `clock_gettime` in POSIX.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
|
||||
// See https://linux.die.net/man/3/clock_gettime
|
||||
func (a *wasi) ClockTimeGet(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
|
||||
func clockTimeGet(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
|
||||
// TODO: precision is currently ignored.
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
|
||||
@@ -110,8 +119,9 @@ func (a *wasi) ClockTimeGet(ctx context.Context, mod api.Module, id uint32, prec
|
||||
case clockIDMonotonic:
|
||||
val = uint64(sysCtx.Nanotime(ctx))
|
||||
case clockIDProcessCputime, clockIDThreadCputime:
|
||||
// Similar to many other runtimes, we only support realtime and monotonic clocks. Other types
|
||||
// are slated to be removed from the next version of WASI.
|
||||
// Similar to many other runtimes, we only support realtime and
|
||||
// monotonic clocks. Other types are slated to be removed from the next
|
||||
// version of WASI.
|
||||
return ErrnoNotsup
|
||||
default:
|
||||
return ErrnoInval
|
||||
|
||||
@@ -2,19 +2,15 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// Test_ClockResGet only tests it is stubbed for GrainLang per #271
|
||||
func Test_ClockResGet(t *testing.T) {
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
|
||||
resultResolution := uint32(1) // arbitrary offset
|
||||
func Test_clockResGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
expectedMemoryMicro := []byte{
|
||||
'?', // resultResolution is after this
|
||||
@@ -30,56 +26,41 @@ func Test_ClockResGet(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
clockID uint64
|
||||
clockID uint32
|
||||
expectedMemory []byte
|
||||
invocation func(clockID uint64) Errno
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "wasi.ClockResGet",
|
||||
clockID: 0,
|
||||
name: "Realtime",
|
||||
clockID: clockIDRealtime,
|
||||
expectedMemory: expectedMemoryMicro,
|
||||
invocation: func(clockID uint64) Errno {
|
||||
return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution)
|
||||
},
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_res_get(id=0,result.resolution=1)
|
||||
<== ESUCCESS
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "wasi.ClockResGet",
|
||||
clockID: 1,
|
||||
name: "Monotonic",
|
||||
clockID: clockIDMonotonic,
|
||||
expectedMemory: expectedMemoryNano,
|
||||
invocation: func(clockID uint64) Errno {
|
||||
return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockResGet,
|
||||
clockID: 0,
|
||||
expectedMemory: expectedMemoryMicro,
|
||||
invocation: func(clockID uint64) Errno {
|
||||
results, err := fn.Call(testCtx, clockID, uint64(resultResolution))
|
||||
require.NoError(t, err)
|
||||
return Errno(results[0]) // results[0] is the errno
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockResGet,
|
||||
clockID: 1,
|
||||
expectedMemory: expectedMemoryNano,
|
||||
invocation: func(clockID uint64) Errno {
|
||||
results, err := fn.Call(testCtx, clockID, uint64(resultResolution))
|
||||
require.NoError(t, err)
|
||||
return Errno(results[0]) // results[0] is the errno
|
||||
},
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_res_get(id=1,result.resolution=1)
|
||||
<== ESUCCESS
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(fmt.Sprintf("%v/clockID=%v", tc.name, tc.clockID), func(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
maskMemory(t, testCtx, mod, len(tc.expectedMemory))
|
||||
|
||||
errno := tc.invocation(tc.clockID)
|
||||
require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno))
|
||||
resultResolution := uint32(1) // arbitrary offset
|
||||
requireErrno(t, ErrnoSuccess, mod, functionClockResGet, uint64(tc.clockID), uint64(resultResolution))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
|
||||
require.True(t, ok)
|
||||
@@ -88,30 +69,42 @@ func Test_ClockResGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClockResGet_Unsupported(t *testing.T) {
|
||||
resultResolution := uint32(1) // arbitrary offset
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
func Test_clockResGet_Unsupported(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
clockID uint64
|
||||
clockID uint32
|
||||
expectedErrno Errno
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "process cputime",
|
||||
clockID: 2,
|
||||
expectedErrno: ErrnoNotsup,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_res_get(id=2,result.resolution=1)
|
||||
<== ENOTSUP
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "thread cputime",
|
||||
clockID: 3,
|
||||
expectedErrno: ErrnoNotsup,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_res_get(id=3,result.resolution=1)
|
||||
<== ENOTSUP
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "undefined",
|
||||
clockID: 100,
|
||||
expectedErrno: ErrnoInval,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_res_get(id=100,result.resolution=1)
|
||||
<== EINVAL
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -119,100 +112,80 @@ func Test_ClockResGet_Unsupported(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := fn.Call(testCtx, tc.clockID, uint64(resultResolution))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
|
||||
defer log.Reset()
|
||||
|
||||
resultResolution := uint32(1) // arbitrary offset
|
||||
requireErrno(t, tc.expectedErrno, mod, functionClockResGet, uint64(tc.clockID), uint64(resultResolution))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClockTimeGet(t *testing.T) {
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
func Test_clockTimeGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
|
||||
clocks := []struct {
|
||||
clock string
|
||||
id uint32
|
||||
tests := []struct {
|
||||
name string
|
||||
clockID uint32
|
||||
expectedMemory []byte
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
clock: "Realtime",
|
||||
id: clockIDRealtime,
|
||||
name: "Realtime",
|
||||
clockID: clockIDRealtime,
|
||||
expectedMemory: []byte{
|
||||
'?', // resultTimestamp is after this
|
||||
0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=1)
|
||||
<== ESUCCESS
|
||||
`,
|
||||
},
|
||||
{
|
||||
clock: "Monotonic",
|
||||
id: clockIDMonotonic,
|
||||
name: "Monotonic",
|
||||
clockID: clockIDMonotonic,
|
||||
expectedMemory: []byte{
|
||||
'?', // resultTimestamp is after this
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // fake nanotime starts at zero
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range clocks {
|
||||
cc := c
|
||||
t.Run(cc.clock, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
invocation func() Errno
|
||||
}{
|
||||
{
|
||||
name: "wasi.ClockTimeGet",
|
||||
invocation: func() Errno {
|
||||
return a.ClockTimeGet(testCtx, mod, cc.id, 0 /* TODO: precision */, resultTimestamp)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockTimeGet,
|
||||
invocation: func() Errno {
|
||||
results, err := fn.Call(testCtx, uint64(cc.id), 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
return errno
|
||||
},
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.clock_time_get(id=1,precision=0,result.timestamp=1)
|
||||
<== ESUCCESS
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset the fake clock
|
||||
sysCtx, err := newSysContext(nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
mod.(*wasm.CallContext).Sys = sysCtx
|
||||
defer log.Reset()
|
||||
|
||||
maskMemory(t, testCtx, mod, len(cc.expectedMemory))
|
||||
maskMemory(t, testCtx, mod, len(tc.expectedMemory))
|
||||
|
||||
errno := tc.invocation()
|
||||
require.Zero(t, errno, ErrnoName(errno))
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
requireErrno(t, ErrnoSuccess, mod, functionClockTimeGet, uint64(tc.clockID), 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(cc.expectedMemory)))
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, cc.expectedMemory, actual)
|
||||
})
|
||||
}
|
||||
require.Equal(t, tc.expectedMemory, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClockTimeGet_Unsupported(t *testing.T) {
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
func Test_clockTimeGet_Unsupported(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
clockID uint64
|
||||
clockID uint32
|
||||
expectedErrno Errno
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "process cputime",
|
||||
@@ -235,24 +208,26 @@ func Test_ClockTimeGet_Unsupported(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := fn.Call(testCtx, tc.clockID, 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
defer log.Reset()
|
||||
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
errno := clockTimeGet(testCtx, mod, tc.clockID, 0 /* TODO: precision */, resultTimestamp)
|
||||
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClockTimeGet_Errors(t *testing.T) {
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
func Test_clockTimeGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resultTimestamp uint32
|
||||
argvBufSize uint32
|
||||
resultTimestamp, argvBufSize uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "resultTimestamp out-of-memory",
|
||||
@@ -269,10 +244,11 @@ func Test_ClockTimeGet_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := fn.Call(testCtx, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
defer log.Reset()
|
||||
|
||||
errno := clockTimeGet(testCtx, mod, 0 /* TODO: id */, 0 /* TODO: precision */, tc.resultTimestamp)
|
||||
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
95
wasi_snapshot_preview1/environ.go
Normal file
95
wasi_snapshot_preview1/environ.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
functionEnvironGet = "environ_get"
|
||||
functionEnvironSizesGet = "environ_sizes_get"
|
||||
)
|
||||
|
||||
// environGet is the WASI function named functionEnvironGet that reads
|
||||
// environment variables.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * environ: offset to begin writing environment offsets in uint32
|
||||
// little-endian encoding to api.Memory
|
||||
// * environSizesGet result environc * 4 bytes are written to this offset
|
||||
// * environBuf: offset to write the null-terminated variables to api.Memory
|
||||
// * the format is like os.Environ: null-terminated "key=val" entries
|
||||
// * environSizesGet result environBufSize bytes are written to this offset
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environSizesGet wrote environc=2 and environBufSize=9 for
|
||||
// environment variables: "a=b", "b=cd" and parameters environ=11 and
|
||||
// environBuf=1, this function writes the below to api.Memory:
|
||||
//
|
||||
// environBufSize uint32le uint32le
|
||||
// +------------------------------------+ +--------+ +--------+
|
||||
// | | | | | |
|
||||
// []byte{?, 'a', '=', 'b', 0, 'b', '=', 'c', 'd', 0, ?, 1, 0, 0, 0, 5, 0, 0, 0, ?}
|
||||
// environBuf --^ ^ ^
|
||||
// environ offset for "a=b" --+ |
|
||||
// environ offset for "b=cd" --+
|
||||
//
|
||||
// See environSizesGet
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func environGet(ctx context.Context, mod api.Module, environ uint32, environBuf uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
return writeOffsetsAndNullTerminatedValues(ctx, mod.Memory(), sysCtx.Environ(), environ, environBuf)
|
||||
}
|
||||
|
||||
// environSizesGet is the WASI function named functionEnvironSizesGet that
|
||||
// reads environment variable sizes.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * resultEnvironc: offset to write the count of environment variables to
|
||||
// api.Memory
|
||||
// * resultEnvironBufSize: offset to write the null-terminated environment
|
||||
// variable length to api.Memory
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoFault: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
|
||||
// resultEnvironBufSize=6, this function writes the below to api.Memory:
|
||||
//
|
||||
// uint32le uint32le
|
||||
// +--------+ +--------+
|
||||
// | | | |
|
||||
// []byte{?, 2, 0, 0, 0, ?, 9, 0, 0, 0, ?}
|
||||
// resultEnvironc --^ ^
|
||||
// 2 variables --+ |
|
||||
// resultEnvironBufSize --|
|
||||
// len([]byte{'a','=','b',0, |
|
||||
// 'b','=','c','d',0}) --+
|
||||
//
|
||||
// See environGet
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
|
||||
// and https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func environSizesGet(ctx context.Context, mod api.Module, resultEnvironc uint32, resultEnvironBufSize uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
mem := mod.Memory()
|
||||
|
||||
if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sysCtx.Environ()))) {
|
||||
return ErrnoFault
|
||||
}
|
||||
if !mem.WriteUint32Le(ctx, resultEnvironBufSize, sysCtx.EnvironSize()) {
|
||||
return ErrnoFault
|
||||
}
|
||||
|
||||
return ErrnoSuccess
|
||||
}
|
||||
164
wasi_snapshot_preview1/environ_test.go
Normal file
164
wasi_snapshot_preview1/environ_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func Test_environGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithEnv("a", "b").WithEnv("b", "cd"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
resultEnviron := uint32(11) // arbitrary offset
|
||||
resultEnvironBuf := uint32(1) // arbitrary offset
|
||||
expectedMemory := []byte{
|
||||
'?', // environBuf is after this
|
||||
'a', '=', 'b', 0, // null terminated "a=b",
|
||||
'b', '=', 'c', 'd', 0, // null terminated "b=cd"
|
||||
'?', // environ is after this
|
||||
1, 0, 0, 0, // little endian-encoded offset of "a=b"
|
||||
5, 0, 0, 0, // little endian-encoded offset of "b=cd"
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
// Invoke environGet and check the memory side effects.
|
||||
requireErrno(t, ErrnoSuccess, mod, functionEnvironGet, uint64(resultEnviron), uint64(resultEnvironBuf))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.environ_get(environ=11,environ_buf=1)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
}
|
||||
|
||||
func Test_environGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithEnv("a", "bc").WithEnv("b", "cd"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
validAddress := uint32(0) // arbitrary valid address as arguments to environ_get. We chose 0 here.
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
environ, environBuf uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "out-of-memory environPtr",
|
||||
environ: memorySize,
|
||||
environBuf: validAddress,
|
||||
},
|
||||
{
|
||||
name: "out-of-memory environBufPtr",
|
||||
environ: validAddress,
|
||||
environBuf: memorySize,
|
||||
},
|
||||
{
|
||||
name: "environPtr exceeds the maximum valid address by 1",
|
||||
// 4*envCount is the expected length for environPtr, 4 is the size of uint32
|
||||
environ: memorySize - 4*2 + 1,
|
||||
environBuf: validAddress,
|
||||
},
|
||||
{
|
||||
name: "environBufPtr exceeds the maximum valid address by 1",
|
||||
environ: validAddress,
|
||||
// "a=bc", "b=cd" size = size of "a=bc0b=cd0" = 10
|
||||
environBuf: memorySize - 10 + 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
errno := environGet(testCtx, mod, tc.environ, tc.environBuf)
|
||||
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_environSizesGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithEnv("a", "b").WithEnv("b", "cd"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
resultEnvironc := uint32(1) // arbitrary offset
|
||||
resultEnvironBufSize := uint32(6) // arbitrary offset
|
||||
expectedMemory := []byte{
|
||||
'?', // resultEnvironc is after this
|
||||
0x2, 0x0, 0x0, 0x0, // little endian-encoded environment variable count
|
||||
'?', // resultEnvironBufSize is after this
|
||||
0x9, 0x0, 0x0, 0x0, // little endian-encoded size of null terminated strings
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
// Invoke environSizesGet and check the memory side effects.
|
||||
requireErrno(t, ErrnoSuccess, mod, functionEnvironSizesGet, uint64(resultEnvironc), uint64(resultEnvironBufSize))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=1,result.environBufSize=6)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
}
|
||||
|
||||
func Test_environSizesGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithEnv("a", "b").WithEnv("b", "cd"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
validAddress := uint32(0) // arbitrary valid address as arguments to environ_sizes_get. We chose 0 here.
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
environc, environBufSize uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "out-of-memory environCountPtr",
|
||||
environc: memorySize,
|
||||
environBufSize: validAddress,
|
||||
},
|
||||
{
|
||||
name: "out-of-memory environBufSizePtr",
|
||||
environc: validAddress,
|
||||
environBufSize: memorySize,
|
||||
},
|
||||
{
|
||||
name: "environCountPtr exceeds the maximum valid address by 1",
|
||||
environc: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of environ
|
||||
environBufSize: validAddress,
|
||||
},
|
||||
{
|
||||
name: "environBufSizePtr exceeds the maximum valid size by 1",
|
||||
environc: validAddress,
|
||||
environBufSize: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errno := environSizesGet(testCtx, mod, tc.environc, tc.environBufSize)
|
||||
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func Example() {
|
||||
// Override default configuration (which discards stdout).
|
||||
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStdout(os.Stdout).WithName("wasi-demo"))
|
||||
if mod != nil {
|
||||
defer mod.Close(ctx)
|
||||
defer r.Close(ctx)
|
||||
}
|
||||
|
||||
// Note: Most compilers do not exit the module after running "_start", unless
|
||||
|
||||
678
wasi_snapshot_preview1/fs.go
Normal file
678
wasi_snapshot_preview1/fs.go
Normal file
@@ -0,0 +1,678 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
functionFdAdvise = "fd_advise"
|
||||
functionFdAllocate = "fd_allocate"
|
||||
functionFdClose = "fd_close"
|
||||
functionFdDatasync = "fd_datasync"
|
||||
functionFdFdstatGet = "fd_fdstat_get"
|
||||
functionFdFdstatSetFlags = "fd_fdstat_set_flags"
|
||||
functionFdFdstatSetRights = "fd_fdstat_set_rights"
|
||||
functionFdFilestatGet = "fd_filestat_get"
|
||||
functionFdFilestatSetSize = "fd_filestat_set_size"
|
||||
functionFdFilestatSetTimes = "fd_filestat_set_times"
|
||||
functionFdPread = "fd_pread"
|
||||
functionFdPrestatGet = "fd_prestat_get"
|
||||
functionFdPrestatDirName = "fd_prestat_dir_name"
|
||||
functionFdPwrite = "fd_pwrite"
|
||||
functionFdRead = "fd_read"
|
||||
functionFdReaddir = "fd_readdir"
|
||||
functionFdRenumber = "fd_renumber"
|
||||
functionFdSeek = "fd_seek"
|
||||
functionFdSync = "fd_sync"
|
||||
functionFdTell = "fd_tell"
|
||||
functionFdWrite = "fd_write"
|
||||
|
||||
functionPathCreateDirectory = "path_create_directory"
|
||||
functionPathFilestatGet = "path_filestat_get"
|
||||
functionPathFilestatSetTimes = "path_filestat_set_times"
|
||||
functionPathLink = "path_link"
|
||||
functionPathOpen = "path_open"
|
||||
functionPathReadlink = "path_readlink"
|
||||
functionPathRemoveDirectory = "path_remove_directory"
|
||||
functionPathRename = "path_rename"
|
||||
functionPathSymlink = "path_symlink"
|
||||
functionPathUnlinkFile = "path_unlink_file"
|
||||
)
|
||||
|
||||
// fdAdvise is the WASI function named functionFdAdvise which provides file
|
||||
// advisory information on a file descriptor.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno
|
||||
var fdAdvise = stubFunction(i32, i64, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdAllocate is the WASI function named functionFdAllocate which forces the
|
||||
// allocation of space in a file.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
|
||||
var fdAllocate = stubFunction(i32, i64, i64) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdClose is the WASI function named functionFdClose which closes a file
|
||||
// descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor to close
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: the fd was not open.
|
||||
//
|
||||
// Note: This is similar to `close` in POSIX.
|
||||
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
|
||||
// and https://linux.die.net/man/3/close
|
||||
func fdClose(ctx context.Context, mod api.Module, fd uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
if ok := sysCtx.FS(ctx).CloseFile(ctx, fd); !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdDatasync is the WASI function named functionFdDatasync which synchronizes
|
||||
// the data of a file to disk.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
|
||||
var fdDatasync = stubFunction(i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdFdstatGet is the WASI function named functionFdFdstatGet which returns the
|
||||
// attributes of a file descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor to get the fdstat attributes data
|
||||
// * resultFdstat: offset to write the result fdstat data
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `resultFdstat` points to an offset out of memory
|
||||
//
|
||||
// fdstat byte layout is 24-byte size, with the following fields:
|
||||
// * fs_filetype 1 byte, to indicate the file type
|
||||
// * fs_flags 2 bytes, to indicate the file descriptor flag
|
||||
// * 5 pad bytes
|
||||
// * fs_right_base 8 bytes, to indicate the current rights of the fd
|
||||
// * fs_right_inheriting 8 bytes, to indicate the maximum rights of the fd
|
||||
//
|
||||
// For example, with a file corresponding with `fd` was a directory (=3) opened
|
||||
// with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1,
|
||||
// this function writes the below to api.Memory:
|
||||
//
|
||||
// uint16le padding uint64le uint64le
|
||||
// uint8 --+ +--+ +-----------+ +--------------------+ +--------------------+
|
||||
// | | | | | | | | |
|
||||
// []byte{?, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}
|
||||
// resultFdstat --^ ^-- fs_flags ^-- fs_right_base ^-- fs_right_inheriting
|
||||
// |
|
||||
// +-- fs_filetype
|
||||
//
|
||||
// Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as
|
||||
// well as additional fields.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
|
||||
// and https://linux.die.net/man/3/fsync
|
||||
func fdFdstatGet(ctx context.Context, mod api.Module, fd uint32, resultStat uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
if _, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
// TODO: actually write the fdstat!
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdFdstatSetFlags is the WASI function named functionFdFdstatSetFlags which
|
||||
// adjusts the flags associated with a file descriptor.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_flagsfd-fd-flags-fdflags---errnoand is stubbed for GrainLang per #271
|
||||
var fdFdstatSetFlags = stubFunction(i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdFdstatSetRights is the WASI function named functionFdFdstatSetRights which
|
||||
// adjusts the rights associated with a file descriptor.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_rightsfd-fd-fs_rights_base-rights-fs_rights_inheriting-rights---errno
|
||||
//
|
||||
// Note: This will never be implemented per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
|
||||
var fdFdstatSetRights = stubFunction(i32, i64, i64) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdFilestatGet is the WASI function named functionFdFilestatGet which returns
|
||||
// the attributes of an open file.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat
|
||||
var fdFilestatGet = stubFunction(i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdFilestatSetSize is the WASI function named functionFdFilestatSetSize which
|
||||
// adjusts the size of an open file.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
|
||||
var fdFilestatSetSize = stubFunction(i32, i64) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
|
||||
// which adjusts the times of an open file.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
|
||||
var fdFilestatSetTimes = stubFunction(i32, i64, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdPread is the WASI function named functionFdPread which reads from a file
|
||||
// descriptor, without using and updating the file descriptor's offset.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size
|
||||
var fdPread = stubFunction(i32, i32, i32, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdPrestatGet is the WASI function named functionFdPrestatGet which returns
|
||||
// the prestat data of a file descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor to get the prestat
|
||||
// * resultPrestat: offset to write the result prestat data
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid or the `fd` is not a pre-opened directory
|
||||
// * ErrnoFault: `resultPrestat` points to an offset out of memory
|
||||
//
|
||||
// prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes.
|
||||
// The only valid tag is `prestat_dir`, which is tag zero. This simplifies the
|
||||
// byte layout to 4 empty bytes followed by the uint32le encoded path length.
|
||||
//
|
||||
// For example, the directory name corresponding with `fd` was "/tmp" and
|
||||
// parameter resultPrestat=1, this function writes the below to api.Memory:
|
||||
//
|
||||
// padding uint32le
|
||||
// uint8 --+ +-----+ +--------+
|
||||
// | | | | |
|
||||
// []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?}
|
||||
// resultPrestat --^ ^
|
||||
// tag --+ |
|
||||
// +-- size in bytes of the string "/tmp"
|
||||
//
|
||||
// See fdPrestatDirName and
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
|
||||
func fdPrestatGet(ctx context.Context, mod api.Module, fd uint32, resultPrestat uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
entry, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
|
||||
if !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
// Zero-value 8-bit tag, and 3-byte zero-value paddings, which is uint32le(0) in short.
|
||||
if !mod.Memory().WriteUint32Le(ctx, resultPrestat, uint32(0)) {
|
||||
return ErrnoFault
|
||||
}
|
||||
// Write the length of the directory name at offset 4.
|
||||
if !mod.Memory().WriteUint32Le(ctx, resultPrestat+4, uint32(len(entry.Path))) {
|
||||
return ErrnoFault
|
||||
}
|
||||
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdPrestatDirName is the WASI function named functionFdPrestatDirName which
|
||||
// returns the path of the pre-opened directory of a file descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor to get the path of the pre-opened directory
|
||||
// * path: offset in api.Memory to write the result path
|
||||
// * pathLen: count of bytes to write to `path`
|
||||
// * This should match the uint32le fdPrestatGet writes to offset
|
||||
// `resultPrestat`+4
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `path` points to an offset out of memory
|
||||
// * ErrnoNametoolong: `pathLen` is longer than the actual length of the result
|
||||
//
|
||||
// For example, the directory name corresponding with `fd` was "/tmp" and
|
||||
// parameters path=1 pathLen=4 (correct), this function will write the below to
|
||||
// api.Memory:
|
||||
//
|
||||
// pathLen
|
||||
// +--------------+
|
||||
// | |
|
||||
// []byte{?, '/', 't', 'm', 'p', ?}
|
||||
// path --^
|
||||
//
|
||||
// See fdPrestatGet
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
|
||||
func fdPrestatDirName(ctx context.Context, mod api.Module, fd uint32, pathPtr uint32, pathLen uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
|
||||
if !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
// Some runtimes may have another semantics. See /RATIONALE.md
|
||||
if uint32(len(f.Path)) < pathLen {
|
||||
return ErrnoNametoolong
|
||||
}
|
||||
|
||||
// TODO: fdPrestatDirName may have to return ErrnoNotdir if the type of the prestat data of `fd` is not a PrestatDir.
|
||||
if !mod.Memory().Write(ctx, pathPtr, []byte(f.Path)[:pathLen]) {
|
||||
return ErrnoFault
|
||||
}
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdPwrite is the WASI function named functionFdPwrite which writes to a file
|
||||
// descriptor, without using and updating the file descriptor's offset.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size
|
||||
var fdPwrite = stubFunction(i32, i32, i32, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdRead is the WASI function named functionFdRead which reads from a file
|
||||
// descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: an opened file descriptor to read data from
|
||||
// * iovs: offset in api.Memory to read offset, size pairs representing where
|
||||
// to write file data
|
||||
// * Both offset and length are encoded as uint32le
|
||||
// * iovsCount: count of memory offset, size pairs to read sequentially
|
||||
// starting at iovs
|
||||
// * resultSize: offset in api.Memory to write the number of bytes read
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `iovs` or `resultSize` point to an offset out of memory
|
||||
// * ErrnoIo: a file system error
|
||||
//
|
||||
// For example, this function needs to first read `iovs` to determine where
|
||||
// to write contents. If parameters iovs=1 iovsCount=2, this function reads two
|
||||
// offset/length pairs from api.Memory:
|
||||
//
|
||||
// iovs[0] iovs[1]
|
||||
// +---------------------+ +--------------------+
|
||||
// | uint32le uint32le| |uint32le uint32le|
|
||||
// +---------+ +--------+ +--------+ +--------+
|
||||
// | | | | | | | |
|
||||
// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
|
||||
// iovs --^ ^ ^ ^
|
||||
// | | | |
|
||||
// offset --+ length --+ offset --+ length --+
|
||||
//
|
||||
// If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter
|
||||
// resultSize=26, this function writes the below to api.Memory:
|
||||
//
|
||||
// iovs[0].length iovs[1].length
|
||||
// +--------------+ +----+ uint32le
|
||||
// | | | | +--------+
|
||||
// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 }
|
||||
// iovs[0].offset --^ ^ ^
|
||||
// iovs[1].offset --+ |
|
||||
// resultSize --+
|
||||
//
|
||||
// Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv
|
||||
//
|
||||
// See fdWrite
|
||||
// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
|
||||
func fdRead(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
reader := internalsys.FdReader(ctx, sysCtx, fd)
|
||||
if reader == nil {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
var nread uint32
|
||||
for i := uint32(0); i < iovsCount; i++ {
|
||||
iovPtr := iovs + i*8
|
||||
offset, ok := mod.Memory().ReadUint32Le(ctx, iovPtr)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
l, ok := mod.Memory().ReadUint32Le(ctx, iovPtr+4)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
b, ok := mod.Memory().Read(ctx, offset, l)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
n, err := reader.Read(b) // Note: n <= l
|
||||
nread += uint32(n)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return ErrnoIo
|
||||
}
|
||||
}
|
||||
if !mod.Memory().WriteUint32Le(ctx, resultSize, nread) {
|
||||
return ErrnoFault
|
||||
}
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdReaddir is the WASI function named functionFdReaddir which reads directory
|
||||
// entries from a directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size
|
||||
var fdReaddir = stubFunction(i32, i32, i32, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdRenumber is the WASI function named functionFdRenumber which atomically
|
||||
// replaces a file descriptor by renumbering another file descriptor.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
|
||||
var fdRenumber = stubFunction(i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdSeek is the WASI function named functionFdSeek which moves the offset of a
|
||||
// file descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor to move the offset of
|
||||
// * offset: signed int64, which is encoded as uint64, input argument to
|
||||
// `whence`, which results in a new offset
|
||||
// * whence: operator that creates the new offset, given `offset` bytes
|
||||
// * If io.SeekStart, new offset == `offset`.
|
||||
// * If io.SeekCurrent, new offset == existing offset + `offset`.
|
||||
// * If io.SeekEnd, new offset == file size of `fd` + `offset`.
|
||||
// * resultNewoffset: offset in api.Memory to write the new offset to,
|
||||
// relative to start of the file
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `resultNewoffset` points to an offset out of memory
|
||||
// * ErrnoInval: `whence` is an invalid value
|
||||
// * ErrnoIo: a file system error
|
||||
//
|
||||
// For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4,
|
||||
// whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below
|
||||
// to api.Memory:
|
||||
//
|
||||
// uint64le
|
||||
// +--------------------+
|
||||
// | |
|
||||
// []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? }
|
||||
// resultNewoffset --^
|
||||
//
|
||||
// Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek
|
||||
//
|
||||
// See io.Seeker
|
||||
// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
|
||||
func fdSeek(ctx context.Context, mod api.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
var seeker io.Seeker
|
||||
// Check to see if the file descriptor is available
|
||||
if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil {
|
||||
return ErrnoBadf
|
||||
// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it.
|
||||
} else if seeker, ok = f.File.(io.Seeker); !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
if whence > io.SeekEnd /* exceeds the largest valid whence */ {
|
||||
return ErrnoInval
|
||||
}
|
||||
newOffset, err := seeker.Seek(int64(offset), int(whence))
|
||||
if err != nil {
|
||||
return ErrnoIo
|
||||
}
|
||||
|
||||
if !mod.Memory().WriteUint32Le(ctx, resultNewoffset, uint32(newOffset)) {
|
||||
return ErrnoFault
|
||||
}
|
||||
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// fdSync is the WASI function named functionFdSync which synchronizes the data
|
||||
// and metadata of a file to disk.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
|
||||
var fdSync = stubFunction(i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdTell is the WASI function named functionFdTell which returns the current
|
||||
// offset of a file descriptor.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
|
||||
var fdTell = stubFunction(i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// fdWrite is the WASI function named functionFdWrite which writes to a file
|
||||
// descriptor.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: an opened file descriptor to write data to
|
||||
// * iovs: offset in api.Memory to read offset, size pairs representing the
|
||||
// data to write to `fd`
|
||||
// * Both offset and length are encoded as uint32le.
|
||||
// * iovsCount: count of memory offset, size pairs to read sequentially
|
||||
// starting at iovs
|
||||
// * resultSize: offset in api.Memory to write the number of bytes written
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `iovs` or `resultSize` point to an offset out of memory
|
||||
// * ErrnoIo: a file system error
|
||||
//
|
||||
// For example, this function needs to first read `iovs` to determine what to
|
||||
// write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two
|
||||
// offset/length pairs from api.Memory:
|
||||
//
|
||||
// iovs[0] iovs[1]
|
||||
// +---------------------+ +--------------------+
|
||||
// | uint32le uint32le| |uint32le uint32le|
|
||||
// +---------+ +--------+ +--------+ +--------+
|
||||
// | | | | | | | |
|
||||
// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
|
||||
// iovs --^ ^ ^ ^
|
||||
// | | | |
|
||||
// offset --+ length --+ offset --+ length --+
|
||||
//
|
||||
// This function reads those chunks api.Memory into the `fd` sequentially.
|
||||
//
|
||||
// iovs[0].length iovs[1].length
|
||||
// +--------------+ +----+
|
||||
// | | | |
|
||||
// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? }
|
||||
// iovs[0].offset --^ ^
|
||||
// iovs[1].offset --+
|
||||
//
|
||||
// Since "wazero" was written, if parameter resultSize=26, this function writes
|
||||
// the below to api.Memory:
|
||||
//
|
||||
// uint32le
|
||||
// +--------+
|
||||
// | |
|
||||
// []byte{ 0..24, ?, 6, 0, 0, 0', ? }
|
||||
// resultSize --^
|
||||
//
|
||||
// Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev
|
||||
//
|
||||
// See fdRead
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
|
||||
// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
|
||||
func fdWrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
writer := internalsys.FdWriter(ctx, sysCtx, fd)
|
||||
if writer == nil {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
var nwritten uint32
|
||||
for i := uint32(0); i < iovsCount; i++ {
|
||||
iovPtr := iovs + i*8
|
||||
offset, ok := mod.Memory().ReadUint32Le(ctx, iovPtr)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
// Note: emscripten has been known to write zero length iovec. However,
|
||||
// it is not common in other compilers, so we don't optimize for it.
|
||||
l, ok := mod.Memory().ReadUint32Le(ctx, iovPtr+4)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
b, ok := mod.Memory().Read(ctx, offset, l)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
n, err := writer.Write(b)
|
||||
if err != nil {
|
||||
return ErrnoIo
|
||||
}
|
||||
nwritten += uint32(n)
|
||||
}
|
||||
if !mod.Memory().WriteUint32Le(ctx, resultSize, nwritten) {
|
||||
return ErrnoFault
|
||||
}
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// pathCreateDirectory is the WASI function named functionPathCreateDirectory
|
||||
// which creates a directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
|
||||
var pathCreateDirectory = stubFunction(i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathFilestatGet is the WASI function named functionPathFilestatGet which
|
||||
// returns the attributes of a file or directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat
|
||||
var pathFilestatGet = stubFunction(i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathFilestatSetTimes is the WASI function named functionPathFilestatSetTimes
|
||||
// which adjusts the timestamps of a file or directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
|
||||
var pathFilestatSetTimes = stubFunction(i32, i32, i32, i32, i64, i64, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathLink is the WASI function named functionPathLink which adjusts the
|
||||
// timestamps of a file or directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link
|
||||
var pathLink = stubFunction(i32, i32, i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathOpen is the WASI function named functionPathOpen which opens a file or
|
||||
// directory. This returns ErrnoBadf if the fd is invalid.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * fd: file descriptor of a directory that `path` is relative to
|
||||
// * dirflags: flags to indicate how to resolve `path`
|
||||
// * path: offset in api.Memory to read the path string from
|
||||
// * pathLen: length of `path`
|
||||
// * oFlags: open flags to indicate the method by which to open the file
|
||||
// * fsRightsBase: rights of the newly created file descriptor for `path`
|
||||
// * fsRightsInheriting: rights of the file descriptors derived from the newly
|
||||
// created file descriptor for `path`
|
||||
// * fdFlags: file descriptor flags
|
||||
// * resultOpenedFd: offset in api.Memory to write the newly created file
|
||||
// descriptor to.
|
||||
// * The result FD value is guaranteed to be less than 2**31
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoBadf: `fd` is invalid
|
||||
// * ErrnoFault: `resultOpenedFd` points to an offset out of memory
|
||||
// * ErrnoNoent: `path` does not exist.
|
||||
// * ErrnoExist: `path` exists, while `oFlags` requires that it must not.
|
||||
// * ErrnoNotdir: `path` is not a directory, while `oFlags` requires it.
|
||||
// * ErrnoIo: a file system error
|
||||
//
|
||||
// For example, this function needs to first read `path` to determine the file
|
||||
// to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero",
|
||||
// pathOpen reads the path from api.Memory:
|
||||
//
|
||||
// pathLen
|
||||
// +------------------------+
|
||||
// | |
|
||||
// []byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... }
|
||||
// path --^
|
||||
//
|
||||
// Then, if parameters resultOpenedFd = 8, and this function opened a new file
|
||||
// descriptor 5 with the given flags, this function writes the below to
|
||||
// api.Memory:
|
||||
//
|
||||
// uint32le
|
||||
// +--------+
|
||||
// | |
|
||||
// []byte{ 0..6, ?, 5, 0, 0, 0, ?}
|
||||
// resultOpenedFd --^
|
||||
//
|
||||
// Notes
|
||||
// * This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat
|
||||
// * The returned file descriptor is not guaranteed to be the lowest-number
|
||||
// * Rights will never be implemented per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
|
||||
func pathOpen(ctx context.Context, mod api.Module, fd, dirflags, pathPtr, pathLen, oflags uint32, fsRightsBase,
|
||||
fsRightsInheriting uint64, fdflags, resultOpenedFd uint32) (errno Errno) {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
fsc := sysCtx.FS(ctx)
|
||||
if _, ok := fsc.OpenedFile(ctx, fd); !ok {
|
||||
return ErrnoBadf
|
||||
}
|
||||
|
||||
b, ok := mod.Memory().Read(ctx, pathPtr, pathLen)
|
||||
if !ok {
|
||||
return ErrnoFault
|
||||
}
|
||||
|
||||
if newFD, err := fsc.OpenFile(ctx, string(b)); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
return ErrnoNoent
|
||||
case errors.Is(err, fs.ErrExist):
|
||||
return ErrnoExist
|
||||
default:
|
||||
return ErrnoIo
|
||||
}
|
||||
} else if !mod.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) {
|
||||
_ = fsc.CloseFile(ctx, newFD)
|
||||
return ErrnoFault
|
||||
}
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// pathReadlink is the WASI function named functionPathReadlink that reads the
|
||||
// contents of a symbolic link.
|
||||
//
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size
|
||||
var pathReadlink = stubFunction(i32, i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathRemoveDirectory is the WASI function named functionPathRemoveDirectory
|
||||
// which removes a directory.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
|
||||
var pathRemoveDirectory = stubFunction(i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathRename is the WASI function named functionPathRename which renames a
|
||||
// file or directory.
|
||||
var pathRename = stubFunction(i32, i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathSymlink is the WASI function named functionPathSymlink which creates a
|
||||
// symbolic link.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink
|
||||
var pathSymlink = stubFunction(i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// pathUnlinkFile is the WASI function named functionPathUnlinkFile which
|
||||
// unlinks a file.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno
|
||||
var pathUnlinkFile = stubFunction(i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
1076
wasi_snapshot_preview1/fs_test.go
Normal file
1076
wasi_snapshot_preview1/fs_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,11 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const functionPollOneoff = "poll_oneoff"
|
||||
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8
|
||||
const (
|
||||
// eventTypeClock is the timeout event named "clock".
|
||||
// eventTypeClock is the timeout event named "name".
|
||||
eventTypeClock = iota
|
||||
// eventTypeFdRead is the data available event named "fd_read".
|
||||
eventTypeFdRead
|
||||
@@ -19,22 +21,23 @@ const (
|
||||
eventTypeFdWrite
|
||||
)
|
||||
|
||||
// PollOneoff is the WASI function named functionPollOneoff that concurrently
|
||||
// pollOneoff is the WASI function named functionPollOneoff that concurrently
|
||||
// polls for the occurrence of a set of events.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * in - pointer to the subscriptions (48 bytes each)
|
||||
// * out - pointer to the resulting events (32 bytes each)
|
||||
// * nsubscriptions - count of subscriptions, zero returns ErrnoInval.
|
||||
// * resultNevents - count of events.
|
||||
// * in: pointer to the subscriptions (48 bytes each)
|
||||
// * out: pointer to the resulting events (32 bytes each)
|
||||
// * nsubscriptions: count of subscriptions, zero returns ErrnoInval.
|
||||
// * resultNevents: count of events.
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoInval - If the parameters are invalid
|
||||
// * ErrnoNotsup - If a parameters is valid, but not yet supported.
|
||||
// * ErrnoFault - if there is not enough memory to read the subscriptions or write results.
|
||||
// * ErrnoInval: the parameters are invalid
|
||||
// * ErrnoNotsup: a parameters is valid, but not yet supported.
|
||||
// * ErrnoFault: there is not enough memory to read the subscriptions or
|
||||
// write results.
|
||||
//
|
||||
// Notes
|
||||
//
|
||||
@@ -43,7 +46,7 @@ const (
|
||||
// * This is similar to `poll` in POSIX.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
|
||||
// See https://linux.die.net/man/3/poll
|
||||
func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
|
||||
func pollOneoff(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
|
||||
if nsubscriptions == 0 {
|
||||
return ErrnoInval
|
||||
}
|
||||
@@ -75,7 +78,7 @@ func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscri
|
||||
eventType := inBuf[inOffset+8] // +8 past userdata
|
||||
switch eventType {
|
||||
case eventTypeClock: // handle later
|
||||
// +8 past userdata +8 clock alignment
|
||||
// +8 past userdata +8 name alignment
|
||||
errno = processClockEvent(ctx, mod, inBuf[inOffset+8+8:])
|
||||
case eventTypeFdRead, eventTypeFdWrite:
|
||||
// +8 past userdata +4 FD alignment
|
||||
@@ -95,7 +98,7 @@ func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscri
|
||||
return ErrnoSuccess
|
||||
}
|
||||
|
||||
// processClockEvent supports only relative clock events, as that's what's used
|
||||
// processClockEvent supports only relative name events, as that's what's used
|
||||
// to implement sleep in various compilers including Rust, Zig and TinyGo.
|
||||
func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno {
|
||||
_ /* ID */ = binary.LittleEndian.Uint32(inBuf[0:8]) // See below
|
||||
@@ -114,7 +117,7 @@ func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno
|
||||
|
||||
// https://linux.die.net/man/3/clock_settime says relative timers are
|
||||
// unaffected. Since this function only supports relative timeout, we can
|
||||
// skip clock ID validation and use a single sleep function.
|
||||
// skip name ID validation and use a single sleep function.
|
||||
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
sysCtx.Nanosleep(ctx, int64(timeout))
|
||||
@@ -127,7 +130,8 @@ func processFDEvent(ctx context.Context, mod api.Module, eventType byte, inBuf [
|
||||
fd := binary.LittleEndian.Uint32(inBuf)
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
|
||||
// Choose the best error, which falls back to unsupported, until we support files.
|
||||
// Choose the best error, which falls back to unsupported, until we support
|
||||
// files.
|
||||
errno := ErrnoNotsup
|
||||
if eventType == eventTypeFdRead && internalsys.FdReader(ctx, sysCtx, fd) == nil {
|
||||
errno = ErrnoBadf
|
||||
|
||||
@@ -3,23 +3,17 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func Test_PollOneoff(t *testing.T) {
|
||||
mod, fn := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil)
|
||||
defer mod.Close(testCtx)
|
||||
func Test_pollOneoff(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mem []byte
|
||||
expectedMem []byte // at offset out
|
||||
}{
|
||||
{
|
||||
name: "monotonic relative",
|
||||
mem: []byte{
|
||||
mem := []byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
|
||||
eventTypeClock, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // event type and padding
|
||||
clockIDMonotonic, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // clockID
|
||||
@@ -27,14 +21,13 @@ func Test_PollOneoff(t *testing.T) {
|
||||
0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // precision (ns)
|
||||
0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // flags (relative)
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
expectedMem: []byte{
|
||||
}
|
||||
|
||||
expectedMem := []byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
|
||||
byte(ErrnoSuccess), 0x0, // errno is 16 bit
|
||||
eventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
in := uint32(0) // past in
|
||||
@@ -42,7 +35,16 @@ func Test_PollOneoff(t *testing.T) {
|
||||
nsubscriptions := uint32(1)
|
||||
resultNevents := uint32(512) // past out
|
||||
|
||||
requireExpectedMem := func(expectedMem []byte) {
|
||||
maskMemory(t, testCtx, mod, 1024)
|
||||
mod.Memory().Write(testCtx, in, mem)
|
||||
|
||||
requireErrno(t, ErrnoSuccess, mod, functionPollOneoff, uint64(in), uint64(out), uint64(nsubscriptions),
|
||||
uint64(resultNevents))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
outMem, ok := mod.Memory().Read(testCtx, out, uint32(len(expectedMem)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMem, outMem)
|
||||
@@ -50,37 +52,11 @@ func Test_PollOneoff(t *testing.T) {
|
||||
nevents, ok := mod.Memory().ReadUint32Le(testCtx, resultNevents)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, nsubscriptions, nevents)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("wasi.PollOneoff", func(t *testing.T) {
|
||||
maskMemory(t, testCtx, mod, 1024)
|
||||
mod.Memory().Write(testCtx, in, tc.mem)
|
||||
|
||||
errno := a.PollOneoff(testCtx, mod, in, out, nsubscriptions, resultNevents)
|
||||
require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno))
|
||||
requireExpectedMem(tc.expectedMem)
|
||||
})
|
||||
|
||||
t.Run(functionPollOneoff, func(t *testing.T) {
|
||||
maskMemory(t, testCtx, mod, 1024)
|
||||
mod.Memory().Write(testCtx, in, tc.mem)
|
||||
|
||||
results, err := fn.Call(testCtx, uint64(in), uint64(out), uint64(nsubscriptions), uint64(resultNevents))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno))
|
||||
requireExpectedMem(tc.expectedMem)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PollOneoff_Errors(t *testing.T) {
|
||||
mod, _ := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil)
|
||||
defer mod.Close(testCtx)
|
||||
func Test_pollOneoff_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -88,6 +64,7 @@ func Test_PollOneoff_Errors(t *testing.T) {
|
||||
mem []byte // at offset in
|
||||
expectedErrno Errno
|
||||
expectedMem []byte // at offset out
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "in out of range",
|
||||
@@ -96,6 +73,10 @@ func Test_PollOneoff_Errors(t *testing.T) {
|
||||
out: 128, // past in
|
||||
resultNevents: 512, //past out
|
||||
expectedErrno: ErrnoFault,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=65536,out=128,nsubscriptions=1,result.nevents=512)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "out out of range",
|
||||
@@ -103,18 +84,30 @@ func Test_PollOneoff_Errors(t *testing.T) {
|
||||
resultNevents: 512, //past out
|
||||
nsubscriptions: 1,
|
||||
expectedErrno: ErrnoFault,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=0,out=65536,nsubscriptions=1,result.nevents=512)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "resultNevents out of range",
|
||||
resultNevents: wasm.MemoryPageSize,
|
||||
nsubscriptions: 1,
|
||||
expectedErrno: ErrnoFault,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=0,out=0,nsubscriptions=1,result.nevents=65536)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "nsubscriptions zero",
|
||||
out: 128, // past in
|
||||
resultNevents: 512, //past out
|
||||
expectedErrno: ErrnoInval,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=0,result.nevents=512)
|
||||
<== EINVAL
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "unsupported eventTypeFdRead",
|
||||
@@ -134,20 +127,27 @@ func Test_PollOneoff_Errors(t *testing.T) {
|
||||
eventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
|
||||
<== ESUCCESS
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
maskMemory(t, testCtx, mod, 1024)
|
||||
|
||||
if tc.mem != nil {
|
||||
mod.Memory().Write(testCtx, tc.in, tc.mem)
|
||||
}
|
||||
|
||||
errno := a.PollOneoff(testCtx, mod, tc.in, tc.out, tc.nsubscriptions, tc.resultNevents)
|
||||
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
|
||||
requireErrno(t, tc.expectedErrno, mod, functionPollOneoff, uint64(tc.in), uint64(tc.out),
|
||||
uint64(tc.nsubscriptions), uint64(tc.resultNevents))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
|
||||
out, ok := mod.Memory().Read(testCtx, tc.out, uint32(len(tc.expectedMem)))
|
||||
require.True(t, ok)
|
||||
|
||||
@@ -8,33 +8,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// functionProcExit terminates the execution of the module with an exit code.
|
||||
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
|
||||
functionProcExit = "proc_exit"
|
||||
|
||||
// importProcExit is the WebAssembly 1.0 Text format import of functionProcExit.
|
||||
importProcExit = `(import "wasi_snapshot_preview1" "proc_exit"
|
||||
(func $wasi.proc_exit (param $rval i32)))`
|
||||
|
||||
// functionProcRaise sends a signal to the process of the calling thread.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-proc_raisesig-signal---errno
|
||||
functionProcRaise = "proc_raise"
|
||||
|
||||
// importProcRaise is the WebAssembly 1.0 Text format import of functionProcRaise.
|
||||
importProcRaise = `(import "wasi_snapshot_preview1" "proc_raise"
|
||||
(func $wasi.proc_raise (param $sig i32) (result (;errno;) i32)))`
|
||||
)
|
||||
|
||||
// ProcExit is the WASI function that terminates the execution of the module with an exit code.
|
||||
// An exit code of 0 indicates successful termination. The meanings of other values are not defined by WASI.
|
||||
// procExit is the WASI function named functionProcExit that terminates the
|
||||
// execution of the module with an exit code. The only successful exit code is
|
||||
// zero.
|
||||
//
|
||||
// * rval - The exit code.
|
||||
// Parameters
|
||||
//
|
||||
// In wazero, this calls api.Module CloseWithExitCode.
|
||||
// * exitCode: exit code.
|
||||
//
|
||||
// Note: importProcExit shows this signature in the WebAssembly 1.0 Text Format.
|
||||
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
|
||||
func (a *wasi) ProcExit(ctx context.Context, mod api.Module, exitCode uint32) {
|
||||
func procExit(ctx context.Context, mod api.Module, exitCode uint32) {
|
||||
// Ensure other callers see the exit code.
|
||||
_ = mod.CloseWithExitCode(ctx, exitCode)
|
||||
|
||||
@@ -44,7 +31,8 @@ func (a *wasi) ProcExit(ctx context.Context, mod api.Module, exitCode uint32) {
|
||||
panic(sys.NewExitError(mod.Name(), exitCode))
|
||||
}
|
||||
|
||||
// ProcRaise is the WASI function named functionProcRaise
|
||||
func (a *wasi) ProcRaise(ctx context.Context, mod api.Module, sig uint32) Errno {
|
||||
return ErrnoNosys // stubbed for GrainLang per #271
|
||||
}
|
||||
// procRaise is the WASI function named functionProcRaise which sends a signal
|
||||
// to the process of the calling thread.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-proc_raisesig-signal---errno
|
||||
var procRaise = stubFunction(i32) // stubbed for GrainLang per #271.
|
||||
|
||||
@@ -5,14 +5,17 @@ import (
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/watzero"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func Test_ProcExit(t *testing.T) {
|
||||
func Test_procExit(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig())
|
||||
defer r.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
exitCode uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "success (exitcode 0)",
|
||||
@@ -28,61 +31,21 @@ func Test_ProcExit(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Note: Unlike most tests, this uses fn, not the 'a' result
|
||||
// parameter. This is because currently, this function body
|
||||
// panics, and we expect Call to unwrap the panic.
|
||||
mod, fn := instantiateModule(testCtx, t, functionProcExit, importProcExit, nil)
|
||||
defer mod.Close(testCtx)
|
||||
defer log.Reset()
|
||||
|
||||
// When ProcExit is called, CallEngine.Call returns immediately,
|
||||
// returning the exit code as the error.
|
||||
_, err := fn.Call(testCtx, uint64(tc.exitCode))
|
||||
// Since procExit panics, any opcodes afterwards cannot be reached.
|
||||
err := require.CapturePanic(func() { procExit(testCtx, mod, tc.exitCode) })
|
||||
require.Equal(t, tc.exitCode, err.(*sys.ExitError).ExitCode())
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var unreachableAfterExit = `(module
|
||||
(import "wasi_snapshot_preview1" "proc_exit"
|
||||
(func $wasi.proc_exit (param $rval i32)))
|
||||
(func $main
|
||||
i32.const 0
|
||||
call $wasi.proc_exit
|
||||
unreachable ;; If abort doesn't panic, this code is reached.
|
||||
)
|
||||
(start $main)
|
||||
)`
|
||||
|
||||
// Test_ProcExit_StopsExecution ensures code that follows a proc_exit isn't invoked.
|
||||
func Test_ProcExit_StopsExecution(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
defer r.Close(testCtx)
|
||||
|
||||
_, err := NewBuilder(r).Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
exitWasm, err := watzero.Wat2Wasm(unreachableAfterExit)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModuleFromBinary(testCtx, exitWasm)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, uint32(0), err.(*sys.ExitError).ExitCode())
|
||||
}
|
||||
|
||||
// Test_ProcRaise only tests it is stubbed for GrainLang per #271
|
||||
func Test_ProcRaise(t *testing.T) {
|
||||
mod, fn := instantiateModule(testCtx, t, functionProcRaise, importProcRaise, nil)
|
||||
defer mod.Close(testCtx)
|
||||
|
||||
t.Run("wasi.ProcRaise", func(t *testing.T) {
|
||||
errno := a.ProcRaise(testCtx, mod, 0)
|
||||
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
|
||||
})
|
||||
|
||||
t.Run(functionProcRaise, func(t *testing.T) {
|
||||
results, err := fn.Call(testCtx, 0)
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
|
||||
})
|
||||
// Test_procRaise only tests it is stubbed for GrainLang per #271
|
||||
func Test_procRaise(t *testing.T) {
|
||||
log := requireErrnoNosys(t, functionProcRaise, 0)
|
||||
require.Equal(t, `
|
||||
--> wasi_snapshot_preview1.proc_raise(sig=0)
|
||||
<-- ENOSYS
|
||||
`, log)
|
||||
}
|
||||
|
||||
52
wasi_snapshot_preview1/random.go
Normal file
52
wasi_snapshot_preview1/random.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const functionRandomGet = "random_get"
|
||||
|
||||
// randomGet is the WASI function named functionRandomGet which writes random
|
||||
// data to a buffer.
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
// * buf: api.Memory offset to write random values
|
||||
// * bufLen: size of random data in bytes
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// * ErrnoFault: `buf` or `bufLen` point to an offset out of memory
|
||||
// * ErrnoIo: a file system error
|
||||
//
|
||||
// For example, if underlying random source was seeded like
|
||||
// `rand.NewSource(42)`, we expect api.Memory to contain:
|
||||
//
|
||||
// bufLen (5)
|
||||
// +--------------------------+
|
||||
// | |
|
||||
// []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?}
|
||||
// buf --^
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
|
||||
func randomGet(ctx context.Context, mod api.Module, buf uint32, bufLen uint32) (errno Errno) {
|
||||
sysCtx := mod.(*wasm.CallContext).Sys
|
||||
randSource := sysCtx.RandSource()
|
||||
|
||||
randomBytes, ok := mod.Memory().Read(ctx, buf, bufLen)
|
||||
if !ok { // out-of-range
|
||||
return ErrnoFault
|
||||
}
|
||||
|
||||
// We can ignore the returned n as it only != byteCount on error
|
||||
if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
|
||||
return ErrnoIo
|
||||
}
|
||||
|
||||
return ErrnoSuccess
|
||||
}
|
||||
114
wasi_snapshot_preview1/random_test.go
Normal file
114
wasi_snapshot_preview1/random_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func Test_randomGet(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithRandSource(deterministicRandomSource()))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
expectedMemory := []byte{
|
||||
'?', // `offset` is after this
|
||||
0x53, 0x8c, 0x7f, 0x96, 0xb1, // random data from seed value of 42
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
length := uint32(5) // arbitrary length,
|
||||
offset := uint32(1) // offset,
|
||||
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
// Invoke randomGet and check the memory side effects!
|
||||
requireErrno(t, ErrnoSuccess, mod, functionRandomGet, uint64(offset), uint64(length))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.random_get(buf=1,buf_len=5)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, offset+length+1)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
}
|
||||
|
||||
func Test_randomGet_Errors(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithRandSource(deterministicRandomSource()))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
memorySize := mod.Memory().Size(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
offset, length uint32
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "out-of-memory",
|
||||
offset: memorySize,
|
||||
length: 1,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.random_get(buf=65536,buf_len=1)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "random length exceeds maximum valid address by 1",
|
||||
offset: 0, // arbitrary valid offset
|
||||
length: memorySize + 1,
|
||||
expectedLog: `
|
||||
==> wasi_snapshot_preview1.random_get(buf=0,buf_len=65537)
|
||||
<== EFAULT
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer log.Reset()
|
||||
|
||||
requireErrno(t, ErrnoFault, mod, functionRandomGet, uint64(tc.offset), uint64(tc.length))
|
||||
require.Equal(t, tc.expectedLog, "\n"+log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_randomGet_SourceError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
randSource io.Reader
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
randSource: iotest.ErrReader(errors.New("RandSource error")),
|
||||
},
|
||||
{
|
||||
name: "incomplete",
|
||||
randSource: bytes.NewReader([]byte{1, 2}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithRandSource(tc.randSource))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
errno := randomGet(testCtx, mod, uint32(1), uint32(5)) // arbitrary offset and length
|
||||
require.Equal(t, ErrnoIo, errno, ErrnoName(errno))
|
||||
require.Equal(t, tc.expectedLog, log.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
9
wasi_snapshot_preview1/sched.go
Normal file
9
wasi_snapshot_preview1/sched.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
const functionSchedYield = "sched_yield"
|
||||
|
||||
// schedYield is the WASI function named functionSchedYield which temporarily
|
||||
// yields execution of the calling thread.
|
||||
//
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
|
||||
var schedYield = stubFunction() // stubbed for GrainLang per #271.
|
||||
16
wasi_snapshot_preview1/sched_test.go
Normal file
16
wasi_snapshot_preview1/sched_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
// Test_schedYield only tests it is stubbed for GrainLang per #271
|
||||
func Test_schedYield(t *testing.T) {
|
||||
log := requireErrnoNosys(t, functionSchedYield)
|
||||
require.Equal(t, `
|
||||
--> wasi_snapshot_preview1.sched_yield()
|
||||
<-- ENOSYS
|
||||
`, log)
|
||||
}
|
||||
25
wasi_snapshot_preview1/sock.go
Normal file
25
wasi_snapshot_preview1/sock.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
const (
|
||||
functionSockRecv = "sock_recv"
|
||||
functionSockSend = "sock_send"
|
||||
functionSockShutdown = "sock_shutdown"
|
||||
)
|
||||
|
||||
// sockRecv is the WASI function named functionSockRecv which receives a
|
||||
// message from a socket.
|
||||
//
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_recvfd-fd-ri_data-iovec_array-ri_flags-riflags---errno-size-roflags
|
||||
var sockRecv = stubFunction(i32, i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// sockSend is the WASI function named functionSockSend which sends a message
|
||||
// on a socket.
|
||||
//
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_sendfd-fd-si_data-ciovec_array-si_flags-siflags---errno-size
|
||||
var sockSend = stubFunction(i32, i32, i32, i32, i32) // stubbed for GrainLang per #271.
|
||||
|
||||
// sockShutdown is the WASI function named functionSockShutdown which shuts
|
||||
// down socket send and receive channels.
|
||||
//
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno
|
||||
var sockShutdown = stubFunction(i32, i32) // stubbed for GrainLang per #271.
|
||||
34
wasi_snapshot_preview1/sock_test.go
Normal file
34
wasi_snapshot_preview1/sock_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
// Test_sockRecv only tests it is stubbed for GrainLang per #271
|
||||
func Test_sockRecv(t *testing.T) {
|
||||
log := requireErrnoNosys(t, functionSockRecv, 0, 0, 0, 0, 0, 0)
|
||||
require.Equal(t, `
|
||||
--> wasi_snapshot_preview1.sock_recv(fd=0,ri_data=0,ri_data_count=0,ri_flags=0,result.ro_datalen=0,result.ro_flags=0)
|
||||
<-- ENOSYS
|
||||
`, log)
|
||||
}
|
||||
|
||||
// Test_sockSend only tests it is stubbed for GrainLang per #271
|
||||
func Test_sockSend(t *testing.T) {
|
||||
log := requireErrnoNosys(t, functionSockSend, 0, 0, 0, 0, 0)
|
||||
require.Equal(t, `
|
||||
--> wasi_snapshot_preview1.sock_send(fd=0,si_data=0,si_data_count=0,si_flags=0,result.so_datalen=0)
|
||||
<-- ENOSYS
|
||||
`, log)
|
||||
}
|
||||
|
||||
// Test_sockShutdown only tests it is stubbed for GrainLang per #271
|
||||
func Test_sockShutdown(t *testing.T) {
|
||||
log := requireErrnoNosys(t, functionSockShutdown, 0, 0)
|
||||
require.Equal(t, `
|
||||
--> wasi_snapshot_preview1.sock_shutdown(fd=0,how=0)
|
||||
<-- ENOSYS
|
||||
`, log)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,11 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
var testMem = &wasm.MemoryInstance{
|
||||
Min: 1,
|
||||
Buffer: []byte{
|
||||
var testMem = []byte{
|
||||
0, // environBuf is after this
|
||||
'a', '=', 'b', 0, // null terminated "a=b",
|
||||
'b', '=', 'c', 'd', 0, // null terminated "b=cd"
|
||||
@@ -18,38 +15,43 @@ var testMem = &wasm.MemoryInstance{
|
||||
1, 0, 0, 0, // little endian-encoded offset of "a=b"
|
||||
5, 0, 0, 0, // little endian-encoded offset of "b=cd"
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
func Test_Benchmark_EnvironGet(t *testing.T) {
|
||||
sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
|
||||
require.NoError(t, err)
|
||||
mod, r, log := requireModule(t, wazero.NewModuleConfig().
|
||||
WithEnv("a", "b").WithEnv("b", "cd"))
|
||||
defer r.Close(testCtx)
|
||||
|
||||
mod := newModule(make([]byte, 20), sysCtx)
|
||||
environGet := (&wasi{}).EnvironGet
|
||||
// Invoke environGet and check the memory side effects.
|
||||
requireErrno(t, ErrnoSuccess, mod, functionEnvironGet, uint64(11), uint64(1))
|
||||
require.Equal(t, `
|
||||
==> wasi_snapshot_preview1.environ_get(environ=11,environ_buf=1)
|
||||
<== ESUCCESS
|
||||
`, "\n"+log.String())
|
||||
|
||||
require.Equal(t, ErrnoSuccess, environGet(testCtx, mod, 11, 1))
|
||||
require.Equal(t, mod.Memory(), testMem)
|
||||
mem, ok := mod.Memory().Read(testCtx, 0, uint32(len(testMem)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, testMem, mem)
|
||||
}
|
||||
|
||||
func Benchmark_EnvironGet(b *testing.B) {
|
||||
sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
compiled, err := r.NewModuleBuilder(b.Name()).
|
||||
ExportMemoryWithMax("memory", 1, 1).
|
||||
Compile(testCtx, wazero.NewCompileConfig())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
mod := newModule([]byte{
|
||||
0, // environBuf is after this
|
||||
'a', '=', 'b', 0, // null terminated "a=b",
|
||||
'b', '=', 'c', 'd', 0, // null terminated "b=cd"
|
||||
0, // environ is after this
|
||||
1, 0, 0, 0, // little endian-encoded offset of "a=b"
|
||||
5, 0, 0, 0, // little endian-encoded offset of "b=cd"
|
||||
0,
|
||||
}, sysCtx)
|
||||
mod, err := r.InstantiateModule(testCtx, compiled, wazero.NewModuleConfig().
|
||||
WithEnv("a", "bc").WithEnv("b", "cd"))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer r.Close(testCtx)
|
||||
|
||||
environGet := (&wasi{}).EnvironGet
|
||||
b.Run("EnvironGet", func(b *testing.B) {
|
||||
b.Run("environGet", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if environGet(testCtx, mod, 0, 4) != ErrnoSuccess {
|
||||
b.Fatal()
|
||||
@@ -57,9 +59,3 @@ func Benchmark_EnvironGet(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newModule(buf []byte, sys *sys.Context) *wasm.CallContext {
|
||||
return wasm.NewCallContext(nil, &wasm.ModuleInstance{
|
||||
Memory: &wasm.MemoryInstance{Min: 1, Buffer: buf},
|
||||
}, sys)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user