Makes CacheNumInUint64 lazy and stops crashing in assemblyscript (#712)

* Makes CacheNumInUint64 lazy and stops crashing in assemblyscript

This makes CacheNumInUint64 lazy so that all tests for function types
don't need to handle it. This also changes the assemblyscript special
functions so they don't crash when attempting to log. Finally, this
refactors `wasm.Func` so that it can enclose the parameter names as it
is more sensible than defining them elsewhere.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-07-22 16:01:20 +08:00
committed by GitHub
parent 4c71c1f33b
commit 866fac2e96
36 changed files with 1179 additions and 967 deletions

View File

@@ -38,6 +38,12 @@ import (
"github.com/tetratelabs/wazero/sys"
)
const (
functionAbort = "abort"
functionTrace = "trace"
functionSeed = "seed"
)
// Instantiate instantiates the "env" module used by AssemblyScript into the
// runtime default namespace.
//
@@ -77,65 +83,33 @@ type FunctionExporter interface {
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
func NewFunctionExporter() FunctionExporter {
return &functionExporter{traceMode: traceDisabled}
return &functionExporter{abortFn: abortMessageEnabled, traceFn: traceDisabled}
}
type traceMode int
const (
traceDisabled traceMode = 0
traceStdout traceMode = 1
traceStderr traceMode = 2
)
type functionExporter struct {
abortMessageDisabled bool
traceMode traceMode
abortFn, traceFn *wasm.Func
}
// WithAbortMessageDisabled implements FunctionExporter.WithAbortMessageDisabled
func (e *functionExporter) WithAbortMessageDisabled() FunctionExporter {
ret := *e // copy
ret.abortMessageDisabled = true
return &ret
return &functionExporter{abortFn: abortMessageDisabled, traceFn: e.traceFn}
}
// WithTraceToStdout implements FunctionExporter.WithTraceToStdout
func (e *functionExporter) WithTraceToStdout() FunctionExporter {
ret := *e // copy
ret.traceMode = traceStdout
return &ret
return &functionExporter{abortFn: e.abortFn, traceFn: traceStdout}
}
// WithTraceToStderr implements FunctionExporter.WithTraceToStderr
func (e *functionExporter) WithTraceToStderr() FunctionExporter {
ret := *e // copy
ret.traceMode = traceStderr
return &ret
return &functionExporter{abortFn: e.abortFn, traceFn: traceStderr}
}
// ExportFunctions implements FunctionExporter.ExportFunctions
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
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", traceFn, "~lib/builtins/trace",
"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4")
builder.ExportFunction("seed", seed, "~lib/builtins/seed")
builder.ExportFunction(functionAbort, e.abortFn)
builder.ExportFunction(functionTrace, e.traceFn)
builder.ExportFunction(functionSeed, seed)
}
// abort is called on unrecoverable errors. This is typically present in Wasm
@@ -150,18 +124,26 @@ func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
// (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
type fnAbort func(
ctx context.Context, mod api.Module, message, fileName, lineNumber, columnNumber uint32,
var abortMessageEnabled = wasm.NewGoFunc(
"abort", "~lib/builtins/abort",
[]string{"message", "fileName", "lineNumber", "columnNumber"},
abortWithMessage,
)
var abortMessageDisabled = abortMessageEnabled.WithGoFunc(abort)
// 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)
mem := mod.Memory()
// Don't panic if there was a problem reading the message
if msg, msgOk := readAssemblyScriptString(ctx, mem, message); msgOk {
if fn, fnOk := readAssemblyScriptString(ctx, mem, fileName); fnOk {
_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
}
}
abort(ctx, mod, message, fileName, lineNumber, columnNumber)
}
@@ -180,25 +162,25 @@ func abort(
panic(sys.NewExitError(mod.Name(), exitCode))
}
// 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}},
}
// traceDisabled ignores the input.
var traceDisabled = traceStdout.WithWasm([]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())
}
// traceStdout implements trace to the configured Stdout.
var traceStdout = wasm.NewGoFunc(functionTrace, "~lib/builtins/trace",
[]string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"},
func(
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(
// traceStderr implements trace to the configured Stderr.
var traceStderr = traceStdout.WithGoFunc(func(
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!')
@@ -212,9 +194,13 @@ func traceTo(
ctx context.Context, mod api.Module, message uint32, nArgs uint32, arg0, arg1, arg2, arg3, arg4 float64,
writer io.Writer,
) {
msg, ok := readAssemblyScriptString(ctx, mod.Memory(), message)
if !ok {
return // don't panic if unable to trace
}
var ret strings.Builder
ret.WriteString("trace: ")
ret.WriteString(requireAssemblyScriptString(ctx, mod, "message", message))
ret.WriteString(msg)
if nArgs >= 1 {
ret.WriteString(" ")
ret.WriteString(formatFloat(arg0))
@@ -236,9 +222,7 @@ func traceTo(
ret.WriteString(formatFloat(arg4))
}
ret.WriteByte('\n')
if _, err := writer.Write([]byte(ret.String())); err != nil {
panic(err)
}
_, _ = writer.Write([]byte(ret.String())) // don't crash if trace logging fails
}
func formatFloat(f float64) string {
@@ -253,30 +237,29 @@ 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 seed(mod api.Module) float64 {
randSource := mod.(*wasm.CallContext).Sys.RandSource()
v, err := ieee754.DecodeFloat64(randSource)
if err != nil {
panic(fmt.Errorf("error reading random seed: %w", err))
}
return v
}
var seed = wasm.NewGoFunc(functionSeed, "~lib/builtins/seed", []string{},
func(mod api.Module) float64 {
randSource := mod.(*wasm.CallContext).Sys.RandSource()
v, err := ieee754.DecodeFloat64(randSource)
if err != nil {
panic(fmt.Errorf("error reading random seed: %w", err))
}
return v
},
)
// requireAssemblyScriptString reads a UTF-16 string created by AssemblyScript.
func requireAssemblyScriptString(ctx context.Context, mod api.Module, fieldName string, offset uint32) string {
// readAssemblyScriptString reads a UTF-16 string created by AssemblyScript.
func readAssemblyScriptString(ctx context.Context, mem api.Memory, offset uint32) (string, bool) {
// Length is four bytes before pointer.
byteCount, ok := mod.Memory().ReadUint32Le(ctx, offset-4)
byteCount, ok := mem.ReadUint32Le(ctx, offset-4)
if !ok || byteCount%2 != 0 {
return "", false
}
buf, ok := mem.Read(ctx, offset, byteCount)
if !ok {
panic(fmt.Errorf("out of memory reading %s", fieldName))
return "", false
}
if byteCount%2 != 0 {
panic(fmt.Errorf("invalid UTF-16 reading %s", fieldName))
}
buf, ok := mod.Memory().Read(ctx, offset, byteCount)
if !ok {
panic(fmt.Errorf("out of memory reading %s", fieldName))
}
return decodeUTF16(buf)
return decodeUTF16(buf), true
}
func decodeUTF16(b []byte) string {

View File

@@ -15,7 +15,8 @@ import (
"github.com/tetratelabs/wazero/api"
. "github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/watzero"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
@@ -23,25 +24,18 @@ import (
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: "",
},
@@ -51,15 +45,19 @@ func TestAbort(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
defer stderr.Reset()
var stderr bytes.Buffer
mod, r, log := requireModule(t, tc.exporter, wazero.NewModuleConfig().WithStderr(&stderr))
defer r.Close(testCtx)
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
err := require.CapturePanic(func() {
tc.abortFn(testCtx, mod, messageOff, filenameOff, 1, 2)
})
_, err := mod.ExportedFunction(functionAbort).
Call(testCtx, uint64(messageOff), uint64(filenameOff), uint64(1), uint64(2))
require.Error(t, err)
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
require.Equal(t, `
==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
`, "\n"+log.String())
require.Equal(t, tc.expected, stderr.String())
})
@@ -68,7 +66,7 @@ func TestAbort(t *testing.T) {
func TestAbort_Error(t *testing.T) {
var stderr bytes.Buffer
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(&stderr))
mod, r, log := requireModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithStderr(&stderr))
defer r.Close(testCtx)
tests := []struct {
@@ -81,16 +79,16 @@ func TestAbort_Error(t *testing.T) {
name: "bad message",
messageUTF16: encodeUTF16("message")[:5],
fileNameUTF16: encodeUTF16("filename"),
expectedLog: `==> env.~lib/builtins/abort(message=4,fileName=13,lineNumber=1,columnNumber=2)
<== ()
expectedLog: `
==> env.~lib/builtins/abort(message=4,fileName=13,lineNumber=1,columnNumber=2)
`,
},
{
name: "bad filename",
messageUTF16: encodeUTF16("message"),
fileNameUTF16: encodeUTF16("filename")[:5],
expectedLog: `==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
<== ()
expectedLog: `
==> env.~lib/builtins/abort(message=4,fileName=22,lineNumber=1,columnNumber=2)
`,
},
}
@@ -99,25 +97,35 @@ func TestAbort_Error(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
defer log.Reset()
defer stderr.Reset()
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
// Since abort panics, any opcodes afterwards cannot be reached.
_ = require.CapturePanic(func() {
abortWithMessage(testCtx, mod, messageOff, filenameOff, 1, 2)
})
_, err := mod.ExportedFunction(functionAbort).
Call(testCtx, uint64(messageOff), uint64(filenameOff), uint64(1), uint64(2))
require.Error(t, err)
require.Equal(t, uint32(255), err.(*sys.ExitError).ExitCode())
require.Equal(t, tc.expectedLog, "\n"+log.String())
require.Equal(t, "", stderr.String()) // nothing output if strings fail to read.
})
}
}
func TestSeed(t *testing.T) {
mod, r := requireModule(t, wazero.NewModuleConfig().
WithRandSource(bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7})))
b := []byte{0, 1, 2, 3, 4, 5, 6, 7}
mod, r, log := requireModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithRandSource(bytes.NewReader(b)))
defer r.Close(testCtx)
require.Equal(t, 7.949928895127363e-275, seed(mod))
ret, err := mod.ExportedFunction(functionSeed).Call(testCtx)
require.NoError(t, err)
require.Equal(t, `
==> env.~lib/builtins/seed()
<== (7.949928895127363e-275)
`, "\n"+log.String())
require.Equal(t, b, u64.LeBytes(ret[0]))
}
func TestSeed_error(t *testing.T) {
@@ -127,14 +135,18 @@ func TestSeed_error(t *testing.T) {
expectedErr string
}{
{
name: "not 8 bytes",
source: bytes.NewReader([]byte{0, 1}),
expectedErr: `error reading random seed: unexpected EOF`,
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`,
},
{
name: "error reading",
source: iotest.ErrReader(errors.New("ice cream")),
expectedErr: `error reading random seed: ice cream`,
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`,
},
}
@@ -142,11 +154,14 @@ func TestSeed_error(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
mod, r := requireModule(t, wazero.NewModuleConfig().WithRandSource(tc.source))
mod, r, log := requireModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithRandSource(tc.source))
defer r.Close(testCtx)
err := require.CapturePanic(func() { seed(mod) })
_, err := mod.ExportedFunction(functionSeed).Call(testCtx)
require.EqualError(t, err, tc.expectedErr)
require.Equal(t, `
==> env.~lib/builtins/seed()
`, "\n"+log.String())
})
}
}
@@ -154,13 +169,17 @@ func TestSeed_error(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)
noArgsLog := `
==> env.~lib/builtins/trace(message=4,nArgs=0,arg0=0,arg1=0,arg2=0,arg3=0,arg4=0)
<== ()
`
tests := []struct {
name string
exporter FunctionExporter
params []uint64
message []byte
outErr bool
expected, expectedLog string
}{
{
@@ -190,7 +209,8 @@ func TestFunctionExporter_Trace(t *testing.T) {
exporter: NewFunctionExporter().WithTraceToStdout(),
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
expected: "trace: hello 1\n",
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=1,arg0=1,arg1=0,arg2=0,arg3=0,arg4=0)
expectedLog: `
==> env.~lib/builtins/trace(message=4,nArgs=1,arg0=1,arg1=0,arg2=0,arg3=0,arg4=0)
<== ()
`,
},
@@ -199,7 +219,8 @@ func TestFunctionExporter_Trace(t *testing.T) {
exporter: NewFunctionExporter().WithTraceToStdout(),
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
expected: "trace: hello 1,2\n",
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=2,arg0=1,arg1=2,arg2=0,arg3=0,arg4=0)
expectedLog: `
==> env.~lib/builtins/trace(message=4,nArgs=2,arg0=1,arg1=2,arg2=0,arg3=0,arg4=0)
<== ()
`,
},
@@ -216,81 +237,24 @@ func TestFunctionExporter_Trace(t *testing.T) {
api.EncodeF64(5),
},
expected: "trace: hello 1,2,3.3,4.4,5\n",
expectedLog: `==> env.~lib/builtins/trace(message=4,nArgs=5,arg0=1,arg1=2,arg2=3.3,arg3=4.4,arg4=5)
expectedLog: `
==> env.~lib/builtins/trace(message=4,nArgs=5,arg0=1,arg1=2,arg2=3.3,arg3=4.4,arg4=5)
<== ()
`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
var stderr, functionLog bytes.Buffer
// Set context to one that has an experimental listener
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, NewLoggingListenerFactory(&functionLog))
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)
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())
require.NoError(t, err)
config := wazero.NewModuleConfig()
if strings.Contains("ToStderr", tc.name) {
config = config.WithStderr(&stderr)
} else {
config = config.WithStdout(&stderr)
}
mod, err := r.InstantiateModule(ctx, code, config)
require.NoError(t, err)
message := encodeUTF16("hello")
ok := mod.Memory().WriteUint32Le(ctx, 0, uint32(len(message)))
require.True(t, ok)
ok = mod.Memory().Write(ctx, uint32(4), message)
require.True(t, ok)
_, err = mod.ExportedFunction("trace").Call(ctx, tc.params...)
require.NoError(t, err)
require.Equal(t, tc.expected, stderr.String())
require.Equal(t, tc.expectedLog, functionLog.String())
})
}
}
func TestTrace_error(t *testing.T) {
tests := []struct {
name string
message []byte
stderr io.Writer
expectedErr string
}{
{
name: "not 8 bytes",
exporter: NewFunctionExporter().WithTraceToStderr(),
message: encodeUTF16("hello")[:5],
stderr: &bytes.Buffer{},
expectedErr: `invalid UTF-16 reading message`,
params: noArgs,
expectedLog: noArgsLog,
},
{
name: "error writing",
message: encodeUTF16("hello"),
stderr: &errWriter{err: errors.New("ice cream")},
expectedErr: `ice cream`,
exporter: NewFunctionExporter().WithTraceToStderr(),
outErr: true,
params: noArgs,
expectedLog: noArgsLog,
},
}
@@ -298,32 +262,45 @@ func TestTrace_error(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(tc.stderr))
var out bytes.Buffer
config := wazero.NewModuleConfig()
if strings.Contains("ToStderr", tc.name) {
config = config.WithStderr(&out)
} else {
config = config.WithStdout(&out)
}
if tc.outErr {
config = config.WithStderr(&errWriter{err: errors.New("ice cream")})
}
mod, r, log := requireModule(t, tc.exporter, config)
defer r.Close(testCtx)
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
message := tc.message
if message == nil {
message = encodeUTF16("hello")
}
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(message)))
require.True(t, ok)
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
ok = mod.Memory().Write(testCtx, uint32(4), message)
require.True(t, ok)
err := require.CapturePanic(func() {
traceToStderr(testCtx, mod, 4, 0, 0, 0, 0, 0, 0)
})
require.EqualError(t, err, tc.expectedErr)
_, err := mod.ExportedFunction(functionTrace).Call(testCtx, tc.params...)
require.NoError(t, err)
require.Equal(t, tc.expected, out.String())
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
func Test_requireAssemblyScriptString(t *testing.T) {
var stderr bytes.Buffer
mod, r := requireModule(t, wazero.NewModuleConfig().WithStderr(&stderr))
defer r.Close(testCtx)
func Test_readAssemblyScriptString(t *testing.T) {
tests := []struct {
name string
memory func(context.Context, api.Memory)
offset int
expected, expectedErr string
name string
memory func(context.Context, api.Memory)
offset int
expected string
expectedOk bool
}{
{
name: "success",
@@ -332,8 +309,9 @@ func Test_requireAssemblyScriptString(t *testing.T) {
b := encodeUTF16("hello")
memory.Write(testCtx, 4, b)
},
offset: 4,
expected: "hello",
offset: 4,
expected: "hello",
expectedOk: true,
},
{
name: "can't read size",
@@ -341,8 +319,8 @@ func Test_requireAssemblyScriptString(t *testing.T) {
b := encodeUTF16("hello")
memory.Write(testCtx, 0, b)
},
offset: 0, // will attempt to read size from offset -4
expectedErr: "out of memory reading message",
offset: 0, // will attempt to read size from offset -4
expectedOk: false,
},
{
name: "odd size",
@@ -351,8 +329,8 @@ func Test_requireAssemblyScriptString(t *testing.T) {
b := encodeUTF16("hello")
memory.Write(testCtx, 4, b)
},
offset: 4,
expectedErr: "invalid UTF-16 reading message",
offset: 4,
expectedOk: false,
},
{
name: "can't read string",
@@ -361,8 +339,8 @@ func Test_requireAssemblyScriptString(t *testing.T) {
b := encodeUTF16("hello")
memory.Write(testCtx, 4, b)
},
offset: 4,
expectedErr: "out of memory reading message",
offset: 4,
expectedOk: false,
},
}
@@ -370,17 +348,12 @@ func Test_requireAssemblyScriptString(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
tc.memory(testCtx, mod.Memory())
mem := wasm.NewMemoryInstance(&wasm.Memory{Min: 1, Cap: 1, Max: 1})
tc.memory(testCtx, mem)
if tc.expectedErr != "" {
err := require.CapturePanic(func() {
_ = requireAssemblyScriptString(testCtx, mod, "message", uint32(tc.offset))
})
require.EqualError(t, err, tc.expectedErr)
} else {
s := requireAssemblyScriptString(testCtx, mod, "message", uint32(tc.offset))
require.Equal(t, tc.expected, s)
}
s, ok := readAssemblyScriptString(testCtx, mem, uint32(tc.offset))
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, s)
})
}
}
@@ -421,15 +394,21 @@ func (w *errWriter) Write([]byte) (int, error) {
return 0, w.err
}
func requireModule(t *testing.T, config wazero.ModuleConfig) (api.Module, api.Closer) {
func requireModule(t *testing.T, fns FunctionExporter, config wazero.ModuleConfig) (api.Module, api.Closer, *bytes.Buffer) {
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())
compiled, err := r.NewModuleBuilder(t.Name()).
ExportMemoryWithMax("memory", 1, 1).
Compile(testCtx, wazero.NewCompileConfig())
builder := r.NewModuleBuilder("env").
ExportMemoryWithMax("memory", 1, 1)
fns.ExportFunctions(builder)
compiled, err := builder.Compile(ctx, wazero.NewCompileConfig())
require.NoError(t, err)
mod, err := r.InstantiateModule(testCtx, compiled, config)
mod, err := r.InstantiateModule(ctx, compiled, config)
require.NoError(t, err)
return mod, r
return mod, r, &log
}

View File

@@ -324,6 +324,8 @@ func (b *moduleBuilder) Compile(ctx context.Context, cConfig CompileConfig) (Com
module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.nameToMemory, b.nameToGlobal, b.r.enabledFeatures)
if err != nil {
return nil, err
} else if err = module.Validate(b.r.enabledFeatures); err != nil {
return nil, err
}
c := &compiledModule{module: module, compiledEngine: b.r.store.Engine}

View File

@@ -51,7 +51,7 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{GoFunc: &fnUint32_uint32}},
@@ -70,7 +70,7 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{GoFunc: &fnUint32_uint32}},
@@ -90,7 +90,7 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{GoFunc: &fnUint64_uint32}},
@@ -110,8 +110,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0, 1},
CodeSection: []*wasm.Code{{GoFunc: &fnUint32_uint32}, {GoFunc: &fnUint64_uint32}},
@@ -134,8 +134,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0, 1},
CodeSection: []*wasm.Code{{GoFunc: &fnUint32_uint32}, {GoFunc: &fnUint64_uint32}},
@@ -159,8 +159,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
},
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
{Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
},
FunctionSection: []wasm.Index{0, 1},
CodeSection: []*wasm.Code{{GoFunc: &fnUint32_uint32}, {GoFunc: &fnUint64_uint32}},
@@ -448,6 +448,9 @@ func TestNewModuleBuilder_Instantiate_Errors(t *testing.T) {
// requireHostModuleEquals is redefined from internal/wasm/host_test.go to avoid an import cycle extracting it.
func requireHostModuleEquals(t *testing.T, expected, actual *wasm.Module) {
// `require.Equal(t, expected, actual)` fails reflect pointers don't match, so brute compare:
for _, tp := range expected.TypeSection {
tp.CacheNumInUint64()
}
require.Equal(t, expected.TypeSection, actual.TypeSection)
require.Equal(t, expected.ImportSection, actual.ImportSection)
require.Equal(t, expected.FunctionSection, actual.FunctionSection)

View File

@@ -51,8 +51,7 @@ type functionExporter struct{}
// ExportFunctions implements FunctionExporter.ExportFunctions
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
builder.ExportFunction("emscripten_notify_memory_growth", emscriptenNotifyMemoryGrowth,
"emscripten_notify_memory_growth", "memory_index")
builder.ExportFunction(notifyMemoryGrowth.Name, notifyMemoryGrowth)
}
// emscriptenNotifyMemoryGrowth is called when wasm is compiled with
@@ -70,7 +69,12 @@ func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
//
// 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
var emscriptenNotifyMemoryGrowth = &wasm.Func{
Type: &wasm.FunctionType{Params: []wasm.ValueType{wasm.ValueTypeI32}},
Code: &wasm.Code{Body: []byte{wasm.OpcodeEnd}},
const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth"
var notifyMemoryGrowth = &wasm.Func{
ExportNames: []string{functionNotifyMemoryGrowth},
Name: functionNotifyMemoryGrowth,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ParamNames: []string{"memory_index"},
Code: &wasm.Code{Body: []byte{wasm.OpcodeEnd}},
}

View File

@@ -21,12 +21,12 @@ func newExample() *wasm.Module {
f32, i32, i64 := wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64
return &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{ParamNumInUint64: 0, ResultNumInUint64: 0},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}},
{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
},
ImportSection: []*wasm.Import{
{

View File

@@ -30,14 +30,8 @@ func TestDecodeModule(t *testing.T) {
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 2,
ResultNumInUint64: 1,
},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 4,
ResultNumInUint64: 1,
},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
},
},
@@ -45,14 +39,8 @@ func TestDecodeModule(t *testing.T) {
name: "type and import section",
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 2,
ResultNumInUint64: 1,
},
{Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32},
ParamNumInUint64: 2,
ResultNumInUint64: 1,
},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}},
},
ImportSection: []*wasm.Import{
{

View File

@@ -96,7 +96,5 @@ func decodeFunctionType(enabledFeatures wasm.Features, r *bytes.Reader) (*wasm.F
Params: paramTypes,
Results: resultTypes,
}
ret.CacheNumInUint64()
return ret, nil
}

View File

@@ -23,52 +23,52 @@ func TestFunctionType(t *testing.T) {
},
{
name: "one param no result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32}},
expected: []byte{0x60, 1, i32, 0},
},
{
name: "no param one result",
input: &wasm.FunctionType{Results: []wasm.ValueType{i32}, ResultNumInUint64: 1},
input: &wasm.FunctionType{Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 0, 1, i32},
},
{
name: "one param one result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 1, i64, 1, i32},
},
{
name: "two params no result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, ParamNumInUint64: 2},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 2, i32, i64, 0},
},
{
name: "two param one result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 2, i32, i64, 1, i32},
},
{
name: "no param two results",
input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}, ResultNumInUint64: 2},
input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 0, 2, i32, i64},
},
{
name: "one param two results",
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}, ParamNumInUint64: 1, ResultNumInUint64: 2},
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 1, i64, 2, i32, i64},
},
{
name: "two param two results",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 2, i32, i64, 2, i32, i64},
},
{
name: "two param two results with funcrefs",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, funcRef}, Results: []wasm.ValueType{funcRef, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, funcRef}, Results: []wasm.ValueType{funcRef, i64}},
expected: []byte{0x60, 2, i32, funcRef, 2, funcRef, i64},
},
{
name: "two param two results with externrefs",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, externRef}, Results: []wasm.ValueType{externRef, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, externRef}, Results: []wasm.ValueType{externRef, i64}},
expected: []byte{0x60, 2, i32, externRef, 2, externRef, i64},
},
}

View File

@@ -148,17 +148,6 @@ 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) (fk FunctionKind, ft *FunctionType, err error) {
p := fn.Type()
@@ -181,8 +170,6 @@ func getFunctionType(fn *reflect.Value) (fk FunctionKind, ft *FunctionType, err
rCount := p.NumOut()
ft = &FunctionType{Params: make([]ValueType, p.NumIn()-pOffset), Results: make([]ValueType, rCount)}
ft.CacheNumInUint64()
for i := 0; i < len(ft.Params); i++ {
pI := p.In(i + pOffset)
if t, ok := getTypeOf(pI.Kind()); ok {

View File

@@ -49,7 +49,7 @@ func TestGetFunctionType(t *testing.T) {
name: "all supported params and i32 result",
inputFunc: func(uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoNoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
{
name: "all supported params and all supported results",
@@ -58,28 +58,27 @@ func TestGetFunctionType(t *testing.T) {
},
expectedKind: FunctionKindGoNoContext,
expectedType: &FunctionType{
Params: []ValueType{i32, i64, f32, f64, externref},
Results: []ValueType{i32, i64, f32, f64, externref},
ParamNumInUint64: 5, ResultNumInUint64: 5,
Params: []ValueType{i32, i64, f32, f64, externref},
Results: []ValueType{i32, i64, f32, f64, externref},
},
},
{
name: "all supported params and i32 result - wasm.Module",
inputFunc: func(api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoModule,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - context.Context",
inputFunc: func(context.Context, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - context.Context and api.Module",
inputFunc: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoContextModule,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
}
for _, tt := range tests {

View File

@@ -10,15 +10,59 @@ import (
)
// Func is a function with an inlined type, typically used for NewHostModule.
// Any corresponding FunctionType will be reused or added to the Module.
type Func struct {
// Type is the equivalent function in the SectionIDType.
// This will resolve to an existing or new element.
Type *FunctionType
// ExportNames is equivalent to the same method on api.FunctionDefinition.
ExportNames []string
// Name is equivalent to the same method on api.FunctionDefinition.
Name string
// ParamTypes is equivalent to the same method on api.FunctionDefinition.
ParamTypes []ValueType
// ParamNames is equivalent to the same method on api.FunctionDefinition.
ParamNames []string
// ResultTypes is equivalent to the same method on api.FunctionDefinition.
ResultTypes []ValueType
// Code is the equivalent function in the SectionIDCode.
Code *Code
}
// NewGoFunc returns a Func for the given parameters or panics.
func NewGoFunc(exportName string, name string, paramNames []string, fn interface{}) *Func {
fnV := reflect.ValueOf(fn)
_, ft, err := getFunctionType(&fnV)
if err != nil {
panic(err)
}
return &Func{
ExportNames: []string{exportName},
Name: name,
ParamTypes: ft.Params,
ResultTypes: ft.Results,
ParamNames: paramNames,
Code: &Code{GoFunc: &fnV},
}
}
// WithGoFunc returns a copy of the function, replacing its Code.GoFunc.
func (f *Func) WithGoFunc(fn interface{}) *Func {
ret := *f
fnV := reflect.ValueOf(fn)
ret.Code = &Code{GoFunc: &fnV}
return &ret
}
// WithWasm returns a copy of the function, replacing its Code.Body.
func (f *Func) WithWasm(body []byte) *Func {
ret := *f
ret.Code = &Code{Body: body}
return &ret
}
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
func NewHostModule(
moduleName string,
@@ -90,67 +134,73 @@ func addFuncs(
funcToNames map[string][]string,
enabledFeatures Features,
) (err error) {
funcCount := uint32(len(nameToGoFunc))
funcNames := make([]string, 0, funcCount)
if m.NameSection == nil {
m.NameSection = &NameSection{}
}
moduleName := m.NameSection.ModuleName
nameToFunc := make(map[string]*Func, len(nameToGoFunc))
funcNames := make([]string, len(nameToFunc))
for k, v := range nameToGoFunc {
if hf, ok := v.(*Func); !ok {
fn := reflect.ValueOf(v)
_, ft, ftErr := getFunctionType(&fn)
if ftErr != nil {
return fmt.Errorf("func[%s.%s] %w", moduleName, k, ftErr)
}
hf = &Func{
ExportNames: []string{k},
Name: k,
ParamTypes: ft.Params,
ResultTypes: ft.Results,
Code: &Code{GoFunc: &fn},
}
if names := funcToNames[k]; names != nil {
namesLen := len(names)
if namesLen > 1 && namesLen-1 != len(ft.Params) {
return fmt.Errorf("func[%s.%s] has %d params, but %d param names", moduleName, k, namesLen-1, len(ft.Params))
}
hf.Name = names[0]
hf.ParamNames = names[1:]
}
nameToFunc[k] = hf
funcNames = append(funcNames, k)
} else {
nameToFunc[hf.Name] = hf
funcNames = append(funcNames, hf.Name)
}
}
// Sort names for consistent iteration
sort.Strings(funcNames)
funcCount := uint32(len(nameToFunc))
m.NameSection.FunctionNames = make([]*NameAssoc, 0, funcCount)
m.FunctionSection = make([]Index, 0, funcCount)
m.CodeSection = make([]*Code, 0, funcCount)
m.FunctionDefinitionSection = make([]*FunctionDefinition, 0, funcCount)
// Sort names for consistent iteration
for k := range nameToGoFunc {
funcNames = append(funcNames, k)
}
sort.Strings(funcNames)
for idx := Index(0); idx < funcCount; idx++ {
exportName := funcNames[idx]
debugName := wasmdebug.FuncName(moduleName, exportName, idx)
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)
}
m.CodeSection = append(m.CodeSection, &Code{GoFunc: &fn})
idx := Index(0)
for _, name := range funcNames {
hf := nameToFunc[name]
debugName := wasmdebug.FuncName(moduleName, name, idx)
typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures)
if typeErr != nil {
return fmt.Errorf("func[%s] %v", debugName, typeErr)
}
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))
m.FunctionSection = append(m.FunctionSection, typeIdx)
m.CodeSection = append(m.CodeSection, hf.Code)
for _, export := range hf.ExportNames {
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: export, Index: idx})
}
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.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]})
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: hf.Name})
if len(hf.ParamNames) > 0 {
localNames := &NameMapAssoc{Index: idx}
for i, n := range names[1:] {
for i, n := range hf.ParamNames {
localNames.NameMap = append(localNames.NameMap, &NameAssoc{Index: Index(i), Name: n})
}
m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames)
} else {
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: exportName})
}
idx++
}
return nil
}
@@ -199,14 +249,21 @@ func addGlobals(m *Module, globals map[string]*Global) error {
return nil
}
func (m *Module) maybeAddType(ft *FunctionType) Index {
func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures Features) (Index, error) {
if len(results) > 1 {
// Guard >1.0 feature multi-value
if err := enabledFeatures.Require(FeatureMultiValue); err != nil {
return 0, fmt.Errorf("multiple result types invalid as %v", err)
}
}
for i, t := range m.TypeSection {
if t.EqualsSignature(ft.Params, ft.Results) {
return Index(i)
if t.EqualsSignature(params, results) {
return Index(i), nil
}
}
result := m.SectionElementCount(SectionIDType)
m.TypeSection = append(m.TypeSection, ft)
return result
toAdd := &FunctionType{Params: params, Results: results}
m.TypeSection = append(m.TypeSection, toAdd)
return result, nil
}

View File

@@ -30,8 +30,6 @@ func swap(x, y uint32) (uint32, uint32) {
}
func TestNewHostModule(t *testing.T) {
i32 := ValueTypeI32
a := wasiAPI{}
functionArgsSizesGet := "args_sizes_get"
fnArgsSizesGet := reflect.ValueOf(a.ArgsSizesGet)
@@ -65,8 +63,8 @@ func TestNewHostModule(t *testing.T) {
},
expected: &Module{
TypeSection: []*FunctionType{
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
{Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}},
},
FunctionSection: []Index{0, 1},
CodeSection: []*Code{{GoFunc: &fnArgsSizesGet}, {GoFunc: &fnFdWrite}},
@@ -90,7 +88,7 @@ func TestNewHostModule(t *testing.T) {
functionSwap: swap,
},
expected: &Module{
TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}},
TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}},
FunctionSection: []Index{0},
CodeSection: []*Code{{GoFunc: &fnSwap}},
ExportSection: []*Export{{Name: "swap", Type: ExternTypeFunc, Index: 0}},
@@ -151,7 +149,7 @@ func TestNewHostModule(t *testing.T) {
},
expected: &Module{
TypeSection: []*FunctionType{
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
},
FunctionSection: []Index{0},
CodeSection: []*Code{{GoFunc: &fnArgsSizesGet}},

View File

@@ -227,6 +227,10 @@ func (m *Module) TypeOfFunction(funcIdx Index) *FunctionType {
}
func (m *Module) Validate(enabledFeatures Features) error {
for _, tp := range m.TypeSection {
tp.CacheNumInUint64()
}
if err := m.validateStartSection(); err != nil {
return err
}
@@ -325,7 +329,9 @@ func (m *Module) validateFunctions(enabledFeatures Features, functions []Index,
if typeIndex >= typeCount {
return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex)
}
if m.CodeSection[idx].GoFunc != nil {
continue
}
if err = m.validateFunction(enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes); err != nil {
return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err)
}
@@ -667,17 +673,21 @@ type FunctionType struct {
}
func (f *FunctionType) CacheNumInUint64() {
for _, tp := range f.Params {
f.ParamNumInUint64++
if tp == ValueTypeV128 {
if f.ParamNumInUint64 == 0 {
for _, tp := range f.Params {
f.ParamNumInUint64++
if tp == ValueTypeV128 {
f.ParamNumInUint64++
}
}
}
for _, tp := range f.Results {
f.ResultNumInUint64++
if tp == ValueTypeV128 {
if f.ResultNumInUint64 == 0 {
for _, tp := range f.Results {
f.ResultNumInUint64++
if tp == ValueTypeV128 {
f.ResultNumInUint64++
}
}
}
}

View File

@@ -143,10 +143,6 @@ func DecodeModule(
if names.ModuleName == "" && names.FunctionNames == nil && names.LocalNames == nil {
module.NameSection = nil
}
for _, tp := range module.TypeSection {
tp.CacheNumInUint64()
}
return
}

View File

@@ -63,7 +63,7 @@ func TestDecodeModule(t *testing.T) {
input: "(module (type (func (param i32 i32) (result i32 i32))))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
},
},
},
@@ -72,7 +72,7 @@ func TestDecodeModule(t *testing.T) {
input: "(module (type (func (param i32) (param i32) (result i32) (result i32))))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
},
},
},
@@ -362,8 +362,8 @@ func TestDecodeModule(t *testing.T) {
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
v_v,
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
ImportSection: []*wasm.Import{
{
@@ -549,7 +549,7 @@ func TestDecodeModule(t *testing.T) {
input: "(module (import \"\" \"\" (func (param i32 i32) (param $v i32) (param i64) (param $t f32))))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5},
{Params: []wasm.ValueType{i32, i32, i32, i64, f32}},
},
ImportSection: []*wasm.Import{{Type: wasm.ExternTypeFunc, DescFunc: 0}},
NameSection: &wasm.NameSection{
@@ -569,8 +569,8 @@ func TestDecodeModule(t *testing.T) {
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
v_v,
{Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 9, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
ImportSection: []*wasm.Import{
{
@@ -598,7 +598,7 @@ func TestDecodeModule(t *testing.T) {
(import "foo" "bar" (func (type 0) (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
ImportSection: []*wasm.Import{{
Module: "foo", Name: "bar",
Type: wasm.ExternTypeFunc,
@@ -613,7 +613,7 @@ func TestDecodeModule(t *testing.T) {
(type $i32 (func (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
ImportSection: []*wasm.Import{{
Module: "foo", Name: "bar",
Type: wasm.ExternTypeFunc,
@@ -628,7 +628,7 @@ func TestDecodeModule(t *testing.T) {
(import "foo" "bar" (func (type $i32) (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
ImportSection: []*wasm.Import{{
Module: "foo", Name: "bar",
Type: wasm.ExternTypeFunc,
@@ -643,7 +643,7 @@ func TestDecodeModule(t *testing.T) {
(type $i32 (func (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
ImportSection: []*wasm.Import{{
Module: "foo", Name: "bar",
Type: wasm.ExternTypeFunc,
@@ -655,7 +655,7 @@ func TestDecodeModule(t *testing.T) {
name: "import func multiple abbreviated results",
input: `(module (import "misc" "swap" (func $swap (param i32 i32) (result i32 i32))))`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}},
ImportSection: []*wasm.Import{{
Module: "misc", Name: "swap",
Type: wasm.ExternTypeFunc,
@@ -856,8 +856,8 @@ func TestDecodeModule(t *testing.T) {
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
v_v,
{Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 9, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
FunctionSection: []wasm.Index{1, 2},
CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}},
@@ -881,8 +881,8 @@ func TestDecodeModule(t *testing.T) {
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
v_v,
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
FunctionSection: []wasm.Index{1, 2},
CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}},
@@ -922,7 +922,7 @@ func TestDecodeModule(t *testing.T) {
(func (type 0) (param i32))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{codeEnd},
},
@@ -934,7 +934,7 @@ func TestDecodeModule(t *testing.T) {
(type $i32 (func (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{codeEnd},
},
@@ -946,7 +946,7 @@ func TestDecodeModule(t *testing.T) {
(func (type $i32) (param i32))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{codeEnd},
},
@@ -958,7 +958,7 @@ func TestDecodeModule(t *testing.T) {
(type $i32 (func (param i32)))
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{codeEnd},
},
@@ -1101,7 +1101,7 @@ func TestDecodeModule(t *testing.T) {
name: "func param IDs",
input: "(module (func $one (param $x i32) (param $y i32) (result i32) local.get 0))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: localGet0End}},
NameSection: &wasm.NameSection{
@@ -1119,7 +1119,7 @@ func TestDecodeModule(t *testing.T) {
(func (param $l i32) (param $r i32) (result i32) local.get 0)
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}},
FunctionSection: []wasm.Index{0, 0},
CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}},
NameSection: &wasm.NameSection{
@@ -1134,7 +1134,7 @@ func TestDecodeModule(t *testing.T) {
name: "func mixed param IDs", // Verifies we can handle less param fields than Params
input: "(module (func (param i32 i32) (param $v i32) (param i64) (param $t f32)))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32, i32, i64, f32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: end}},
NameSection: &wasm.NameSection{
@@ -1148,7 +1148,7 @@ func TestDecodeModule(t *testing.T) {
name: "func multiple abbreviated results",
input: "(module (func $swap (param i32 i32) (result i32 i32) local.get 1 local.get 0))",
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}},
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0x01, wasm.OpcodeLocalGet, 0x00, wasm.OpcodeEnd}}},
NameSection: &wasm.NameSection{
@@ -1393,7 +1393,7 @@ func TestDecodeModule(t *testing.T) {
)`,
expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{

View File

@@ -9,44 +9,19 @@ import (
var (
f32, f64, i32, i64 = wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64
i32_v = &wasm.FunctionType{Params: []wasm.ValueType{i32},
ParamNumInUint64: 1,
}
v_i32 = &wasm.FunctionType{Results: []wasm.ValueType{i32},
ResultNumInUint64: 1,
}
v_i32i64 = &wasm.FunctionType{Results: []wasm.ValueType{i32, i64},
ResultNumInUint64: 2,
}
f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 1,
ResultNumInUint64: 1,
}
i64_i64 = &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64},
ParamNumInUint64: 1,
ResultNumInUint64: 1,
}
i32i64_v = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64},
ParamNumInUint64: 2,
}
i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 2,
ResultNumInUint64: 1,
}
i32i64_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 2,
ResultNumInUint64: 1,
}
i32i32i32i32_i32 = &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32},
ParamNumInUint64: 4,
ResultNumInUint64: 1,
}
i32_v = &wasm.FunctionType{Params: []wasm.ValueType{i32}}
v_i32 = &wasm.FunctionType{Results: []wasm.ValueType{i32}}
v_i32i64 = &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}}
f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}}
i64_i64 = &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}
i32i64_v = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}}
i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
i32i64_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}}
i32i32i32i32_i32 = &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}
i32i32i32i32i32i64i64i32i32_i32 = &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
Results: []wasm.ValueType{i32},
ParamNumInUint64: 9,
ResultNumInUint64: 1,
Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
Results: []wasm.ValueType{i32},
}
)
@@ -125,7 +100,7 @@ func TestTypeParser(t *testing.T) {
{
name: "mixed param abbreviation", // Verifies we can handle less param fields than param types
input: "(type (func (param i32 i32) (param i32) (param i64) (param f32)))",
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5},
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}},
},
// Below are changes to test/core/br.wast from the commit that added "multi-value" support.
@@ -134,58 +109,55 @@ func TestTypeParser(t *testing.T) {
{
name: "multi-value - v_i64f32 abbreviated",
input: "(type (func (result i64 f32)))",
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2},
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
},
{
name: "multi-value - i32i64_f32f64 abbreviated",
input: "(type (func (param i32 i64) (result f32 f64)))",
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
},
{
name: "multi-value - v_i64f32",
input: "(type (func (result i64) (result f32)))",
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2},
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
},
{
name: "multi-value - i32i64_f32f64",
input: "(type (func (param i32) (param i64) (result f32) (result f64)))",
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
},
{
name: "multi-value - i32i64_f32f64 named",
input: "(type (func (param $x i32) (param $y i64) (result f32) (result f64)))",
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2},
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
},
{
name: "multi-value - i64i64f32_f32i32 results abbreviated in groups",
input: "(type (func (result i64 i64 f32) (result f32 i32)))",
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}, ResultNumInUint64: 5},
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 params and results abbreviated in groups",
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
expected: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4, ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
expected: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4, ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
expected: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4, ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
@@ -193,7 +165,7 @@ func TestTypeParser(t *testing.T) {
input: "(type (func (result) (result) (result i64 i64) (result) (result f32) (result)))",
// Abbreviations have min length zero, which implies no-op results are ok.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}, ResultNumInUint64: 3},
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}},
},
{
name: "multi-value - empty abbreviated params and results",
@@ -204,9 +176,8 @@ func TestTypeParser(t *testing.T) {
// Abbreviations have min length zero, which implies no-op results are ok.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
expected: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 5, ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
}
@@ -408,9 +379,6 @@ func parseFunctionType(
tp := newTypeParser(enabledFeatures, typeNamespace, setFunc)
// typeParser starts after the '(type', so we need to eat it first!
_, _, err := lex(skipTokens(2, tp.begin), []byte(input))
if parsed != nil {
parsed.CacheNumInUint64()
}
return parsed, tp, err
}

View File

@@ -74,7 +74,7 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
{
name: "mixed param abbreviation", // Verifies we can handle less param fields than param types
input: "((param i32 i32) (param i32) (param i64) (param f32))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5},
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}},
},
// Below are changes to test/core/br.wast from the commit that added "multi-value" support.
@@ -83,73 +83,56 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
{
name: "multi-value - v_i64f32 abbreviated",
input: "((result i64 f32))",
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2},
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
},
{
name: "multi-value - i32i64_f32f64 abbreviated",
input: "((param i32 i64) (result f32 f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64},
ParamNumInUint64: 2,
ResultNumInUint64: 2,
},
name: "multi-value - i32i64_f32f64 abbreviated",
input: "((param i32 i64) (result f32 f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
},
{
name: "multi-value - v_i64f32",
input: "((result i64) (result f32))",
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2},
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
},
{
name: "multi-value - i32i64_f32f64",
input: "((param i32) (param i64) (result f32) (result f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64},
ParamNumInUint64: 2,
ResultNumInUint64: 2,
},
name: "multi-value - i32i64_f32f64",
input: "((param i32) (param i64) (result f32) (result f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
},
{
name: "multi-value - i32i64_f32f64 named",
input: "((param $x i32) (param $y i64) (result f32) (result f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64},
ParamNumInUint64: 2,
ResultNumInUint64: 2,
},
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}, &wasm.NameAssoc{Index: 1, Name: "y"}},
name: "multi-value - i32i64_f32f64 named",
input: "((param $x i32) (param $y i64) (result f32) (result f64))",
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}, &wasm.NameAssoc{Index: 1, Name: "y"}},
},
{
name: "multi-value - i64i64f32_f32i32 results abbreviated in groups",
input: "((result i64 i64 f32) (result f32 i32))",
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32},
ResultNumInUint64: 5,
},
name: "multi-value - i64i64f32_f32i32 results abbreviated in groups",
input: "((result i64 i64 f32) (result f32 i32))",
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 params and results abbreviated in groups",
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
expectedInlinedType: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4,
ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
expectedInlinedType: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4,
ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
expectedInlinedType: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 4,
ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
},
{
@@ -157,9 +140,7 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
input: "((result) (result) (result i64 i64) (result) (result f32) (result))",
// Abbreviations have min length zero, which implies no-op results are ok.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32},
ResultNumInUint64: 3,
},
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}},
},
{
name: "multi-value - empty abbreviated params and results",
@@ -170,10 +151,8 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
// Abbreviations have min length zero, which implies no-op results are ok.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
expectedInlinedType: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
ParamNumInUint64: 5,
ResultNumInUint64: 4,
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
Results: []wasm.ValueType{f32, f64, f64, i32},
},
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 4, Name: "x"}},
},
@@ -185,8 +164,6 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
return tp, func(t *testing.T) {
// We should have inlined the type, and it is the first type use, which means the inlined index is zero
require.Zero(t, tp.inlinedTypeIndices[0].inlinedIdx)
exp := tp.inlinedTypes[0]
exp.CacheNumInUint64()
require.Equal(t, []*wasm.FunctionType{tc.expectedInlinedType}, tp.inlinedTypes)
}
})
@@ -225,9 +202,7 @@ func TestTypeUseParser_UnresolvedType(t *testing.T) {
if tc.expectedInlinedType == nil {
require.Zero(t, len(tp.inlinedTypes), "expected no inlinedTypes")
} else {
exp := tp.inlinedTypes[0]
exp.CacheNumInUint64()
require.Equal(t, tc.expectedInlinedType, exp)
require.Equal(t, tc.expectedInlinedType, tp.inlinedTypes[0])
}
}
})
@@ -372,9 +347,6 @@ func TestTypeUseParser_ReuseExistingInlinedType(t *testing.T) {
require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse))
return tp, func(t *testing.T) {
for _, it := range tp.inlinedTypes {
it.CacheNumInUint64()
}
// verify it wasn't duplicated
require.Equal(t, []*wasm.FunctionType{i32i64_v, tc.expectedInlinedType}, tp.inlinedTypes)
// last two inlined types are the same
@@ -420,9 +392,6 @@ func TestTypeUseParser_BeginResets(t *testing.T) {
require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse))
return tp, func(t *testing.T) {
for _, it := range tp.inlinedTypes {
it.CacheNumInUint64()
}
// this is the second inlined type
require.Equal(t, []*wasm.FunctionType{i32i64_i32, tc.expectedInlinedType}, tp.inlinedTypes)
}

