Makes ModuleConfig an interface and fixes mutability bug (#520)
This makes wazero.ModuleConfig an interface instead of a struct to prevent it from being used incorrectly. For example, even though the fields are not exported, someone can mistakenly instantiate this when it is a struct, and in doing so violate internal assumptions. This also fixes a mutability bug in the implementation. Follow-up from #519 and the last in this series Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
500
config.go
500
config.go
@@ -23,7 +23,7 @@ import (
|
||||
type RuntimeConfig interface {
|
||||
|
||||
// WithFeatureBulkMemoryOperations adds instructions modify ranges of memory or table entries
|
||||
// ("bulk-memory-operations"). This defaults to false as the feature was not finished in WebAssembly 1.0 (20191205).
|
||||
// ("bulk-memory-operations"). This defaults to false as the feature was not finished in WebAssembly 1.0.
|
||||
//
|
||||
// Here are the notable effects:
|
||||
// * Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop` instructions.
|
||||
@@ -36,29 +36,29 @@ type RuntimeConfig interface {
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/reference-types/Overview.md
|
||||
// See https://github.com/WebAssembly/spec/pull/1287
|
||||
WithFeatureBulkMemoryOperations(enabled bool) RuntimeConfig
|
||||
WithFeatureBulkMemoryOperations(bool) RuntimeConfig
|
||||
|
||||
// WithFeatureMultiValue enables multiple values ("multi-value"). This defaults to false as the feature was not finished
|
||||
// in WebAssembly 1.0 (20191205).
|
||||
// WithFeatureMultiValue enables multiple values ("multi-value"). This defaults to false as the feature was not
|
||||
// finished in WebAssembly 1.0 (20191205).
|
||||
//
|
||||
// Here are the notable effects:
|
||||
// * Function (`func`) types allow more than one result
|
||||
// * Block types (`block`, `loop` and `if`) can be arbitrary function types
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
WithFeatureMultiValue(enabled bool) RuntimeConfig
|
||||
WithFeatureMultiValue(bool) RuntimeConfig
|
||||
|
||||
// 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
|
||||
// will fail to parse.
|
||||
WithFeatureMutableGlobal(enabled bool) RuntimeConfig
|
||||
WithFeatureMutableGlobal(bool) RuntimeConfig
|
||||
|
||||
// WithFeatureNonTrappingFloatToIntConversion enables non-trapping float-to-int conversions.
|
||||
// ("nontrapping-float-to-int-conversion"). This defaults to false as the feature was not in WebAssembly 1.0 (20191205).
|
||||
// ("nontrapping-float-to-int-conversion"). This defaults to false as the feature was not in WebAssembly 1.0.
|
||||
//
|
||||
// The only effect of enabling this is allowing the following instructions, which return 0 on NaN instead of panicking.
|
||||
// The only effect of enabling is allowing the following instructions, which return 0 on NaN instead of panicking.
|
||||
// * `i32.trunc_sat_f32_s`
|
||||
// * `i32.trunc_sat_f32_u`
|
||||
// * `i32.trunc_sat_f64_s`
|
||||
@@ -69,19 +69,19 @@ type RuntimeConfig interface {
|
||||
// * `i64.trunc_sat_f64_u`
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
|
||||
WithFeatureNonTrappingFloatToIntConversion(enabled bool) RuntimeConfig
|
||||
WithFeatureNonTrappingFloatToIntConversion(bool) RuntimeConfig
|
||||
|
||||
// WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false as the
|
||||
// feature was not in WebAssembly 1.0 (20191205).
|
||||
// WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false
|
||||
// as the feature was not in WebAssembly 1.0.
|
||||
//
|
||||
// Here are the notable effects:
|
||||
// * Adds instructions `i32.extend8_s`, `i32.extend16_s`, `i64.extend8_s`, `i64.extend16_s` and `i64.extend32_s`
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
WithFeatureSignExtensionOps(enabled bool) RuntimeConfig
|
||||
WithFeatureSignExtensionOps(bool) RuntimeConfig
|
||||
|
||||
// WithMemoryCapacityPages is a function that determines memory capacity in pages (65536 bytes per page). The inputs are
|
||||
// the min and possibly nil max defined by the module, and the default is to return the min.
|
||||
// WithMemoryCapacityPages is a function that determines memory capacity in pages (65536 bytes per page). The input
|
||||
// are the min and possibly nil max defined by the module, and the default is to return the min.
|
||||
//
|
||||
// Ex. To set capacity to max when exists:
|
||||
// c = c.WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32 {
|
||||
@@ -93,9 +93,9 @@ type RuntimeConfig interface {
|
||||
//
|
||||
// This function is used at compile time (ModuleBuilder.Build or Runtime.CompileModule). Compile will err if the
|
||||
// function returns a value lower than minPages or greater than WithMemoryLimitPages.
|
||||
WithMemoryCapacityPages(maxCapacityPages func(minPages uint32, maxPages *uint32) uint32) RuntimeConfig
|
||||
WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32) RuntimeConfig
|
||||
|
||||
// WithMemoryLimitPages limits the maximum number of pages a module can define from 65536 pages (4GiB) to a lower value.
|
||||
// WithMemoryLimitPages limits the maximum number of pages a module can define from 65536 pages (4GiB) to the input.
|
||||
//
|
||||
// Notes:
|
||||
// * If a module defines no memory max value, Runtime.CompileModule sets max to the limit.
|
||||
@@ -105,9 +105,9 @@ type RuntimeConfig interface {
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-types%E2%91%A0
|
||||
WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig
|
||||
WithMemoryLimitPages(uint32) RuntimeConfig
|
||||
|
||||
// WithWasmCore1 enables features included in the WebAssembly Core Specification 1.0 (20191205). Selecting this
|
||||
// WithWasmCore1 enables features included in the WebAssembly Core Specification 1.0. Selecting this
|
||||
// overwrites any currently accumulated features with only those included in this W3C recommendation.
|
||||
//
|
||||
// This is default because as of mid 2022, this is the only version that is a Web Standard (W3C Recommendation).
|
||||
@@ -145,66 +145,56 @@ var engineLessConfig = &runtimeConfig{
|
||||
memoryCapacityPages: func(minPages uint32, maxPages *uint32) uint32 { return minPages },
|
||||
}
|
||||
|
||||
// clone ensures all fields are copied even if nil.
|
||||
func (c *runtimeConfig) clone() *runtimeConfig {
|
||||
return &runtimeConfig{
|
||||
enabledFeatures: c.enabledFeatures,
|
||||
newEngine: c.newEngine,
|
||||
memoryLimitPages: c.memoryLimitPages,
|
||||
memoryCapacityPages: c.memoryCapacityPages,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRuntimeConfigJIT compiles WebAssembly modules into runtime.GOARCH-specific assembly for optimal performance.
|
||||
//
|
||||
// Note: This panics at runtime the runtime.GOOS or runtime.GOARCH does not support JIT. Use NewRuntimeConfig to safely
|
||||
// detect and fallback to NewRuntimeConfigInterpreter if needed.
|
||||
func NewRuntimeConfigJIT() RuntimeConfig {
|
||||
ret := engineLessConfig.clone()
|
||||
ret := *engineLessConfig // copy
|
||||
ret.newEngine = jit.NewEngine
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
|
||||
func NewRuntimeConfigInterpreter() RuntimeConfig {
|
||||
ret := engineLessConfig.clone()
|
||||
ret := *engineLessConfig // copy
|
||||
ret.newEngine = interpreter.NewEngine
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureBulkMemoryOperations implements RuntimeConfig.WithFeatureBulkMemoryOperations
|
||||
func (c *runtimeConfig) WithFeatureBulkMemoryOperations(enabled bool) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureBulkMemoryOperations, enabled)
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureMultiValue implements RuntimeConfig.WithFeatureMultiValue
|
||||
func (c *runtimeConfig) WithFeatureMultiValue(enabled bool) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMultiValue, enabled)
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureMutableGlobal implements RuntimeConfig.WithFeatureMutableGlobal
|
||||
func (c *runtimeConfig) WithFeatureMutableGlobal(enabled bool) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMutableGlobal, enabled)
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureNonTrappingFloatToIntConversion implements RuntimeConfig.WithFeatureNonTrappingFloatToIntConversion
|
||||
func (c *runtimeConfig) WithFeatureNonTrappingFloatToIntConversion(enabled bool) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureNonTrappingFloatToIntConversion, enabled)
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureSignExtensionOps implements RuntimeConfig.WithFeatureSignExtensionOps
|
||||
func (c *runtimeConfig) WithFeatureSignExtensionOps(enabled bool) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureSignExtensionOps, enabled)
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithMemoryCapacityPages implements RuntimeConfig.WithMemoryCapacityPages
|
||||
@@ -212,33 +202,33 @@ func (c *runtimeConfig) WithMemoryCapacityPages(maxCapacityPages func(minPages u
|
||||
if maxCapacityPages == nil {
|
||||
return c // Instead of erring.
|
||||
}
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.memoryCapacityPages = maxCapacityPages
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
|
||||
func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.memoryLimitPages = memoryLimitPages
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithWasmCore1 implements RuntimeConfig.WithWasmCore1
|
||||
func (c *runtimeConfig) WithWasmCore1() RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = wasm.Features20191205
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithWasmCore2 implements RuntimeConfig.WithWasmCore2
|
||||
func (c *runtimeConfig) WithWasmCore2() RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret := *c // copy
|
||||
ret.enabledFeatures = wasm.Features20220419
|
||||
return ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
// CompiledCode is a WebAssembly 1.0 (20191205) module ready to be instantiated (Runtime.InstantiateModule) as an
|
||||
// CompiledCode is a WebAssembly 1.0 module ready to be instantiated (Runtime.InstantiateModule) as an
|
||||
// api.Module.
|
||||
//
|
||||
// Note: In WebAssembly language, this is a decoded, validated, and possibly also compiled module. wazero avoids using
|
||||
@@ -273,9 +263,159 @@ func (c *compiledCode) Close(_ context.Context) error {
|
||||
// Note: While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect.
|
||||
// See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
|
||||
//
|
||||
// TODO: This is accidentally mutable. A follow-up PR should change it to be immutable as that's how baseline
|
||||
// configuration can be used safely in modules instantiated on different goroutines.
|
||||
type ModuleConfig struct {
|
||||
// Note: ModuleConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
|
||||
type ModuleConfig interface {
|
||||
|
||||
// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
|
||||
// none.
|
||||
//
|
||||
// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
|
||||
// read by functions imported from other modules.
|
||||
//
|
||||
// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
|
||||
// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
|
||||
// argument to the same value set via WithName.
|
||||
//
|
||||
// Note: This does not default to os.Args as that violates sandboxing.
|
||||
// Note: Runtime.InstantiateModule errs if any value is empty.
|
||||
// See https://linux.die.net/man/3/argv
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
WithArgs(...string) ModuleConfig
|
||||
|
||||
// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
|
||||
//
|
||||
// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
|
||||
// default to the current process environment as that would violate sandboxing. This also does not preserve order.
|
||||
//
|
||||
// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
|
||||
// they could be read by functions imported from other modules.
|
||||
//
|
||||
// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
|
||||
// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
|
||||
// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
|
||||
//
|
||||
// Note: Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
|
||||
// See https://linux.die.net/man/3/environ
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
WithEnv(key, value string) ModuleConfig
|
||||
|
||||
// WithFS assigns the file system to use for any paths beginning at "/". Defaults to not found.
|
||||
//
|
||||
// Ex. This sets a read-only, embedded file-system to serve files under the root ("/") and working (".") directories:
|
||||
//
|
||||
// //go:embed testdata/index.html
|
||||
// var testdataIndex embed.FS
|
||||
//
|
||||
// rooted, err := fs.Sub(testdataIndex, "testdata")
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// // "index.html" is accessible as both "/index.html" and "./index.html" because we didn't use WithWorkDirFS.
|
||||
// config := wazero.NewModuleConfig().WithFS(rooted)
|
||||
//
|
||||
// Note: This sets WithWorkDirFS to the same file-system unless already set.
|
||||
WithFS(fs.FS) ModuleConfig
|
||||
|
||||
// WithImport replaces a specific import module and name with a new one. This allows you to break up a monolithic
|
||||
// module imports, such as "env". This can also help reduce cyclic dependencies.
|
||||
//
|
||||
// For example, if a module was compiled with one module owning all imports:
|
||||
// (import "js" "tbl" (table $tbl 4 funcref))
|
||||
// (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)))
|
||||
//
|
||||
// Use this function to import "increment" and "decrement" from the module "go" and other imports from "wasm":
|
||||
// config.WithImportModule("js", "wasm")
|
||||
// config.WithImport("wasm", "increment", "go", "increment")
|
||||
// config.WithImport("wasm", "decrement", "go", "decrement")
|
||||
//
|
||||
// Upon instantiation, imports resolve as if they were compiled like so:
|
||||
// (import "wasm" "tbl" (table $tbl 4 funcref))
|
||||
// (import "go" "increment" (func $increment (result i32)))
|
||||
// (import "go" "decrement" (func $decrement (result i32)))
|
||||
// (import "wasm" "wasm_increment" (func $wasm_increment (result i32)))
|
||||
// (import "wasm" "wasm_decrement" (func $wasm_decrement (result i32)))
|
||||
//
|
||||
// Note: Any WithImport instructions happen in order, after any WithImportModule instructions.
|
||||
WithImport(oldModule, oldName, newModule, newName string) ModuleConfig
|
||||
|
||||
// WithImportModule replaces every import with oldModule with newModule. This is helpful for modules who have
|
||||
// transitioned to a stable status since the underlying wasm was compiled.
|
||||
//
|
||||
// For example, if a module was compiled like below, with an old module for WASI:
|
||||
// (import "wasi_unstable" "args_get" (func (param i32, i32) (result i32)))
|
||||
//
|
||||
// Use this function to update it to the current version:
|
||||
// config.WithImportModule("wasi_unstable", wasi.ModuleSnapshotPreview1)
|
||||
//
|
||||
// See WithImport for a comprehensive example.
|
||||
// Note: Any WithImportModule instructions happen in order, before any WithImport instructions.
|
||||
WithImportModule(oldModule, newModule string) ModuleConfig
|
||||
|
||||
// WithName configures the module name. Defaults to what was decoded from the module source.
|
||||
//
|
||||
// If the source was in WebAssembly 1.0 Binary Format, this defaults to what was decoded from the custom name
|
||||
// section. Otherwise, if it was decoded from Text Format, this defaults to the module ID stripped of leading '$'.
|
||||
//
|
||||
// For example, if the Module was decoded from the text format `(module $math)`, the default name is "math".
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%91%A2
|
||||
WithName(string) ModuleConfig
|
||||
|
||||
// WithStartFunctions configures the functions to call after the module is instantiated. Defaults to "_start".
|
||||
//
|
||||
// Note: If any function doesn't exist, it is skipped. However, all functions that do exist are called in order.
|
||||
WithStartFunctions(...string) ModuleConfig
|
||||
|
||||
// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
|
||||
//
|
||||
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
|
||||
// be used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stderr
|
||||
WithStderr(io.Writer) ModuleConfig
|
||||
|
||||
// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
|
||||
//
|
||||
// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could
|
||||
// be used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stdin
|
||||
WithStdin(io.Reader) ModuleConfig
|
||||
|
||||
// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
|
||||
//
|
||||
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
|
||||
// be used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stdout
|
||||
WithStdout(io.Writer) ModuleConfig
|
||||
|
||||
// WithWorkDirFS indicates the file system to use for any paths beginning at "./". Defaults to the same as WithFS.
|
||||
//
|
||||
// Ex. This sets a read-only, embedded file-system as the root ("/"), and a mutable one as the working directory ("."):
|
||||
//
|
||||
// //go:embed appA
|
||||
// var rootFS embed.FS
|
||||
//
|
||||
// // Files relative to this source under appA are available under "/" and files relative to "/work/appA" under ".".
|
||||
// config := wazero.NewModuleConfig().WithFS(rootFS).WithWorkDirFS(os.DirFS("/work/appA"))
|
||||
//
|
||||
// Note: os.DirFS documentation includes important notes about isolation, which also applies to fs.Sub. As of Go 1.18,
|
||||
// the built-in file-systems are not jailed (chroot). See https://github.com/golang/go/issues/42322
|
||||
WithWorkDirFS(fs.FS) ModuleConfig
|
||||
}
|
||||
|
||||
type moduleConfig struct {
|
||||
name string
|
||||
startFunctions []string
|
||||
stdin io.Reader
|
||||
@@ -300,8 +440,8 @@ type ModuleConfig struct {
|
||||
replacedImportModules map[string]string
|
||||
}
|
||||
|
||||
func NewModuleConfig() *ModuleConfig {
|
||||
return &ModuleConfig{
|
||||
func NewModuleConfig() ModuleConfig {
|
||||
return &moduleConfig{
|
||||
startFunctions: []string{"_start"},
|
||||
environKeys: map[string]int{},
|
||||
preopenFD: uint32(3), // after stdin/stdout/stderr
|
||||
@@ -310,205 +450,101 @@ func NewModuleConfig() *ModuleConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// WithName configures the module name. Defaults to what was decoded from the module source.
|
||||
//
|
||||
// If the source was in WebAssembly 1.0 (20191205) Binary Format, this defaults to what was decoded from the custom name
|
||||
// section. Otherwise, if it was decoded from Text Format, this defaults to the module ID stripped of leading '$'.
|
||||
//
|
||||
// For example, if the Module was decoded from the text format `(module $math)`, the default name is "math".
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%91%A2
|
||||
func (c *ModuleConfig) WithName(name string) *ModuleConfig {
|
||||
c.name = name
|
||||
return c
|
||||
// WithArgs implements ModuleConfig.WithArgs
|
||||
func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.args = args
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithImport replaces a specific import module and name with a new one. This allows you to break up a monolithic
|
||||
// module imports, such as "env". This can also help reduce cyclic dependencies.
|
||||
//
|
||||
// For example, if a module was compiled with one module owning all imports:
|
||||
// (import "js" "tbl" (table $tbl 4 funcref))
|
||||
// (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)))
|
||||
//
|
||||
// Use this function to import "increment" and "decrement" from the module "go" and other imports from "wasm":
|
||||
// config.WithImportModule("js", "wasm")
|
||||
// config.WithImport("wasm", "increment", "go", "increment")
|
||||
// config.WithImport("wasm", "decrement", "go", "decrement")
|
||||
//
|
||||
// Upon instantiation, imports resolve as if they were compiled like so:
|
||||
// (import "wasm" "tbl" (table $tbl 4 funcref))
|
||||
// (import "go" "increment" (func $increment (result i32)))
|
||||
// (import "go" "decrement" (func $decrement (result i32)))
|
||||
// (import "wasm" "wasm_increment" (func $wasm_increment (result i32)))
|
||||
// (import "wasm" "wasm_decrement" (func $wasm_decrement (result i32)))
|
||||
//
|
||||
// Note: Any WithImport instructions happen in order, after any WithImportModule instructions.
|
||||
func (c *ModuleConfig) WithImport(oldModule, oldName, newModule, newName string) *ModuleConfig {
|
||||
if c.replacedImports == nil {
|
||||
c.replacedImports = map[string][2]string{}
|
||||
// WithEnv implements ModuleConfig.WithEnv
|
||||
func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
// Check to see if this key already exists and update it.
|
||||
if i, ok := ret.environKeys[key]; ok {
|
||||
ret.environ[i+1] = value // environ is pair-indexed, so the value is 1 after the key.
|
||||
} else {
|
||||
ret.environKeys[key] = len(ret.environ)
|
||||
ret.environ = append(ret.environ, key, value)
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFS implements ModuleConfig.WithFS
|
||||
func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.setFS("/", fs)
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithImport implements ModuleConfig.WithImport
|
||||
func (c *moduleConfig) WithImport(oldModule, oldName, newModule, newName string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
if ret.replacedImports == nil {
|
||||
ret.replacedImports = map[string][2]string{}
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.WriteString(oldModule)
|
||||
builder.WriteByte(0) // delimit with NUL as module and name can be any UTF-8 characters.
|
||||
builder.WriteString(oldName)
|
||||
c.replacedImports[builder.String()] = [2]string{newModule, newName}
|
||||
return c
|
||||
ret.replacedImports[builder.String()] = [2]string{newModule, newName}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithImportModule replaces every import with oldModule with newModule. This is helpful for modules who have
|
||||
// transitioned to a stable status since the underlying wasm was compiled.
|
||||
//
|
||||
// For example, if a module was compiled like below, with an old module for WASI:
|
||||
// (import "wasi_unstable" "args_get" (func (param i32, i32) (result i32)))
|
||||
//
|
||||
// Use this function to update it to the current version:
|
||||
// config.WithImportModule("wasi_unstable", wasi.ModuleSnapshotPreview1)
|
||||
//
|
||||
// See WithImport for a comprehensive example.
|
||||
// Note: Any WithImportModule instructions happen in order, before any WithImport instructions.
|
||||
func (c *ModuleConfig) WithImportModule(oldModule, newModule string) *ModuleConfig {
|
||||
if c.replacedImportModules == nil {
|
||||
c.replacedImportModules = map[string]string{}
|
||||
// WithImportModule implements ModuleConfig.WithImportModule
|
||||
func (c *moduleConfig) WithImportModule(oldModule, newModule string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
if ret.replacedImportModules == nil {
|
||||
ret.replacedImportModules = map[string]string{}
|
||||
}
|
||||
c.replacedImportModules[oldModule] = newModule
|
||||
return c
|
||||
ret.replacedImportModules[oldModule] = newModule
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithStartFunctions configures the functions to call after the module is instantiated. Defaults to "_start".
|
||||
//
|
||||
// Note: If any function doesn't exist, it is skipped. However, all functions that do exist are called in order.
|
||||
func (c *ModuleConfig) WithStartFunctions(startFunctions ...string) *ModuleConfig {
|
||||
c.startFunctions = startFunctions
|
||||
return c
|
||||
// WithName implements ModuleConfig.WithName
|
||||
func (c *moduleConfig) WithName(name string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.name = name
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
|
||||
//
|
||||
// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could be
|
||||
// used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stdin
|
||||
func (c *ModuleConfig) WithStdin(stdin io.Reader) *ModuleConfig {
|
||||
c.stdin = stdin
|
||||
return c
|
||||
// WithStartFunctions implements ModuleConfig.WithStartFunctions
|
||||
func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.startFunctions = startFunctions
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
|
||||
//
|
||||
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
|
||||
// be used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stdout
|
||||
func (c *ModuleConfig) WithStdout(stdout io.Writer) *ModuleConfig {
|
||||
c.stdout = stdout
|
||||
return c
|
||||
// WithStderr implements ModuleConfig.WithStderr
|
||||
func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.stderr = stderr
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
|
||||
//
|
||||
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
|
||||
// be used by functions imported from other modules.
|
||||
//
|
||||
// Note: The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
|
||||
// Note: This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
|
||||
// See https://linux.die.net/man/3/stderr
|
||||
func (c *ModuleConfig) WithStderr(stderr io.Writer) *ModuleConfig {
|
||||
c.stderr = stderr
|
||||
return c
|
||||
// WithStdin implements ModuleConfig.WithStdin
|
||||
func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.stdin = stdin
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
|
||||
// none.
|
||||
//
|
||||
// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
|
||||
// read by functions imported from other modules.
|
||||
//
|
||||
// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
|
||||
// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
|
||||
// argument to the same value set via WithName.
|
||||
//
|
||||
// Note: This does not default to os.Args as that violates sandboxing.
|
||||
// Note: Runtime.InstantiateModule errs if any value is empty.
|
||||
// See https://linux.die.net/man/3/argv
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func (c *ModuleConfig) WithArgs(args ...string) *ModuleConfig {
|
||||
c.args = args
|
||||
return c
|
||||
// WithStdout implements ModuleConfig.WithStdout
|
||||
func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.stdout = stdout
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
|
||||
//
|
||||
// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
|
||||
// default to the current process environment as that would violate sandboxing. This also does not preserve order.
|
||||
//
|
||||
// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
|
||||
// they could be read by functions imported from other modules.
|
||||
//
|
||||
// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
|
||||
// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
|
||||
// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
|
||||
//
|
||||
// Note: Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
|
||||
// See https://linux.die.net/man/3/environ
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
func (c *ModuleConfig) WithEnv(key, value string) *ModuleConfig {
|
||||
// Check to see if this key already exists and update it.
|
||||
if i, ok := c.environKeys[key]; ok {
|
||||
c.environ[i+1] = value // environ is pair-indexed, so the value is 1 after the key.
|
||||
} else {
|
||||
c.environKeys[key] = len(c.environ)
|
||||
c.environ = append(c.environ, key, value)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithFS assigns the file system to use for any paths beginning at "/". Defaults to not found.
|
||||
//
|
||||
// Ex. This sets a read-only, embedded file-system to serve files under the root ("/") and working (".") directories:
|
||||
//
|
||||
// //go:embed testdata/index.html
|
||||
// var testdataIndex embed.FS
|
||||
//
|
||||
// rooted, err := fs.Sub(testdataIndex, "testdata")
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// // "index.html" is accessible as both "/index.html" and "./index.html" because we didn't use WithWorkDirFS.
|
||||
// config := wazero.NewModuleConfig().WithFS(rooted)
|
||||
//
|
||||
// Note: This sets WithWorkDirFS to the same file-system unless already set.
|
||||
func (c *ModuleConfig) WithFS(fs fs.FS) *ModuleConfig {
|
||||
c.setFS("/", fs)
|
||||
return c
|
||||
}
|
||||
|
||||
// WithWorkDirFS indicates the file system to use for any paths beginning at "./". Defaults to the same as WithFS.
|
||||
//
|
||||
// Ex. This sets a read-only, embedded file-system as the root ("/"), and a mutable one as the working directory ("."):
|
||||
//
|
||||
// //go:embed appA
|
||||
// var rootFS embed.FS
|
||||
//
|
||||
// // Files relative to this source under appA are available under "/" and files relative to "/work/appA" under ".".
|
||||
// config := wazero.NewModuleConfig().WithFS(rootFS).WithWorkDirFS(os.DirFS("/work/appA"))
|
||||
//
|
||||
// Note: os.DirFS documentation includes important notes about isolation, which also applies to fs.Sub. As of Go 1.18,
|
||||
// the built-in file-systems are not jailed (chroot). See https://github.com/golang/go/issues/42322
|
||||
func (c *ModuleConfig) WithWorkDirFS(fs fs.FS) *ModuleConfig {
|
||||
c.setFS(".", fs)
|
||||
return c
|
||||
// WithWorkDirFS implements ModuleConfig.WithWorkDirFS
|
||||
func (c *moduleConfig) WithWorkDirFS(fs fs.FS) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.setFS(".", fs)
|
||||
return &ret
|
||||
}
|
||||
|
||||
// setFS maps a path to a file-system. This is only used for base paths: "/" and ".".
|
||||
func (c *ModuleConfig) setFS(path string, fs fs.FS) {
|
||||
func (c *moduleConfig) setFS(path string, fs fs.FS) {
|
||||
// Check to see if this key already exists and update it.
|
||||
entry := &wasm.FileEntry{Path: path, FS: fs}
|
||||
if fd, ok := c.preopenPaths[path]; ok {
|
||||
@@ -521,7 +557,7 @@ func (c *ModuleConfig) setFS(path string, fs fs.FS) {
|
||||
}
|
||||
|
||||
// toSysContext creates a baseline wasm.SysContext configured by ModuleConfig.
|
||||
func (c *ModuleConfig) toSysContext() (sys *wasm.SysContext, err error) {
|
||||
func (c *moduleConfig) toSysContext() (sys *wasm.SysContext, err error) {
|
||||
var environ []string // Intentionally doesn't pre-allocate to reduce logic to default to nil.
|
||||
// Same validation as syscall.Setenv for Linux
|
||||
for i := 0; i < len(c.environ); i += 2 {
|
||||
@@ -562,7 +598,7 @@ func (c *ModuleConfig) toSysContext() (sys *wasm.SysContext, err error) {
|
||||
return wasm.NewSysContext(math.MaxUint32, c.args, environ, c.stdin, c.stdout, c.stderr, preopens)
|
||||
}
|
||||
|
||||
func (c *ModuleConfig) replaceImports(module *wasm.Module) *wasm.Module {
|
||||
func (c *moduleConfig) replaceImports(module *wasm.Module) *wasm.Module {
|
||||
if (c.replacedImportModules == nil && c.replacedImports == nil) || module.ImportSection == nil {
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -195,87 +195,87 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
|
||||
func TestModuleConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
with func(*ModuleConfig) *ModuleConfig
|
||||
expected *ModuleConfig
|
||||
with func(ModuleConfig) ModuleConfig
|
||||
expected ModuleConfig
|
||||
}{
|
||||
{
|
||||
name: "WithName",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithName("wazero")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
name: "wazero",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithName - empty",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithName("")
|
||||
},
|
||||
expected: &ModuleConfig{},
|
||||
expected: &moduleConfig{},
|
||||
},
|
||||
{
|
||||
name: "WithImport",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "abort", "assemblyscript", "abort")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - empty to non-empty - module",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("", "abort", "assemblyscript", "abort")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"\000abort": {"assemblyscript", "abort"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - non-empty to empty - module",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "abort", "", "abort")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"", "abort"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - empty to non-empty - name",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "", "assemblyscript", "abort")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000": {"assemblyscript", "abort"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - non-empty to empty - name",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "abort", "assemblyscript", "")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", ""}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - override",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "abort", "assemblyscript", "abort").
|
||||
WithImport("env", "abort", "go", "exit")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"go", "exit"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImport - twice",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImport("env", "abort", "assemblyscript", "abort").
|
||||
WithImport("wasi_unstable", "proc_exit", "wasi_snapshot_preview1", "proc_exit")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImports: map[string][2]string{
|
||||
"env\000abort": {"assemblyscript", "abort"},
|
||||
"wasi_unstable\000proc_exit": {"wasi_snapshot_preview1", "proc_exit"},
|
||||
@@ -284,48 +284,48 @@ func TestModuleConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "WithImportModule",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImportModule("env", "assemblyscript")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"env": "assemblyscript"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImportModule - empty to non-empty",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImportModule("", "assemblyscript")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"": "assemblyscript"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImportModule - non-empty to empty",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImportModule("env", "")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"env": ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImportModule - override",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImportModule("env", "assemblyscript").
|
||||
WithImportModule("env", "go")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"env": "go"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithImportModule - twice",
|
||||
with: func(c *ModuleConfig) *ModuleConfig {
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithImportModule("env", "go").
|
||||
WithImportModule("wasi_unstable", "wasi_snapshot_preview1")
|
||||
},
|
||||
expected: &ModuleConfig{
|
||||
expected: &moduleConfig{
|
||||
replacedImportModules: map[string]string{
|
||||
"env": "go",
|
||||
"wasi_unstable": "wasi_snapshot_preview1",
|
||||
@@ -337,9 +337,11 @@ func TestModuleConfig(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
input := &ModuleConfig{}
|
||||
input := &moduleConfig{}
|
||||
rc := tc.with(input)
|
||||
require.Equal(t, tc.expected, rc)
|
||||
// The source wasn't modified
|
||||
require.Equal(t, &moduleConfig{}, input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -347,21 +349,21 @@ func TestModuleConfig(t *testing.T) {
|
||||
func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *ModuleConfig
|
||||
config ModuleConfig
|
||||
input *wasm.Module
|
||||
expected *wasm.Module
|
||||
expectSame bool
|
||||
}{
|
||||
{
|
||||
name: "no config, no imports",
|
||||
config: &ModuleConfig{},
|
||||
config: &moduleConfig{},
|
||||
input: &wasm.Module{},
|
||||
expected: &wasm.Module{},
|
||||
expectSame: true,
|
||||
},
|
||||
{
|
||||
name: "no config",
|
||||
config: &ModuleConfig{},
|
||||
config: &moduleConfig{},
|
||||
input: &wasm.Module{
|
||||
ImportSection: []*wasm.Import{
|
||||
{
|
||||
@@ -380,7 +382,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "replacedImportModules",
|
||||
config: &ModuleConfig{
|
||||
config: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"wasi_unstable": "wasi_snapshot_preview1"},
|
||||
},
|
||||
input: &wasm.Module{
|
||||
@@ -414,7 +416,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "replacedImportModules doesn't match",
|
||||
config: &ModuleConfig{
|
||||
config: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"env": ""},
|
||||
},
|
||||
input: &wasm.Module{
|
||||
@@ -435,7 +437,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "replacedImports",
|
||||
config: &ModuleConfig{
|
||||
config: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
||||
},
|
||||
input: &wasm.Module{
|
||||
@@ -469,7 +471,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "replacedImports don't match",
|
||||
config: &ModuleConfig{
|
||||
config: &moduleConfig{
|
||||
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
||||
},
|
||||
input: &wasm.Module{
|
||||
@@ -490,7 +492,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "replacedImportModules and replacedImports",
|
||||
config: &ModuleConfig{
|
||||
config: &moduleConfig{
|
||||
replacedImportModules: map[string]string{"js": "wasm"},
|
||||
replacedImports: map[string][2]string{
|
||||
"wasm\000increment": {"go", "increment"},
|
||||
@@ -561,7 +563,7 @@ func TestModuleConfig_replaceImports(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := tc.config.replaceImports(tc.input)
|
||||
actual := tc.config.(*moduleConfig).replaceImports(tc.input)
|
||||
if tc.expectSame {
|
||||
require.Same(t, tc.input, actual)
|
||||
} else {
|
||||
@@ -578,7 +580,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *ModuleConfig
|
||||
input ModuleConfig
|
||||
expected *wasm.SysContext
|
||||
}{
|
||||
{
|
||||
@@ -783,7 +785,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sys, err := tc.input.toSysContext()
|
||||
sys, err := tc.input.(*moduleConfig).toSysContext()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, sys)
|
||||
})
|
||||
@@ -793,7 +795,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
func TestModuleConfig_toSysContext_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *ModuleConfig
|
||||
input ModuleConfig
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
@@ -836,7 +838,7 @@ func TestModuleConfig_toSysContext_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.input.toSysContext()
|
||||
_, err := tc.input.(*moduleConfig).toSysContext()
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
23
wasm.go
23
wasm.go
@@ -76,7 +76,7 @@ type Runtime interface {
|
||||
// defer wasm.Close()
|
||||
//
|
||||
// Note: When the context is nil, it defaults to context.Background.
|
||||
InstantiateModuleFromCodeWithConfig(ctx context.Context, source []byte, config *ModuleConfig) (api.Module, error)
|
||||
InstantiateModuleFromCodeWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error)
|
||||
|
||||
// InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
|
||||
//
|
||||
@@ -114,7 +114,7 @@ type Runtime interface {
|
||||
//
|
||||
// Note: When the context is nil, it defaults to context.Background.
|
||||
// Note: Config is copied during instantiation: Later changes to config do not affect the instantiated result.
|
||||
InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, config *ModuleConfig) (mod api.Module, err error)
|
||||
InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, config ModuleConfig) (api.Module, error)
|
||||
}
|
||||
|
||||
func NewRuntime() Runtime {
|
||||
@@ -217,7 +217,7 @@ func (r *runtime) InstantiateModuleFromCode(ctx context.Context, source []byte)
|
||||
}
|
||||
|
||||
// InstantiateModuleFromCodeWithConfig implements Runtime.InstantiateModuleFromCodeWithConfig
|
||||
func (r *runtime) InstantiateModuleFromCodeWithConfig(ctx context.Context, source []byte, config *ModuleConfig) (api.Module, error) {
|
||||
func (r *runtime) InstantiateModuleFromCodeWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error) {
|
||||
if compiled, err := r.CompileModule(ctx, source); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -233,17 +233,22 @@ func (r *runtime) InstantiateModule(ctx context.Context, compiled CompiledCode)
|
||||
}
|
||||
|
||||
// InstantiateModuleWithConfig implements Runtime.InstantiateModuleWithConfig
|
||||
func (r *runtime) InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, config *ModuleConfig) (mod api.Module, err error) {
|
||||
var sysCtx *wasm.SysContext
|
||||
if sysCtx, err = config.toSysContext(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *runtime) InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, mConfig ModuleConfig) (mod api.Module, err error) {
|
||||
code, ok := compiled.(*compiledCode)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("unsupported wazero.CompiledCode implementation: %#v", compiled))
|
||||
}
|
||||
|
||||
config, ok := mConfig.(*moduleConfig)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("unsupported wazero.ModuleConfig implementation: %#v", mConfig))
|
||||
}
|
||||
|
||||
var sysCtx *wasm.SysContext
|
||||
if sysCtx, err = config.toSysContext(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := config.name
|
||||
if name == "" && code.module.NameSection != nil && code.module.NameSection.ModuleName != "" {
|
||||
name = code.module.NameSection.ModuleName
|
||||
|
||||
15
wasm_test.go
15
wasm_test.go
@@ -477,6 +477,21 @@ func TestInstantiateModuleWithConfig_PanicsOnWrongCompiledCodeImpl(t *testing.T)
|
||||
require.EqualError(t, err, "unsupported wazero.CompiledCode implementation: <nil>")
|
||||
}
|
||||
|
||||
func TestInstantiateModuleWithConfig_PanicsOnWrongModuleConfigImpl(t *testing.T) {
|
||||
r := NewRuntime()
|
||||
code, err := r.CompileModule(testCtx, []byte(`(module)`))
|
||||
require.NoError(t, err)
|
||||
defer code.Close(testCtx)
|
||||
|
||||
// It causes maintenance to define an impl of ModuleConfig in tests just to verify the error when it is wrong.
|
||||
// Instead, we pass nil which is implicitly the wrong type, as that's less work!
|
||||
err = require.CapturePanic(func() {
|
||||
_, _ = r.InstantiateModuleWithConfig(testCtx, code, nil)
|
||||
})
|
||||
|
||||
require.EqualError(t, err, "unsupported wazero.ModuleConfig implementation: <nil>")
|
||||
}
|
||||
|
||||
// TestInstantiateModuleWithConfig_WithName tests that we can pre-validate (cache) a module and instantiate it under
|
||||
// different names. This pattern is used in wapc-go.
|
||||
func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user