diff --git a/api/wasm.go b/api/wasm.go index 904644c6..118f3c1d 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -409,33 +409,6 @@ func DecodeF64(input uint64) float64 { return math.Float64frombits(input) } -// ImportRenamer applies during compilation after a module has been decoded from wasm, but before it is instantiated. -// -// For example, you may have a module like below, but the exported functions are in two different modules: -// (import "js" "increment" (func $increment (result i32))) -// (import "js" "decrement" (func $decrement (result i32))) -// (import "js" "wasm_increment" (func $wasm_increment (result i32))) -// (import "js" "wasm_decrement" (func $wasm_decrement (result i32))) -// -// The below breaks up the imports: "increment" and "decrement" from the module "go" and other functions from "wasm": -// renamer := func(externType api.ExternType, oldModule, oldName string) (string, string) { -// if externType != api.ExternTypeFunc { -// return oldModule, oldName -// } -// switch oldName { -// case "increment", "decrement": return "go", oldName -// default: return "wasm", oldName -// } -// } -// -// The resulting CompiledModule imports will look identical to this: -// (import "go" "increment" (func $increment (result i32))) -// (import "go" "decrement" (func $decrement (result i32))) -// (import "wasm" "wasm_increment" (func $wasm_increment (result i32))) -// (import "wasm" "wasm_decrement" (func $wasm_decrement (result i32))) -// -type ImportRenamer func(externType ExternType, oldModule, oldName string) (newModule, newName string) - // MemorySizer applies during compilation after a module has been decoded from wasm, but before it is instantiated. // This determines the amount of memory pages (65536 bytes per page) to use when a memory is instantiated as a []byte. // diff --git a/assemblyscript/assemblyscript.go b/assemblyscript/assemblyscript.go index 7dbca198..50b5ce90 100644 --- a/assemblyscript/assemblyscript.go +++ b/assemblyscript/assemblyscript.go @@ -1,19 +1,24 @@ -// Package assemblyscript contains Go-defined special functions imported by AssemblyScript under the module name "env". +// Package assemblyscript contains Go-defined special functions imported by +// AssemblyScript under the module name "env". // // Special Functions // -// AssemblyScript code import the below special functions when not using WASI. Sometimes only "abort" +// AssemblyScript code import the below special functions when not using WASI. +// Note: Sometimes only "abort" is imported. // -// * "abort" - exits with 255 with an abort message written to wazero.ModuleConfig WithStderr. +// * "abort" - exits with 255 with an abort message written to +// wazero.ModuleConfig WithStderr. // * "trace" - no output unless. -// * "seed" - uses wazero.ModuleConfig WithRandSource as the source of seed values. +// * "seed" - uses wazero.ModuleConfig WithRandSource as the source of seed +// values. // // Relationship to WASI // -// A program compiled to use WASI, via "import wasi" in any file, won't import these functions. -// See wasi_snapshot_preview1.InstantiateSnapshotPreview1 +// A program compiled to use WASI, via "import wasi" in any file, won't import +// these functions. // -// See https://www.assemblyscript.org/concepts.html#special-imports +// See wasi_snapshot_preview1.Instantiate and +// https://www.assemblyscript.org/concepts.html#special-imports package assemblyscript import ( @@ -31,44 +36,46 @@ import ( "github.com/tetratelabs/wazero/sys" ) -// Instantiate instantiates the "env" module used by AssemblyScript into the runtime default namespace. +// Instantiate instantiates the "env" module used by AssemblyScript into the +// runtime default namespace. // // Notes // // * Closing the wazero.Runtime has the same effect as closing the result. -// * To instantiate into another wazero.Namespace, use NewBuilder instead. +// * To add more functions to the "env" module, use FunctionExporter. +// * To instantiate into another wazero.Namespace, use FunctionExporter. func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { - return NewBuilder(r).Instantiate(ctx, r) + return r.NewModuleBuilder("env"). + ExportFunctions(NewFunctionExporter().ExportFunctions()). + Instantiate(ctx, r) } -// Builder configures the "env" module used by AssemblyScript for later use via Compile or Instantiate. -type Builder interface { - // WithAbortMessageDisabled configures the AssemblyScript abort function to discard any message. - WithAbortMessageDisabled() Builder +// FunctionExporter configures the functions in the "env" module used by +// AssemblyScript. +type FunctionExporter interface { + // WithAbortMessageDisabled configures the AssemblyScript abort function to + // discard any message. + WithAbortMessageDisabled() FunctionExporter - // WithTraceToStdout configures the AssemblyScript trace function to output messages to Stdout, as configured by - // wazero.ModuleConfig WithStdout. - WithTraceToStdout() Builder + // WithTraceToStdout configures the AssemblyScript trace function to output + // messages to Stdout, as configured by wazero.ModuleConfig WithStdout. + WithTraceToStdout() FunctionExporter - // WithTraceToStderr configures the AssemblyScript trace function to output messages to Stderr, as configured by - // wazero.ModuleConfig WithStderr. Because of the potential volume of trace messages, it is often more appropriate - // to use WithTraceToStdout instead. - WithTraceToStderr() Builder - - // Compile compiles the "env" module that can instantiated in any namespace (wazero.Namespace). + // WithTraceToStderr configures the AssemblyScript trace function to output + // messages to Stderr, as configured by wazero.ModuleConfig WithStderr. // - // Note: This has the same effect as the same function on wazero.ModuleBuilder. - Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error) + // Because of the potential volume of trace messages, it is often more + // appropriate to use WithTraceToStdout instead. + WithTraceToStderr() FunctionExporter - // Instantiate instantiates the "env" module into the provided namespace. - // - // Note: This has the same effect as the same function on wazero.ModuleBuilder. - Instantiate(context.Context, wazero.Namespace) (api.Closer, error) + // ExportFunctions builds functions to export with a wazero.ModuleBuilder + // named "env". + ExportFunctions() (nameToGoFunc map[string]interface{}) } -// NewBuilder returns a new Builder with trace disabled. -func NewBuilder(r wazero.Runtime) Builder { - return &builder{r: r, traceMode: traceDisabled} +// NewFunctionExporter returns a FunctionExporter object with trace disabled. +func NewFunctionExporter() FunctionExporter { + return &functionExporter{traceMode: traceDisabled} } type traceMode int @@ -79,54 +86,44 @@ const ( traceStderr traceMode = 2 ) -type builder struct { - r wazero.Runtime +type functionExporter struct { abortMessageDisabled bool traceMode traceMode } -// WithAbortMessageDisabled implements Builder.WithAbortMessageDisabled -func (b *builder) WithAbortMessageDisabled() Builder { - ret := *b // copy +// WithAbortMessageDisabled implements FunctionExporter.WithAbortMessageDisabled +func (e *functionExporter) WithAbortMessageDisabled() FunctionExporter { + ret := *e // copy ret.abortMessageDisabled = true return &ret } -// WithTraceToStdout implements Builder.WithTraceToStdout -func (b *builder) WithTraceToStdout() Builder { - ret := *b // copy +// WithTraceToStdout implements FunctionExporter.WithTraceToStdout +func (e *functionExporter) WithTraceToStdout() FunctionExporter { + ret := *e // copy ret.traceMode = traceStdout return &ret } -// WithTraceToStderr implements Builder.WithTraceToStderr -func (b *builder) WithTraceToStderr() Builder { - ret := *b // copy +// WithTraceToStderr implements FunctionExporter.WithTraceToStderr +func (e *functionExporter) WithTraceToStderr() FunctionExporter { + ret := *e // copy ret.traceMode = traceStderr return &ret } -// moduleBuilder returns a new wazero.ModuleBuilder -func (b *builder) moduleBuilder() wazero.ModuleBuilder { - env := &assemblyscript{abortMessageDisabled: b.abortMessageDisabled, traceMode: b.traceMode} - return b.r.NewModuleBuilder("env"). - ExportFunction("abort", env.abort). - ExportFunction("trace", env.trace). - ExportFunction("seed", env.seed) +// ExportFunctions implements FunctionExporter.ExportFunctions +func (e *functionExporter) ExportFunctions() (nameToGoFunc map[string]interface{}) { + env := &assemblyscript{abortMessageDisabled: e.abortMessageDisabled, traceMode: e.traceMode} + return map[string]interface{}{ + "abort": env.abort, + "trace": env.trace, + "seed": env.seed, + } } -// Compile implements Builder.Compile -func (b *builder) Compile(ctx context.Context, config wazero.CompileConfig) (wazero.CompiledModule, error) { - return b.moduleBuilder().Compile(ctx, config) -} - -// Instantiate implements Builder.Instantiate -func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Closer, error) { - return b.moduleBuilder().Instantiate(ctx, ns) -} - -// assemblyScript includes "Special imports" only used In AssemblyScript when a user didn't add `import "wasi"` to their -// entry file. +// assemblyScript includes "Special imports" only used In AssemblyScript when a +// user didn't add `import "wasi"` to their entry file. // // See https://www.assemblyscript.org/concepts.html#special-imports // See https://www.assemblyscript.org/concepts.html#targeting-wasi @@ -137,13 +134,15 @@ type assemblyscript struct { traceMode traceMode } -// abort is called on unrecoverable errors. This is typically present in Wasm compiled from AssemblyScript, if -// assertions are enabled or errors are thrown. +// abort is called on unrecoverable errors. This is typically present in Wasm +// compiled from AssemblyScript, if assertions are enabled or errors are +// thrown. // -// The implementation writes the message to stderr, unless abortMessageDisabled, and closes the module with exit code -// 255. +// The implementation writes the message to stderr, unless +// abortMessageDisabled, and closes the module with exit code 255. // -// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format: +// Here's the import in a user's module that ends up using this, in WebAssembly +// 1.0 (MVP) Text Format: // (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 @@ -179,9 +178,11 @@ func (a *assemblyscript) abort( panic(sys.NewExitError(mod.Name(), exitCode)) } -// trace implements the same named function in AssemblyScript (ex. trace('Hello World!')) +// trace implements the same named function in AssemblyScript. Ex. +// trace('Hello World!') // -// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format: +// Here's the import in a user's module that ends up using this, in WebAssembly +// 1.0 (MVP) Text Format: // (import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64))) // // See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L61 @@ -236,9 +237,11 @@ func formatFloat(f float64) string { return strconv.FormatFloat(f, 'g', -1, 64) } -// seed is called when the AssemblyScript's random number generator needs to be seeded +// seed is called when the AssemblyScript's random number generator needs to be +// seeded. // -// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format: +// Here's the import in a user's module that ends up using this, in WebAssembly +// 1.0 (MVP) Text Format: // (import "env" "seed" (func $~lib/builtins/seed (result f64))) // // See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L111 diff --git a/assemblyscript/assemblyscript_example_test.go b/assemblyscript/assemblyscript_example_test.go new file mode 100644 index 00000000..56d0d811 --- /dev/null +++ b/assemblyscript/assemblyscript_example_test.go @@ -0,0 +1,45 @@ +package assemblyscript_test + +import ( + "context" + _ "embed" + "log" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/assemblyscript" +) + +// This shows how to instantiate AssemblyScript's special imports. +func Example_instantiate() { + ctx := context.Background() + + r := wazero.NewRuntime() + defer r.Close(ctx) // This closes everything this Runtime created. + + // This adds the "env" module to the runtime, with AssemblyScript's special + // function imports. + if _, err := assemblyscript.Instantiate(ctx, r); err != nil { + log.Panicln(err) + } + + // Output: +} + +// This shows how to instantiate AssemblyScript's special imports when you also +// need other functions in the "env" module. +func Example_functionExporter() { + ctx := context.Background() + + r := wazero.NewRuntime() + defer r.Close(ctx) // This closes everything this Runtime created. + + // First construct your own module builder for "env" + envBuilder := r.NewModuleBuilder("env"). + ExportFunction("get_int", func() uint32 { return 1 }) + + // Now, add AssemblyScript special function imports into it. + envBuilder.ExportFunctions(assemblyscript.NewFunctionExporter(). + WithAbortMessageDisabled().ExportFunctions()) + + // Output: +} diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go index 4cbd3470..92d9c59c 100644 --- a/assemblyscript/assemblyscript_test.go +++ b/assemblyscript/assemblyscript_test.go @@ -41,17 +41,17 @@ var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") func TestAbort(t *testing.T) { tests := []struct { name string - enabled bool + exporter FunctionExporter expected string }{ { name: "enabled", - enabled: true, + exporter: NewFunctionExporter(), expected: "message at filename:1:2\n", }, { name: "disabled", - enabled: false, + exporter: NewFunctionExporter().WithAbortMessageDisabled(), expected: "", }, } @@ -65,13 +65,10 @@ func TestAbort(t *testing.T) { out := &bytes.Buffer{} - if tc.enabled { - _, err := Instantiate(testCtx, r) - require.NoError(t, err) - } else { - _, err := NewBuilder(r).WithAbortMessageDisabled().Instantiate(testCtx, r) - require.NoError(t, err) - } + _, err := r.NewModuleBuilder("env"). + ExportFunctions(tc.exporter.ExportFunctions()). + Instantiate(testCtx, r) + require.NoError(t, err) abortWasm, err := watzero.Wat2Wasm(abortWat) require.NoError(t, err) @@ -93,6 +90,54 @@ func TestAbort(t *testing.T) { } } +func TestAbort_Error(t *testing.T) { + tests := []struct { + name string + messageUTF16 []byte + fileNameUTF16 []byte + }{ + { + name: "bad message", + messageUTF16: encodeUTF16("message")[:5], + fileNameUTF16: encodeUTF16("filename"), + }, + { + name: "bad filename", + messageUTF16: encodeUTF16("message"), + fileNameUTF16: encodeUTF16("filename")[:5], + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + r := wazero.NewRuntime() + defer r.Close(testCtx) + + _, err := Instantiate(testCtx, r) + require.NoError(t, err) + + abortWasm, err := watzero.Wat2Wasm(abortWat) + require.NoError(t, err) + + compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig()) + require.NoError(t, err) + + out := &bytes.Buffer{} + exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out) + mod, err := r.InstantiateModule(testCtx, compiled, exporter) + require.NoError(t, err) + + messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16) + + _, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2) + require.NoError(t, err) + require.Equal(t, "", out.String()) // nothing output if strings fail to read. + }) + } +} + var unreachableAfterAbort = `(module (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (func $main @@ -106,12 +151,15 @@ var unreachableAfterAbort = `(module (start $main) )` -// TestAbort_StopsExecution ensures code that follows an abort isn't invoked. -func TestAbort_StopsExecution(t *testing.T) { +// TestAbort_UnreachableAfter ensures code that follows an abort isn't invoked. +func TestAbort_UnreachableAfter(t *testing.T) { r := wazero.NewRuntime() defer r.Close(testCtx) - _, err := NewBuilder(r).WithAbortMessageDisabled().Instantiate(testCtx, r) + _, err := r.NewModuleBuilder("env"). + // Disable the abort message as we are passing invalid memory offsets. + ExportFunctions(NewFunctionExporter().WithAbortMessageDisabled().ExportFunctions()). + Instantiate(testCtx, r) require.NoError(t, err) abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort) @@ -149,47 +197,95 @@ func TestSeed(t *testing.T) { require.Equal(t, uint64(506097522914230528), res[0]) } +func TestSeed_error(t *testing.T) { + tests := []struct { + name string + source io.Reader + expectedErr string + }{ + { + name: "not 8 bytes", + source: bytes.NewReader([]byte{0, 1}), + expectedErr: `error reading random seed: unexpected EOF (recovered by wazero) +wasm stack trace: + env.seed() f64`, + }, + { + name: "error reading", + source: iotest.ErrReader(errors.New("ice cream")), + expectedErr: `error reading random seed: ice cream (recovered by wazero) +wasm stack trace: + env.seed() f64`, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + r := wazero.NewRuntime() + defer r.Close(testCtx) + + _, err := Instantiate(testCtx, r) + require.NoError(t, err) + + seedWasm, err := watzero.Wat2Wasm(seedWat) + require.NoError(t, err) + + compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig()) + require.NoError(t, err) + + exporter := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source) + mod, err := r.InstantiateModule(testCtx, compiled, exporter) + require.NoError(t, err) + + _, err = mod.ExportedFunction("seed").Call(testCtx) + require.EqualError(t, err, tc.expectedErr) + }) + } +} + func TestTrace(t *testing.T) { noArgs := []uint64{4, 0, 0, 0, 0, 0, 0} tests := []struct { name string - mode traceMode + exporter FunctionExporter params []uint64 expected string }{ - { - name: "stderr", - mode: traceStderr, - params: noArgs, - expected: "trace: hello\n", - }, - { - name: "stdout", - mode: traceStdout, - params: noArgs, - expected: "trace: hello\n", - }, { name: "disabled", - mode: traceDisabled, + exporter: NewFunctionExporter(), params: noArgs, expected: "", }, { - name: "one", - mode: traceStdout, + name: "ToStderr", + exporter: NewFunctionExporter().WithTraceToStderr(), + params: noArgs, + expected: "trace: hello\n", + }, + { + name: "ToStdout - no args", + exporter: NewFunctionExporter().WithTraceToStdout(), + params: noArgs, + expected: "trace: hello\n", + }, + { + name: "ToStdout - one arg", + exporter: NewFunctionExporter().WithTraceToStdout(), params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0}, expected: "trace: hello 1\n", }, { - name: "two", - mode: traceStdout, + name: "ToStdout - two args", + exporter: NewFunctionExporter().WithTraceToStdout(), params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0}, expected: "trace: hello 1,2\n", }, { - name: "five", - mode: traceStdout, + name: "ToStdout - five args", + exporter: NewFunctionExporter().WithTraceToStdout(), params: []uint64{ 4, 5, @@ -210,24 +306,9 @@ func TestTrace(t *testing.T) { r := wazero.NewRuntime() defer r.Close(testCtx) - out := &bytes.Buffer{} - - as := NewBuilder(r) - modConfig := wazero.NewModuleConfig() - switch tc.mode { - case traceStderr: - as = as.WithTraceToStderr() - modConfig = modConfig.WithStderr(out) - case traceStdout: - as = as.WithTraceToStdout() - modConfig = modConfig.WithStdout(out) - case traceDisabled: - // Set but not used - modConfig = modConfig.WithStderr(out) - modConfig = modConfig.WithStdout(out) - } - - _, err := as.Instantiate(testCtx, r) + _, err := r.NewModuleBuilder("env"). + ExportFunctions(tc.exporter.ExportFunctions()). + Instantiate(testCtx, r) require.NoError(t, err) traceWasm, err := watzero.Wat2Wasm(traceWat) @@ -236,7 +317,15 @@ func TestTrace(t *testing.T) { code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig()) require.NoError(t, err) - mod, err := r.InstantiateModule(testCtx, code, modConfig) + out := &bytes.Buffer{} + config := wazero.NewModuleConfig() + if tc.name == "ToStderr" { + config.WithStderr(out) + } else { + config.WithStdout(out) + } + + mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithStdout(out).WithStderr(out)) require.NoError(t, err) message := encodeUTF16("hello") @@ -252,7 +341,65 @@ func TestTrace(t *testing.T) { } } -func TestReadAssemblyScriptString(t *testing.T) { +func TestTrace_error(t *testing.T) { + tests := []struct { + name string + message []byte + out io.Writer + expectedErr string + }{ + { + name: "not 8 bytes", + message: encodeUTF16("hello")[:5], + out: &bytes.Buffer{}, + expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero) +wasm stack trace: + env.trace(i32,i32,f64,f64,f64,f64,f64)`, + }, + { + name: "error writing", + message: encodeUTF16("hello"), + out: &errWriter{err: errors.New("ice cream")}, + expectedErr: `ice cream (recovered by wazero) +wasm stack trace: + env.trace(i32,i32,f64,f64,f64,f64,f64)`, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + r := wazero.NewRuntime() + defer r.Close(testCtx) + + _, err := r.NewModuleBuilder("env"). + ExportFunctions(NewFunctionExporter().WithTraceToStdout().ExportFunctions()). + Instantiate(testCtx, r) + require.NoError(t, err) + + traceWasm, err := watzero.Wat2Wasm(traceWat) + require.NoError(t, err) + + compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig()) + require.NoError(t, err) + + exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out) + mod, err := r.InstantiateModule(testCtx, compiled, exporter) + require.NoError(t, err) + + ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message))) + require.True(t, ok) + ok = mod.Memory().Write(testCtx, uint32(4), tc.message) + require.True(t, ok) + + _, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0) + require.EqualError(t, err, tc.expectedErr) + }) + } +} + +func Test_readAssemblyScriptString(t *testing.T) { tests := []struct { name string memory func(api.Memory) @@ -325,54 +472,6 @@ func TestReadAssemblyScriptString(t *testing.T) { } } -func TestAbort_error(t *testing.T) { - tests := []struct { - name string - messageUTF16 []byte - fileNameUTF16 []byte - }{ - { - name: "bad message", - messageUTF16: encodeUTF16("message")[:5], - fileNameUTF16: encodeUTF16("filename"), - }, - { - name: "bad filename", - messageUTF16: encodeUTF16("message"), - fileNameUTF16: encodeUTF16("filename")[:5], - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - r := wazero.NewRuntime() - defer r.Close(testCtx) - - _, err := Instantiate(testCtx, r) - require.NoError(t, err) - - abortWasm, err := watzero.Wat2Wasm(abortWat) - require.NoError(t, err) - - compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig()) - require.NoError(t, err) - - out := &bytes.Buffer{} - config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out) - mod, err := r.InstantiateModule(testCtx, compiled, config) - require.NoError(t, err) - - messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16) - - _, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2) - require.NoError(t, err) - require.Equal(t, "", out.String()) // nothing output if strings fail to read. - }) - } -} - func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) { off := 0 ok := mem.WriteUint32Le(testCtx, uint32(off), uint32(len(messageUTF16))) @@ -391,110 +490,6 @@ func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fi return messageOff, filenameOff } -func TestSeed_error(t *testing.T) { - tests := []struct { - name string - source io.Reader - expectedErr string - }{ - { - name: "not 8 bytes", - source: bytes.NewReader([]byte{0, 1}), - expectedErr: `error reading random seed: unexpected EOF (recovered by wazero) -wasm stack trace: - env.seed() f64`, - }, - { - name: "error reading", - source: iotest.ErrReader(errors.New("ice cream")), - expectedErr: `error reading random seed: ice cream (recovered by wazero) -wasm stack trace: - env.seed() f64`, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - r := wazero.NewRuntime() - defer r.Close(testCtx) - - _, err := Instantiate(testCtx, r) - require.NoError(t, err) - - seedWasm, err := watzero.Wat2Wasm(seedWat) - require.NoError(t, err) - - compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig()) - require.NoError(t, err) - - config := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source) - mod, err := r.InstantiateModule(testCtx, compiled, config) - require.NoError(t, err) - - _, err = mod.ExportedFunction("seed").Call(testCtx) - require.EqualError(t, err, tc.expectedErr) - }) - } -} - -func TestTrace_error(t *testing.T) { - tests := []struct { - name string - message []byte - out io.Writer - expectedErr string - }{ - { - name: "not 8 bytes", - message: encodeUTF16("hello")[:5], - out: &bytes.Buffer{}, - expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero) -wasm stack trace: - env.trace(i32,i32,f64,f64,f64,f64,f64)`, - }, - { - name: "error writing", - message: encodeUTF16("hello"), - out: &errWriter{err: errors.New("ice cream")}, - expectedErr: `ice cream (recovered by wazero) -wasm stack trace: - env.trace(i32,i32,f64,f64,f64,f64,f64)`, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - r := wazero.NewRuntime() - defer r.Close(testCtx) - - _, err := NewBuilder(r).WithTraceToStdout().Instantiate(testCtx, r) - require.NoError(t, err) - - traceWasm, err := watzero.Wat2Wasm(traceWat) - require.NoError(t, err) - - compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig()) - require.NoError(t, err) - - config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out) - mod, err := r.InstantiateModule(testCtx, compiled, config) - require.NoError(t, err) - - ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message))) - require.True(t, ok) - ok = mod.Memory().Write(testCtx, uint32(4), tc.message) - require.True(t, ok) - - _, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0) - require.EqualError(t, err, tc.expectedErr) - }) - } -} - func encodeUTF16(s string) []byte { runes := utf16.Encode([]rune(s)) b := make([]byte, len(runes)*2) diff --git a/config.go b/config.go index 919fb4e2..9b9ef31e 100644 --- a/config.go +++ b/config.go @@ -291,32 +291,23 @@ func (c *compiledModule) Close(_ context.Context) error { // CompileConfig allows you to override what was decoded from wasm, prior to compilation (ModuleBuilder.Compile or // Runtime.CompileModule). // -// For example, WithImportRenamer allows you to override hard-coded names that don't match your requirements. +// For example, WithMemorySizer allows you to override memoryc size that doesn't match your requirements. // // Note: CompileConfig is immutable. Each WithXXX function returns a new instance including the corresponding change. type CompileConfig interface { - - // WithImportRenamer can rename imports or break them into different modules. No default. - // A nil function is invalid and ignored. - // - // Note: This is currently not relevant for ModuleBuilder as it has no means to define imports. - WithImportRenamer(api.ImportRenamer) CompileConfig - // WithMemorySizer are the allocation parameters used for a Wasm memory. // The default is to set cap=min and max=65536 if unset. A nil function is invalid and ignored. WithMemorySizer(api.MemorySizer) CompileConfig } type compileConfig struct { - importRenamer api.ImportRenamer - memorySizer api.MemorySizer + memorySizer api.MemorySizer } // NewCompileConfig returns a CompileConfig that can be used for configuring module compilation. func NewCompileConfig() CompileConfig { return &compileConfig{ - importRenamer: nil, - memorySizer: wasm.MemorySizer, + memorySizer: wasm.MemorySizer, } } @@ -326,16 +317,6 @@ func (c *compileConfig) clone() *compileConfig { return &ret } -// WithImportRenamer implements CompileConfig.WithImportRenamer -func (c *compileConfig) WithImportRenamer(importRenamer api.ImportRenamer) CompileConfig { - if importRenamer == nil { - return c - } - ret := c.clone() - ret.importRenamer = importRenamer - return ret -} - // WithMemorySizer implements CompileConfig.WithMemorySizer func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConfig { if memorySizer == nil { diff --git a/config_test.go b/config_test.go index 0ece5ca9..c1188c46 100644 --- a/config_test.go +++ b/config_test.go @@ -9,7 +9,6 @@ import ( "testing" "testing/fstest" - "github.com/tetratelabs/wazero/api" internalsys "github.com/tetratelabs/wazero/internal/sys" testfs "github.com/tetratelabs/wazero/internal/testing/fs" "github.com/tetratelabs/wazero/internal/testing/require" @@ -190,12 +189,6 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) { } func TestCompileConfig(t *testing.T) { - im := func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) { - return "a", oldName - } - im2 := func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) { - return "b", oldName - } mp := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { return 0, 1, 1 } @@ -204,20 +197,6 @@ func TestCompileConfig(t *testing.T) { with func(CompileConfig) CompileConfig expected *compileConfig }{ - { - name: "WithImportRenamer", - with: func(c CompileConfig) CompileConfig { - return c.WithImportRenamer(im) - }, - expected: &compileConfig{importRenamer: im}, - }, - { - name: "WithImportRenamer twice", - with: func(c CompileConfig) CompileConfig { - return c.WithImportRenamer(im).WithImportRenamer(im2) - }, - expected: &compileConfig{importRenamer: im2}, - }, { name: "WithMemorySizer", with: func(c CompileConfig) CompileConfig { @@ -242,7 +221,6 @@ func TestCompileConfig(t *testing.T) { // We cannot compare func, but we can compare reflect.Value // See https://go.dev/ref/spec#Comparison_operators - require.Equal(t, reflect.ValueOf(tc.expected.importRenamer), reflect.ValueOf(rc.importRenamer)) require.Equal(t, reflect.ValueOf(tc.expected.memorySizer), reflect.ValueOf(rc.memorySizer)) // The source wasn't modified require.Equal(t, &compileConfig{}, input) diff --git a/examples/replace-import/.gitignore b/examples/replace-import/.gitignore deleted file mode 100644 index 2885c81e..00000000 --- a/examples/replace-import/.gitignore +++ /dev/null @@ -1 +0,0 @@ -replace-import diff --git a/examples/replace-import/README.md b/examples/replace-import/README.md deleted file mode 100644 index 394c8b40..00000000 --- a/examples/replace-import/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## Replace import example - -This example shows how to override a module name hard-coded in a WebAssembly -module. This is similar to what some tools call "linking". diff --git a/examples/replace-import/replace-import.go b/examples/replace-import/replace-import.go deleted file mode 100644 index 86e9e21a..00000000 --- a/examples/replace-import/replace-import.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "context" - _ "embed" - "fmt" - "log" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/api" -) - -// needsImportWasm was generated by the following: -// cd testdata; wat2wasm --debug-names needs_import.wat -//go:embed testdata/needs_import.wasm -var needsImportWasm []byte - -// main shows how to override a module or function name hard-coded in a -// WebAssembly module. This is similar to what some tools call "linking". -func main() { - // Choose the context to use for function calls. - ctx := context.Background() - - // Create a new WebAssembly Runtime. - r := wazero.NewRuntime() - defer r.Close(ctx) // This closes everything this Runtime created. - - // Instantiate a Go-defined module named "assemblyscript" that exports a - // function to close the module that calls "abort". - host, err := r.NewModuleBuilder("assemblyscript"). - ExportFunction("abort", func(ctx context.Context, m api.Module, messageOffset, fileNameOffset, line, col uint32) { - _ = m.CloseWithExitCode(ctx, 255) - }).Instantiate(ctx, r) - if err != nil { - log.Panicln(err) - } - defer host.Close(ctx) - - // Compile the WebAssembly module, replacing the import "env.abort" with - // "assemblyscript.abort". - compileConfig := wazero.NewCompileConfig(). - WithImportRenamer(func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) { - if oldModule == "env" && oldName == "abort" { - return "assemblyscript", "abort" - } - return oldModule, oldName - }) - - code, err := r.CompileModule(ctx, needsImportWasm, compileConfig) - if err != nil { - log.Panicln(err) - } - defer code.Close(ctx) - - // Instantiate the WebAssembly module. - mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig()) - if err != nil { - log.Panicln(err) - } - defer mod.Close(ctx) - - // Since the above worked, the exported function closes the module. - _, err = mod.ExportedFunction("abort").Call(ctx, 0, 0, 0, 0) - fmt.Println(err) -} diff --git a/examples/replace-import/replace-import_test.go b/examples/replace-import/replace-import_test.go deleted file mode 100644 index 64ae1117..00000000 --- a/examples/replace-import/replace-import_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "testing" - - "github.com/tetratelabs/wazero/internal/testing/maintester" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -// Test_main ensures the following will work: -// -// go run replace-import.go -func Test_main(t *testing.T) { - stdout, _ := maintester.TestMain(t, main, "replace-import") - require.Equal(t, "module \"needs-import\" closed with exit_code(255)\n", stdout) -} diff --git a/examples/replace-import/testdata/needs_import.wasm b/examples/replace-import/testdata/needs_import.wasm deleted file mode 100644 index a6ce539a..00000000 Binary files a/examples/replace-import/testdata/needs_import.wasm and /dev/null differ diff --git a/examples/replace-import/testdata/needs_import.wat b/examples/replace-import/testdata/needs_import.wat deleted file mode 100644 index 9a0d3cf0..00000000 --- a/examples/replace-import/testdata/needs_import.wat +++ /dev/null @@ -1,3 +0,0 @@ -(module $needs-import - (func (export "abort") (import "env" "abort") (param i32 i32 i32 i32)) -) diff --git a/runtime.go b/runtime.go index 5ac81e59..c36e2b4a 100644 --- a/runtime.go +++ b/runtime.go @@ -174,13 +174,6 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte, cConfig Comp return nil, err } - // Replace imports if any configuration exists to do so. - if importRenamer := config.importRenamer; importRenamer != nil { - for _, i := range internal.ImportSection { - i.Module, i.Name = importRenamer(i.Type, i.Module, i.Name) - } - } - internal.AssignModuleID(binary) if err = r.store.Engine.CompileModule(ctx, internal); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 08b41e78..e33a3216 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -78,56 +78,6 @@ func TestRuntime_CompileModule(t *testing.T) { Max: 3, }, code.module.MemorySection) }) - - t.Run("WithImportReplacements", func(t *testing.T) { - testBin, err := watzero.Wat2Wasm(`(module - (import "js" "increment" (func $increment (result i32))) - (import "js" "decrement" (func $decrement (result i32))) - (import "js" "wasm_increment" (func $wasm_increment (result i32))) - (import "js" "wasm_decrement" (func $wasm_decrement (result i32))) -)`) - require.NoError(t, err) - - m, err := r.CompileModule(testCtx, testBin, NewCompileConfig(). - WithImportRenamer(func(externType api.ExternType, oldModule, oldName string) (string, string) { - if externType != api.ExternTypeFunc { - return oldModule, oldName - } - switch oldName { - case "increment", "decrement": - return "go", oldName - case "wasm_increment", "wasm_decrement": - return "wasm", oldName - default: - return oldModule, oldName - } - })) - require.NoError(t, err) - code := m.(*compiledModule) - - require.Equal(t, []*wasm.Import{ - { - Module: "go", Name: "increment", - Type: wasm.ExternTypeFunc, - DescFunc: 0, - }, - { - Module: "go", Name: "decrement", - Type: wasm.ExternTypeFunc, - DescFunc: 0, - }, - { - Module: "wasm", Name: "wasm_increment", - Type: wasm.ExternTypeFunc, - DescFunc: 0, - }, - { - Module: "wasm", Name: "wasm_decrement", - Type: wasm.ExternTypeFunc, - DescFunc: 0, - }, - }, code.module.ImportSection) - }) } func TestRuntime_CompileModule_Errors(t *testing.T) {