View File

@@ -22,7 +22,7 @@ func newExample() *wasm.Module {
return &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1},
{ParamNumInUint64: 0, ResultNumInUint64: 0},
{},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1},
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, ParamNumInUint64: 1, ResultNumInUint64: 1},
{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1},

View File

@@ -119,7 +119,9 @@ func TestCompile(t *testing.T) {
if enabledFeatures == 0 {
enabledFeatures = wasm.Features20220419
}
for _, tp := range tc.module.TypeSection {
tp.CacheNumInUint64()
}
res, err := CompileFunctions(ctx, enabledFeatures, tc.module)
require.NoError(t, err)
require.Equal(t, tc.expected, res[0])
@@ -535,6 +537,9 @@ func TestCompile_MultiValue(t *testing.T) {
if enabledFeatures == 0 {
enabledFeatures = wasm.Features20220419
}
for _, tp := range tc.module.TypeSection {
tp.CacheNumInUint64()
}
res, err := CompileFunctions(ctx, enabledFeatures, tc.module)
require.NoError(t, err)
require.Equal(t, tc.expected, res[0])
@@ -565,7 +570,9 @@ func TestCompile_NonTrappingFloatToIntConversion(t *testing.T) {
Types: []*wasm.FunctionType{f32_i32},
TableTypes: []wasm.RefType{},
}
for _, tp := range module.TypeSection {
tp.CacheNumInUint64()
}
res, err := CompileFunctions(ctx, wasm.FeatureNonTrappingFloatToIntConversion, module)
require.NoError(t, err)
require.Equal(t, expected, res[0])
@@ -590,7 +597,9 @@ func TestCompile_SignExtensionOps(t *testing.T) {
Types: []*wasm.FunctionType{i32_i32},
TableTypes: []wasm.RefType{},
}
for _, tp := range module.TypeSection {
tp.CacheNumInUint64()
}
res, err := CompileFunctions(ctx, wasm.FeatureSignExtensionOps, module)
require.NoError(t, err)
require.Equal(t, expected, res[0])

View File

@@ -44,10 +44,14 @@ const (
// 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)
}
var argsGet = wasm.NewGoFunc(
functionArgsGet, functionArgsGet,
[]string{"argv", "argv_buf"},
func(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.
@@ -78,15 +82,19 @@ func argsGet(ctx context.Context, mod api.Module, argv, argvBuf uint32) Errno {
// 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()
var argsSizesGet = wasm.NewGoFunc(
functionArgsSizesGet, functionArgsSizesGet,
[]string{"result.argc", "result.argv_buf_size"},
func(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
}
if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sysCtx.Args()))) {
return ErrnoFault
}
if !mem.WriteUint32Le(ctx, resultArgvBufSize, sysCtx.ArgsSize()) {
return ErrnoFault
}
return ErrnoSuccess
},
)

View File

@@ -53,28 +53,32 @@ const (
// 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 clockResGet(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
var clockResGet = wasm.NewGoFunc(
functionClockResGet, functionClockResGet,
[]string{"id", "result.resolution"},
func(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
var resolution uint64 // ns
switch id {
case clockIDRealtime:
resolution = uint64(sysCtx.WalltimeResolution())
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.
return ErrnoNotsup
default:
return ErrnoInval
}
if !mod.Memory().WriteUint64Le(ctx, resultResolution, resolution) {
return ErrnoFault
}
return ErrnoSuccess
}
var resolution uint64 // ns
switch id {
case clockIDRealtime:
resolution = uint64(sysCtx.WalltimeResolution())
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.
return ErrnoNotsup
default:
return ErrnoInval
}
if !mod.Memory().WriteUint64Le(ctx, resultResolution, resolution) {
return ErrnoFault
}
return ErrnoSuccess
},
)
// clockTimeGet is the WASI function named functionClockTimeGet that returns
// the time value of a name (time.Now).
@@ -107,28 +111,32 @@ func clockResGet(ctx context.Context, mod api.Module, id uint32, resultResolutio
// 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 clockTimeGet(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
// TODO: precision is currently ignored.
sysCtx := mod.(*wasm.CallContext).Sys
var clockTimeGet = wasm.NewGoFunc(
functionClockTimeGet, functionClockTimeGet,
[]string{"id", "precision", "result.timestamp"},
func(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
// TODO: precision is currently ignored.
sysCtx := mod.(*wasm.CallContext).Sys
var val uint64
switch id {
case clockIDRealtime:
sec, nsec := sysCtx.Walltime(ctx)
val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec)
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.
return ErrnoNotsup
default:
return ErrnoInval
}
var val uint64
switch id {
case clockIDRealtime:
sec, nsec := sysCtx.Walltime(ctx)
val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec)
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.
return ErrnoNotsup
default:
return ErrnoInval
}
if !mod.Memory().WriteUint64Le(ctx, resultTimestamp, val) {
return ErrnoFault
}
return ErrnoSuccess
}
if !mod.Memory().WriteUint64Le(ctx, resultTimestamp, val) {
return ErrnoFault
}
return ErrnoSuccess
},
)

