diff --git a/RATIONALE.md b/RATIONALE.md index 0c9a1b70..8c90c83f 100644 --- a/RATIONALE.md +++ b/RATIONALE.md @@ -280,8 +280,6 @@ space. It is accepted that the options pattern is common in Go, which is the mai wazero's configuration types cover the three main scopes of WebAssembly use: * `RuntimeConfig`: This is the broadest scope, so applies also to compilation and instantiation. Ex. This controls the WebAssembly Specification Version. -* `CompileConfig`: This affects any compilation related concerns not defined in - the binary format. Ex. This controls the allocation of linear memory. * `ModuleConfig`: This affects modules instantiated after compilation and what resources are allowed. Ex. This defines how or if STDOUT is captured. diff --git a/api/wasm.go b/api/wasm.go index 92181015..d4aad01f 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -486,18 +486,3 @@ func EncodeF64(input float64) uint64 { func DecodeF64(input uint64) float64 { return math.Float64frombits(input) } - -// 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: -// -// capIsMax := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { -// if maxPages != nil { -// return minPages, *maxPages, *maxPages -// } -// return minPages, minPages, 65536 -// } -// -// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem -type MemorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go index c7769477..cf2de795 100644 --- a/cmd/wazero/wazero.go +++ b/cmd/wazero/wazero.go @@ -94,7 +94,7 @@ func doRun(args []string, stdOut io.Writer, stdErr io.Writer, exit func(code int WithSysNanotime(). WithSysWalltime(). WithArgs(append([]string{wasmExe}, wasmArgs...)...) - code, err := rt.CompileModule(ctx, wasm, wazero.NewCompileConfig()) + code, err := rt.CompileModule(ctx, wasm) if err != nil { fmt.Fprintf(stdErr, "error compiling wasm binary: %v\n", err) exit(1) diff --git a/config.go b/config.go index 9977236d..5642f7d5 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package wazero import ( "context" "errors" + "fmt" "io" "io/fs" "math" @@ -41,6 +42,28 @@ type RuntimeConfig interface { // defaults to api.CoreFeaturesV2, even though it is not yet a Web // Standard (REC). WithCoreFeatures(api.CoreFeatures) RuntimeConfig + + // WithMemoryLimitPages overrides the maximum pages allowed per memory. The + // default is 65536, allowing 4GB total memory per instance. Setting a + // value larger than default will panic. + // + // Ex. To reduce the largest possible memory size from 4GB to 128KB: + // rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2) + // + // Note: Wasm has 32-bit memory and each page is 65536 (2^16) bytes. This + // implies a max of 65536 (2^16) addressable pages. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig + + // WithMemoryCapacityFromMax eagerly allocates max memory, unless max is + // not defined. The default is false, which means minimum memory is + // allocated and any call to grow memory results in re-allocations. + // + // Ex. To ensure any memory.grow instruction will never re-allocate: + // rConfig = wazero.NewRuntimeConfig().WithMemoryCapacityFromMax(true) + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig } // NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment, @@ -50,14 +73,18 @@ func NewRuntimeConfig() RuntimeConfig { } type runtimeConfig struct { - enabledFeatures api.CoreFeatures - isInterpreter bool - newEngine func(context.Context, api.CoreFeatures) wasm.Engine + enabledFeatures api.CoreFeatures + memoryLimitPages uint32 + memoryCapacityFromMax bool + isInterpreter bool + newEngine func(context.Context, api.CoreFeatures) wasm.Engine } // engineLessConfig helps avoid copy/pasting the wrong defaults. var engineLessConfig = &runtimeConfig{ - enabledFeatures: api.CoreFeaturesV2, + enabledFeatures: api.CoreFeaturesV2, + memoryLimitPages: wasm.MemoryLimitPages, + memoryCapacityFromMax: false, } // NewRuntimeConfigCompiler compiles WebAssembly modules into @@ -101,6 +128,24 @@ func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfi return ret } +// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages +func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig { + ret := c.clone() + // This panics instead of returning an error as it is unlikely. + if memoryLimitPages > wasm.MemoryLimitPages { + panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages)) + } + ret.memoryLimitPages = memoryLimitPages + return ret +} + +// WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax +func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig { + ret := c.clone() + ret.memoryCapacityFromMax = memoryCapacityFromMax + return ret +} + // CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module. // // In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using @@ -165,45 +210,6 @@ func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition { return c.module.ExportedFunctions() } -// CompileConfig allows you to override what was decoded from wasm, prior to compilation (HostModuleBuilder.Compile or -// Runtime.CompileModule). -// -// For example, WithMemorySizer allows you to override memory size that doesn't match your requirements. -// -// Note: CompileConfig is immutable. Each WithXXX function returns a new instance including the corresponding change. -type CompileConfig interface { - // WithMemorySizer are the allocation parameters used for a Wasm memory. - // The default is to set cap=min and max=65536 if unset. A nil function is invalid and ignored. - WithMemorySizer(api.MemorySizer) CompileConfig -} - -type compileConfig struct { - memorySizer api.MemorySizer -} - -// NewCompileConfig returns a CompileConfig that can be used for configuring module compilation. -func NewCompileConfig() CompileConfig { - return &compileConfig{ - memorySizer: wasm.MemorySizer, - } -} - -// clone makes a deep copy of this compile config. -func (c *compileConfig) clone() *compileConfig { - ret := *c // copy except maps which share a ref - return &ret -} - -// WithMemorySizer implements CompileConfig.WithMemorySizer -func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConfig { - if memorySizer == nil { - return c - } - ret := c.clone() - ret.memorySizer = memorySizer - return ret -} - // ModuleConfig configures resources needed by functions that have low-level interactions with the host operating // system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated // multiple times. @@ -284,7 +290,7 @@ type ModuleConfig interface { // otherwise, is compiler-specific. See /RATIONALE.md for notes. WithFS(fs.FS) ModuleConfig - // WithName configures the module name. Defaults to what was decoded or overridden via CompileConfig.WithModuleName. + // WithName configures the module name. Defaults to what was decoded from the name section. WithName(string) ModuleConfig // WithStartFunctions configures the functions to call after the module is diff --git a/config_test.go b/config_test.go index 78db1cbf..3cc59622 100644 --- a/config_test.go +++ b/config_test.go @@ -6,7 +6,6 @@ import ( "io" "io/fs" "math" - "reflect" "testing" "testing/fstest" @@ -33,7 +32,26 @@ func TestRuntimeConfig(t *testing.T) { enabledFeatures: api.CoreFeaturesV1, }, }, + { + name: "memoryLimitPages", + with: func(c RuntimeConfig) RuntimeConfig { + return c.WithMemoryLimitPages(10) + }, + expected: &runtimeConfig{ + memoryLimitPages: 10, + }, + }, + { + name: "memoryCapacityFromMax", + with: func(c RuntimeConfig) RuntimeConfig { + return c.WithMemoryCapacityFromMax(true) + }, + expected: &runtimeConfig{ + memoryCapacityFromMax: true, + }, + }, } + for _, tt := range tests { tc := tt @@ -45,46 +63,14 @@ func TestRuntimeConfig(t *testing.T) { require.Equal(t, &runtimeConfig{}, input) }) } -} -func TestCompileConfig(t *testing.T) { - mp := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { - return 0, 1, 1 - } - tests := []struct { - name string - with func(CompileConfig) CompileConfig - expected *compileConfig - }{ - { - name: "WithMemorySizer", - with: func(c CompileConfig) CompileConfig { - return c.WithMemorySizer(mp) - }, - expected: &compileConfig{memorySizer: mp}, - }, - { - name: "WithMemorySizer twice", - with: func(c CompileConfig) CompileConfig { - return c.WithMemorySizer(wasm.MemorySizer).WithMemorySizer(mp) - }, - expected: &compileConfig{memorySizer: mp}, - }, - } - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - input := &compileConfig{} - rc := tc.with(input).(*compileConfig) - - // We cannot compare func, but we can compare reflect.Value - // See https://go.dev/ref/spec#Comparison_operators - require.Equal(t, reflect.ValueOf(tc.expected.memorySizer), reflect.ValueOf(rc.memorySizer)) - // The source wasn't modified - require.Equal(t, &compileConfig{}, input) + t.Run("memoryLimitPages invalid panics", func(t *testing.T) { + err := require.CapturePanic(func() { + input := &runtimeConfig{} + input.WithMemoryLimitPages(wasm.MemoryLimitPages + 1) }) - } + require.EqualError(t, err, "memoryLimitPages invalid: 65537 > 65536") + }) } func TestModuleConfig(t *testing.T) { diff --git a/examples/allocation/zig/greet.go b/examples/allocation/zig/greet.go index 01fea07a..4015984e 100644 --- a/examples/allocation/zig/greet.go +++ b/examples/allocation/zig/greet.go @@ -45,7 +45,7 @@ func run() error { // Instantiate a WebAssembly module that imports the "log" function defined // in "env" and exports "memory" and functions we'll use in this example. - compiled, err := r.CompileModule(ctx, greetWasm, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(ctx, greetWasm) if err != nil { return err } diff --git a/examples/namespace/counter.go b/examples/namespace/counter.go index 9b5a02dc..ad42d8f2 100644 --- a/examples/namespace/counter.go +++ b/examples/namespace/counter.go @@ -29,7 +29,7 @@ func main() { defer r.Close(ctx) // This closes everything this Runtime created. // Compile WebAssembly that requires its own "env" module. - compiled, err := r.CompileModule(ctx, counterWasm, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(ctx, counterWasm) if err != nil { log.Panicln(err) } diff --git a/experimental/compilation_cache_example_test.go b/experimental/compilation_cache_example_test.go index 0737c3ab..35a8ef89 100644 --- a/experimental/compilation_cache_example_test.go +++ b/experimental/compilation_cache_example_test.go @@ -41,7 +41,7 @@ func newRuntimeCompileClose(ctx context.Context) { r := wazero.NewRuntime(ctx) defer r.Close(ctx) // This closes everything this Runtime created except the file system cache. - _, err := r.CompileModule(ctx, fsWasm, wazero.NewCompileConfig()) + _, err := r.CompileModule(ctx, fsWasm) if err != nil { log.Panicln(err) } diff --git a/experimental/listener_example_test.go b/experimental/listener_example_test.go index 90475859..90c5f654 100644 --- a/experimental/listener_example_test.go +++ b/experimental/listener_example_test.go @@ -64,7 +64,7 @@ func Example_customListenerFactory() { wasi_snapshot_preview1.MustInstantiate(ctx, r) // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, listenerWasm, wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, listenerWasm) if err != nil { log.Panicln(err) } diff --git a/experimental/listener_test.go b/experimental/listener_test.go index dedc8319..e034522c 100644 --- a/experimental/listener_test.go +++ b/experimental/listener_test.go @@ -53,7 +53,7 @@ func TestFunctionListenerFactory(t *testing.T) { t.Run("fails on compile if compiler", func(t *testing.T) { r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler()) defer r.Close(testCtx) // This closes everything this Runtime created. - _, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig()) + _, err := r.CompileModule(ctx, bin) require.EqualError(t, err, "context includes a FunctionListenerFactoryKey, which is only supported in the interpreter") }) @@ -62,7 +62,7 @@ func TestFunctionListenerFactory(t *testing.T) { r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) defer r.Close(ctx) // This closes everything this Runtime created. - _, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig()) + _, err := r.CompileModule(ctx, bin) require.NoError(t, err) // Ensure each function was converted to a listener eagerly diff --git a/experimental/logging/log_listener_example_test.go b/experimental/logging/log_listener_example_test.go index 6b381b04..2aa318da 100644 --- a/experimental/logging/log_listener_example_test.go +++ b/experimental/logging/log_listener_example_test.go @@ -30,7 +30,7 @@ func Example_newLoggingListenerFactory() { wasi_snapshot_preview1.MustInstantiate(ctx, r) // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, listenerWasm, wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, listenerWasm) if err != nil { log.Panicln(err) } diff --git a/imports/assemblyscript/assemblyscript_test.go b/imports/assemblyscript/assemblyscript_test.go index 7a578c0b..bf07c13d 100644 --- a/imports/assemblyscript/assemblyscript_test.go +++ b/imports/assemblyscript/assemblyscript_test.go @@ -435,7 +435,7 @@ func requireProxyModule(t *testing.T, fns FunctionExporter, config wazero.Module proxyBin := proxy.GetProxyModuleBinary("env", envCompiled) - proxyCompiled, err := r.CompileModule(ctx, proxyBin, wazero.NewCompileConfig()) + proxyCompiled, err := r.CompileModule(ctx, proxyBin) require.NoError(t, err) mod, err := r.InstantiateModule(ctx, proxyCompiled, config) diff --git a/imports/assemblyscript/example/assemblyscript.go b/imports/assemblyscript/example/assemblyscript.go index 524b8d47..4f59a2cd 100644 --- a/imports/assemblyscript/example/assemblyscript.go +++ b/imports/assemblyscript/example/assemblyscript.go @@ -38,7 +38,7 @@ func main() { } // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, asWasm, wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, asWasm) if err != nil { log.Panicln(err) } diff --git a/imports/go/example/stars.go b/imports/go/example/stars.go index 4249ac8e..f3588daf 100644 --- a/imports/go/example/stars.go +++ b/imports/go/example/stars.go @@ -46,7 +46,7 @@ func main() { // Compile the WebAssembly module using the default configuration. start := time.Now() - compiled, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(ctx, bin) if err != nil { log.Panicln(err) } diff --git a/imports/go/example/stars_test.go b/imports/go/example/stars_test.go index 66b3588f..8499e62e 100644 --- a/imports/go/example/stars_test.go +++ b/imports/go/example/stars_test.go @@ -71,7 +71,7 @@ func Benchmark_main(b *testing.B) { if err != nil { b.Fatal(err) } - compiled, err := r.CompileModule(ctx, bin, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(ctx, bin) if err != nil { b.Fatal(err) } diff --git a/imports/wasi_snapshot_preview1/example/cat.go b/imports/wasi_snapshot_preview1/example/cat.go index 79db3426..bfcc0a13 100644 --- a/imports/wasi_snapshot_preview1/example/cat.go +++ b/imports/wasi_snapshot_preview1/example/cat.go @@ -77,7 +77,7 @@ func main() { } // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, catWasm, wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, catWasm) if err != nil { log.Panicln(err) } diff --git a/imports/wasi_snapshot_preview1/example_test.go b/imports/wasi_snapshot_preview1/example_test.go index 6350a7c9..aaed1cee 100644 --- a/imports/wasi_snapshot_preview1/example_test.go +++ b/imports/wasi_snapshot_preview1/example_test.go @@ -36,7 +36,7 @@ func Example() { defer wm.Close(testCtx) // Compile the WebAssembly module using the default configuration. - code, err := r.CompileModule(ctx, exitOnStartWasm, wazero.NewCompileConfig()) + code, err := r.CompileModule(ctx, exitOnStartWasm) if err != nil { log.Panicln(err) } diff --git a/imports/wasi_snapshot_preview1/usage_test.go b/imports/wasi_snapshot_preview1/usage_test.go index f05dd286..8cec8cc2 100644 --- a/imports/wasi_snapshot_preview1/usage_test.go +++ b/imports/wasi_snapshot_preview1/usage_test.go @@ -25,7 +25,7 @@ func TestInstantiateModule(t *testing.T) { require.NoError(t, err) defer wm.Close(testCtx) - compiled, err := r.CompileModule(testCtx, wasiArg, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(testCtx, wasiArg) require.NoError(t, err) defer compiled.Close(testCtx) diff --git a/imports/wasi_snapshot_preview1/wasi_bench_test.go b/imports/wasi_snapshot_preview1/wasi_bench_test.go index 9ec011d2..66e4d6a4 100644 --- a/imports/wasi_snapshot_preview1/wasi_bench_test.go +++ b/imports/wasi_snapshot_preview1/wasi_bench_test.go @@ -46,7 +46,7 @@ func Benchmark_EnvironGet(b *testing.B) { compiled, err := r.CompileModule(testCtx, binaryformat.EncodeModule(&wasm.Module{ MemorySection: &wasm.Memory{Min: 1, Max: 1}, ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}}, - }), wazero.NewCompileConfig()) + })) if err != nil { b.Fatal(err) } diff --git a/imports/wasi_snapshot_preview1/wasi_test.go b/imports/wasi_snapshot_preview1/wasi_test.go index 57b58be9..bc2d8679 100644 --- a/imports/wasi_snapshot_preview1/wasi_test.go +++ b/imports/wasi_snapshot_preview1/wasi_test.go @@ -42,7 +42,7 @@ func requireProxyModule(t *testing.T, config wazero.ModuleConfig) (api.Module, a proxyBin := proxy.GetProxyModuleBinary(ModuleName, wasiModuleCompiled) - proxyCompiled, err := r.CompileModule(ctx, proxyBin, wazero.NewCompileConfig()) + proxyCompiled, err := r.CompileModule(ctx, proxyBin) require.NoError(t, err) mod, err := r.InstantiateModule(ctx, proxyCompiled, config) @@ -72,7 +72,7 @@ func requireErrnoNosys(t *testing.T, funcName string, params ...uint64) string { proxyBin := proxy.GetProxyModuleBinary(ModuleName, wasiModuleCompiled) - proxyCompiled, err := r.CompileModule(ctx, proxyBin, wazero.NewCompileConfig()) + proxyCompiled, err := r.CompileModule(ctx, proxyBin) require.NoError(t, err) mod, err := r.InstantiateModule(ctx, proxyCompiled, wazero.NewModuleConfig()) diff --git a/internal/gojs/compiler_test.go b/internal/gojs/compiler_test.go index fc561e6d..de22303a 100644 --- a/internal/gojs/compiler_test.go +++ b/internal/gojs/compiler_test.go @@ -27,7 +27,7 @@ func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig) r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig()) defer r.Close(ctx) - compiled, compileErr := r.CompileModule(ctx, testBin, wazero.NewCompileConfig()) + compiled, compileErr := r.CompileModule(ctx, testBin) if compileErr != nil { err = compileErr return @@ -85,7 +85,7 @@ func TestMain(m *testing.M) { // one test from a cache-miss performance penalty. rt := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig()) defer rt.Close(testCtx) - _, err = rt.CompileModule(testCtx, testBin, wazero.NewCompileConfig()) + _, err = rt.CompileModule(testCtx, testBin) 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 234203b5..7fa8f48c 100644 --- a/internal/integration_test/bench/bench_test.go +++ b/internal/integration_test/bench/bench_test.go @@ -79,7 +79,7 @@ func BenchmarkCompilation(b *testing.B) { } func runCompilation(b *testing.B, r wazero.Runtime) wazero.CompiledModule { - compiled, err := r.CompileModule(testCtx, caseWasm, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(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 index 52a66ab1..82649520 100644 --- a/internal/integration_test/bench/codec_test.go +++ b/internal/integration_test/bench/codec_test.go @@ -89,7 +89,7 @@ func newExample() *wasm.Module { func TestExampleUpToDate(t *testing.T) { t.Run("binary.DecodeModule", func(t *testing.T) { - m, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemorySizer) + m, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false) require.NoError(t, err) require.Equal(t, example, m) }) @@ -116,7 +116,7 @@ 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, api.CoreFeaturesV2, wasm.MemorySizer); err != nil { + if _, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false); err != nil { b.Fatal(err) } } diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go index a9b6f403..b73e8df4 100644 --- a/internal/integration_test/engine/adhoc_test.go +++ b/internal/integration_test/engine/adhoc_test.go @@ -26,14 +26,6 @@ const ( var memoryCapacityPages = uint32(2) -var compileConfig = wazero.NewCompileConfig(). - WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { - if maxPages != nil { - return minPages, memoryCapacityPages, *maxPages - } - return minPages, memoryCapacityPages, memoryCapacityPages - }) - var moduleConfig = wazero.NewModuleConfig() var tests = map[string]func(t *testing.T, r wazero.Runtime){ @@ -529,7 +521,7 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) { // Import that module. binary := callReturnImportWasm(t, imported.Name(), t.Name()+"-importing", i32) - importingCode, err = r.CompileModule(testCtx, binary, compileConfig) + importingCode, err = r.CompileModule(testCtx, binary) require.NoError(t, err) importing, err = r.InstantiateModule(testCtx, importingCode, moduleConfig) @@ -622,7 +614,7 @@ func testMultipleInstantiation(t *testing.T, r wazero.Runtime) { }}, ExportSection: []*wasm.Export{{Name: "store"}}, }) - compiled, err := r.CompileModule(testCtx, bin, compileConfig) + compiled, err := r.CompileModule(testCtx, bin) require.NoError(t, err) defer compiled.Close(testCtx) diff --git a/internal/integration_test/fs/fs_test.go b/internal/integration_test/fs/fs_test.go index 98d233ef..7f193d79 100644 --- a/internal/integration_test/fs/fs_test.go +++ b/internal/integration_test/fs/fs_test.go @@ -158,7 +158,7 @@ func TestReader(t *testing.T) { sys := wazero.NewModuleConfig().WithFS(realFs) // Create a module that just delegates to wasi functions. - compiled, err := r.CompileModule(testCtx, fsWasm, wazero.NewCompileConfig()) + compiled, err := r.CompileModule(testCtx, fsWasm) require.NoError(t, err) mod, err := r.InstantiateModule(testCtx, compiled, sys) diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go index 26877e84..6febf928 100644 --- a/internal/integration_test/spectest/spectest.go +++ b/internal/integration_test/spectest/spectest.go @@ -327,7 +327,7 @@ var spectestWasm []byte // 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, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, enabledFeatures api.CoreFeatures) { - mod, err := binaryformat.DecodeModule(spectestWasm, api.CoreFeaturesV2, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(spectestWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false) require.NoError(t, err) // (global (export "global_i32") i32 (i32.const 666)) @@ -422,7 +422,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func( case "module": buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) require.NoError(t, err, msg) - mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemoryLimitPages, false) require.NoError(t, err, msg) require.NoError(t, mod.Validate(enabledFeatures)) mod.AssignModuleID(buf) @@ -563,7 +563,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func( // // 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 := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false) require.NoError(t, err, msg) err = mod.Validate(s.EnabledFeatures) @@ -592,7 +592,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func( } func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, buf []byte, msg string) { - mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false) if err != nil { return } @@ -860,7 +860,7 @@ func TestBinaryEncoder(t *testing.T, testDataFS embed.FS, enabledFeatures api.Co buf = requireStripCustomSections(t, buf) - mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemorySizer) + mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemoryLimitPages, false) require.NoError(t, err) encodedBuf := binaryformat.EncodeModule(mod) diff --git a/internal/integration_test/vs/runtime.go b/internal/integration_test/vs/runtime.go index 31ac2c37..124b446b 100644 --- a/internal/integration_test/vs/runtime.go +++ b/internal/integration_test/vs/runtime.go @@ -108,7 +108,7 @@ func (r *wazeroRuntime) Compile(ctx context.Context, cfg *RuntimeConfig) (err er return err } } - r.compiled, err = r.runtime.CompileModule(ctx, cfg.ModuleWasm, wazero.NewCompileConfig()) + r.compiled, err = r.runtime.CompileModule(ctx, cfg.ModuleWasm) return } diff --git a/internal/wasm/binary/decoder.go b/internal/wasm/binary/decoder.go index 354ce514..6747dce9 100644 --- a/internal/wasm/binary/decoder.go +++ b/internal/wasm/binary/decoder.go @@ -16,7 +16,8 @@ import ( func DecodeModule( binary []byte, enabledFeatures api.CoreFeatures, - memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, + memoryCapacityFromMax bool, ) (*wasm.Module, error) { r := bytes.NewReader(binary) @@ -31,6 +32,8 @@ func DecodeModule( return nil, ErrInvalidVersion } + memorySizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax) + m := &wasm.Module{} for { // TODO: except custom sections, all others are required to be in order, but we aren't checking yet. @@ -77,7 +80,7 @@ func DecodeModule( case wasm.SectionIDType: m.TypeSection, err = decodeTypeSection(enabledFeatures, r) case wasm.SectionIDImport: - if m.ImportSection, err = decodeImportSection(r, memorySizer, enabledFeatures); err != nil { + if m.ImportSection, err = decodeImportSection(r, memorySizer, memoryLimitPages, enabledFeatures); err != nil { return nil, err // avoid re-wrapping the error. } case wasm.SectionIDFunction: @@ -85,7 +88,7 @@ func DecodeModule( case wasm.SectionIDTable: m.TableSection, err = decodeTableSection(r, enabledFeatures) case wasm.SectionIDMemory: - m.MemorySection, err = decodeMemorySection(r, memorySizer) + m.MemorySection, err = decodeMemorySection(r, memorySizer, memoryLimitPages) case wasm.SectionIDGlobal: if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil { return nil, err // avoid re-wrapping the error. @@ -128,3 +131,20 @@ func DecodeModule( } return m, nil } + +// memorySizer derives min, capacity and max pages from decoded wasm. +type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32) + +// newMemorySizer sets capacity to minPages unless max is defined and +// memoryCapacityFromMax is true. +func newMemorySizer(memoryLimitPages uint32, memoryCapacityFromMax bool) memorySizer { + return func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { + if maxPages != nil { + if memoryCapacityFromMax { + return minPages, *maxPages, *maxPages + } + return minPages, minPages, *maxPages + } + return minPages, minPages, memoryLimitPages + } +} diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index 1b990e62..d0c328b4 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -81,7 +81,7 @@ func TestDecodeModule(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - m, e := DecodeModule(EncodeModule(tc.input), api.CoreFeaturesV1, wasm.MemorySizer) + m, e := DecodeModule(EncodeModule(tc.input), api.CoreFeaturesV1, wasm.MemoryLimitPages, false) require.NoError(t, e) require.Equal(t, tc.input, m) }) @@ -92,7 +92,7 @@ func TestDecodeModule(t *testing.T) { wasm.SectionIDCustom, 0xf, // 15 bytes in this section 0x04, 'm', 'e', 'm', 'e', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0) - m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemorySizer) + m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false) require.NoError(t, e) require.Equal(t, &wasm.Module{}, m) }) @@ -107,14 +107,14 @@ func TestDecodeModule(t *testing.T) { subsectionIDModuleName, 0x07, // 7 bytes in this subsection 0x06, // the Module name simple is 6 bytes long 's', 'i', 'm', 'p', 'l', 'e') - m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemorySizer) + m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false) require.NoError(t, e) require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m) }) t.Run("data count section disabled", func(t *testing.T) { input := append(append(Magic, version...), wasm.SectionIDDataCount, 1, 0) - _, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemorySizer) + _, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false) require.EqualError(t, e, `data count section not supported as feature "bulk-memory-operations" is disabled`) }) } @@ -164,7 +164,7 @@ func TestDecodeModule_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - _, e := DecodeModule(tc.input, api.CoreFeaturesV1, wasm.MemorySizer) + _, e := DecodeModule(tc.input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false) require.EqualError(t, e, tc.expectedErr) }) } diff --git a/internal/wasm/binary/import.go b/internal/wasm/binary/import.go index d34c290d..094b488b 100644 --- a/internal/wasm/binary/import.go +++ b/internal/wasm/binary/import.go @@ -13,6 +13,7 @@ func decodeImport( r *bytes.Reader, idx uint32, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, enabledFeatures api.CoreFeatures, ) (i *wasm.Import, err error) { i = &wasm.Import{} @@ -35,7 +36,7 @@ func decodeImport( case wasm.ExternTypeTable: i.DescTable, err = decodeTable(r, enabledFeatures) case wasm.ExternTypeMemory: - i.DescMem, err = decodeMemory(r, memorySizer) + i.DescMem, err = decodeMemory(r, memorySizer, memoryLimitPages) case wasm.ExternTypeGlobal: i.DescGlobal, err = decodeGlobalType(r) default: diff --git a/internal/wasm/binary/memory.go b/internal/wasm/binary/memory.go index 488c416e..7ce40e43 100644 --- a/internal/wasm/binary/memory.go +++ b/internal/wasm/binary/memory.go @@ -12,6 +12,7 @@ import ( func decodeMemory( r *bytes.Reader, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, ) (*wasm.Memory, error) { min, maxP, err := decodeLimitsType(r) if err != nil { @@ -21,7 +22,7 @@ func decodeMemory( min, capacity, max := memorySizer(min, maxP) mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil} - return mem, mem.Validate() + return mem, mem.Validate(memoryLimitPages) } // encodeMemory returns the wasm.Memory encoded in WebAssembly 1.0 (20191205) Binary Format. diff --git a/internal/wasm/binary/memory_test.go b/internal/wasm/binary/memory_test.go index 23e44fdc..c5fb3fab 100644 --- a/internal/wasm/binary/memory_test.go +++ b/internal/wasm/binary/memory_test.go @@ -9,6 +9,80 @@ import ( "github.com/tetratelabs/wazero/internal/wasm" ) +func Test_newMemorySizer(t *testing.T) { + zero := uint32(0) + one := uint32(1) + limit := wasm.MemoryLimitPages + + tests := []struct { + name string + memoryCapacityFromMax bool + min uint32 + max *uint32 + expectedMin, expectedCapacity, expectedMax uint32 + }{ + { + name: "min 0", + min: zero, + max: &limit, + expectedMin: zero, + expectedCapacity: zero, + expectedMax: limit, + }, + { + name: "min 0 defaults max to limit", + min: zero, + expectedMin: zero, + expectedCapacity: zero, + expectedMax: limit, + }, + { + name: "min 0, max 0", + min: zero, + max: &zero, + expectedMin: zero, + expectedCapacity: zero, + expectedMax: zero, + }, + { + name: "min 0, max 1", + min: zero, + max: &one, + expectedMin: zero, + expectedCapacity: zero, + expectedMax: one, + }, + { + name: "min 0, max 1 memoryCapacityFromMax", + memoryCapacityFromMax: true, + min: zero, + max: &one, + expectedMin: zero, + expectedCapacity: one, + expectedMax: one, + }, + { + name: "min=max", + min: one, + max: &one, + expectedMin: one, + expectedCapacity: one, + expectedMax: one, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + sizer := newMemorySizer(limit, tc.memoryCapacityFromMax) + min, capacity, max := sizer(tc.min, tc.max) + require.Equal(t, tc.expectedMin, min) + require.Equal(t, tc.expectedCapacity, capacity) + require.Equal(t, tc.expectedMax, max) + }) + } +} + func TestMemoryType(t *testing.T) { zero := uint32(0) max := wasm.MemoryLimitPages @@ -20,12 +94,12 @@ func TestMemoryType(t *testing.T) { }{ { name: "min 0", - input: &wasm.Memory{Max: wasm.MemoryLimitPages, IsMaxEncoded: true}, + input: &wasm.Memory{Max: max, IsMaxEncoded: true}, expected: []byte{0x1, 0, 0x80, 0x80, 0x4}, }, { name: "min 0 default max", - input: &wasm.Memory{Max: wasm.MemoryLimitPages}, + input: &wasm.Memory{Max: max}, expected: []byte{0x0, 0}, }, { @@ -59,7 +133,7 @@ func TestMemoryType(t *testing.T) { }) t.Run(fmt.Sprintf("decode %s", tc.name), func(t *testing.T) { - binary, err := decodeMemory(bytes.NewReader(b), wasm.MemorySizer) + binary, err := decodeMemory(bytes.NewReader(b), newMemorySizer(max, false), max) require.NoError(t, err) require.Equal(t, binary, tc.input) }) @@ -67,6 +141,8 @@ func TestMemoryType(t *testing.T) { } func TestDecodeMemoryType_Errors(t *testing.T) { + max := wasm.MemoryLimitPages + tests := []struct { name string input []byte @@ -93,7 +169,7 @@ func TestDecodeMemoryType_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - _, err := decodeMemory(bytes.NewReader(tc.input), wasm.MemorySizer) + _, err := decodeMemory(bytes.NewReader(tc.input), newMemorySizer(max, false), max) require.EqualError(t, err, tc.expectedErr) }) } diff --git a/internal/wasm/binary/section.go b/internal/wasm/binary/section.go index 3711778b..bd343ddb 100644 --- a/internal/wasm/binary/section.go +++ b/internal/wasm/binary/section.go @@ -28,6 +28,7 @@ func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]*wa func decodeImportSection( r *bytes.Reader, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, enabledFeatures api.CoreFeatures, ) ([]*wasm.Import, error) { vs, _, err := leb128.DecodeUint32(r) @@ -37,7 +38,7 @@ func decodeImportSection( result := make([]*wasm.Import, vs) for i := uint32(0); i < vs; i++ { - if result[i], err = decodeImport(r, i, memorySizer, enabledFeatures); err != nil { + if result[i], err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures); err != nil { return nil, err } } @@ -84,6 +85,7 @@ func decodeTableSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]*w func decodeMemorySection( r *bytes.Reader, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, ) (*wasm.Memory, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { @@ -93,7 +95,7 @@ func decodeMemorySection( return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs) } - return decodeMemory(r, memorySizer) + return decodeMemory(r, memorySizer, memoryLimitPages) } func decodeGlobalSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]*wasm.Global, error) { diff --git a/internal/wasm/binary/section_test.go b/internal/wasm/binary/section_test.go index 2da3e5a4..c2b1bc75 100644 --- a/internal/wasm/binary/section_test.go +++ b/internal/wasm/binary/section_test.go @@ -81,6 +81,8 @@ func TestTableSection_Errors(t *testing.T) { } func TestMemorySection(t *testing.T) { + max := wasm.MemoryLimitPages + three := uint32(3) tests := []struct { name string @@ -101,7 +103,7 @@ func TestMemorySection(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - memories, err := decodeMemorySection(bytes.NewReader(tc.input), wasm.MemorySizer) + memories, err := decodeMemorySection(bytes.NewReader(tc.input), newMemorySizer(max, false), max) require.NoError(t, err) require.Equal(t, tc.expected, memories) }) @@ -109,6 +111,8 @@ func TestMemorySection(t *testing.T) { } func TestMemorySection_Errors(t *testing.T) { + max := wasm.MemoryLimitPages + tests := []struct { name string input []byte @@ -129,7 +133,7 @@ func TestMemorySection_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - _, err := decodeMemorySection(bytes.NewReader(tc.input), wasm.MemorySizer) + _, err := decodeMemorySection(bytes.NewReader(tc.input), newMemorySizer(max, false), max) require.EqualError(t, err, tc.expectedErr) }) } diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go index 08922032..9ca5c535 100644 --- a/internal/wasm/memory.go +++ b/internal/wasm/memory.go @@ -24,15 +24,6 @@ const ( MemoryPageSizeInBits = 16 ) -// 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 { - return minPages, minPages, *maxPages - } - return minPages, minPages, MemoryLimitPages -} - // compile-time check to ensure MemoryInstance implements api.Memory var _ api.Memory = &MemoryInstance{} diff --git a/internal/wasm/memory_test.go b/internal/wasm/memory_test.go index 816bae5e..c40bff78 100644 --- a/internal/wasm/memory_test.go +++ b/internal/wasm/memory_test.go @@ -14,21 +14,6 @@ func TestMemoryPageConsts(t *testing.T) { require.Equal(t, MemoryLimitPages, uint32(1<<16)) } -func TestMemoryPages(t *testing.T) { - t.Run("cap=min, nil max", func(t *testing.T) { - min, capacity, max := MemorySizer(1, nil) - require.Equal(t, uint32(1), min) - require.Equal(t, uint32(1), capacity) - require.Equal(t, MemoryLimitPages, max) - }) - t.Run("cap=min, max", func(t *testing.T) { - min, capacity, max := MemorySizer(1, uint32Ptr(2)) - require.Equal(t, uint32(1), min) - require.Equal(t, uint32(1), capacity) - require.Equal(t, uint32(2), max) - }) -} - func TestMemoryPagesToBytesNum(t *testing.T) { for _, numPage := range []uint32{0, 1, 5, 10} { require.Equal(t, uint64(numPage*MemoryPageSize), MemoryPagesToBytesNum(numPage)) diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 36005c7e..dcd637e7 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -751,24 +751,24 @@ type Memory struct { } // Validate ensures values assigned to Min, Cap and Max are within valid thresholds. -func (m *Memory) Validate() error { +func (m *Memory) Validate(memoryLimitPages uint32) error { min, capacity, max := m.Min, m.Cap, m.Max - if max > MemoryLimitPages { + if max > memoryLimitPages { return fmt.Errorf("max %d pages (%s) over limit of %d pages (%s)", - max, PagesToUnitOfBytes(max), MemoryLimitPages, PagesToUnitOfBytes(MemoryLimitPages)) - } else if min > MemoryLimitPages { + max, PagesToUnitOfBytes(max), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) + } else if min > memoryLimitPages { return fmt.Errorf("min %d pages (%s) over limit of %d pages (%s)", - min, PagesToUnitOfBytes(min), MemoryLimitPages, PagesToUnitOfBytes(MemoryLimitPages)) + min, PagesToUnitOfBytes(min), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) } else if min > max { return fmt.Errorf("min %d pages (%s) > max %d pages (%s)", min, PagesToUnitOfBytes(min), max, PagesToUnitOfBytes(max)) } else if capacity < min { return fmt.Errorf("capacity %d pages (%s) less than minimum %d pages (%s)", capacity, PagesToUnitOfBytes(capacity), min, PagesToUnitOfBytes(min)) - } else if capacity > MemoryLimitPages { + } else if capacity > memoryLimitPages { return fmt.Errorf("capacity %d pages (%s) over limit of %d pages (%s)", - capacity, PagesToUnitOfBytes(capacity), MemoryLimitPages, PagesToUnitOfBytes(MemoryLimitPages)) + capacity, PagesToUnitOfBytes(capacity), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) } return nil } diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index c75e5756..3f6224d2 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -109,7 +109,7 @@ func TestMemory_Validate(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - err := tc.mem.Validate() + err := tc.mem.Validate(MemoryLimitPages) if tc.expectedErr == "" { require.NoError(t, err) } else { diff --git a/runtime.go b/runtime.go index 2b7527aa..17d2c190 100644 --- a/runtime.go +++ b/runtime.go @@ -34,7 +34,7 @@ type Runtime interface { NewHostModuleBuilder(moduleName string) HostModuleBuilder // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid. - // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig or CompileConfig. + // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig. // // There are two main reasons to use CompileModule instead of InstantiateModuleFromBinary: // - Improve performance when the same module is instantiated multiple times under different names @@ -43,10 +43,10 @@ type Runtime interface { // # Notes // // - The resulting module name defaults to what was binary from the custom name section. - // - Any pre-compilation done after decoding the source is dependent on RuntimeConfig or CompileConfig. + // - Any pre-compilation done after decoding the source is dependent on RuntimeConfig. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 - CompileModule(ctx context.Context, binary []byte, config CompileConfig) (CompiledModule, error) + CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) // InstantiateModuleFromBinary instantiates a module from the WebAssembly binary (%.wasm) or errs if invalid. // @@ -130,20 +130,24 @@ func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime { config := rConfig.(*runtimeConfig) store, ns := wasm.NewStore(config.enabledFeatures, config.newEngine(ctx, config.enabledFeatures)) return &runtime{ - store: store, - ns: &namespace{store: store, ns: ns}, - enabledFeatures: config.enabledFeatures, - isInterpreter: config.isInterpreter, + store: store, + ns: &namespace{store: store, ns: ns}, + enabledFeatures: config.enabledFeatures, + memoryLimitPages: config.memoryLimitPages, + memoryCapacityFromMax: config.memoryCapacityFromMax, + isInterpreter: config.isInterpreter, } } // runtime allows decoupling of public interfaces from internal representation. type runtime struct { - store *wasm.Store - ns *namespace - enabledFeatures api.CoreFeatures - isInterpreter bool - compiledModules []*compiledModule + store *wasm.Store + ns *namespace + enabledFeatures api.CoreFeatures + memoryLimitPages uint32 + memoryCapacityFromMax bool + isInterpreter bool + compiledModules []*compiledModule } // NewNamespace implements Runtime.NewNamespace. @@ -157,17 +161,16 @@ func (r *runtime) Module(moduleName string) api.Module { } // CompileModule implements Runtime.CompileModule -func (r *runtime) CompileModule(ctx context.Context, binary []byte, cConfig CompileConfig) (CompiledModule, error) { +func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) { if binary == nil { return nil, errors.New("binary == nil") } - config := cConfig.(*compileConfig) if len(binary) < 4 || !bytes.Equal(binary[0:4], binaryformat.Magic) { return nil, errors.New("invalid binary") } - internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, config.memorySizer) + internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, r.memoryLimitPages, r.memoryCapacityFromMax) if err != nil { return nil, err } else if err = internal.Validate(r.enabledFeatures); err != nil { @@ -215,7 +218,7 @@ func buildListeners(ctx context.Context, r *runtime, internal *wasm.Module) ([]e // 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 { + if compiled, err := r.CompileModule(ctx, binary); err != nil { return nil, err } else { compiled.(*compiledModule).closeWithModule = true diff --git a/runtime_test.go b/runtime_test.go index 426df376..a795281d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -63,7 +63,7 @@ func TestRuntime_CompileModule(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - m, err := r.CompileModule(testCtx, tc.wasm, NewCompileConfig()) + m, err := r.CompileModule(testCtx, tc.wasm) require.NoError(t, err) code := m.(*compiledModule) if tc.expectedName != "" { @@ -72,29 +72,11 @@ func TestRuntime_CompileModule(t *testing.T) { require.Equal(t, r.(*runtime).store.Engine, code.compiledEngine) }) } - - t.Run("WithMemorySizer", func(t *testing.T) { - testWasm := binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 1}}) - - m, err := r.CompileModule(testCtx, testWasm, NewCompileConfig(). - WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { - return 1, 2, 3 - })) - require.NoError(t, err) - code := m.(*compiledModule) - - require.Equal(t, &wasm.Memory{ - Min: 1, - Cap: 2, - Max: 3, - }, code.module.MemorySection) - }) } func TestRuntime_CompileModule_Errors(t *testing.T) { tests := []struct { name string - config CompileConfig wasm []byte expectedErr string }{ @@ -107,29 +89,6 @@ func TestRuntime_CompileModule_Errors(t *testing.T) { wasm: append(binaryformat.Magic, []byte("yolo")...), expectedErr: "invalid version header", }, - { - 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 - }), - 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 - }), - 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", wasm: binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}), @@ -144,11 +103,7 @@ func TestRuntime_CompileModule_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - config := tc.config - if config == nil { - config = NewCompileConfig() - } - _, err := r.CompileModule(testCtx, tc.wasm, config) + _, err := r.CompileModule(testCtx, tc.wasm) require.EqualError(t, err, tc.expectedErr) }) } @@ -315,7 +270,7 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) { StartSection: &one, }) - code, err := r.CompileModule(testCtx, binary, NewCompileConfig()) + code, err := r.CompileModule(testCtx, binary) require.NoError(t, err) // Instantiate the module, which calls the start function. This will fail if the context wasn't as intended. @@ -400,7 +355,7 @@ func TestRuntime_InstantiateModule_WithName(t *testing.T) { r := NewRuntime(testCtx) defer r.Close(testCtx) - base, err := r.CompileModule(testCtx, binaryNamedZero, NewCompileConfig()) + base, err := r.CompileModule(testCtx, binaryNamedZero) require.NoError(t, err) require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName) @@ -442,7 +397,7 @@ func TestRuntime_InstantiateModule_ExitError(t *testing.T) { StartSection: &one, }) - code, err := r.CompileModule(testCtx, binary, NewCompileConfig()) + code, err := r.CompileModule(testCtx, binary) require.NoError(t, err) // Instantiate the module, which calls the start function. @@ -479,7 +434,7 @@ func TestRuntime_CloseWithExitCode(t *testing.T) { t.Run(tc.name, func(t *testing.T) { r := NewRuntime(testCtx) - code, err := r.CompileModule(testCtx, bin, NewCompileConfig()) + code, err := r.CompileModule(testCtx, bin) require.NoError(t, err) // Instantiate two modules. @@ -526,7 +481,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, binaryNamedZero, NewCompileConfig()) + _, err := r.CompileModule(testCtx, binaryNamedZero) require.NoError(t, err) require.Equal(t, uint32(1), engine.CompiledModuleCount())