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 0000000000..2dbc9adfdc Binary files /dev/null and b/examples/basic/testdata/add.wasm differ 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 0000000000..ab9a87ee00 Binary files /dev/null and b/examples/import-go/testdata/age_calculator.wasm differ 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.wasm b/examples/multiple-results/testdata/multi_value.wasm new file mode 100644 index 0000000000..384e2ba254 Binary files /dev/null and b/examples/multiple-results/testdata/multi_value.wasm differ 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.wasm b/examples/multiple-results/testdata/result_offset.wasm new file mode 100644 index 0000000000..03b32a1252 Binary files /dev/null and b/examples/multiple-results/testdata/result_offset.wasm differ 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.wasm b/examples/replace-import/testdata/needs_import.wasm new file mode 100644 index 0000000000..a6ce539a47 Binary files /dev/null and b/examples/replace-import/testdata/needs_import.wasm differ 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.wasm b/experimental/testdata/clock.wasm new file mode 100644 index 0000000000..7bc61367c4 Binary files /dev/null and b/experimental/testdata/clock.wasm differ 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.wasm b/experimental/testdata/fs.wasm new file mode 100644 index 0000000000..12fbab438a Binary files /dev/null and b/experimental/testdata/fs.wasm differ 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.wasm b/experimental/testdata/listener.wasm new file mode 100644 index 0000000000..9a4b280ffa Binary files /dev/null and b/experimental/testdata/listener.wasm differ 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.wasm b/internal/integration_test/fs/testdata/fs.wasm new file mode 100644 index 0000000000..8e7b5f8fa4 Binary files /dev/null and b/internal/integration_test/fs/testdata/fs.wasm differ 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.wasm b/wasi_snapshot_preview1/testdata/exit_on_start.wasm new file mode 100644 index 0000000000..6bbf007f97 Binary files /dev/null and b/wasi_snapshot_preview1/testdata/exit_on_start.wasm differ 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)