View File

@@ -191,16 +191,28 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
name: "process cputime",
clockID: 2,
expectedErrno: ErrnoNotsup,
expectedLog: `
==> wasi_snapshot_preview1.clock_time_get(id=2,precision=0,result.timestamp=1)
<== ENOTSUP
`,
},
{
name: "thread cputime",
clockID: 3,
expectedErrno: ErrnoNotsup,
expectedLog: `
==> wasi_snapshot_preview1.clock_time_get(id=3,precision=0,result.timestamp=1)
<== ENOTSUP
`,
},
{
name: "undefined",
clockID: 100,
expectedErrno: ErrnoInval,
expectedLog: `
==> wasi_snapshot_preview1.clock_time_get(id=100,precision=0,result.timestamp=1)
<== EINVAL
`,
},
}
@@ -211,9 +223,9 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
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())
requireErrno(t, tc.expectedErrno, mod, functionClockTimeGet, uint64(tc.clockID), uint64(0) /* TODO: precision */, uint64(resultTimestamp))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
@@ -232,11 +244,18 @@ func Test_clockTimeGet_Errors(t *testing.T) {
{
name: "resultTimestamp out-of-memory",
resultTimestamp: memorySize,
expectedLog: `
==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65536)
<== EFAULT
`,
},
{
name: "resultTimestamp exceeds the maximum valid address by 1",
resultTimestamp: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args
expectedLog: `
==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65533)
<== EFAULT
`,
},
}
@@ -246,9 +265,8 @@ func Test_clockTimeGet_Errors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
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())
requireErrno(t, ErrnoFault, mod, functionClockTimeGet, uint64(0) /* TODO: id */, uint64(0) /* TODO: precision */, uint64(tc.resultTimestamp))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

