From 5a2bbcfa507f83a37b7d434a3bbab2813f81850d Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sat, 28 May 2022 18:46:28 -0700 Subject: [PATCH 1/2] Drops support for the WebAssembly text format This drops the text format (%.wat) and renames InstantiateModuleFromCode to InstantiateModuleFromBinary as it is no longer ambiguous. We decided to stop supporting the text format as it isn't typically used in production, yet costs a lot of work to develop. Given the resources available and the increased work added with WebAssembly 2.0 and soon WASI 2, we can't afford to spend the time on it. The old parser is used only internally and will eventually be moved to its own repository named watzero, possibly towards archival. See #59 Signed-off-by: Adrian Cole --- README.md | 4 +- api/wasm.go | 4 +- assemblyscript/assemblyscript_test.go | 31 +++- config.go | 4 +- example_test.go | 19 ++- examples/allocation/rust/greet.go | 2 +- examples/allocation/tinygo/greet.go | 2 +- examples/basic/add.go | 14 +- examples/basic/testdata/add.wasm | Bin 0 -> 71 bytes examples/basic/testdata/add.wat | 15 ++ examples/import-go/age-calculator.go | 39 +---- .../import-go/testdata/age_calculator.wasm | Bin 0 -> 188 bytes .../import-go/testdata/age_calculator.wat | 25 ++++ examples/multiple-results/README.md | 3 +- examples/multiple-results/multiple-results.go | 127 +++++++--------- .../multiple-results/testdata/multi_value.wat | 16 ++ .../testdata/result_offset.wat | 32 ++++ examples/namespace/counter.go | 3 +- examples/replace-import/replace-import.go | 14 +- .../replace-import/testdata/needs_import.wat | 3 + experimental/fs_example_test.go | 26 ++-- experimental/listener_example_test.go | 14 +- experimental/testdata/clock.wat | 9 ++ experimental/testdata/fs.wat | 9 ++ experimental/testdata/listener.wat | 7 + experimental/time_example_test.go | 15 +- internal/integration_test/bench/bench_test.go | 4 +- internal/integration_test/bench/codec_test.go | 131 ++++++++++++++++ .../integration_test/engine/adhoc_test.go | 34 +++-- .../integration_test/engine/hammer_test.go | 12 +- internal/integration_test/fs/fs_test.go | 22 +-- internal/integration_test/fs/testdata/fs.wat | 20 +++ .../integration_test/spectest/spectest.go | 21 +-- internal/integration_test/vs/codec_test.go | 71 --------- internal/wasm/host.go | 2 +- internal/wasm/memory.go | 2 +- internal/wasm/module.go | 14 +- .../text => watzero/internal}/decoder.go | 2 +- .../text => watzero/internal}/decoder_test.go | 2 +- .../{wasm/text => watzero/internal}/errors.go | 2 +- .../text => watzero/internal}/errors_test.go | 2 +- .../text => watzero/internal}/func_parser.go | 2 +- .../internal}/func_parser_test.go | 2 +- .../internal}/index_namespace.go | 2 +- .../internal}/index_namespace_test.go | 2 +- .../{wasm/text => watzero/internal}/lexer.go | 2 +- .../text => watzero/internal}/lexer_test.go | 2 +- .../internal}/memory_parser.go | 2 +- .../internal}/memory_parser_test.go | 2 +- .../text => watzero/internal}/numbers.go | 2 +- .../text => watzero/internal}/numbers_test.go | 2 +- .../{wasm/text => watzero/internal}/token.go | 2 +- .../text => watzero/internal}/token_test.go | 2 +- .../text => watzero/internal}/type_parser.go | 2 +- .../internal}/type_parser_test.go | 2 +- .../internal}/typeuse_parser.go | 2 +- .../internal}/typeuse_parser_test.go | 2 +- .../vs => watzero}/testdata/example.wat | 0 internal/watzero/watzero.go | 23 +++ .../vs/codec.go => watzero/watzero_test.go} | 41 ++--- internal/wazeroir/compiler_test.go | 9 +- namespace.go | 2 +- runtime.go | 51 +++---- runtime_test.go | 141 +++++++++--------- site/content/_index.md | 4 +- wasi_snapshot_preview1/example_test.go | 18 +-- .../testdata/exit_on_start.wat | 9 ++ wasi_snapshot_preview1/wasi.go | 2 +- wasi_snapshot_preview1/wasi_test.go | 8 +- 69 files changed, 651 insertions(+), 467 deletions(-) create mode 100644 examples/basic/testdata/add.wasm create mode 100644 examples/basic/testdata/add.wat create mode 100644 examples/import-go/testdata/age_calculator.wasm create mode 100644 examples/import-go/testdata/age_calculator.wat create mode 100644 examples/multiple-results/testdata/multi_value.wat create mode 100644 examples/multiple-results/testdata/result_offset.wat create mode 100644 examples/replace-import/testdata/needs_import.wat create mode 100644 experimental/testdata/clock.wat create mode 100644 experimental/testdata/fs.wat create mode 100644 experimental/testdata/listener.wat create mode 100644 internal/integration_test/bench/codec_test.go create mode 100644 internal/integration_test/fs/testdata/fs.wat delete mode 100644 internal/integration_test/vs/codec_test.go rename internal/{wasm/text => watzero/internal}/decoder.go (99%) rename internal/{wasm/text => watzero/internal}/decoder_test.go (99%) rename internal/{wasm/text => watzero/internal}/errors.go (99%) rename internal/{wasm/text => watzero/internal}/errors_test.go (99%) rename internal/{wasm/text => watzero/internal}/func_parser.go (99%) rename internal/{wasm/text => watzero/internal}/func_parser_test.go (99%) rename internal/{wasm/text => watzero/internal}/index_namespace.go (99%) rename internal/{wasm/text => watzero/internal}/index_namespace_test.go (99%) rename internal/{wasm/text => watzero/internal}/lexer.go (99%) rename internal/{wasm/text => watzero/internal}/lexer_test.go (99%) rename internal/{wasm/text => watzero/internal}/memory_parser.go (99%) rename internal/{wasm/text => watzero/internal}/memory_parser_test.go (99%) rename internal/{wasm/text => watzero/internal}/numbers.go (99%) rename internal/{wasm/text => watzero/internal}/numbers_test.go (99%) rename internal/{wasm/text => watzero/internal}/token.go (99%) rename internal/{wasm/text => watzero/internal}/token_test.go (97%) rename internal/{wasm/text => watzero/internal}/type_parser.go (99%) rename internal/{wasm/text => watzero/internal}/type_parser_test.go (99%) rename internal/{wasm/text => watzero/internal}/typeuse_parser.go (99%) rename internal/{wasm/text => watzero/internal}/typeuse_parser_test.go (99%) rename internal/{integration_test/vs => watzero}/testdata/example.wat (100%) create mode 100644 internal/watzero/watzero.go rename internal/{integration_test/vs/codec.go => watzero/watzero_test.go} (80%) create mode 100644 wasi_snapshot_preview1/testdata/exit_on_start.wat diff --git a/README.md b/README.md index 19d40e4f1e..cbe1caf3eb 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ func main() { // Read a WebAssembly binary containing an exported "fac" function. // * Ex. (func (export "fac") (param i64) (result i64) ... - source, err := os.ReadFile("./path/to/fac.wasm") + wasm, err := os.ReadFile("./path/to/fac.wasm") if err != nil { log.Panicln(err) } @@ -35,7 +35,7 @@ func main() { defer r.Close(ctx) // This closes everything this Runtime created. // Instantiate the module and return its exported functions - module, err := r.InstantiateModuleFromCode(ctx, source) + module, err := r.InstantiateModuleFromBinary(ctx, wasm) if err != nil { log.Panicln(err) } diff --git a/api/wasm.go b/api/wasm.go index 5768c12c33..9896f06fba 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -393,7 +393,7 @@ func DecodeF64(input uint64) float64 { return math.Float64frombits(input) } -// ImportRenamer applies during compilation after a module has been decoded from source, but before it is instantiated. +// 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))) @@ -420,7 +420,7 @@ func DecodeF64(input uint64) float64 { // type ImportRenamer func(externType ExternType, oldModule, oldName string) (newModule, newName string) -// MemorySizer applies during compilation after a module has been decoded from source, but before it is instantiated. +// 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. // // Ex. Here's how to set the capacity to max instead of min, when set: diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go index 9dc2b84f32..2266b21507 100644 --- a/assemblyscript/assemblyscript_test.go +++ b/assemblyscript/assemblyscript_test.go @@ -13,26 +13,27 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/internal/watzero" "github.com/tetratelabs/wazero/sys" ) -var abortWasm = []byte(`(module +var abortWat = `(module (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (memory 1 1) (export "abort" (func 0)) -)`) +)` -var seedWasm = []byte(`(module +var seedWat = `(module (import "env" "seed" (func $~lib/builtins/seed (result f64))) (memory 1 1) (export "seed" (func 0)) -)`) +)` -var traceWasm = []byte(`(module +var traceWat = `(module (import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64))) (memory 1 1) (export "trace" (func 0)) -)`) +)` // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") @@ -72,6 +73,9 @@ func TestAbort(t *testing.T) { require.NoError(t, err) } + abortWasm, err := watzero.Wat2Wasm(abortWat) + require.NoError(t, err) + code, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig()) require.NoError(t, err) @@ -98,6 +102,9 @@ func TestSeed(t *testing.T) { _, err := Instantiate(testCtx, r) require.NoError(t, err) + seedWasm, err := watzero.Wat2Wasm(seedWat) + require.NoError(t, err) + code, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig()) require.NoError(t, err) @@ -194,6 +201,9 @@ func TestTrace(t *testing.T) { _, err := as.Instantiate(testCtx, r) require.NoError(t, err) + traceWasm, err := watzero.Wat2Wasm(traceWat) + require.NoError(t, err) + code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig()) require.NoError(t, err) @@ -314,6 +324,9 @@ func TestAbort_error(t *testing.T) { _, 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) @@ -381,6 +394,9 @@ wasm stack trace: _, 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) @@ -429,6 +445,9 @@ wasm stack trace: _, 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) diff --git a/config.go b/config.go index 0add2328e7..8d6dbfe2e1 100644 --- a/config.go +++ b/config.go @@ -53,7 +53,7 @@ type RuntimeConfig interface { // WithFeatureMutableGlobal allows globals to be mutable. This defaults to true as the feature was finished in // WebAssembly 1.0 (20191205). // - // When false, an api.Global can never be cast to an api.MutableGlobal, and any source that includes global vars + // When false, an api.Global can never be cast to an api.MutableGlobal, and any wasm that includes global vars // will fail to parse. WithFeatureMutableGlobal(bool) RuntimeConfig @@ -279,7 +279,7 @@ func (c *compiledModule) Close(_ context.Context) error { return nil } -// CompileConfig allows you to override what was decoded from source, prior to compilation (ModuleBuilder.Compile or +// 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. diff --git a/example_test.go b/example_test.go index cbc6be8203..374d22c225 100644 --- a/example_test.go +++ b/example_test.go @@ -7,9 +7,14 @@ import ( "log" ) +// addWasm was generated by the following: +// cd examples/basic/testdata/testdata; wat2wasm --debug-names add.wat +//go:embed examples/basic/testdata/add.wasm +var addWasm []byte + // This is an example of how to use WebAssembly via adding two numbers. // -// See https://github.com/tetratelabs/wazero/tree/main/examples for more examples. +// See https://github.com/tetratelabs/wazero/tree/main/examples for more. func Example() { // Choose the context to use for function calls. ctx := context.Background() @@ -17,15 +22,9 @@ func Example() { // Create a new WebAssembly Runtime. r := NewRuntime() - // Add a module to the runtime named "wasm/math" which exports one function "add", implemented in WebAssembly. - mod, err := r.InstantiateModuleFromCode(ctx, []byte(`(module $wasm/math - (func $add (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add - ) - (export "add" (func $add)) -)`)) + // Add a module to the runtime named "wasm/math" which exports one function + // "add", implemented in WebAssembly. + mod, err := r.InstantiateModuleFromBinary(ctx, addWasm) if err != nil { log.Panicln(err) } diff --git a/examples/allocation/rust/greet.go b/examples/allocation/rust/greet.go index 709a396108..cf68349bc1 100644 --- a/examples/allocation/rust/greet.go +++ b/examples/allocation/rust/greet.go @@ -38,7 +38,7 @@ func main() { // Instantiate a WebAssembly module that imports the "log" function defined // in "env" and exports "memory" and functions we'll use in this example. - mod, err := r.InstantiateModuleFromCode(ctx, greetWasm) + mod, err := r.InstantiateModuleFromBinary(ctx, greetWasm) if err != nil { log.Panicln(err) } diff --git a/examples/allocation/tinygo/greet.go b/examples/allocation/tinygo/greet.go index 9ed29b7cb5..0e5818edee 100644 --- a/examples/allocation/tinygo/greet.go +++ b/examples/allocation/tinygo/greet.go @@ -45,7 +45,7 @@ func main() { // Instantiate a WebAssembly module that imports the "log" function defined // in "env" and exports "memory" and functions we'll use in this example. - mod, err := r.InstantiateModuleFromCode(ctx, greetWasm) + mod, err := r.InstantiateModuleFromBinary(ctx, greetWasm) if err != nil { log.Panicln(err) } diff --git a/examples/basic/add.go b/examples/basic/add.go index ad5a679061..d60226b5cf 100644 --- a/examples/basic/add.go +++ b/examples/basic/add.go @@ -12,6 +12,11 @@ import ( "github.com/tetratelabs/wazero/api" ) +// addWasm was generated by the following: +// cd testdata; wat2wasm --debug-names add.wat +//go:embed testdata/add.wasm +var addWasm []byte + // main implements a basic function in both Go and WebAssembly. func main() { // Choose the context to use for function calls. @@ -22,14 +27,7 @@ func main() { defer r.Close(ctx) // This closes everything this Runtime created. // Add a module to the runtime named "wasm/math" which exports one function "add", implemented in WebAssembly. - wasm, err := r.InstantiateModuleFromCode(ctx, []byte(`(module $wasm/math - (func $add (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add - ) - (export "add" (func $add)) -)`)) + wasm, err := r.InstantiateModuleFromBinary(ctx, addWasm) if err != nil { log.Panicln(err) } diff --git a/examples/basic/testdata/add.wasm b/examples/basic/testdata/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2dbc9adfdc3b70b65c42d75b825156481bc8c29f GIT binary patch literal 71 zcmWN|K?;B{3iNG}TNDPOnOfq`94090-g!AL@=GFfZDoT;d& WJPVeoo9~03QC7G6kW2{plHU)6I18l! literal 0 HcmV?d00001 diff --git a/examples/basic/testdata/add.wat b/examples/basic/testdata/add.wat new file mode 100644 index 0000000000..f7880c33fe --- /dev/null +++ b/examples/basic/testdata/add.wat @@ -0,0 +1,15 @@ +(module + ;; Define the optional module name. '$' prefixing is a part of the text format. + $wasm/math + + ;; add returns $x+$y. + ;; + ;; Notes: + ;; * The stack begins empty and anything left must match the result type. + ;; * export allows api.Module to return this via ExportedFunction("add") + (func (export "add") (param $x i32) (param $y i32) (result i32) + local.get $x ;; stack: [$x] + local.get $y ;; stack: [$x, $y] + i32.add ;; stack: [$x+$y] + ) +) diff --git a/examples/import-go/age-calculator.go b/examples/import-go/age-calculator.go index 92e42037d2..9f8535c53f 100644 --- a/examples/import-go/age-calculator.go +++ b/examples/import-go/age-calculator.go @@ -12,6 +12,11 @@ import ( "github.com/tetratelabs/wazero" ) +// ageCalculatorWasm was generated by the following: +// cd testdata; wat2wasm --debug-names age_calculator.wat +//go:embed testdata/age_calculator.wasm +var ageCalculatorWasm []byte + // main shows how to define, import and call a Go-defined function from a // WebAssembly-defined function. // @@ -51,39 +56,7 @@ func main() { // // Note: The import syntax in both Text and Binary format is the same // regardless of if the function was defined in Go or WebAssembly. - ageCalculator, err := r.InstantiateModuleFromCode(ctx, []byte(` -;; Define the optional module name. '$' prefixing is a part of the text format. -(module $age-calculator - - ;; In WebAssembly, you don't import an entire module, rather each function. - ;; This imports the functions and gives them names which are easier to read - ;; than the alternative (zero-based index). - ;; - ;; Note: Importing unused functions is not an error in WebAssembly. - (import "env" "log_i32" (func $log (param i32))) - (import "env" "current_year" (func $year (result i32))) - - ;; get_age looks up the current year and subtracts the input from it. - ;; Note: The stack begins empty and anything left must match the result type. - (func $get_age (param $year_born i32) (result i32) - ;; stack: [] - call $year ;; stack: [$year.result] - local.get 0 ;; stack: [$year.result, $year_born] - i32.sub ;; stack: [$year.result-$year_born] - ) - ;; export allows api.Module to return this via ExportedFunction("get_age") - (export "get_age" (func $get_age)) - - ;; log_age - (func $log_age (param $year_born i32) - ;; stack: [] - local.get 0 ;; stack: [$year_born] - call $get_age ;; stack: [$get_age.result] - call $log ;; stack: [] - ) - (export "log_age" (func $log_age)) -)`)) - // ^^ Note: wazero's text compiler is incomplete #59. We are using it anyway to keep this example dependency free. + ageCalculator, err := r.InstantiateModuleFromBinary(ctx, ageCalculatorWasm) if err != nil { log.Panicln(err) } diff --git a/examples/import-go/testdata/age_calculator.wasm b/examples/import-go/testdata/age_calculator.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ab9a87ee00445fb3146d673cb75a143c538cf21b GIT binary patch literal 188 zcmYjLOA5j;6rA^7A!?--p=*nKH+m1Rkj4^)wZ8Qus-UyH31~T4Arn2Y}973;BP!L+3Z=NrWTKKg%&l} sC~+;k87+2Iiky1m`q`N{1!@G6hN#HBzdAk?=@*!=&c41)!OOq+03{77{Qv*} literal 0 HcmV?d00001 diff --git a/examples/import-go/testdata/age_calculator.wat b/examples/import-go/testdata/age_calculator.wat new file mode 100644 index 0000000000..8e3f869d89 --- /dev/null +++ b/examples/import-go/testdata/age_calculator.wat @@ -0,0 +1,25 @@ +(module $age-calculator + + ;; In WebAssembly, you don't import an entire module, rather each function. + ;; This imports the functions and gives them names which are easier to read + ;; than the alternative (zero-based index). + ;; + ;; Note: Importing unused functions is not an error in WebAssembly. + (import "env" "log_i32" (func $log (param i32))) + (import "env" "current_year" (func $year (result i32))) + + ;; get_age looks up the current year and subtracts the input from it. + (func $get_age (export "get_age") (param $year_born i32) (result i32) + call $year ;; stack: [$year.result] + local.get $year_born ;; stack: [$year.result, $year_born] + i32.sub ;; stack: [$year.result-$year_born] + ) + + ;; log_age calls $log with the result of $get_age + (func (export "log_age") (param $year_born i32) + ;; stack: [] + local.get $year_born ;; stack: [$year_born] + call $get_age ;; stack: [$get_age.result] + call $log ;; stack: [] + ) +) diff --git a/examples/multiple-results/README.md b/examples/multiple-results/README.md index 33633e60f7..e7d81d8818 100644 --- a/examples/multiple-results/README.md +++ b/examples/multiple-results/README.md @@ -1,3 +1,4 @@ ## Multiple results example -This example shows how to return more than one result from WebAssembly or Go-defined functions. +This example shows how to return more than one result from WebAssembly or +Go-defined functions. diff --git a/examples/multiple-results/multiple-results.go b/examples/multiple-results/multiple-results.go index 6eee398ea2..0c8c884f0b 100644 --- a/examples/multiple-results/multiple-results.go +++ b/examples/multiple-results/multiple-results.go @@ -10,17 +10,20 @@ import ( "github.com/tetratelabs/wazero/api" ) -// main implements functions with multiple returns values, using both an approach portable with any WebAssembly 1.0 -// (20191205) runtime, as well one dependent on the "multiple-results" feature. +// main implements functions with multiple returns values, using both an +// approach portable with any WebAssembly 1.0 runtime, as well one dependent +// on the "multiple-results" feature. // -// The portable approach uses parameters to return additional results. The parameter value is a memory offset to write -// the next value. This is the same approach used by WASI snapshot-01! +// The portable approach uses parameters to return additional results. The +// parameter value is a memory offset to write the next value. This is the same +// approach used by WASI. // * resultOffsetWasmFunctions // * resultOffsetHostFunctions // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md // -// Another approach is to enable the "multiple-results" feature. While "multiple-results" is not yet a W3C recommendation, most -// WebAssembly runtimes support it by default. +// Another approach is to enable the "multiple-results" feature. While +// "multiple-results" is not yet a W3C recommendation, most WebAssembly +// runtimes support it by default, and it is include in the draft of 2.0. // * multiValueWasmFunctions // * multiValueHostFunctions // See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md @@ -32,37 +35,41 @@ func main() { r := wazero.NewRuntime() defer r.Close(ctx) // This closes everything this Runtime created. - // Add a module that uses offset parameters for multiple results, with functions defined in WebAssembly. + // Add a module that uses offset parameters for multiple results. + + // ... defined in WebAssembly. wasm, err := resultOffsetWasmFunctions(ctx, r) if err != nil { log.Panicln(err) } - // Add a module that uses offset parameters for multiple results, with functions defined in Go. + // ... defined in Go. host, err := resultOffsetHostFunctions(ctx, r) if err != nil { log.Panicln(err) } - // wazero enables only W3C recommended features by default. Opt-in to other features like so: + // wazero enables WebAssembly 1.0 by default. Opt-in to other features: runtimeWithMultiValue := wazero.NewRuntimeWithConfig( wazero.NewRuntimeConfig().WithFeatureMultiValue(true), // ^^ Note: WebAssembly 2.0 (WithWasmCore2) includes "multi-value". ) - // Add a module that uses multiple results values, with functions defined in WebAssembly. + // Add a module that uses multiple results values + + // ... defined in WebAssembly. wasmWithMultiValue, err := multiValueWasmFunctions(ctx, runtimeWithMultiValue) if err != nil { log.Panicln(err) } - // Add a module that uses multiple results values, with functions defined in Go. + // ... defined in Go. hostWithMultiValue, err := multiValueHostFunctions(ctx, runtimeWithMultiValue) if err != nil { log.Panicln(err) } - // Call the same function in all modules and print the results to the console. + // Call the function from each module and print the results to the console. for _, mod := range []api.Module{wasm, host, wasmWithMultiValue, hostWithMultiValue} { getAge := mod.ExportedFunction("call_get_age") results, err := getAge.Call(ctx) @@ -74,58 +81,28 @@ func main() { } } -// resultOffsetWasmFunctions defines Wasm functions that illustrate multiple results using a technique compatible -// with any WebAssembly 1.0 (20191205) runtime. +// resultOffsetHostFunctions defines host functions that illustrate multiple +// results using a technique compatible with any WebAssembly 1.0 runtime. // -// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write -// the result. At the end of your function, you load that location. -func resultOffsetWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { - return r.InstantiateModuleFromCode(ctx, []byte(`(module $result-offset/wasm - ;; To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB). - (memory 1 1) - - ;; Define a function that returns a result, while a second result is written to memory. - (func $get_age (param $result_offset.age i32) (result (;errno;) i32) - local.get 0 ;; stack = [$result_offset.age] - i64.const 37 ;; stack = [$result_offset.age, 37] - i64.store ;; stack = [] - i32.const 0 ;; stack = [0] - ) - - ;; Now, define a function that shows the Wasm mechanics returning something written to a result parameter. - ;; The caller provides a memory offset to the callee, so that it knows where to write the second result. - (func $call_get_age (result i64) - i32.const 8 ;; stack = [8] $result_offset.age parameter to get_age (arbitrary memory offset 8) - call $get_age ;; stack = [errno] result of get_age - drop ;; stack = [] - - i32.const 8 ;; stack = [8] same value as the $result_offset.age parameter - i64.load ;; stack = [age] - ) - - ;; Export the function, so that we can test it! - (export "call_get_age" (func $call_get_age)) -)`)) -} - -// resultOffsetHostFunctions defines host functions that illustrate multiple results using a technique compatible -// with any WebAssembly 1.0 (20191205) runtime. -// -// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write -// the result. At the end of your function, you load that location. +// To return a value in WASM written to a result parameter, you have to define +// memory and pass a location to write the result. At the end of your function, +// you load that location. func resultOffsetHostFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { return r.NewModuleBuilder("result-offset/host"). - // To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB). + // To use result parameters, we need scratch memory. Allocate the least + // possible: 1 page (64KB). ExportMemoryWithMax("mem", 1, 1). - // Define a function that returns a result, while a second result is written to memory. + // get_age returns a result, while a second result is written to memory. ExportFunction("get_age", func(ctx context.Context, m api.Module, resultOffsetAge uint32) (errno uint32) { if m.Memory().WriteUint64Le(ctx, resultOffsetAge, 37) { return 0 } return 1 // overflow }). - // Now, define a function that shows the Wasm mechanics returning something written to a result parameter. - // The caller provides a memory offset to the callee, so that it knows where to write the second result. + // Now, define a function that shows the Wasm mechanics returning + // something written to a result parameter. The caller provides a + // memory offset to the callee, so that it knows where to write the + // second result. ExportFunction("call_get_age", func(ctx context.Context, m api.Module) (age uint64) { resultOffsetAge := uint32(8) // arbitrary memory offset (in bytes) _, _ = m.ExportedFunction("get_age").Call(ctx, uint64(resultOffsetAge)) @@ -134,28 +111,19 @@ func resultOffsetHostFunctions(ctx context.Context, r wazero.Runtime) (api.Modul }).Instantiate(ctx, r) } -// multiValueWasmFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature. -func multiValueWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { - return r.InstantiateModuleFromCode(ctx, []byte(`(module $multi-value/wasm - - ;; Define a function that returns two results - (func $get_age (result (;age;) i64 (;errno;) i32) - i64.const 37 ;; stack = [37] - i32.const 0 ;; stack = [37, 0] - ) - - ;; Now, define a function that returns only the first result. - (func $call_get_age (result i64) - call $get_age ;; stack = [37, errno] result of get_age - drop ;; stack = [37] - ) - - ;; Export the function, so that we can test it! - (export "call_get_age" (func $call_get_age)) -)`)) +// resultOffsetWasm was generated by the following: +// cd testdata; wat2wasm --debug-names result_offset.wat +//go:embed testdata/result_offset.wasm +var resultOffsetWasm []byte + +// resultOffsetWasmFunctions are the WebAssembly equivalent of the Go-defined +// resultOffsetHostFunctions. The source is in testdata/result_offset.wat +func resultOffsetWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { + return r.InstantiateModuleFromBinary(ctx, resultOffsetWasm) } -// multiValueHostFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature. +// multiValueHostFunctions defines Wasm functions that illustrate multiple +// results using the "multiple-results" feature. func multiValueHostFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { return r.NewModuleBuilder("multi-value/host"). // Define a function that returns two results @@ -170,3 +138,14 @@ func multiValueHostFunctions(ctx context.Context, r wazero.Runtime) (api.Module, return results[0] }).Instantiate(ctx, r) } + +// multiValueWasm was generated by the following: +// cd testdata; wat2wasm --debug-names multi_value.wat +//go:embed testdata/multi_value.wasm +var multiValueWasm []byte + +// multiValueWasmFunctions are the WebAssembly equivalent of the Go-defined +// multiValueHostFunctions. The source is in testdata/multi-value.wat +func multiValueWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { + return r.InstantiateModuleFromBinary(ctx, multiValueWasm) +} diff --git a/examples/multiple-results/testdata/multi_value.wat b/examples/multiple-results/testdata/multi_value.wat new file mode 100644 index 0000000000..4191cd77e8 --- /dev/null +++ b/examples/multiple-results/testdata/multi_value.wat @@ -0,0 +1,16 @@ +;; multiValueWasmFunctions defines Wasm functions that illustrate multiple +;; results using the "multiple-results" feature. +(module $multi-value/wasm + + ;; Define a function that returns two results + (func $get_age (result (;age;) i64 (;errno;) i32) + i64.const 37 ;; stack = [37] + i32.const 0 ;; stack = [37, 0] + ) + + ;; Now, define a function that returns only the first result. + (func (export "call_get_age") (result i64) + call $get_age ;; stack = [37, errno] result of get_age + drop ;; stack = [37] + ) +) diff --git a/examples/multiple-results/testdata/result_offset.wat b/examples/multiple-results/testdata/result_offset.wat new file mode 100644 index 0000000000..f1078c5148 --- /dev/null +++ b/examples/multiple-results/testdata/result_offset.wat @@ -0,0 +1,32 @@ +;; result-offset/wasm defines Wasm functions that illustrate multiple results +;; using a technique compatible with any WebAssembly 1.0 runtime. +;; +;; To return a value in WASM written to a result parameter, you have to define +;; memory and pass a location to write the result. At the end of your function, +;; you load that location. +(module $result-offset/wasm + ;; To use result parameters, we need scratch memory. Allocate the least + ;; possible: 1 page (64KB). + (memory 1 1) + + ;; get_age returns a result, while a second result is written to memory. + (func $get_age (param $result_offset.age i32) (result (;errno;) i32) + local.get 0 ;; stack = [$result_offset.age] + i64.const 37 ;; stack = [$result_offset.age, 37] + i64.store ;; stack = [] + i32.const 0 ;; stack = [0] + ) + + ;; Now, define a function that shows the Wasm mechanics returning something + ;; written to a result parameter. + ;; The caller provides a memory offset to the callee, so that it knows where + ;; to write the second result. + (func (export "call_get_age") (result i64) + i32.const 8 ;; stack = [8] arbitrary $result_offset.age param to get_age + call $get_age ;; stack = [errno] result of get_age + drop ;; stack = [] + + i32.const 8 ;; stack = [8] same value as the $result_offset.age parameter + i64.load ;; stack = [age] + ) +) diff --git a/examples/namespace/counter.go b/examples/namespace/counter.go index e7707d75ec..696261503b 100644 --- a/examples/namespace/counter.go +++ b/examples/namespace/counter.go @@ -10,7 +10,8 @@ import ( "github.com/tetratelabs/wazero/api" ) -// counterWasm was created by `cd testdata; wat2wasm --debug-names counter.wat`. +// counterWasm was generated by the following: +// cd testdata; wat2wasm --debug-names counter.wat //go:embed testdata/counter.wasm var counterWasm []byte diff --git a/examples/replace-import/replace-import.go b/examples/replace-import/replace-import.go index d9d7e296bc..86e9e21a3c 100644 --- a/examples/replace-import/replace-import.go +++ b/examples/replace-import/replace-import.go @@ -10,6 +10,11 @@ import ( "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() { @@ -31,7 +36,8 @@ func main() { } defer host.Close(ctx) - // Compile the WebAssembly module, replacing the import "env.abort" with "assemblyscript.abort". + // 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" { @@ -40,11 +46,7 @@ func main() { return oldModule, oldName }) - code, err := r.CompileModule(ctx, []byte(`(module $needs-import - (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) - - (export "abort" (func 0)) ;; exports the import for testing -)`), compileConfig) + code, err := r.CompileModule(ctx, needsImportWasm, compileConfig) if err != nil { log.Panicln(err) } diff --git a/examples/replace-import/testdata/needs_import.wat b/examples/replace-import/testdata/needs_import.wat new file mode 100644 index 0000000000..9a0d3cf0af --- /dev/null +++ b/examples/replace-import/testdata/needs_import.wat @@ -0,0 +1,3 @@ +(module $needs-import + (func (export "abort") (import "env" "abort") (param i32 i32 i32 i32)) +) diff --git a/experimental/fs_example_test.go b/experimental/fs_example_test.go index c45c168a8e..e1a85f7ecd 100644 --- a/experimental/fs_example_test.go +++ b/experimental/fs_example_test.go @@ -2,6 +2,7 @@ package experimental_test import ( "context" + _ "embed" "fmt" "log" "os" @@ -11,7 +12,13 @@ import ( "github.com/tetratelabs/wazero/wasi_snapshot_preview1" ) -// This is a basic example of overriding the file system via WithFS. The main goal is to show how it is configured. +// fsWasm was generated by the following: +// cd testdata; wat2wasm --debug-names fs.wat +//go:embed testdata/fs.wasm +var fsWasm []byte + +// This is a basic example of overriding the file system via WithFS. The main +// goal is to show how it is configured. func Example_withFS() { ctx := context.Background() @@ -22,20 +29,14 @@ func Example_withFS() { log.Panicln(err) } - // Instantiate a module that only re-exports a WASI function that uses the filesystem. - mod, err := r.InstantiateModuleFromCode(ctx, []byte(`(module - (import "wasi_snapshot_preview1" "fd_prestat_dir_name" - (func $wasi.fd_prestat_dir_name (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32))) - - (memory 1 1) ;; memory is required for WASI - - (export "fd_prestat_dir_name" (func 0)) -)`)) + // Instantiate a module exporting a WASI function that uses the filesystem. + mod, err := r.InstantiateModuleFromBinary(ctx, fsWasm) if err != nil { log.Panicln(err) } - // Setup the filesystem overlay, noting that it can fail if the directory is invalid and must be closed. + // Setup the filesystem overlay, noting that it can fail if the directory is + // invalid and must be closed. ctx, closer, err := experimental.WithFS(ctx, os.DirFS(".")) if err != nil { log.Panicln(err) @@ -47,7 +48,8 @@ func Example_withFS() { pathLen := 1 // length we expect the path to be. pathOffset := 0 // where to write pathLen bytes. - // By default, there are no pre-opened directories. If the configuration was wrong, this call would fail. + // By default, there are no pre-opened directories. If the configuration + // was wrong, this call would fail. results, err := fdPrestatDirName.Call(ctx, uint64(fd), uint64(pathOffset), uint64(pathLen)) if err != nil { log.Panicln(err) diff --git a/experimental/listener_example_test.go b/experimental/listener_example_test.go index b6c63597a7..38d7cef987 100644 --- a/experimental/listener_example_test.go +++ b/experimental/listener_example_test.go @@ -2,6 +2,7 @@ package experimental_test import ( "context" + _ "embed" "fmt" "io" "log" @@ -12,6 +13,11 @@ import ( "github.com/tetratelabs/wazero/wasi_snapshot_preview1" ) +// listenerWasm was generated by the following: +// cd testdata; wat2wasm --debug-names listener.wat +//go:embed testdata/listener.wasm +var listenerWasm []byte + // loggerFactory implements experimental.FunctionListenerFactory to log all function calls to the console. type loggerFactory struct{} @@ -67,13 +73,7 @@ func Example_listener() { } // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, []byte(`(module $listener - (import "wasi_snapshot_preview1" "random_get" - (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32))) - (func i32.const 0 i32.const 4 call 0 drop) ;; write 4 bytes of random data - (memory 1 1) - (start 1) ;; call the second function -)`), wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, listenerWasm, wazero.NewCompileConfig()) if err != nil { log.Panicln(err) } diff --git a/experimental/testdata/clock.wat b/experimental/testdata/clock.wat new file mode 100644 index 0000000000..74bb6a529d --- /dev/null +++ b/experimental/testdata/clock.wat @@ -0,0 +1,9 @@ +(module + (func ;; re-export fd_prestat_dir_name, imported from WASI + (export "clock_time_get") + (import "wasi_snapshot_preview1" "clock_time_get") + (param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32) + ) + + (memory (export "memory") 1 1) ;; memory is required for WASI +) diff --git a/experimental/testdata/fs.wat b/experimental/testdata/fs.wat new file mode 100644 index 0000000000..55bfd5f55e --- /dev/null +++ b/experimental/testdata/fs.wat @@ -0,0 +1,9 @@ +(module + (func ;; re-export fd_prestat_dir_name, imported from WASI + (export "fd_prestat_dir_name") + (import "wasi_snapshot_preview1" "fd_prestat_dir_name") + (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32) + ) + + (memory (export "memory") 1 1) ;; memory is required for WASI +) diff --git a/experimental/testdata/listener.wat b/experimental/testdata/listener.wat new file mode 100644 index 0000000000..0868a40d1b --- /dev/null +++ b/experimental/testdata/listener.wat @@ -0,0 +1,7 @@ +(module $listener + (import "wasi_snapshot_preview1" "random_get" + (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32))) + (func i32.const 0 i32.const 4 call 0 drop) ;; write 4 bytes of random data + (memory 1 1) + (start 1) ;; call the second function +) diff --git a/experimental/time_example_test.go b/experimental/time_example_test.go index 92683c93b1..6487798d2c 100644 --- a/experimental/time_example_test.go +++ b/experimental/time_example_test.go @@ -2,6 +2,7 @@ package experimental_test import ( "context" + _ "embed" "fmt" "log" "time" @@ -13,6 +14,11 @@ import ( const epochNanos = uint64(1640995200000000000) // midnight UTC 2022-01-01 +// clockWasm was generated by the following: +// cd testdata; wat2wasm --debug-names clock.wat +//go:embed testdata/clock.wasm +var clockWasm []byte + // This is a basic example of overriding the clock via WithTimeNowUnixNano. The main goal is to show how it is configured. func Example_withTimeNowUnixNano() { ctx := context.Background() @@ -25,14 +31,7 @@ func Example_withTimeNowUnixNano() { } // Instantiate a module that only re-exports a WASI function that uses the clock. - mod, err := r.InstantiateModuleFromCode(ctx, []byte(`(module - (import "wasi_snapshot_preview1" "clock_time_get" - (func $wasi.clock_time_get (param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32))) - - (memory 1 1) ;; memory is required for WASI - - (export "clock_time_get" (func 0)) -)`)) + mod, err := r.InstantiateModuleFromBinary(ctx, clockWasm) if err != nil { log.Panicln(err) } diff --git a/internal/integration_test/bench/bench_test.go b/internal/integration_test/bench/bench_test.go index 4025956acf..3cd1304779 100644 --- a/internal/integration_test/bench/bench_test.go +++ b/internal/integration_test/bench/bench_test.go @@ -156,8 +156,8 @@ func runRandomMatMul(b *testing.B, m api.Module) { func instantiateHostFunctionModuleWithEngine(b *testing.B, config wazero.RuntimeConfig) api.Module { r := createRuntime(b, config) - // InstantiateModuleFromCode runs the "_start" function which is what TinyGo compiles "main" to. - m, err := r.InstantiateModuleFromCode(testCtx, caseWasm) + // InstantiateModuleFromBinary runs the "_start" function which is what TinyGo compiles "main" to. + m, err := r.InstantiateModuleFromBinary(testCtx, caseWasm) if err != nil { b.Fatal(err) } diff --git a/internal/integration_test/bench/codec_test.go b/internal/integration_test/bench/codec_test.go new file mode 100644 index 0000000000..cabc7a65c2 --- /dev/null +++ b/internal/integration_test/bench/codec_test.go @@ -0,0 +1,131 @@ +package bench + +import ( + "testing" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasm/binary" + "github.com/tetratelabs/wazero/wasi_snapshot_preview1" +) + +// example holds the latest supported features as described in the comments of exampleText +var example = newExample() + +// exampleWasm is the exampleText encoded in the WebAssembly 1.0 binary format. +var exampleWasm = binary.EncodeModule(example) + +func newExample() *wasm.Module { + three := wasm.Index(3) + 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}, + }, + ImportSection: []*wasm.Import{ + { + Module: "wasi_snapshot_preview1", Name: "args_sizes_get", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, { + Module: "wasi_snapshot_preview1", Name: "fd_write", + Type: wasm.ExternTypeFunc, + DescFunc: 2, + }, + }, + FunctionSection: []wasm.Index{wasm.Index(1), wasm.Index(1), wasm.Index(0), wasm.Index(3), wasm.Index(4), wasm.Index(5)}, + CodeSection: []*wasm.Code{ + {Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}}, + {Body: []byte{wasm.OpcodeEnd}}, + {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}}, + {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeI64Extend16S, wasm.OpcodeEnd}}, + {Body: []byte{ + wasm.OpcodeLocalGet, 0x00, + wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32S, + wasm.OpcodeEnd, + }}, + {Body: []byte{wasm.OpcodeLocalGet, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}}, + }, + MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: three, IsMaxEncoded: true}, + ExportSection: []*wasm.Export{ + {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)}, + {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)}, + {Name: "mem", Type: wasm.ExternTypeMemory, Index: wasm.Index(0)}, + {Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(7)}, + }, + StartSection: &three, + NameSection: &wasm.NameSection{ + ModuleName: "example", + FunctionNames: wasm.NameMap{ + {Index: wasm.Index(0), Name: "wasi.args_sizes_get"}, + {Index: wasm.Index(1), Name: "wasi.fd_write"}, + {Index: wasm.Index(2), Name: "call_hello"}, + {Index: wasm.Index(3), Name: "hello"}, + {Index: wasm.Index(4), Name: "addInt"}, + {Index: wasm.Index(7), Name: "swap"}, + }, + LocalNames: wasm.IndirectNameMap{ + {Index: wasm.Index(1), NameMap: wasm.NameMap{ + {Index: wasm.Index(0), Name: "fd"}, + {Index: wasm.Index(1), Name: "iovs_ptr"}, + {Index: wasm.Index(2), Name: "iovs_len"}, + {Index: wasm.Index(3), Name: "nwritten_ptr"}, + }}, + {Index: wasm.Index(4), NameMap: wasm.NameMap{ + {Index: wasm.Index(0), Name: "value_1"}, + {Index: wasm.Index(1), Name: "value_2"}, + }}, + }, + }, + } +} + +func TestExampleUpToDate(t *testing.T) { + t.Run("binary.DecodeModule", func(t *testing.T) { + m, err := binary.DecodeModule(exampleWasm, wasm.Features20220419, wasm.MemorySizer) + require.NoError(t, err) + require.Equal(t, example, m) + }) + + t.Run("Executable", func(t *testing.T) { + r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithWasmCore2()) + + // Add WASI to satisfy import tests + wm, err := wasi_snapshot_preview1.Instantiate(testCtx, r) + require.NoError(t, err) + defer wm.Close(testCtx) + + // Decode and instantiate the module + module, err := r.InstantiateModuleFromBinary(testCtx, exampleWasm) + require.NoError(t, err) + defer module.Close(testCtx) + + // Call the swap function as a smoke test + results, err := module.ExportedFunction("swap").Call(testCtx, 1, 2) + require.NoError(t, err) + require.Equal(t, []uint64{2, 1}, results) + }) +} + +func BenchmarkCodec(b *testing.B) { + b.Run("binary.DecodeModule", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := binary.DecodeModule(exampleWasm, wasm.Features20220419, wasm.MemorySizer); err != nil { + b.Fatal(err) + } + } + }) + b.Run("binary.EncodeModule", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = binary.EncodeModule(example) + } + }) +} diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go index 00e5db4469..f3219c50c8 100644 --- a/internal/integration_test/engine/adhoc_test.go +++ b/internal/integration_test/engine/adhoc_test.go @@ -14,6 +14,7 @@ import ( "github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/watzero" "github.com/tetratelabs/wazero/sys" ) @@ -96,7 +97,7 @@ func testReftypeImports(t *testing.T, r wazero.Runtime) { require.NoError(t, err) defer host.Close(testCtx) - module, err := r.InstantiateModuleFromCode(testCtx, reftypeImportsWasm) + module, err := r.InstantiateModuleFromBinary(testCtx, reftypeImportsWasm) require.NoError(t, err) defer module.Close(testCtx) @@ -108,7 +109,7 @@ func testReftypeImports(t *testing.T, r wazero.Runtime) { } func testHugeStack(t *testing.T, r wazero.Runtime) { - module, err := r.InstantiateModuleFromCode(testCtx, hugestackWasm) + module, err := r.InstantiateModuleFromBinary(testCtx, hugestackWasm) require.NoError(t, err) defer module.Close(testCtx) @@ -127,7 +128,7 @@ func testUnreachable(t *testing.T, r wazero.Runtime) { _, err := r.NewModuleBuilder("host").ExportFunction("cause_unreachable", callUnreachable).Instantiate(testCtx, r) require.NoError(t, err) - module, err := r.InstantiateModuleFromCode(testCtx, unreachableWasm) + module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm) require.NoError(t, err) defer module.Close(testCtx) @@ -150,7 +151,7 @@ func testRecursiveEntry(t *testing.T, r wazero.Runtime) { _, err := r.NewModuleBuilder("env").ExportFunction("host_func", hostfunc).Instantiate(testCtx, r) require.NoError(t, err) - module, err := r.InstantiateModuleFromCode(testCtx, recursiveWasm) + module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm) require.NoError(t, err) defer module.Close(testCtx) @@ -180,7 +181,7 @@ func testImportedAndExportedFunc(t *testing.T, r wazero.Runtime) { require.NoError(t, err) defer host.Close(testCtx) - module, err := r.InstantiateModuleFromCode(testCtx, []byte(`(module $test + module, err := r.InstantiateModuleFromBinary(testCtx, wat2wasm(`(module $test (import "" "store_int" (func $store_int (param $offset i32) (param $val i64) (result (;errno;) i32))) (memory $memory 1 1) @@ -228,7 +229,7 @@ func testNestedGoContext(t *testing.T, r wazero.Runtime) { defer imported.Close(testCtx) // Instantiate a module that uses Wasm code to call the host function. - importing, err = r.InstantiateModuleFromCode(testCtx, []byte(fmt.Sprintf(`(module $%[1]s + importing, err = r.InstantiateModuleFromBinary(testCtx, wat2wasm(fmt.Sprintf(`(module $%[1]s (import "%[2]s" "outer" (func $outer (param i32) (result i32))) (import "%[2]s" "inner" (func $inner (param i32) (result i32))) (func $call_outer (param i32) (result i32) local.get 0 call $outer) @@ -272,7 +273,7 @@ func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) { for test := range fns { t.Run(test, func(t *testing.T) { // Instantiate a module that uses Wasm code to call the host function. - importing, err = r.InstantiateModuleFromCode(testCtx, []byte(fmt.Sprintf(`(module $%[1]s + importing, err = r.InstantiateModuleFromBinary(testCtx, wat2wasm(fmt.Sprintf(`(module $%[1]s (import "%[2]s" "%[3]s" (func $%[3]s (param i32) (result i32))) (func $call_%[3]s (param i32) (result i32) local.get 0 call $%[3]s) (export "call->%[3]s" (func $call_%[3]s)) @@ -338,7 +339,7 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) { } { t.Run(test.name, func(t *testing.T) { // Instantiate a module that uses Wasm code to call the host function. - importing, err := r.InstantiateModuleFromCode(testCtx, []byte(fmt.Sprintf(`(module $%[1]s + importing, err := r.InstantiateModuleFromBinary(testCtx, wat2wasm(fmt.Sprintf(`(module $%[1]s (import "%[2]s" "%[3]s" (func $%[3]s (param %[3]s) (result %[3]s))) (func $call_%[3]s (param %[3]s) (result %[3]s) local.get 0 call $%[3]s) (export "call->%[3]s" (func $call_%[3]s)) @@ -353,8 +354,8 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) { } } -func callReturnImportSource(importedModule, importingModule string) []byte { - return []byte(fmt.Sprintf(`(module $%[1]s +func callReturnImportWasm(importedModule, importingModule string) []byte { + return wat2wasm(fmt.Sprintf(`(module $%[1]s ;; test an imported function by re-exporting it (import "%[2]s" "return_input" (func $return_input (param i32) (result i32))) (export "return_input" (func $return_input)) @@ -365,6 +366,11 @@ func callReturnImportSource(importedModule, importingModule string) []byte { )`, importingModule, importedModule)) } +func wat2wasm(wat string) []byte { + wasm, _ := watzero.Wat2Wasm(wat) + return wasm +} + func testCloseInFlight(t *testing.T, r wazero.Runtime) { tests := []struct { name, function string @@ -438,8 +444,8 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) { defer imported.Close(testCtx) // Import that module. - source := callReturnImportSource(imported.Name(), t.Name()+"-importing") - importingCode, err = r.CompileModule(testCtx, source, compileConfig) + binary := callReturnImportWasm(imported.Name(), t.Name()+"-importing") + importingCode, err = r.CompileModule(testCtx, binary, compileConfig) require.NoError(t, err) importing, err = r.InstantiateModule(testCtx, importingCode, moduleConfig) @@ -467,7 +473,7 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) { func testMemOps(t *testing.T, r wazero.Runtime) { // Instantiate a module that manages its memory - memory, err := r.InstantiateModuleFromCode(testCtx, []byte(`(module $memory + memory, err := r.InstantiateModuleFromBinary(testCtx, wat2wasm(`(module $memory (func $grow (param $delta i32) (result (;previous_size;) i32) local.get 0 memory.grow) (func $size (result (;size;) i32) memory.size) @@ -531,7 +537,7 @@ func testMemOps(t *testing.T, r wazero.Runtime) { } func testMultipleInstantiation(t *testing.T, r wazero.Runtime) { - compiled, err := r.CompileModule(testCtx, []byte(`(module $test + compiled, err := r.CompileModule(testCtx, wat2wasm(`(module $test (memory 1) (func $store i32.const 1 ;; memory offset diff --git a/internal/integration_test/engine/hammer_test.go b/internal/integration_test/engine/hammer_test.go index f192fb584e..06a2844b4b 100644 --- a/internal/integration_test/engine/hammer_test.go +++ b/internal/integration_test/engine/hammer_test.go @@ -35,8 +35,8 @@ func closeImportingModuleWhileInUse(t *testing.T, r wazero.Runtime) { require.NoError(t, importing.Close(testCtx)) // Prove a module can be redefined even with in-flight calls. - source := callReturnImportSource(imported.Name(), importing.Name()) - importing, err := r.InstantiateModuleFromCode(testCtx, source) + binary := callReturnImportWasm(imported.Name(), importing.Name()) + importing, err := r.InstantiateModuleFromBinary(testCtx, binary) require.NoError(t, err) return imported, importing }) @@ -55,8 +55,8 @@ func closeImportedModuleWhileInUse(t *testing.T, r wazero.Runtime) { require.NoError(t, err) // Redefine the importing module, which should link to the redefined host module. - source := callReturnImportSource(imported.Name(), importing.Name()) - importing, err = r.InstantiateModuleFromCode(testCtx, source) + binary := callReturnImportWasm(imported.Name(), importing.Name()) + importing, err = r.InstantiateModuleFromBinary(testCtx, binary) require.NoError(t, err) return imported, importing @@ -84,8 +84,8 @@ func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported defer imported.Close(testCtx) // Import that module. - source := callReturnImportSource(imported.Name(), t.Name()+"-importing") - importing, err := r.InstantiateModuleFromCode(testCtx, source) + binary := callReturnImportWasm(imported.Name(), t.Name()+"-importing") + importing, err := r.InstantiateModuleFromBinary(testCtx, binary) require.NoError(t, err) defer importing.Close(testCtx) diff --git a/internal/integration_test/fs/fs_test.go b/internal/integration_test/fs/fs_test.go index 3f74d7c0a6..e00ee38a76 100644 --- a/internal/integration_test/fs/fs_test.go +++ b/internal/integration_test/fs/fs_test.go @@ -21,6 +21,11 @@ var testCtx = context.Background() //go:embed testdata/animals.txt var animals []byte +// fsWasm was generated by the following: +// cd testdata; wat2wasm --debug-names fs.wat +//go:embed testdata/fs.wasm +var fsWasm []byte + // wasiFs is an implementation of fs.Fs calling into wasiWASI. Not thread-safe because we use // fixed Memory offsets for transferring data with wasm. type wasiFs struct { @@ -152,22 +157,7 @@ func TestReader(t *testing.T) { sys := wazero.NewModuleConfig().WithWorkDirFS(realFs) // Create a module that just delegates to wasi functions. - compiled, err := r.CompileModule(testCtx, []byte(`(module - (import "wasi_snapshot_preview1" "path_open" - (func $wasi.path_open (param $fd i32) (param $dirflags i32) (param $path i32) (param $path_len i32) (param $oflags i32) (param $fs_rights_base i64) (param $fs_rights_inheriting i64) (param $fdflags i32) (param $result.opened_fd i32) (result (;errno;) i32))) - (import "wasi_snapshot_preview1" "fd_close" - (func $wasi.fd_close (param $fd i32) (result (;errno;) i32))) - (import "wasi_snapshot_preview1" "fd_read" - (func $wasi.fd_read (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32))) - (import "wasi_snapshot_preview1" "fd_seek" - (func $wasi.fd_seek (param $fd i32) (param $offset i64) (param $whence i32) (param $result.newoffset i32) (result (;errno;) i32))) - (memory 1 1) ;; just an arbitrary size big enough for tests - (export "memory" (memory 0)) - (export "path_open" (func $wasi.path_open)) - (export "fd_close" (func $wasi.fd_close)) - (export "fd_read" (func $wasi.fd_read)) - (export "fd_seek" (func $wasi.fd_seek)) -)`), wazero.NewCompileConfig()) + compiled, err := r.CompileModule(testCtx, fsWasm, wazero.NewCompileConfig()) require.NoError(t, err) mod, err := r.InstantiateModule(testCtx, compiled, sys) diff --git a/internal/integration_test/fs/testdata/fs.wat b/internal/integration_test/fs/testdata/fs.wat new file mode 100644 index 0000000000..9aac6939a9 --- /dev/null +++ b/internal/integration_test/fs/testdata/fs.wat @@ -0,0 +1,20 @@ +(module $wasi_fs_exports + + (func (export "path_open") + (import "wasi_snapshot_preview1" "path_open") + (param $fd i32) (param $dirflags i32) (param $path i32) (param $path_len i32) (param $oflags i32) (param $fs_rights_base i64) (param $fs_rights_inheriting i64) (param $fdflags i32) (param $result.opened_fd i32) (result (;errno;) i32)) + + (func (export "fd_close") + (import "wasi_snapshot_preview1" "fd_close") + (param $fd i32) (result (;errno;) i32)) + + (func (export "fd_read") + (import "wasi_snapshot_preview1" "fd_read") + (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32)) + + (func (export "fd_seek") + (import "wasi_snapshot_preview1" "fd_seek") + (param $fd i32) (param $offset i64) (param $whence i32) (param $result.newoffset i32) (result (;errno;) i32)) + + (memory (export "memory") 1 1) ;; memory is required for WASI +) diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go index bb19764579..32bd0afddb 100644 --- a/internal/integration_test/spectest/spectest.go +++ b/internal/integration_test/spectest/spectest.go @@ -17,9 +17,9 @@ import ( "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/u64" "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/internal/wasm/binary" - "github.com/tetratelabs/wazero/internal/wasm/text" + binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary" "github.com/tetratelabs/wazero/internal/wasmruntime" + "github.com/tetratelabs/wazero/internal/watzero" ) // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. @@ -265,7 +265,7 @@ func (c command) expectedError() (err error) { // See https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/imports.wast // See https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/script/js.ml#L13-L25 func addSpectestModule(t *testing.T, s *wasm.Store, ns *wasm.Namespace) { - mod, err := text.DecodeModule([]byte(`(module $spectest + w, err := watzero.Wat2Wasm(`(module $spectest (; TODO (global (export "global_i32") i32) (global (export "global_i64") i64) @@ -301,7 +301,10 @@ func addSpectestModule(t *testing.T, s *wasm.Store, ns *wasm.Namespace) { (func (param f64 f64) local.get 0 drop local.get 1 drop) (export "print_f64_f64" (func 6)) -)`), wasm.Features20191205, wasm.MemorySizer) +)`) + require.NoError(t, err) + + mod, err := binaryformat.DecodeModule(w, wasm.Features20220419, wasm.MemorySizer) require.NoError(t, err) // (global (export "global_i32") i32 (i32.const 666)) @@ -397,7 +400,7 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E case "module": buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) require.NoError(t, err, msg) - mod, err := binary.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) require.NoError(t, err, msg) require.NoError(t, mod.Validate(enabledFeatures)) mod.AssignModuleID(buf) @@ -530,7 +533,7 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E // // In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to // retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case. - mod, err := binary.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) require.NoError(t, err, msg) err = mod.Validate(s.EnabledFeatures) @@ -558,7 +561,7 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E } func requireInstantiationError(t *testing.T, s *wasm.Store, ns *wasm.Namespace, buf []byte, msg string) { - mod, err := binary.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) if err != nil { return } @@ -704,10 +707,10 @@ func TestBinaryEncoder(t *testing.T, testDataFS embed.FS, enabledFeatures wasm.F buf = requireStripCustomSections(t, buf) - mod, err := binary.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) require.NoError(t, err) - encodedBuf := binary.EncodeModule(mod) + encodedBuf := binaryformat.EncodeModule(mod) require.Equal(t, buf, encodedBuf) }) } diff --git a/internal/integration_test/vs/codec_test.go b/internal/integration_test/vs/codec_test.go deleted file mode 100644 index 8ce4ad2500..0000000000 --- a/internal/integration_test/vs/codec_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package vs - -import ( - _ "embed" - "testing" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/internal/testing/require" - "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/internal/wasm/binary" - "github.com/tetratelabs/wazero/internal/wasm/text" - "github.com/tetratelabs/wazero/wasi_snapshot_preview1" -) - -func TestExampleUpToDate(t *testing.T) { - t.Run("binary.DecodeModule", func(t *testing.T) { - m, err := binary.DecodeModule(exampleBinary, wasm.Features20220419, wasm.MemorySizer) - require.NoError(t, err) - require.Equal(t, example, m) - }) - - t.Run("text.DecodeModule", func(t *testing.T) { - m, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemorySizer) - require.NoError(t, err) - require.Equal(t, example, m) - }) - - t.Run("Executable", func(t *testing.T) { - r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithWasmCore2()) - - // Add WASI to satisfy import tests - wm, err := wasi_snapshot_preview1.Instantiate(testCtx, r) - require.NoError(t, err) - defer wm.Close(testCtx) - - // Decode and instantiate the module - module, err := r.InstantiateModuleFromCode(testCtx, exampleBinary) - require.NoError(t, err) - defer module.Close(testCtx) - - // Call the swap function as a smoke test - results, err := module.ExportedFunction("swap").Call(testCtx, 1, 2) - require.NoError(t, err) - require.Equal(t, []uint64{2, 1}, results) - }) -} - -func BenchmarkCodec(b *testing.B) { - b.Run("binary.DecodeModule", func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - if _, err := binary.DecodeModule(exampleBinary, wasm.Features20220419, wasm.MemorySizer); err != nil { - b.Fatal(err) - } - } - }) - b.Run("binary.EncodeModule", func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _ = binary.EncodeModule(example) - } - }) - b.Run("text.DecodeModule", func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - if _, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemorySizer); err != nil { - b.Fatal(err) - } - } - }) -} diff --git a/internal/wasm/host.go b/internal/wasm/host.go index beb98f0ec8..d98aab132d 100644 --- a/internal/wasm/host.go +++ b/internal/wasm/host.go @@ -66,7 +66,7 @@ func NewHostModule( } } - // Assins the ModuleID by calculating sha256 on inputs as host modules do not have `source` to hash. + // Assins the ModuleID by calculating sha256 on inputs as host modules do not have `wasm` to hash. m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v:%v:%v", moduleName, nameToGoFunc, nameToMemory, nameToGlobal, enabledFeatures))) return diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go index 3e99e0f241..b8687e47dc 100644 --- a/internal/wasm/memory.go +++ b/internal/wasm/memory.go @@ -25,7 +25,7 @@ const ( MemoryPageSizeInBits = 16 ) -// MemorySizer is the default function that derives min, capacity and max pages from decoded source. The capacity +// MemorySizer is the default function that derives min, capacity and max pages from decoded wasm. The capacity // returned is set to minPages and max defaults to MemoryLimitPages when maxPages is nil. var MemorySizer api.MemorySizer = func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { if maxPages != nil { diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 2106a3ec45..cac6207f7c 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -16,15 +16,15 @@ import ( "github.com/tetratelabs/wazero/internal/wasmdebug" ) -// DecodeModule parses the configured source into a Module. This function returns when the source is exhausted or -// an error occurs. The result can be initialized for use via Store.Instantiate. +// DecodeModule parses the WebAssembly Binary Format (%.wasm) into a Module. This function returns when the input is +// exhausted or an error occurs. The result can be initialized for use via Store.Instantiate. // // Here's a description of the return values: // * result is the module parsed or nil on error // * err is a FormatError invoking the parser, dangling block comments or unexpected characters. // See binary.DecodeModule and text.DecodeModule type DecodeModule func( - source []byte, + wasm []byte, enabledFeatures Features, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), ) (result *Module, err error) @@ -175,7 +175,7 @@ type Module struct { // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions DataCountSection *uint32 - // ID is the sha256 value of the source code (text/binary) and is used for caching. + // ID is the sha256 value of the source wasm and is used for caching. ID ModuleID } @@ -190,9 +190,9 @@ const ( MaximumTableIndex = uint32(1 << 27) ) -// AssignModuleID calculates a sha256 checksum on `source` and set Module.ID to the result. -func (m *Module) AssignModuleID(source []byte) { - m.ID = sha256.Sum256(source) +// AssignModuleID calculates a sha256 checksum on `wasm` and set Module.ID to the result. +func (m *Module) AssignModuleID(wasm []byte) { + m.ID = sha256.Sum256(wasm) } // TypeOfFunction returns the wasm.SectionIDType index for the given function namespace index or nil. diff --git a/internal/wasm/text/decoder.go b/internal/watzero/internal/decoder.go similarity index 99% rename from internal/wasm/text/decoder.go rename to internal/watzero/internal/decoder.go index 1a5db88663..089e1836ef 100644 --- a/internal/wasm/text/decoder.go +++ b/internal/watzero/internal/decoder.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/decoder_test.go b/internal/watzero/internal/decoder_test.go similarity index 99% rename from internal/wasm/text/decoder_test.go rename to internal/watzero/internal/decoder_test.go index 43545f981b..1ad9feffff 100644 --- a/internal/wasm/text/decoder_test.go +++ b/internal/watzero/internal/decoder_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( _ "embed" diff --git a/internal/wasm/text/errors.go b/internal/watzero/internal/errors.go similarity index 99% rename from internal/wasm/text/errors.go rename to internal/watzero/internal/errors.go index d084e16d5a..214a17b2db 100644 --- a/internal/wasm/text/errors.go +++ b/internal/watzero/internal/errors.go @@ -1,4 +1,4 @@ -package text +package internal import ( "fmt" diff --git a/internal/wasm/text/errors_test.go b/internal/watzero/internal/errors_test.go similarity index 99% rename from internal/wasm/text/errors_test.go rename to internal/watzero/internal/errors_test.go index 9775c36446..43043d3633 100644 --- a/internal/wasm/text/errors_test.go +++ b/internal/watzero/internal/errors_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/func_parser.go b/internal/watzero/internal/func_parser.go similarity index 99% rename from internal/wasm/text/func_parser.go rename to internal/watzero/internal/func_parser.go index 25d4834db1..01b169f765 100644 --- a/internal/wasm/text/func_parser.go +++ b/internal/watzero/internal/func_parser.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/func_parser_test.go b/internal/watzero/internal/func_parser_test.go similarity index 99% rename from internal/wasm/text/func_parser_test.go rename to internal/watzero/internal/func_parser_test.go index 3e9adbecb1..9d73d7a0e7 100644 --- a/internal/wasm/text/func_parser_test.go +++ b/internal/watzero/internal/func_parser_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/index_namespace.go b/internal/watzero/internal/index_namespace.go similarity index 99% rename from internal/wasm/text/index_namespace.go rename to internal/watzero/internal/index_namespace.go index 913dadfd4a..0e192fd3ca 100644 --- a/internal/wasm/text/index_namespace.go +++ b/internal/watzero/internal/index_namespace.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/index_namespace_test.go b/internal/watzero/internal/index_namespace_test.go similarity index 99% rename from internal/wasm/text/index_namespace_test.go rename to internal/watzero/internal/index_namespace_test.go index 3b405c2a2c..ec703813d8 100644 --- a/internal/wasm/text/index_namespace_test.go +++ b/internal/watzero/internal/index_namespace_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/lexer.go b/internal/watzero/internal/lexer.go similarity index 99% rename from internal/wasm/text/lexer.go rename to internal/watzero/internal/lexer.go index d751bdcd37..ebd335b191 100644 --- a/internal/wasm/text/lexer.go +++ b/internal/watzero/internal/lexer.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/lexer_test.go b/internal/watzero/internal/lexer_test.go similarity index 99% rename from internal/wasm/text/lexer_test.go rename to internal/watzero/internal/lexer_test.go index 6b7773433f..7a1b4e71db 100644 --- a/internal/wasm/text/lexer_test.go +++ b/internal/watzero/internal/lexer_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "fmt" diff --git a/internal/wasm/text/memory_parser.go b/internal/watzero/internal/memory_parser.go similarity index 99% rename from internal/wasm/text/memory_parser.go rename to internal/watzero/internal/memory_parser.go index c5000a187d..94e07e5da3 100644 --- a/internal/wasm/text/memory_parser.go +++ b/internal/watzero/internal/memory_parser.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/memory_parser_test.go b/internal/watzero/internal/memory_parser_test.go similarity index 99% rename from internal/wasm/text/memory_parser_test.go rename to internal/watzero/internal/memory_parser_test.go index 9416785993..72ba3fc42d 100644 --- a/internal/wasm/text/memory_parser_test.go +++ b/internal/watzero/internal/memory_parser_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "testing" diff --git a/internal/wasm/text/numbers.go b/internal/watzero/internal/numbers.go similarity index 99% rename from internal/wasm/text/numbers.go rename to internal/watzero/internal/numbers.go index 791bc4d9e9..4bfa638f85 100644 --- a/internal/wasm/text/numbers.go +++ b/internal/watzero/internal/numbers.go @@ -1,4 +1,4 @@ -package text +package internal // decodeUint32 decodes an uint32 from a tokenUN or returns false on overflow // diff --git a/internal/wasm/text/numbers_test.go b/internal/watzero/internal/numbers_test.go similarity index 99% rename from internal/wasm/text/numbers_test.go rename to internal/watzero/internal/numbers_test.go index a4422e73a9..fa334edda8 100644 --- a/internal/wasm/text/numbers_test.go +++ b/internal/watzero/internal/numbers_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "math" diff --git a/internal/wasm/text/token.go b/internal/watzero/internal/token.go similarity index 99% rename from internal/wasm/text/token.go rename to internal/watzero/internal/token.go index cdcb182722..32185fe458 100644 --- a/internal/wasm/text/token.go +++ b/internal/watzero/internal/token.go @@ -1,4 +1,4 @@ -package text +package internal // token is the set of tokens defined by the WebAssembly Text Format 1.0 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#tokens%E2%91%A0 diff --git a/internal/wasm/text/token_test.go b/internal/watzero/internal/token_test.go similarity index 97% rename from internal/wasm/text/token_test.go rename to internal/watzero/internal/token_test.go index fbb1db7f39..cad3b23394 100644 --- a/internal/wasm/text/token_test.go +++ b/internal/watzero/internal/token_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "testing" diff --git a/internal/wasm/text/type_parser.go b/internal/watzero/internal/type_parser.go similarity index 99% rename from internal/wasm/text/type_parser.go rename to internal/watzero/internal/type_parser.go index d79312fbfe..e6cc0a687e 100644 --- a/internal/wasm/text/type_parser.go +++ b/internal/watzero/internal/type_parser.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/type_parser_test.go b/internal/watzero/internal/type_parser_test.go similarity index 99% rename from internal/wasm/text/type_parser_test.go rename to internal/watzero/internal/type_parser_test.go index fcb0ff019d..529a6a5540 100644 --- a/internal/wasm/text/type_parser_test.go +++ b/internal/watzero/internal/type_parser_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "testing" diff --git a/internal/wasm/text/typeuse_parser.go b/internal/watzero/internal/typeuse_parser.go similarity index 99% rename from internal/wasm/text/typeuse_parser.go rename to internal/watzero/internal/typeuse_parser.go index 97acefd223..1f3d77b99a 100644 --- a/internal/wasm/text/typeuse_parser.go +++ b/internal/watzero/internal/typeuse_parser.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/wasm/text/typeuse_parser_test.go b/internal/watzero/internal/typeuse_parser_test.go similarity index 99% rename from internal/wasm/text/typeuse_parser_test.go rename to internal/watzero/internal/typeuse_parser_test.go index 93cb0fa821..e75e2d6fde 100644 --- a/internal/wasm/text/typeuse_parser_test.go +++ b/internal/watzero/internal/typeuse_parser_test.go @@ -1,4 +1,4 @@ -package text +package internal import ( "errors" diff --git a/internal/integration_test/vs/testdata/example.wat b/internal/watzero/testdata/example.wat similarity index 100% rename from internal/integration_test/vs/testdata/example.wat rename to internal/watzero/testdata/example.wat diff --git a/internal/watzero/watzero.go b/internal/watzero/watzero.go new file mode 100644 index 0000000000..b98d98ba81 --- /dev/null +++ b/internal/watzero/watzero.go @@ -0,0 +1,23 @@ +package watzero + +import ( + internalwasm "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasm/binary" + "github.com/tetratelabs/wazero/internal/watzero/internal" +) + +// Wat2Wasm converts the WebAssembly Text Format (%.wat) into the Binary Format (%.wasm). +// This function returns when the input is exhausted or an error occurs. +// +// Here's a description of the return values: +// * result is the module parsed or nil on error +// * err is an error invoking the parser, dangling block comments or unexpected characters. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#text-format%E2%91%A0 +func Wat2Wasm(wat string) ([]byte, error) { + if m, err := internal.DecodeModule([]byte(wat), internalwasm.Features20220419, internalwasm.MemorySizer); err != nil { + return nil, err + } else { + return binary.EncodeModule(m), nil + } +} diff --git a/internal/integration_test/vs/codec.go b/internal/watzero/watzero_test.go similarity index 80% rename from internal/integration_test/vs/codec.go rename to internal/watzero/watzero_test.go index a1aee16bf0..325270bf05 100644 --- a/internal/integration_test/vs/codec.go +++ b/internal/watzero/watzero_test.go @@ -1,23 +1,20 @@ -package vs +package watzero import ( _ "embed" "testing" + "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm/binary" - "github.com/tetratelabs/wazero/internal/wasm/text" ) -// example holds the latest supported features as described in the comments of exampleText +// example holds the latest supported features as described in the comments of exampleWat var example = newExample() -// exampleText is different from exampleWat because the parser doesn't yet support all features. +// exampleWat is different from exampleWat because the parser doesn't yet support all features. //go:embed testdata/example.wat -var exampleText []byte - -// exampleBinary is the exampleText encoded in the WebAssembly 1.0 binary format. -var exampleBinary = binary.EncodeModule(example) +var exampleWat string func newExample() *wasm.Module { three := wasm.Index(3) @@ -89,22 +86,16 @@ func newExample() *wasm.Module { } } -func BenchmarkWat2Wasm(b *testing.B, vsName string, vsWat2Wasm func([]byte) error) { - b.Run("wazero", func(b *testing.B) { - for i := 0; i < b.N; i++ { - if m, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemorySizer); err != nil { - b.Fatal(err) - } else { - _ = binary.EncodeModule(m) - } - } - }) - b.Run(vsName, func(b *testing.B) { - for i := 0; i < b.N; i++ { - err := vsWat2Wasm(exampleText) - if err != nil { - panic(err) - } +func BenchmarkWat2Wasm(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Wat2Wasm(exampleWat); err != nil { + b.Fatal(err) } - }) + } +} + +func TestWat2Wasm(t *testing.T) { + wasm, err := Wat2Wasm(exampleWat) + require.NoError(t, err) + require.Equal(t, binary.EncodeModule(example), wasm) } diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go index f8ec6ebf20..3e316492c9 100644 --- a/internal/wazeroir/compiler_test.go +++ b/internal/wazeroir/compiler_test.go @@ -9,7 +9,8 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/internal/wasm/text" + binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary" + "github.com/tetratelabs/wazero/internal/watzero" ) // ctx is an arbitrary, non-default context. @@ -603,8 +604,10 @@ func requireCompilationResult(t *testing.T, enabledFeatures wasm.Features, expec require.Equal(t, expected, res[0]) } -func requireModuleText(t *testing.T, source string) *wasm.Module { - m, err := text.DecodeModule([]byte(source), wasm.Features20220419, wasm.MemorySizer) +func requireModuleText(t *testing.T, wat string) *wasm.Module { + binary, err := watzero.Wat2Wasm(wat) + require.NoError(t, err) + m, err := binaryformat.DecodeModule(binary, wasm.Features20220419, wasm.MemorySizer) require.NoError(t, err) return m } diff --git a/namespace.go b/namespace.go index 8691875a7b..ac03b7e809 100644 --- a/namespace.go +++ b/namespace.go @@ -36,7 +36,7 @@ type Namespace interface { // // Everything below here can be closed, but will anyway due to above. // _, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, n) - // mod, _ := n.InstantiateModuleFromCode(ctx, source) + // mod, _ := n.InstantiateModuleFromBinary(ctx, wasm) // // See Closer CloseWithExitCode(ctx context.Context, exitCode uint32) error diff --git a/runtime.go b/runtime.go index 9c39450649..11056caf2a 100644 --- a/runtime.go +++ b/runtime.go @@ -8,8 +8,7 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/internal/wasm/binary" - "github.com/tetratelabs/wazero/internal/wasm/text" + binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary" ) // Runtime allows embedding of WebAssembly modules. @@ -19,7 +18,7 @@ import ( // r := wazero.NewRuntime() // defer r.Close(ctx) // This closes everything this Runtime created. // -// module, _ := r.InstantiateModuleFromCode(ctx, source) +// module, _ := r.InstantiateModuleFromBinary(ctx, wasm) type Runtime interface { // NewModuleBuilder lets you create modules out of functions defined in Go. // @@ -32,10 +31,10 @@ type Runtime interface { // _, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r) NewModuleBuilder(moduleName string) ModuleBuilder - // CompileModule decodes the WebAssembly text or binary source or errs if invalid. - // When the context is nil, it defaults to context.Background. + // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid. + // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig or CompileConfig. // - // There are two main reasons to use CompileModule instead of InstantiateModuleFromCode: + // There are two main reasons to use CompileModule instead of InstantiateModuleFromBinary: // * Improve performance when the same module is instantiated multiple times under different names // * Reduce the amount of errors that can occur during InstantiateModule. // @@ -45,9 +44,9 @@ type Runtime interface { // * Any pre-compilation done after decoding the source is dependent on RuntimeConfig or CompileConfig. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 - CompileModule(ctx context.Context, source []byte, config CompileConfig) (CompiledModule, error) + CompileModule(ctx context.Context, binary []byte, config CompileConfig) (CompiledModule, error) - // InstantiateModuleFromCode instantiates a module from the WebAssembly text or binary source or errs if invalid. + // InstantiateModuleFromBinary instantiates a module from the WebAssembly binary (%.wasm) or errs if invalid. // When the context is nil, it defaults to context.Background. // // Ex. @@ -55,14 +54,14 @@ type Runtime interface { // r := wazero.NewRuntime() // defer r.Close(ctx) // This closes everything this Runtime created. // - // module, _ := r.InstantiateModuleFromCode(ctx, source) + // module, _ := r.InstantiateModuleFromBinary(ctx, wasm) // // Notes // // * This is a convenience utility that chains CompileModule with InstantiateModule. To instantiate the same // source multiple times, use CompileModule as InstantiateModule avoids redundant decoding and/or compilation. // * To avoid using configuration defaults, use InstantiateModule instead. - InstantiateModuleFromCode(ctx context.Context, source []byte) (api.Module, error) + InstantiateModuleFromBinary(ctx context.Context, source []byte) (api.Module, error) // Namespace is the default namespace of this runtime, and is embedded for convenience. Most users will only use the // default namespace. @@ -112,7 +111,7 @@ type Runtime interface { // // // Everything below here can be closed, but will anyway due to above. // _, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r) - // mod, _ := r.InstantiateModuleFromCode(ctx, source) + // mod, _ := r.InstantiateModuleFromBinary(ctx, wasm) CloseWithExitCode(ctx context.Context, exitCode uint32) error // Closer closes all namespace and compiled code by delegating to CloseWithExitCode with an exit code of zero. @@ -160,9 +159,9 @@ func (r *runtime) Module(moduleName string) api.Module { } // CompileModule implements Runtime.CompileModule -func (r *runtime) CompileModule(ctx context.Context, source []byte, cConfig CompileConfig) (CompiledModule, error) { - if source == nil { - return nil, errors.New("source == nil") +func (r *runtime) CompileModule(ctx context.Context, binary []byte, cConfig CompileConfig) (CompiledModule, error) { + if binary == nil { + return nil, errors.New("binary == nil") } config, ok := cConfig.(*compileConfig) @@ -170,24 +169,16 @@ func (r *runtime) CompileModule(ctx context.Context, source []byte, cConfig Comp panic(fmt.Errorf("unsupported wazero.CompileConfig implementation: %#v", cConfig)) } - if len(source) < 8 { // Ex. less than magic+version in binary or '(module)' in text - return nil, errors.New("invalid source") - } - - // Peek to see if this is a binary or text format - var decoder wasm.DecodeModule - if bytes.Equal(source[0:4], binary.Magic) { - decoder = binary.DecodeModule - } else { - decoder = text.DecodeModule + if len(binary) < 4 || !bytes.Equal(binary[0:4], binaryformat.Magic) { + return nil, errors.New("invalid binary") } - internal, err := decoder(source, r.enabledFeatures, config.memorySizer) + internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, config.memorySizer) if err != nil { return nil, err } else if err = internal.Validate(r.enabledFeatures); err != nil { // TODO: decoders should validate before returning, as that allows - // them to err with the correct source position. + // them to err with the correct position in the wasm binary. return nil, err } @@ -198,7 +189,7 @@ func (r *runtime) CompileModule(ctx context.Context, source []byte, cConfig Comp } } - internal.AssignModuleID(source) + internal.AssignModuleID(binary) if err = r.store.Engine.CompileModule(ctx, internal); err != nil { return nil, err @@ -209,9 +200,9 @@ func (r *runtime) CompileModule(ctx context.Context, source []byte, cConfig Comp return c, nil } -// InstantiateModuleFromCode implements Runtime.InstantiateModuleFromCode -func (r *runtime) InstantiateModuleFromCode(ctx context.Context, source []byte) (api.Module, error) { - if compiled, err := r.CompileModule(ctx, source, NewCompileConfig()); err != nil { +// InstantiateModuleFromBinary implements Runtime.InstantiateModuleFromBinary +func (r *runtime) InstantiateModuleFromBinary(ctx context.Context, binary []byte) (api.Module, error) { + if compiled, err := r.CompileModule(ctx, binary, NewCompileConfig()); err != nil { return nil, err } else { compiled.(*compiledModule).closeWithModule = true diff --git a/runtime_test.go b/runtime_test.go index c2cd299971..dfd7dbe941 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "errors" - "fmt" "math" "testing" @@ -12,12 +11,17 @@ import ( "github.com/tetratelabs/wazero/internal/leb128" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/internal/wasm/binary" + binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary" + "github.com/tetratelabs/wazero/internal/watzero" "github.com/tetratelabs/wazero/sys" ) -// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. -var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") +var ( + binaryNamedZero = binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "0"}}) + // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. + testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") + zero = wasm.Index(0) +) func TestNewRuntimeWithConfig_PanicsOnWrongImpl(t *testing.T) { // It causes maintenance to define an impl of RuntimeConfig in tests just to verify the error when it is wrong. @@ -33,33 +37,20 @@ func TestRuntime_CompileModule(t *testing.T) { tests := []struct { name string runtime Runtime - source []byte + wasm []byte expectedName string }{ { - name: "text no name", - source: []byte(`(module)`), - }, - { - name: "text empty name", - source: []byte(`(module $)`), - }, - { - name: "text name", - source: []byte(`(module $test)`), - expectedName: "test", - }, - { - name: "binary no name section", - source: binary.EncodeModule(&wasm.Module{}), + name: "no name section", + wasm: binaryformat.EncodeModule(&wasm.Module{}), }, { - name: "binary empty NameSection.ModuleName", - source: binary.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{}}), + name: "empty NameSection.ModuleName", + wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{}}), }, { - name: "binary NameSection.ModuleName", - source: binary.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}), + name: "NameSection.ModuleName", + wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}), expectedName: "test", }, } @@ -71,7 +62,7 @@ func TestRuntime_CompileModule(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - m, err := r.CompileModule(testCtx, tc.source, NewCompileConfig()) + m, err := r.CompileModule(testCtx, tc.wasm, NewCompileConfig()) require.NoError(t, err) code := m.(*compiledModule) if tc.expectedName != "" { @@ -82,9 +73,9 @@ func TestRuntime_CompileModule(t *testing.T) { } t.Run("WithMemorySizer", func(t *testing.T) { - source := []byte(`(module (memory 1))`) + testWasm := binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 1}}) - m, err := r.CompileModule(testCtx, source, NewCompileConfig(). + m, err := r.CompileModule(testCtx, testWasm, NewCompileConfig(). WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { return 1, 2, 3 })) @@ -99,14 +90,15 @@ func TestRuntime_CompileModule(t *testing.T) { }) t.Run("WithImportReplacements", func(t *testing.T) { - source := []byte(`(module + 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, source, NewCompileConfig(). + m, err := r.CompileModule(testCtx, testBin, NewCompileConfig(). WithImportRenamer(func(externType api.ExternType, oldModule, oldName string) (string, string) { if externType != api.ExternTypeFunc { return oldModule, oldName @@ -152,47 +144,44 @@ func TestRuntime_CompileModule_Errors(t *testing.T) { tests := []struct { name string config CompileConfig - source []byte + wasm []byte expectedErr string }{ { name: "nil", - expectedErr: "source == nil", + expectedErr: "binary == nil", }, { name: "invalid binary", - source: append(binary.Magic, []byte("yolo")...), + wasm: append(binaryformat.Magic, []byte("yolo")...), expectedErr: "invalid version header", }, - { - name: "invalid text", - source: []byte(`(modular)`), - expectedErr: "1:2: unexpected field: modular", - }, - { - name: "memory has too many pages text", - source: []byte(`(module (memory 70000))`), - expectedErr: "1:17: min 70000 pages (4 Gi) over limit of 65536 pages (4 Gi) in module.memory[0]", - }, { name: "memory cap < min", // only one test to avoid duplicating tests in module_test.go config: NewCompileConfig().WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { return 3, 1, 3 }), - source: []byte(`(module (memory 3))`), - expectedErr: "1:17: capacity 1 pages (64 Ki) less than minimum 3 pages (192 Ki) in module.memory[0]", + wasm: binaryformat.EncodeModule(&wasm.Module{ + MemorySection: &wasm.Memory{Min: 3}, + }), + expectedErr: "section memory: capacity 1 pages (64 Ki) less than minimum 3 pages (192 Ki)", }, { name: "memory cap < min exported", // only one test to avoid duplicating tests in module_test.go config: NewCompileConfig().WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { return 3, 2, 3 }), - source: []byte(`(module (memory 3) (export "memory" (memory 0)))`), - expectedErr: "1:17: capacity 2 pages (128 Ki) less than minimum 3 pages (192 Ki) in module.memory[0]", + wasm: binaryformat.EncodeModule(&wasm.Module{ + MemorySection: &wasm.Memory{}, + ExportSection: []*wasm.Export{ + {Name: "memory", Type: api.ExternTypeMemory}, + }, + }), + expectedErr: "section memory: capacity 2 pages (128 Ki) less than minimum 3 pages (192 Ki)", }, { - name: "memory has too many pages binary", - source: binary.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}), + name: "memory has too many pages", + wasm: binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}), expectedErr: "section memory: max 70000 pages (4 Gi) over limit of 65536 pages (4 Gi)", }, } @@ -208,7 +197,7 @@ func TestRuntime_CompileModule_Errors(t *testing.T) { if config == nil { config = NewCompileConfig() } - _, err := r.CompileModule(testCtx, tc.source, config) + _, err := r.CompileModule(testCtx, tc.wasm, config) require.EqualError(t, err, tc.expectedErr) }) } @@ -383,7 +372,7 @@ func TestModule_FunctionContext(t *testing.T) { source := requireImportAndExportFunction(t, r, hostFn, functionName) // Instantiate the module and get the export of the above hostFn - module, err := r.InstantiateModuleFromCode(tc.ctx, source) + module, err := r.InstantiateModuleFromBinary(tc.ctx, source) require.NoError(t, err) // This fails if the function wasn't invoked, or had an unexpected context. @@ -410,10 +399,13 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) { Instantiate(testCtx, r) require.NoError(t, err) - code, err := r.CompileModule(testCtx, []byte(`(module $runtime_test.go - (import "env" "start" (func $start)) - (start $start) -)`), NewCompileConfig()) + binary := binaryformat.EncodeModule(&wasm.Module{ + TypeSection: []*wasm.FunctionType{{}}, + ImportSection: []*wasm.Import{{Module: "env", Name: "start", Type: wasm.ExternTypeFunc, DescFunc: 0}}, + StartSection: &zero, + }) + + code, err := r.CompileModule(testCtx, binary, NewCompileConfig()) require.NoError(t, err) // Instantiate the module, which calls the start function. This will fail if the context wasn't as intended. @@ -427,21 +419,23 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) { require.Equal(t, uint32(2), r.(*runtime).store.Engine.CompiledModuleCount()) } -// TestRuntime_InstantiateModuleFromCode_DoesntEnforce_Start ensures wapc-go work when modules import WASI, but don't +// TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start ensures wapc-go work when modules import WASI, but don't // export "_start". -func TestRuntime_InstantiateModuleFromCode_DoesntEnforce_Start(t *testing.T) { +func TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start(t *testing.T) { r := NewRuntime() defer r.Close(testCtx) - mod, err := r.InstantiateModuleFromCode(testCtx, []byte(`(module $wasi_test.go - (memory 1) - (export "memory" (memory 0)) -)`)) + binary := binaryformat.EncodeModule(&wasm.Module{ + MemorySection: &wasm.Memory{Min: 1}, + ExportSection: []*wasm.Export{{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}}, + }) + + mod, err := r.InstantiateModuleFromBinary(testCtx, binary) require.NoError(t, err) require.NoError(t, mod.Close(testCtx)) } -func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) { +func TestRuntime_InstantiateModuleFromBinary_UsesContext(t *testing.T) { r := NewRuntime() defer r.Close(testCtx) @@ -459,18 +453,21 @@ func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) { defer host.Close(testCtx) // Start the module as a WASI command. This will fail if the context wasn't as intended. - _, err = r.InstantiateModuleFromCode(testCtx, []byte(`(module $start + startWasm, err := watzero.Wat2Wasm(`(module $start (import "" "start" (func $start)) (memory 1) (export "_start" (func $start)) (export "memory" (memory 0)) -)`)) +)`) + require.NoError(t, err) + + _, err = r.InstantiateModuleFromBinary(testCtx, startWasm) require.NoError(t, err) require.True(t, calledStart) } -func TestRuntime_InstantiateModuleFromCode_ErrorOnStart(t *testing.T) { +func TestRuntime_InstantiateModuleFromBinary_ErrorOnStart(t *testing.T) { tests := []struct { name, wasm string }{ @@ -507,7 +504,7 @@ func TestRuntime_InstantiateModuleFromCode_ErrorOnStart(t *testing.T) { require.NoError(t, err) // Start the module as a WASI command. We expect it to fail. - _, err = r.InstantiateModuleFromCode(testCtx, []byte(tc.wasm)) + _, err = r.InstantiateModuleFromBinary(testCtx, []byte(tc.wasm)) require.Error(t, err) // Close the imported module, which should remove its compiler cache. @@ -536,7 +533,7 @@ func TestRuntime_InstantiateModule_PanicsOnWrongModuleConfigImpl(t *testing.T) { r := NewRuntime() defer r.Close(testCtx) - code, err := r.CompileModule(testCtx, []byte(`(module)`), NewCompileConfig()) + code, err := r.CompileModule(testCtx, binaryformat.EncodeModule(&wasm.Module{}), NewCompileConfig()) require.NoError(t, err) // It causes maintenance to define an impl of ModuleConfig in tests just to verify the error when it is wrong. @@ -554,7 +551,7 @@ func TestRuntime_InstantiateModule_WithName(t *testing.T) { r := NewRuntime() defer r.Close(testCtx) - base, err := r.CompileModule(testCtx, []byte(`(module $0 (memory 1))`), NewCompileConfig()) + base, err := r.CompileModule(testCtx, binaryNamedZero, NewCompileConfig()) require.NoError(t, err) require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName) @@ -654,7 +651,7 @@ func TestRuntime_Close_ClosesCompiledModules(t *testing.T) { defer r.Close(testCtx) // Normally compiled modules are closed when instantiated but this is never instantiated. - _, err := r.CompileModule(testCtx, []byte(`(module $0 (memory 1))`), NewCompileConfig()) + _, err := r.CompileModule(testCtx, binaryNamedZero, NewCompileConfig()) require.NoError(t, err) require.Equal(t, uint32(1), engine.CompiledModuleCount()) @@ -670,9 +667,11 @@ func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx con _, err := r.NewModuleBuilder("host").ExportFunction(functionName, hostFn).Instantiate(testCtx, r) require.NoError(t, err) - return []byte(fmt.Sprintf( - `(module (import "host" "%[1]s" (func (result i64))) (export "%[1]s" (func 0)))`, functionName, - )) + return binaryformat.EncodeModule(&wasm.Module{ + TypeSection: []*wasm.FunctionType{{Results: []wasm.ValueType{wasm.ValueTypeI64}}}, + ImportSection: []*wasm.Import{{Module: "host", Name: functionName, Type: wasm.ExternTypeFunc, DescFunc: 0}}, + ExportSection: []*wasm.Export{{Name: functionName, Type: wasm.ExternTypeFunc, Index: 0}}, + }) } type mockEngine struct { diff --git a/site/content/_index.md b/site/content/_index.md index 507b5983a3..fb2bf26403 100644 --- a/site/content/_index.md +++ b/site/content/_index.md @@ -22,7 +22,7 @@ func main() { // Read a WebAssembly binary containing an exported "fac" function. // * Ex. (func (export "fac") (param i64) (result i64) ... - source, err := os.ReadFile("./path/to/fac.wasm") + wasm, err := os.ReadFile("./path/to/fac.wasm") if err != nil { log.Panicln(err) } @@ -32,7 +32,7 @@ func main() { defer r.Close(ctx) // This closes everything this Runtime created. // Instantiate the module and return its exported functions - module, err := r.InstantiateModuleFromCode(ctx, source) + module, err := r.InstantiateModuleFromBinary(ctx, wasm) if err != nil { log.Panicln(err) } diff --git a/wasi_snapshot_preview1/example_test.go b/wasi_snapshot_preview1/example_test.go index 1560b4e890..e19830cd14 100644 --- a/wasi_snapshot_preview1/example_test.go +++ b/wasi_snapshot_preview1/example_test.go @@ -2,6 +2,7 @@ package wasi_snapshot_preview1 import ( "context" + _ "embed" "fmt" "log" "os" @@ -10,6 +11,11 @@ import ( "github.com/tetratelabs/wazero/sys" ) +// exitOnStartWasm was generated by the following: +// cd testdata; wat2wasm --debug-names exit_on_start.wat +//go:embed testdata/exit_on_start.wasm +var exitOnStartWasm []byte + // This is an example of how to use WebAssembly System Interface (WASI) with its simplest function: "proc_exit". // // See https://github.com/tetratelabs/wazero/tree/main/examples/wasi for another example. @@ -28,17 +34,7 @@ func Example() { defer wm.Close(testCtx) // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, []byte(` -(module - (import "wasi_snapshot_preview1" "proc_exit" (func $wasi.proc_exit (param $rval i32))) - - (func $main - i32.const 2 ;; push $rval onto the stack - call $wasi.proc_exit ;; return a sys.ExitError to the caller - ) - (export "_start" (func $main)) -) -`), wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, exitOnStartWasm, wazero.NewCompileConfig()) if err != nil { log.Panicln(err) } diff --git a/wasi_snapshot_preview1/testdata/exit_on_start.wat b/wasi_snapshot_preview1/testdata/exit_on_start.wat new file mode 100644 index 0000000000..16ca926513 --- /dev/null +++ b/wasi_snapshot_preview1/testdata/exit_on_start.wat @@ -0,0 +1,9 @@ +(module $exit_on_start + (import "wasi_snapshot_preview1" "proc_exit" + (func $wasi.proc_exit (param $rval i32))) + + (func (export "_start") + i32.const 2 ;; push $rval onto the stack + call $wasi.proc_exit ;; return a sys.ExitError to the caller + ) +) diff --git a/wasi_snapshot_preview1/wasi.go b/wasi_snapshot_preview1/wasi.go index 4590801668..301b4675c0 100644 --- a/wasi_snapshot_preview1/wasi.go +++ b/wasi_snapshot_preview1/wasi.go @@ -9,7 +9,7 @@ // defer r.Close(ctx) // This closes everything this Runtime created. // // _, _ = wasi_snapshot_preview1.Instantiate(ctx, r) -// mod, _ := r.InstantiateModuleFromCode(ctx, source) +// mod, _ := r.InstantiateModuleFromBinary(ctx, wasm) // // See https://github.com/WebAssembly/WASI package wasi_snapshot_preview1 diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go index 525445986a..c0b992f9c2 100644 --- a/wasi_snapshot_preview1/wasi_test.go +++ b/wasi_snapshot_preview1/wasi_test.go @@ -23,6 +23,7 @@ import ( internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/watzero" "github.com/tetratelabs/wazero/sys" ) @@ -2282,12 +2283,15 @@ func instantiateModule(ctx context.Context, t *testing.T, wasifunction, wasiimpo _, err := Instantiate(testCtx, r) require.NoError(t, err) - compiled, err := r.CompileModule(ctx, []byte(fmt.Sprintf(`(module + binary, err := watzero.Wat2Wasm(fmt.Sprintf(`(module %[2]s (memory 1 1) ;; just an arbitrary size big enough for tests (export "memory" (memory 0)) (export "%[1]s" (func $wasi.%[1]s)) -)`, wasifunction, wasiimport)), wazero.NewCompileConfig()) +)`, wasifunction, wasiimport)) + require.NoError(t, err) + + compiled, err := r.CompileModule(ctx, binary, wazero.NewCompileConfig()) require.NoError(t, err) defer compiled.Close(ctx) From acd5b2695c3ee606d2816fd5918eace65e047e4d Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 1 Jun 2022 17:36:43 +0800 Subject: [PATCH 2/2] whoops Signed-off-by: Adrian Cole --- .../multiple-results/testdata/multi_value.wasm | Bin 0 -> 104 bytes .../multiple-results/testdata/result_offset.wasm | Bin 0 -> 143 bytes .../replace-import/testdata/needs_import.wasm | Bin 0 -> 71 bytes experimental/testdata/clock.wasm | Bin 0 -> 132 bytes experimental/testdata/fs.wasm | Bin 0 -> 129 bytes experimental/testdata/listener.wasm | Bin 0 -> 144 bytes internal/integration_test/fs/testdata/fs.wasm | Bin 0 -> 461 bytes .../testdata/exit_on_start.wasm | Bin 0 -> 137 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/multiple-results/testdata/multi_value.wasm create mode 100644 examples/multiple-results/testdata/result_offset.wasm create mode 100644 examples/replace-import/testdata/needs_import.wasm create mode 100644 experimental/testdata/clock.wasm create mode 100644 experimental/testdata/fs.wasm create mode 100644 experimental/testdata/listener.wasm create mode 100644 internal/integration_test/fs/testdata/fs.wasm create mode 100644 wasi_snapshot_preview1/testdata/exit_on_start.wasm diff --git a/examples/multiple-results/testdata/multi_value.wasm b/examples/multiple-results/testdata/multi_value.wasm new file mode 100644 index 0000000000000000000000000000000000000000..384e2ba2543635feb9fe28ec57eaabb0bf94c610 GIT binary patch literal 104 zcmXZUI|_g>5Jk~9;}29Mw)R#cu0c1F3=%|0Z2Wb)brtOIJ&@HC07}__uiJpGAYf3U wC?CZ+J=smmWZ$CT7(?A7i=a?tXu?IjMXCJmwz)mT?Y28VFeN1a#}U6e9{vCn#sB~S literal 0 HcmV?d00001 diff --git a/examples/multiple-results/testdata/result_offset.wasm b/examples/multiple-results/testdata/result_offset.wasm new file mode 100644 index 0000000000000000000000000000000000000000..03b32a1252bd2244264b9ad9b05717615c80d214 GIT binary patch literal 143 zcmXYqu?~VT00rM`F%Zgtx)>%F2M2X@wf;zHOkpXK*cvzab!+hE%N=YO34o>OICJj6 zkCXyjWlZEXtGo5y3@#cwxa2iLp~Y=~kpo`aZaL We*Y#Vk^d`Vmj+lbl3AovsuN!i93B7w literal 0 HcmV?d00001 diff --git a/examples/replace-import/testdata/needs_import.wasm b/examples/replace-import/testdata/needs_import.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a6ce539a47a9d43bfb1510a3fb3d35c4a31f741d GIT binary patch literal 71 zcmZQbEY4+QU|?Y6U`$}C2Lc8rUPk8ByfW6rr2L{11_pLcMi`Spk|i%OHk!mogbRi?wqSz*S_t84@e^d0CqSu^;5CB zGaeUG@tUaUGn+3HzlE3EE{r+6yh&vLU<25+xVAmu-+@b;D6%^tG~ncp{fY!p$;s^} M)_Jt+&rzdTzIf9n3IG5A literal 0 HcmV?d00001 diff --git a/experimental/testdata/fs.wasm b/experimental/testdata/fs.wasm new file mode 100644 index 0000000000000000000000000000000000000000..12fbab438af9ddebb7df45001e88062261ec2277 GIT binary patch literal 129 zcmZvSK?=e!6hvpzh}{?3oktKn(f31A8n7|+2fNJGHG60Ac<=^lI{|Ryb7-xxxjW}m zEs13%#vTH{XA5AqKcL&=;+n8$0C{^A}u| BB%%NS literal 0 HcmV?d00001 diff --git a/experimental/testdata/listener.wasm b/experimental/testdata/listener.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9a4b280ffa04d21f65b9e1a10412f63f81fb9b19 GIT binary patch literal 144 zcmYMsu?~VT6ouh)E*PN<8ciJBT}>Pvd6H7&RhvK|Ey~ENi2!KW!nP<)o zASWIsm%L5E4e8a7c8L1%I$cb3;of)lsUwgHxyrdzkfvgZ8g;(KvBEVEu2Ymn>(i(~ YBX`8+XP*Cp>;;1C_I_}^%(fbcFMf|9yZ`_I literal 0 HcmV?d00001 diff --git a/internal/integration_test/fs/testdata/fs.wasm b/internal/integration_test/fs/testdata/fs.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8e7b5f8fa48528a9131ef1c94f7b0d66eb1a43db GIT binary patch literal 461 zcmaKo%}&H15P%0t|6-zhFus6ylRfx6EN-VQv4w_Vx9VYg^__eU-$$UD-OI{hfZ?0@ zGJv9y0RVdxyLyT1bXpd+>{tvUCeogg{F8}?S&^1x)I`1Ya&maoIB)Ao`Ci%y4Zszo zY8$y9EFrKfL>UN&ssa8FK!~r9<${G(KIfO>3&XipJgqgwh@*Ay9N?P?qeg_yU7DsP ziEop2p7<9NPy=rpRyEqSgX)NjI9pbw2?sQyuqmJ4hB~La-jjS$gvI^&u1$|ldu_Th z_!C}j9MWO%Pw|tmkwL+th>N5X+8&8lG0GsxZhmRfADFF3@V11W7juthkJ(>CgY)|G E17Y)&rvLx| literal 0 HcmV?d00001 diff --git a/wasi_snapshot_preview1/testdata/exit_on_start.wasm b/wasi_snapshot_preview1/testdata/exit_on_start.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6bbf007f975d3619e3796e9fb3ad04dc078c5367 GIT binary patch literal 137 zcmX}kOA5j;6a~rw>ghXc(s0kD;cA54Ip zd0I6)lfB0L48_D`7iZUN7gKl``*sDWgxvLPOfH%V*tcwOmjedeYOk}!xZk7+{zrHu Q#_hfQ7a0UZofdr&Ujw%x>;M1& literal 0 HcmV?d00001