diff --git a/assemblyscript/assemblyscript.go b/assemblyscript/assemblyscript.go index 70797fc5..d234989b 100644 --- a/assemblyscript/assemblyscript.go +++ b/assemblyscript/assemblyscript.go @@ -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 { diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go index e45d677f..e3215c19 100644 --- a/assemblyscript/assemblyscript_test.go +++ b/assemblyscript/assemblyscript_test.go @@ -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 } diff --git a/builder.go b/builder.go index a832922c..e086d7d9 100644 --- a/builder.go +++ b/builder.go @@ -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} diff --git a/builder_test.go b/builder_test.go index f57f70d3..c1c772ed 100644 --- a/builder_test.go +++ b/builder_test.go @@ -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) diff --git a/emscripten/emscripten.go b/emscripten/emscripten.go index 7ba6db47..78f689ec 100644 --- a/emscripten/emscripten.go +++ b/emscripten/emscripten.go @@ -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}}, } diff --git a/internal/integration_test/bench/codec_test.go b/internal/integration_test/bench/codec_test.go index cabc7a65..47341a5c 100644 --- a/internal/integration_test/bench/codec_test.go +++ b/internal/integration_test/bench/codec_test.go @@ -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{ { diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index 68efa1d9..a55c28fc 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -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{ { diff --git a/internal/wasm/binary/function.go b/internal/wasm/binary/function.go index 67d4a2b9..3a246e2f 100644 --- a/internal/wasm/binary/function.go +++ b/internal/wasm/binary/function.go @@ -96,7 +96,5 @@ func decodeFunctionType(enabledFeatures wasm.Features, r *bytes.Reader) (*wasm.F Params: paramTypes, Results: resultTypes, } - - ret.CacheNumInUint64() return ret, nil } diff --git a/internal/wasm/binary/function_test.go b/internal/wasm/binary/function_test.go index efeb30af..8acbdf32 100644 --- a/internal/wasm/binary/function_test.go +++ b/internal/wasm/binary/function_test.go @@ -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}, }, } diff --git a/internal/wasm/gofunc.go b/internal/wasm/gofunc.go index 60cef086..2f1ab806 100644 --- a/internal/wasm/gofunc.go +++ b/internal/wasm/gofunc.go @@ -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 { diff --git a/internal/wasm/gofunc_test.go b/internal/wasm/gofunc_test.go index 57c215bb..d9c696ad 100644 --- a/internal/wasm/gofunc_test.go +++ b/internal/wasm/gofunc_test.go @@ -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 { diff --git a/internal/wasm/host.go b/internal/wasm/host.go index 4f74ec21..7cf4eec5 100644 --- a/internal/wasm/host.go +++ b/internal/wasm/host.go @@ -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 } diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go index 941f0bab..03981b03 100644 --- a/internal/wasm/host_test.go +++ b/internal/wasm/host_test.go @@ -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}}, diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 2aeb0e8a..446cd438 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -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++ + } } } } diff --git a/internal/watzero/internal/decoder.go b/internal/watzero/internal/decoder.go index 089e1836..2cce6610 100644 --- a/internal/watzero/internal/decoder.go +++ b/internal/watzero/internal/decoder.go @@ -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 } diff --git a/internal/watzero/internal/decoder_test.go b/internal/watzero/internal/decoder_test.go index 1ad9feff..d58bd207 100644 --- a/internal/watzero/internal/decoder_test.go +++ b/internal/watzero/internal/decoder_test.go @@ -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{ diff --git a/internal/watzero/internal/type_parser_test.go b/internal/watzero/internal/type_parser_test.go index 529a6a55..96339763 100644 --- a/internal/watzero/internal/type_parser_test.go +++ b/internal/watzero/internal/type_parser_test.go @@ -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 } diff --git a/internal/watzero/internal/typeuse_parser_test.go b/internal/watzero/internal/typeuse_parser_test.go index e75e2d6f..ac152f12 100644 --- a/internal/watzero/internal/typeuse_parser_test.go +++ b/internal/watzero/internal/typeuse_parser_test.go @@ -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) } diff --git a/internal/watzero/watzero_test.go b/internal/watzero/watzero_test.go index 325270bf..965ae1d1 100644 --- a/internal/watzero/watzero_test.go +++ b/internal/watzero/watzero_test.go @@ -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}, diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go index aecd974f..450f2476 100644 --- a/internal/wazeroir/compiler_test.go +++ b/internal/wazeroir/compiler_test.go @@ -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]) diff --git a/wasi_snapshot_preview1/args.go b/wasi_snapshot_preview1/args.go index e0f6928b..69203ceb 100644 --- a/wasi_snapshot_preview1/args.go +++ b/wasi_snapshot_preview1/args.go @@ -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 + }, +) diff --git a/wasi_snapshot_preview1/clock.go b/wasi_snapshot_preview1/clock.go index 7d01f6d2..f0121525 100644 --- a/wasi_snapshot_preview1/clock.go +++ b/wasi_snapshot_preview1/clock.go @@ -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 + }, +) diff --git a/wasi_snapshot_preview1/clock_test.go b/wasi_snapshot_preview1/clock_test.go index c5928995..bbb6ba42 100644 --- a/wasi_snapshot_preview1/clock_test.go +++ b/wasi_snapshot_preview1/clock_test.go @@ -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()) }) } } diff --git a/wasi_snapshot_preview1/environ.go b/wasi_snapshot_preview1/environ.go index 027adcc3..adb34a0f 100644 --- a/wasi_snapshot_preview1/environ.go +++ b/wasi_snapshot_preview1/environ.go @@ -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 + }, +) diff --git a/wasi_snapshot_preview1/environ_test.go b/wasi_snapshot_preview1/environ_test.go index 708c93ef..1f7dd55d 100644 --- a/wasi_snapshot_preview1/environ_test.go +++ b/wasi_snapshot_preview1/environ_test.go @@ -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()) }) } } diff --git a/wasi_snapshot_preview1/fs.go b/wasi_snapshot_preview1/fs.go index 39b09ee5..94249abc 100644 --- a/wasi_snapshot_preview1/fs.go +++ b/wasi_snapshot_preview1/fs.go @@ -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"}, +) diff --git a/wasi_snapshot_preview1/fs_test.go b/wasi_snapshot_preview1/fs_test.go index a4b7e01d..c928afe5 100644 --- a/wasi_snapshot_preview1/fs_test.go +++ b/wasi_snapshot_preview1/fs_test.go @@ -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()) }) } } diff --git a/wasi_snapshot_preview1/poll.go b/wasi_snapshot_preview1/poll.go index d05b6093..192fcde0 100644 --- a/wasi_snapshot_preview1/poll.go +++ b/wasi_snapshot_preview1/poll.go @@ -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. diff --git a/wasi_snapshot_preview1/proc.go b/wasi_snapshot_preview1/proc.go index a539547c..db610cb2 100644 --- a/wasi_snapshot_preview1/proc.go +++ b/wasi_snapshot_preview1/proc.go @@ -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"}) diff --git a/wasi_snapshot_preview1/proc_test.go b/wasi_snapshot_preview1/proc_test.go index 5462d619..9465dc5d 100644 --- a/wasi_snapshot_preview1/proc_test.go +++ b/wasi_snapshot_preview1/proc_test.go @@ -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()) }) } } diff --git a/wasi_snapshot_preview1/random.go b/wasi_snapshot_preview1/random.go index e00eec9e..e685c209 100644 --- a/wasi_snapshot_preview1/random.go +++ b/wasi_snapshot_preview1/random.go @@ -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 + }, +) diff --git a/wasi_snapshot_preview1/random_test.go b/wasi_snapshot_preview1/random_test.go index ff20967a..068cfa70 100644 --- a/wasi_snapshot_preview1/random_test.go +++ b/wasi_snapshot_preview1/random_test.go @@ -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()) }) } } diff --git a/wasi_snapshot_preview1/sched.go b/wasi_snapshot_preview1/sched.go index 3f6f0409..65e74bf5 100644 --- a/wasi_snapshot_preview1/sched.go +++ b/wasi_snapshot_preview1/sched.go @@ -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) diff --git a/wasi_snapshot_preview1/sock.go b/wasi_snapshot_preview1/sock.go index 6344517b..55a847b3 100644 --- a/wasi_snapshot_preview1/sock.go +++ b/wasi_snapshot_preview1/sock.go @@ -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"}, +) diff --git a/wasi_snapshot_preview1/wasi.go b/wasi_snapshot_preview1/wasi.go index 5153441a..45747cbf 100644 --- a/wasi_snapshot_preview1/wasi.go +++ b/wasi_snapshot_preview1/wasi.go @@ -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}}, } } diff --git a/wasi_snapshot_preview1/wasi_bench_test.go b/wasi_snapshot_preview1/wasi_bench_test.go index eb4b1859..0d5d7c76 100644 --- a/wasi_snapshot_preview1/wasi_bench_test.go +++ b/wasi_snapshot_preview1/wasi_bench_test.go @@ -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)) } } })