View File

@@ -44,10 +44,14 @@ const (
// 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)
}
var environGet = wasm.NewGoFunc(
functionEnvironGet, functionEnvironGet,
[]string{"environ", "environ_buf"},
func(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.
@@ -80,16 +84,20 @@ func environGet(ctx context.Context, mod api.Module, environ uint32, environBuf
// 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()
var environSizesGet = wasm.NewGoFunc(
functionEnvironSizesGet, functionEnvironSizesGet,
[]string{"result.environc", "result.environBufSize"},
func(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
}
if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sysCtx.Environ()))) {
return ErrnoFault
}
if !mem.WriteUint32Le(ctx, resultEnvironBufSize, sysCtx.EnvironSize()) {
return ErrnoFault
}
return ErrnoSuccess
}
return ErrnoSuccess
},
)

View File

@@ -55,23 +55,39 @@ func Test_environGet_Errors(t *testing.T) {
name: "out-of-memory environPtr",
environ: memorySize,
environBuf: validAddress,
expectedLog: `
==> wasi_snapshot_preview1.environ_get(environ=65536,environ_buf=0)
<== EFAULT
`,
},
{
name: "out-of-memory environBufPtr",
environ: validAddress,
environBuf: memorySize,
expectedLog: `
==> wasi_snapshot_preview1.environ_get(environ=0,environ_buf=65536)
<== EFAULT
`,
},
{
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,
expectedLog: `
==> wasi_snapshot_preview1.environ_get(environ=65529,environ_buf=0)
<== EFAULT
`,
},
{
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,
expectedLog: `
==> wasi_snapshot_preview1.environ_get(environ=0,environ_buf=65527)
<== EFAULT
`,
},
}
@@ -81,9 +97,8 @@ func Test_environGet_Errors(t *testing.T) {
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())
requireErrno(t, ErrnoFault, mod, functionEnvironGet, uint64(tc.environ), uint64(tc.environBuf))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
@@ -134,21 +149,37 @@ func Test_environSizesGet_Errors(t *testing.T) {
name: "out-of-memory environCountPtr",
environc: memorySize,
environBufSize: validAddress,
expectedLog: `
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=65536,result.environBufSize=0)
<== EFAULT
`,
},
{
name: "out-of-memory environBufSizePtr",
environc: validAddress,
environBufSize: memorySize,
expectedLog: `
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environBufSize=65536)
<== EFAULT
`,
},
{
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,
expectedLog: `
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=65533,result.environBufSize=0)
<== EFAULT
`,
},
{
name: "environBufSizePtr exceeds the maximum valid size by 1",
environc: validAddress,
environBufSize: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
expectedLog: `
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environBufSize=65533)
<== EFAULT
`,
},
}
@@ -156,9 +187,10 @@ func Test_environSizesGet_Errors(t *testing.T) {
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())
defer log.Reset()
requireErrno(t, ErrnoFault, mod, functionEnvironSizesGet, uint64(tc.environc), uint64(tc.environBufSize))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

