From 14d892d31002a6976d8656b0a15ca0152f098a66 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Mon, 11 Jul 2022 14:57:26 +0800 Subject: [PATCH] Removes api.ImportRenamer for a different approach to "env" (#680) Before, we introduced a type `api.ImportRenamer` to resolve conflicts where the "env" module was shared between AssemblyScript and user-defined functions. This API was never used in GitHub, and is complicated. AssemblyScript also isn't the only ABI to share the "env" module, as other web APIs like Emscripten do also. The less complicated approach is to have packages that need to share "env" use `ModuleBuilder.ExportFunctions` instead, and use namespaces as needed if there is overlap. Signed-off-by: Adrian Cole --- api/wasm.go | 27 -- assemblyscript/assemblyscript.go | 145 ++++--- assemblyscript/assemblyscript_example_test.go | 45 ++ assemblyscript/assemblyscript_test.go | 405 +++++++++--------- config.go | 25 +- config_test.go | 22 - examples/replace-import/.gitignore | 1 - examples/replace-import/README.md | 4 - examples/replace-import/replace-import.go | 65 --- .../replace-import/replace-import_test.go | 16 - .../replace-import/testdata/needs_import.wasm | Bin 71 -> 0 bytes .../replace-import/testdata/needs_import.wat | 3 - runtime.go | 7 - runtime_test.go | 50 --- 14 files changed, 322 insertions(+), 493 deletions(-) create mode 100644 assemblyscript/assemblyscript_example_test.go delete mode 100644 examples/replace-import/.gitignore delete mode 100644 examples/replace-import/README.md delete mode 100644 examples/replace-import/replace-import.go delete mode 100644 examples/replace-import/replace-import_test.go delete mode 100644 examples/replace-import/testdata/needs_import.wasm delete mode 100644 examples/replace-import/testdata/needs_import.wat 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 a6ce539a47a9d43bfb1510a3fb3d35c4a31f741d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71 zcmZQbEY4+QU|?Y6U`$}C2Lc8rUPk8ByfW6rr2L{11_pLcMi`Spk|i%OH