View File

@@ -50,13 +50,21 @@ const (
// 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.
var fdAdvise = stubFunction(
functionFdAdvise,
[]wasm.ValueType{i32, i64, i64, i32},
[]string{"fd", "offset", "len", "result.advice"},
)
// 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.
var fdAllocate = stubFunction(
functionFdAllocate,
[]wasm.ValueType{i32, i64, i64},
[]string{"fd", "offset", "len"},
)
// fdClose is the WASI function named functionFdClose which closes a file
// descriptor.
@@ -73,20 +81,28 @@ var fdAllocate = stubFunction(i32, i64, i64) // stubbed for GrainLang per #271.
// 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
}
var fdClose = wasm.NewGoFunc(
functionFdClose, functionFdClose,
[]string{"fd"},
func(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
}
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.
var fdDatasync = stubFunction(
functionFdDatasync,
[]wasm.ValueType{i32},
[]string{"fd"},
)
// fdFdstatGet is the WASI function named functionFdFdstatGet which returns the
// attributes of a file descriptor.
@@ -125,20 +141,28 @@ var fdDatasync = stubFunction(i32) // stubbed for GrainLang per #271.
// 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
}
var fdFdstatGet = wasm.NewGoFunc(
functionFdFdstatGet, functionFdFdstatGet,
[]string{"fd", "result.stat"},
func(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.
var fdFdstatSetFlags = stubFunction(
functionFdFdstatSetFlags,
[]wasm.ValueType{i32, i32},
[]string{"fd", "flags"},
)
// fdFdstatSetRights is the WASI function named functionFdFdstatSetRights which
// adjusts the rights associated with a file descriptor.
@@ -146,31 +170,51 @@ var fdFdstatSetFlags = stubFunction(i32, i32) // stubbed for GrainLang per #271.
// 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.
var fdFdstatSetRights = stubFunction(
functionFdFdstatSetRights,
[]wasm.ValueType{i32, i64, i64},
[]string{"fd", "fs_rights_base", "fs_rights_inheriting"},
)
// 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.
var fdFilestatGet = stubFunction(
functionFdFilestatGet,
[]wasm.ValueType{i32, i32},
[]string{"fd", "result.buf"},
)
// 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.
var fdFilestatSetSize = stubFunction(
functionFdFilestatSetSize,
[]wasm.ValueType{i32, i64},
[]string{"fd", "size"},
)
// 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.
var fdFilestatSetTimes = stubFunction(
functionFdFilestatSetTimes,
[]wasm.ValueType{i32, i64, i64, i32},
[]string{"fd", "atim", "mtim", "fst_flags"},
)
// 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.
var fdPread = stubFunction(
functionFdPread,
[]wasm.ValueType{i32, i32, i32, i64, i32},
[]string{"fd", "iovs", "iovs_len", "offset", "result.nread"},
)
// fdPrestatGet is the WASI function named functionFdPrestatGet which returns
// the prestat data of a file descriptor.
@@ -203,24 +247,28 @@ var fdPread = stubFunction(i32, i32, i32, i64, i32) // stubbed for GrainLang per
//
// 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
}
var fdPrestatGet = wasm.NewGoFunc(
functionFdPrestatGet, functionFdPrestatGet,
[]string{"fd", "result.prestat"},
func(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
}
// 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
}
return ErrnoSuccess
},
)
// fdPrestatDirName is the WASI function named functionFdPrestatDirName which
// returns the path of the pre-opened directory of a file descriptor.
@@ -252,30 +300,37 @@ func fdPrestatGet(ctx context.Context, mod api.Module, fd uint32, resultPrestat
//
// 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
}
var fdPrestatDirName = wasm.NewGoFunc(
functionFdPrestatDirName, functionFdPrestatDirName,
[]string{"fd", "path", "path_len"},
func(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
}
// 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
}
// 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.
var fdPwrite = stubFunction(functionFdPwrite,
[]wasm.ValueType{i32, i32, i32, i64, i32},
[]string{"fd", "iovs", "iovs_len", "offset", "result.nwritten"},
)
// fdRead is the WASI function named functionFdRead which reads from a file
// descriptor.
@@ -326,53 +381,65 @@ var fdPwrite = stubFunction(i32, i32, i32, i64, i32) // stubbed for GrainLang pe
//
// 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 fdRead = wasm.NewGoFunc(
functionFdRead, functionFdRead,
[]string{"fd", "iovs", "iovs_len", "result.size"},
func(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 {
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
}
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
}
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.
var fdReaddir = stubFunction(
functionFdReaddir,
[]wasm.ValueType{i32, i32, i32, i64, i32},
[]string{"fd", "buf", "buf_len", "cookie", "result.bufused"},
)
// 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.
var fdRenumber = stubFunction(
functionFdRenumber,
[]wasm.ValueType{i32, i32},
[]string{"fd", "to"},
)
// fdSeek is the WASI function named functionFdSeek which moves the offset of a
// file descriptor.
@@ -411,43 +478,55 @@ var fdRenumber = stubFunction(i32, i32) // stubbed for GrainLang per #271.
//
// 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
}
var fdSeek = wasm.NewGoFunc(
functionFdSeek, functionFdSeek,
[]string{"fd", "offset", "whence", "result.newoffset"},
func(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 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
}
if !mod.Memory().WriteUint32Le(ctx, resultNewoffset, uint32(newOffset)) {
return ErrnoFault
}
return ErrnoSuccess
}
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.
var fdSync = stubFunction(
functionFdSync,
[]wasm.ValueType{i32},
[]string{"fd"},
)
// 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.
var fdTell = stubFunction(
functionFdTell,
[]wasm.ValueType{i32, i32},
[]string{"fd", "result.offset"},
)
// fdWrite is the WASI function named functionFdWrite which writes to a file
// descriptor.
@@ -506,65 +585,85 @@ var fdTell = stubFunction(i32, i32) // stubbed for GrainLang per #271.
// 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 fdWrite = wasm.NewGoFunc(
functionFdWrite, functionFdWrite,
[]string{"fd", "iovs", "iovs_len", "result.size"},
func(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 {
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
}
// 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
}
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.
var pathCreateDirectory = stubFunction(
functionPathCreateDirectory,
[]wasm.ValueType{i32, i32, i32},
[]string{"fd", "path", "path_len"},
)
// 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.
var pathFilestatGet = stubFunction(
functionPathFilestatGet,
[]wasm.ValueType{i32, i32, i32, i32, i32},
[]string{"fd", "flags", "path", "path_len", "result.buf"},
)
// 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.
var pathFilestatSetTimes = stubFunction(
functionPathFilestatSetTimes,
[]wasm.ValueType{i32, i32, i32, i32, i64, i64, i32},
[]string{"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags"},
)
// 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.
var pathLink = stubFunction(
functionPathLink,
[]wasm.ValueType{i32, i32, i32, i32, i32, i32, i32},
[]string{"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"},
)
// pathOpen is the WASI function named functionPathOpen which opens a file or
// directory. This returns ErrnoBadf if the fd is invalid.
@@ -620,59 +719,83 @@ var pathLink = stubFunction(i32, i32, i32, i32, i32, i32, i32) // stubbed for Gr
// * 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
var pathOpen = wasm.NewGoFunc(
functionPathOpen, functionPathOpen,
[]string{"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd"},
func(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
}
} else if !mod.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) {
_ = fsc.CloseFile(ctx, newFD)
return ErrnoFault
}
return ErrnoSuccess
}
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.
var pathReadlink = stubFunction(
functionPathReadlink,
[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
[]string{"fd", "path", "path_len", "buf", "buf_len", "result.bufused"},
)
// 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.
var pathRemoveDirectory = stubFunction(
functionPathRemoveDirectory,
[]wasm.ValueType{i32, i32, i32},
[]string{"fd", "path", "path_len"},
)
// 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.
var pathRename = stubFunction(
functionPathRename,
[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
[]string{"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"},
)
// 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.
var pathSymlink = stubFunction(
functionPathSymlink,
[]wasm.ValueType{i32, i32, i32, i32, i32},
[]string{"old_path", "old_path_len", "fd", "new_path", "new_path_len"},
)
// 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.
var pathUnlinkFile = stubFunction(
functionPathUnlinkFile,
[]wasm.ValueType{i32, i32, i32},
[]string{"fd", "path", "path_len"},
)

View File

@@ -113,22 +113,38 @@ func Test_fdFdstatGet(t *testing.T) {
name: "file",
fd: fileFd,
// TODO: expectedMem for a file
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=4,result.stat=0)
<== ESUCCESS
`,
},
{
name: "dir",
fd: dirFd,
// TODO: expectedMem for a dir
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=0)
<== ESUCCESS
`,
},
{
name: "bad FD",
fd: math.MaxUint32,
expectedErrno: ErrnoBadf,
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=4294967295,result.stat=0)
<== EBADF
`,
},
{
name: "resultStat exceeds the maximum valid address by 1",
fd: dirFd,
resultStat: memorySize - 24 + 1,
// TODO: ErrnoFault
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=65513)
<== ESUCCESS
`,
},
}
@@ -138,9 +154,8 @@ func Test_fdFdstatGet(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
defer log.Reset()
errno := fdFdstatGet(testCtx, mod, tc.fd, tc.resultStat)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
require.Equal(t, tc.expectedLog, log.String())
requireErrno(t, tc.expectedErrno, mod, functionFdFdstatGet, uint64(tc.fd), uint64(tc.resultStat))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
@@ -238,18 +253,27 @@ func Test_fdPrestatGet_Errors(t *testing.T) {
fd uint32
resultPrestat uint32
expectedErrno Errno
expectedLog string
}{
{
name: "invalid FD",
fd: 42, // arbitrary invalid FD
resultPrestat: 0, // valid offset
expectedErrno: ErrnoBadf,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0)
<== EBADF
`,
},
{
name: "out-of-memory resultPrestat",
fd: fd,
resultPrestat: memorySize,
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=65536)
<== EFAULT
`,
},
// TODO: non pre-opened file == api.ErrnoBadf
}
@@ -260,8 +284,8 @@ func Test_fdPrestatGet_Errors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
defer log.Reset()
errno := fdPrestatGet(testCtx, mod, tc.fd, tc.resultPrestat)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
requireErrno(t, tc.expectedErrno, mod, functionFdPrestatGet, uint64(tc.fd), uint64(tc.resultPrestat))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
@@ -307,6 +331,7 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
path uint32
pathLen uint32
expectedErrno Errno
expectedLog string
}{
{
name: "out-of-memory path",
@@ -314,6 +339,10 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
path: memorySize,
pathLen: pathLen,
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65536,path_len=4)
<== EFAULT
`,
},
{
name: "path exceeds the maximum valid address by 1",
@@ -321,6 +350,10 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
path: memorySize - pathLen + 1,
pathLen: pathLen,
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65533,path_len=4)
<== EFAULT
`,
},
{
name: "pathLen exceeds the length of the dir name",
@@ -328,6 +361,10 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
path: validAddress,
pathLen: pathLen + 1,
expectedErrno: ErrnoNametoolong,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=0,path_len=5)
<== ENAMETOOLONG
`,
},
{
name: "invalid fd",
@@ -335,6 +372,10 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
path: validAddress,
pathLen: pathLen,
expectedErrno: ErrnoBadf,
expectedLog: `
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=42,path=0,path_len=4)
<== EBADF
`,
},
// TODO: non pre-opened file == ErrnoBadf
}
@@ -345,8 +386,8 @@ func Test_fdPrestatDirName_Errors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
defer log.Reset()
errno := fdPrestatDirName(testCtx, mod, tc.fd, tc.path, tc.pathLen)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
requireErrno(t, tc.expectedErrno, mod, functionFdPrestatDirName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}
@@ -410,11 +451,16 @@ func Test_fdRead_Errors(t *testing.T) {
fd, iovs, iovsCount, resultSize uint32
memory []byte
expectedErrno Errno
expectedLog string
}{
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
expectedErrno: ErrnoBadf,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65536,iovs_len=65536,result.size=65536)
<== EBADF
`,
},
{
name: "out-of-memory reading iovs[0].offset",
@@ -422,6 +468,10 @@ func Test_fdRead_Errors(t *testing.T) {
iovs: 1,
memory: []byte{'?'},
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535,result.size=65535)
<== EFAULT
`,
},
{
name: "out-of-memory reading iovs[0].length",
@@ -432,6 +482,10 @@ func Test_fdRead_Errors(t *testing.T) {
9, 0, 0, 0, // = iovs[0].offset
},
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.size=65531)
<== EFAULT
`,
},
{
name: "iovs[0].offset is outside memory",
@@ -443,6 +497,10 @@ func Test_fdRead_Errors(t *testing.T) {
1, 0, 0, 0, // = iovs[0].length
},
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.size=65527)
<== EFAULT
`,
},
{
name: "length to read exceeds memory by 1",
@@ -455,6 +513,10 @@ func Test_fdRead_Errors(t *testing.T) {
'?',
},
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65526)
<== EFAULT
`,
},
{
name: "resultSize offset is outside memory",
@@ -468,6 +530,10 @@ func Test_fdRead_Errors(t *testing.T) {
'?',
},
expectedErrno: ErrnoFault,
expectedLog: `
==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65536)
<== EFAULT
`,
},
}
@@ -481,8 +547,8 @@ func Test_fdRead_Errors(t *testing.T) {
memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory)
require.True(t, memoryWriteOK)
errno := fdRead(testCtx, mod, tc.fd, tc.iovs+offset, tc.iovsCount+offset, tc.resultSize+offset)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
requireErrno(t, tc.expectedErrno, mod, functionFdRead, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.resultSize+offset))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

View File

@@ -46,57 +46,61 @@ 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 pollOneoff(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
if nsubscriptions == 0 {
return ErrnoInval
}
mem := mod.Memory()
// Ensure capacity prior to the read loop to reduce error handling.
inBuf, ok := mem.Read(ctx, in, nsubscriptions*48)
if !ok {
return ErrnoFault
}
outBuf, ok := mem.Read(ctx, out, nsubscriptions*32)
if !ok {
return ErrnoFault
}
// Eagerly write the number of events which will equal subscriptions unless
// there's a fault in parsing (not processing).
if !mod.Memory().WriteUint32Le(ctx, resultNevents, nsubscriptions) {
return ErrnoFault
}
// Loop through all subscriptions and write their output.
for sub := uint32(0); sub < nsubscriptions; sub++ {
inOffset := sub * 48
outOffset := sub * 32
var errno Errno
eventType := inBuf[inOffset+8] // +8 past userdata
switch eventType {
case eventTypeClock: // handle later
// +8 past userdata +8 name alignment
errno = processClockEvent(ctx, mod, inBuf[inOffset+8+8:])
case eventTypeFdRead, eventTypeFdWrite:
// +8 past userdata +4 FD alignment
errno = processFDEvent(ctx, mod, eventType, inBuf[inOffset+8+4:])
default:
var pollOneoff = wasm.NewGoFunc(
functionPollOneoff, functionPollOneoff,
[]string{"in", "out", "nsubscriptions", "result.nevents"},
func(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
if nsubscriptions == 0 {
return ErrnoInval
}
// Write the event corresponding to the processed subscription.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata
outBuf[outOffset+8] = byte(errno) // uint16, but safe as < 255
outBuf[outOffset+9] = 0
binary.LittleEndian.PutUint32(outBuf[outOffset+10:], uint32(eventType))
// TODO: When FD events are supported, write outOffset+16
}
return ErrnoSuccess
}
mem := mod.Memory()
// Ensure capacity prior to the read loop to reduce error handling.
inBuf, ok := mem.Read(ctx, in, nsubscriptions*48)
if !ok {
return ErrnoFault
}
outBuf, ok := mem.Read(ctx, out, nsubscriptions*32)
if !ok {
return ErrnoFault
}
// Eagerly write the number of events which will equal subscriptions unless
// there's a fault in parsing (not processing).
if !mod.Memory().WriteUint32Le(ctx, resultNevents, nsubscriptions) {
return ErrnoFault
}
// Loop through all subscriptions and write their output.
for sub := uint32(0); sub < nsubscriptions; sub++ {
inOffset := sub * 48
outOffset := sub * 32
var errno Errno
eventType := inBuf[inOffset+8] // +8 past userdata
switch eventType {
case eventTypeClock: // handle later
// +8 past userdata +8 name alignment
errno = processClockEvent(ctx, mod, inBuf[inOffset+8+8:])
case eventTypeFdRead, eventTypeFdWrite:
// +8 past userdata +4 FD alignment
errno = processFDEvent(ctx, mod, eventType, inBuf[inOffset+8+4:])
default:
return ErrnoInval
}
// Write the event corresponding to the processed subscription.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata
outBuf[outOffset+8] = byte(errno) // uint16, but safe as < 255
outBuf[outOffset+9] = 0
binary.LittleEndian.PutUint32(outBuf[outOffset+10:], uint32(eventType))
// TODO: When FD events are supported, write outOffset+16
}
return ErrnoSuccess
},
)
// processClockEvent supports only relative name events, as that's what's used
// to implement sleep in various compilers including Rust, Zig and TinyGo.

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
@@ -21,18 +22,22 @@ const (
// * exitCode: exit code.
//
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
func procExit(ctx context.Context, mod api.Module, exitCode uint32) {
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
var procExit = wasm.NewGoFunc(
functionProcExit, functionProcExit,
[]string{"rval"},
func(ctx context.Context, mod api.Module, exitCode uint32) {
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(mod.Name(), exitCode))
}
// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(mod.Name(), exitCode))
},
)
// 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.
var procRaise = stubFunction(functionProcRaise, []wasm.ValueType{i32}, []string{"sig"})

View File

@@ -20,10 +20,16 @@ func Test_procExit(t *testing.T) {
{
name: "success (exitcode 0)",
exitCode: 0,
expectedLog: `
==> wasi_snapshot_preview1.proc_exit(rval=0)
`,
},
{
name: "arbitrary non-zero exitcode",
exitCode: 42,
expectedLog: `
==> wasi_snapshot_preview1.proc_exit(rval=42)
`,
},
}
@@ -34,9 +40,9 @@ func Test_procExit(t *testing.T) {
defer log.Reset()
// Since procExit panics, any opcodes afterwards cannot be reached.
err := require.CapturePanic(func() { procExit(testCtx, mod, tc.exitCode) })
_, err := mod.ExportedFunction(functionProcExit).Call(testCtx, uint64(tc.exitCode))
require.Equal(t, tc.exitCode, err.(*sys.ExitError).ExitCode())
require.Equal(t, tc.expectedLog, log.String())
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

View File

@@ -34,19 +34,23 @@ const functionRandomGet = "random_get"
// 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()
var randomGet = wasm.NewGoFunc(
functionRandomGet, functionRandomGet,
[]string{"buf", "buf_len"},
func(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
}
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
}
// 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
}
return ErrnoSuccess
},
)

View File

@@ -92,10 +92,18 @@ func Test_randomGet_SourceError(t *testing.T) {
{
name: "error",
randSource: iotest.ErrReader(errors.New("RandSource error")),
expectedLog: `
==> wasi_snapshot_preview1.random_get(buf=1,buf_len=5)
<== EIO
`,
},
{
name: "incomplete",
randSource: bytes.NewReader([]byte{1, 2}),
expectedLog: `
==> wasi_snapshot_preview1.random_get(buf=1,buf_len=5)
<== EIO
`,
},
}
@@ -106,9 +114,8 @@ func Test_randomGet_SourceError(t *testing.T) {
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())
requireErrno(t, ErrnoIo, mod, functionRandomGet, uint64(1), uint64(5)) // arbitrary offset and length
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

View File

@@ -6,4 +6,4 @@ const functionSchedYield = "sched_yield"
// 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.
var schedYield = stubFunction(functionSchedYield, nil, nil)

View File

@@ -1,5 +1,7 @@
package wasi_snapshot_preview1
import "github.com/tetratelabs/wazero/internal/wasm"
const (
functionSockRecv = "sock_recv"
functionSockSend = "sock_send"
@@ -10,16 +12,28 @@ const (
// 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.
var sockRecv = stubFunction(
functionSockRecv,
[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
[]string{"fd", "ri_data", "ri_data_count", "ri_flags", "result.ro_datalen", "result.ro_flags"},
)
// 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.
var sockSend = stubFunction(
functionSockSend,
[]wasm.ValueType{i32, i32, i32, i32, i32},
[]string{"fd", "si_data", "si_data_count", "si_flags", "result.so_datalen"},
)
// 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.
var sockShutdown = stubFunction(
functionSockShutdown,
[]wasm.ValueType{i32, i32},
[]string{"fd", "how"},
)

View File

@@ -111,96 +111,51 @@ func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Clo
func exportFunctions(builder wazero.ModuleBuilder) {
// Note:se are ordered per spec for consistency even if the resulting map can't guarantee that.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
builder.ExportFunction(functionArgsGet, argsGet,
functionArgsGet, "argv", "argv_buf")
builder.ExportFunction(functionArgsSizesGet, argsSizesGet,
functionArgsSizesGet, "result.argc", "result.argv_buf_size")
builder.ExportFunction(functionEnvironGet, environGet,
functionEnvironGet, "environ", "environ_buf")
builder.ExportFunction(functionEnvironSizesGet, environSizesGet,
functionEnvironSizesGet, "result.environc", "result.environBufSize")
builder.ExportFunction(functionClockResGet, clockResGet,
functionClockResGet, "id", "result.resolution")
builder.ExportFunction(functionClockTimeGet, clockTimeGet,
functionClockTimeGet, "id", "precision", "result.timestamp")
builder.ExportFunction(functionFdAdvise, fdAdvise,
functionFdAdvise, "fd", "offset", "len", "result.advice")
builder.ExportFunction(functionFdAllocate, fdAllocate,
functionFdAllocate, "fd", "offset", "len")
builder.ExportFunction(functionFdClose, fdClose,
functionFdClose, "fd")
builder.ExportFunction(functionFdDatasync, fdDatasync,
functionFdDatasync, "fd")
builder.ExportFunction(functionFdFdstatGet, fdFdstatGet,
functionFdFdstatGet, "fd", "result.stat")
builder.ExportFunction(functionFdFdstatSetFlags, fdFdstatSetFlags,
functionFdFdstatSetFlags, "fd", "flags")
builder.ExportFunction(functionFdFdstatSetRights, fdFdstatSetRights,
functionFdFdstatSetRights, "fd", "fs_rights_base", "fs_rights_inheriting")
builder.ExportFunction(functionFdFilestatGet, fdFilestatGet,
functionFdFilestatGet, "fd", "result.buf")
builder.ExportFunction(functionFdFilestatSetSize, fdFilestatSetSize,
functionFdFilestatSetSize, "fd", "size")
builder.ExportFunction(functionFdFilestatSetTimes, fdFilestatSetTimes,
functionFdFilestatSetTimes, "fd", "atim", "mtim", "fst_flags")
builder.ExportFunction(functionFdPread, fdPread,
functionFdPread, "fd", "iovs", "iovs_len", "offset", "result.nread")
builder.ExportFunction(functionFdPrestatGet, fdPrestatGet,
functionFdPrestatGet, "fd", "result.prestat")
builder.ExportFunction(functionFdPrestatDirName, fdPrestatDirName,
functionFdPrestatDirName, "fd", "path", "path_len")
builder.ExportFunction(functionFdPwrite, fdPwrite,
functionFdPwrite, "fd", "iovs", "iovs_len", "offset", "result.nwritten")
builder.ExportFunction(functionFdRead, fdRead,
functionFdRead, "fd", "iovs", "iovs_len", "result.size")
builder.ExportFunction(functionFdReaddir, fdReaddir,
functionFdReaddir, "fd", "buf", "buf_len", "cookie", "result.bufused")
builder.ExportFunction(functionFdRenumber, fdRenumber,
functionFdRenumber, "fd", "to")
builder.ExportFunction(functionFdSeek, fdSeek,
functionFdSeek, "fd", "offset", "whence", "result.newoffset")
builder.ExportFunction(functionFdSync, fdSync,
functionFdSync, "fd")
builder.ExportFunction(functionFdTell, fdTell,
functionFdTell, "fd", "result.offset")
builder.ExportFunction(functionFdWrite, fdWrite,
functionFdWrite, "fd", "iovs", "iovs_len", "result.size")
builder.ExportFunction(functionPathCreateDirectory, pathCreateDirectory,
functionPathCreateDirectory, "fd", "path", "path_len")
builder.ExportFunction(functionPathFilestatGet, pathFilestatGet,
functionPathFilestatGet, "fd", "flags", "path", "path_len", "result.buf")
builder.ExportFunction(functionPathFilestatSetTimes, pathFilestatSetTimes,
functionPathFilestatSetTimes, "fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags")
builder.ExportFunction(functionPathLink, pathLink,
functionPathLink, "old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len")
builder.ExportFunction(functionPathOpen, pathOpen,
functionPathOpen, "fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd")
builder.ExportFunction(functionPathReadlink, pathReadlink,
functionPathReadlink, "fd", "path", "path_len", "buf", "buf_len", "result.bufused")
builder.ExportFunction(functionPathRemoveDirectory, pathRemoveDirectory,
functionPathRemoveDirectory, "fd", "path", "path_len")
builder.ExportFunction(functionPathRename, pathRename,
functionPathRename, "fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len")
builder.ExportFunction(functionPathSymlink, pathSymlink,
functionPathSymlink, "old_path", "old_path_len", "fd", "new_path", "new_path_len")
builder.ExportFunction(functionPathUnlinkFile, pathUnlinkFile,
functionPathUnlinkFile, "fd", "path", "path_len")
builder.ExportFunction(functionPollOneoff, pollOneoff,
functionPollOneoff, "in", "out", "nsubscriptions", "result.nevents")
builder.ExportFunction(functionProcExit, procExit,
functionProcExit, "rval")
builder.ExportFunction(functionProcRaise, procRaise,
functionProcRaise, "sig")
builder.ExportFunction(functionSchedYield, schedYield,
functionSchedYield)
builder.ExportFunction(functionRandomGet, randomGet,
functionRandomGet, "buf", "buf_len")
builder.ExportFunction(functionSockRecv, sockRecv,
functionSockRecv, "fd", "ri_data", "ri_data_count", "ri_flags", "result.ro_datalen", "result.ro_flags")
builder.ExportFunction(functionSockSend, sockSend,
functionSockSend, "fd", "si_data", "si_data_count", "si_flags", "result.so_datalen")
builder.ExportFunction(functionSockShutdown, sockShutdown,
functionSockShutdown, "fd", "how")
builder.ExportFunction(argsGet.Name, argsGet)
builder.ExportFunction(argsSizesGet.Name, argsSizesGet)
builder.ExportFunction(environGet.Name, environGet)
builder.ExportFunction(environSizesGet.Name, environSizesGet)
builder.ExportFunction(clockResGet.Name, clockResGet)
builder.ExportFunction(clockTimeGet.Name, clockTimeGet)
builder.ExportFunction(fdAdvise.Name, fdAdvise)
builder.ExportFunction(fdAllocate.Name, fdAllocate)
builder.ExportFunction(fdClose.Name, fdClose)
builder.ExportFunction(fdDatasync.Name, fdDatasync)
builder.ExportFunction(fdFdstatGet.Name, fdFdstatGet)
builder.ExportFunction(fdFdstatSetFlags.Name, fdFdstatSetFlags)
builder.ExportFunction(fdFdstatSetRights.Name, fdFdstatSetRights)
builder.ExportFunction(fdFilestatGet.Name, fdFilestatGet)
builder.ExportFunction(fdFilestatSetSize.Name, fdFilestatSetSize)
builder.ExportFunction(fdFilestatSetTimes.Name, fdFilestatSetTimes)
builder.ExportFunction(fdPread.Name, fdPread)
builder.ExportFunction(fdPrestatGet.Name, fdPrestatGet)
builder.ExportFunction(fdPrestatDirName.Name, fdPrestatDirName)
builder.ExportFunction(fdPwrite.Name, fdPwrite)
builder.ExportFunction(fdRead.Name, fdRead)
builder.ExportFunction(fdReaddir.Name, fdReaddir)
builder.ExportFunction(fdRenumber.Name, fdRenumber)
builder.ExportFunction(fdSeek.Name, fdSeek)
builder.ExportFunction(fdSync.Name, fdSync)
builder.ExportFunction(fdTell.Name, fdTell)
builder.ExportFunction(fdWrite.Name, fdWrite)
builder.ExportFunction(pathCreateDirectory.Name, pathCreateDirectory)
builder.ExportFunction(pathFilestatGet.Name, pathFilestatGet)
builder.ExportFunction(pathFilestatSetTimes.Name, pathFilestatSetTimes)
builder.ExportFunction(pathLink.Name, pathLink)
builder.ExportFunction(pathOpen.Name, pathOpen)
builder.ExportFunction(pathReadlink.Name, pathReadlink)
builder.ExportFunction(pathRemoveDirectory.Name, pathRemoveDirectory)
builder.ExportFunction(pathRename.Name, pathRename)
builder.ExportFunction(pathSymlink.Name, pathSymlink)
builder.ExportFunction(pathUnlinkFile.Name, pathUnlinkFile)
builder.ExportFunction(pollOneoff.Name, pollOneoff)
builder.ExportFunction(procExit.Name, procExit)
builder.ExportFunction(procRaise.Name, procRaise)
builder.ExportFunction(schedYield.Name, schedYield)
builder.ExportFunction(randomGet.Name, randomGet)
builder.ExportFunction(sockRecv.Name, sockRecv)
builder.ExportFunction(sockSend.Name, sockSend)
builder.ExportFunction(sockShutdown.Name, sockShutdown)
}
func writeOffsetsAndNullTerminatedValues(ctx context.Context, mem api.Memory, values []string, offsets, bytes uint32) Errno {
@@ -225,15 +180,14 @@ func writeOffsetsAndNullTerminatedValues(ctx context.Context, mem api.Memory, va
return ErrnoSuccess
}
// stubFunction returns a function for the given params which returns ErrnoNosys.
func stubFunction(params ...wasm.ValueType) *wasm.Func {
// stubFunction stubs for GrainLang per #271.
func stubFunction(name string, paramTypes []wasm.ValueType, paramNames []string) *wasm.Func {
return &wasm.Func{
Type: &wasm.FunctionType{
Params: params,
Results: []wasm.ValueType{wasm.ValueTypeI32},
ParamNumInUint64: len(params),
ResultNumInUint64: 1,
},
Code: &wasm.Code{Body: []byte{wasm.OpcodeI32Const, byte(ErrnoNosys), wasm.OpcodeEnd}},
Name: name,
ExportNames: []string{name},
ParamTypes: paramTypes,
ParamNames: paramNames,
ResultTypes: []wasm.ValueType{i32},
Code: &wasm.Code{Body: []byte{wasm.OpcodeI32Const, byte(ErrnoNosys), wasm.OpcodeEnd}},
}
}

View File

@@ -53,8 +53,13 @@ func Benchmark_EnvironGet(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()
results, err := mod.ExportedFunction(functionEnvironGet).Call(testCtx, uint64(0), uint64(4))
if err != nil {
b.Fatal(err)
}
errno := Errno(results[0])
if errno != ErrnoSuccess {
b.Fatal(ErrnoName(errno))
}
}
})