diff options
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/config.go')
-rw-r--r-- | vendor/github.com/tetratelabs/wazero/config.go | 876 |
1 files changed, 876 insertions, 0 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/config.go b/vendor/github.com/tetratelabs/wazero/config.go new file mode 100644 index 000000000..819a76df5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/config.go @@ -0,0 +1,876 @@ +package wazero + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "math" + "net" + "time" + + "github.com/tetratelabs/wazero/api" + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/engine/interpreter" + "github.com/tetratelabs/wazero/internal/engine/wazevo" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/platform" + internalsock "github.com/tetratelabs/wazero/internal/sock" + internalsys "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/sys" +) + +// RuntimeConfig controls runtime behavior, with the default implementation as +// NewRuntimeConfig +// +// The example below explicitly limits to Wasm Core 1.0 features as opposed to +// relying on defaults: +// +// rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1) +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - RuntimeConfig is immutable. Each WithXXX function returns a new instance +// including the corresponding change. +type RuntimeConfig interface { + // WithCoreFeatures sets the WebAssembly Core specification features this + // runtime supports. Defaults to api.CoreFeaturesV2. + // + // Example of disabling a specific feature: + // features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false) + // rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features) + // + // # Why default to version 2.0? + // + // Many compilers that target WebAssembly require features after + // api.CoreFeaturesV1 by default. For example, TinyGo v0.24+ requires + // api.CoreFeatureBulkMemoryOperations. To avoid runtime errors, wazero + // 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 if the maximum is + // not encoded in a Wasm binary. Setting a value larger than default will panic. + // + // This example reduces 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. + // + // This example ensures 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 + // + // Note: if the memory maximum is not encoded in a Wasm binary, this + // results in allocating 4GB. See the doc on WithMemoryLimitPages for detail. + WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig + + // WithDebugInfoEnabled toggles DWARF based stack traces in the face of + // runtime errors. Defaults to true. + // + // Those who wish to disable this, can like so: + // + // r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithDebugInfoEnabled(false) + // + // When disabled, a stack trace message looks like: + // + // wasm stack trace: + // .runtime._panic(i32) + // .myFunc() + // .main.main() + // .runtime.run() + // ._start() + // + // When enabled, the stack trace includes source code information: + // + // wasm stack trace: + // .runtime._panic(i32) + // 0x16e2: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6 + // .myFunc() + // 0x190b: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:19:7 + // .main.main() + // 0x18ed: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:4:3 + // .runtime.run() + // 0x18cc: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/scheduler_none.go:26:10 + // ._start() + // 0x18b6: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_wasm_wasi.go:22:5 + // + // Note: This only takes into effect when the original Wasm binary has the + // DWARF "custom sections" that are often stripped, depending on + // optimization flags passed to the compiler. + WithDebugInfoEnabled(bool) RuntimeConfig + + // WithCompilationCache configures how runtime caches the compiled modules. In the default configuration, compilation results are + // only in-memory until Runtime.Close is closed, and not shareable by multiple Runtime. + // + // Below defines the shared cache across multiple instances of Runtime: + // + // // Creates the new Cache and the runtime configuration with it. + // cache := wazero.NewCompilationCache() + // defer cache.Close() + // config := wazero.NewRuntimeConfig().WithCompilationCache(c) + // + // // Creates two runtimes while sharing compilation caches. + // foo := wazero.NewRuntimeWithConfig(context.Background(), config) + // bar := wazero.NewRuntimeWithConfig(context.Background(), config) + // + // # Cache Key + // + // Cached files are keyed on the version of wazero. This is obtained from go.mod of your application, + // and we use it to verify the compatibility of caches against the currently-running wazero. + // However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct + // version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976. + // As a consequence, your cache won't contain the correct version information and always be treated as `dev` version. + // To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests. + WithCompilationCache(CompilationCache) RuntimeConfig + + // WithCustomSections toggles parsing of "custom sections". Defaults to false. + // + // When enabled, it is possible to retrieve custom sections from a CompiledModule: + // + // config := wazero.NewRuntimeConfig().WithCustomSections(true) + // r := wazero.NewRuntimeWithConfig(ctx, config) + // c, err := r.CompileModule(ctx, wasm) + // customSections := c.CustomSections() + WithCustomSections(bool) RuntimeConfig + + // WithCloseOnContextDone ensures the executions of functions to be closed under one of the following circumstances: + // + // - context.Context passed to the Call method of api.Function is canceled during execution. (i.e. ctx by context.WithCancel) + // - context.Context passed to the Call method of api.Function reaches timeout during execution. (i.e. ctx by context.WithTimeout or context.WithDeadline) + // - Close or CloseWithExitCode of api.Module is explicitly called during execution. + // + // This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of + // api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the + // entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated + // machine codes against async Goroutine preemption" section in RATIONALE.md for detail. + // + // Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces + // interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason, + // this is disabled by default. + // + // See examples in context_done_example_test.go for the end-to-end demonstrations. + // + // When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and + // the api.Module from which the functions are derived is made closed. + WithCloseOnContextDone(bool) RuntimeConfig +} + +// NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment, +// or the interpreter otherwise. +func NewRuntimeConfig() RuntimeConfig { + return newRuntimeConfig() +} + +type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine + +type runtimeConfig struct { + enabledFeatures api.CoreFeatures + memoryLimitPages uint32 + memoryCapacityFromMax bool + engineKind engineKind + dwarfDisabled bool // negative as defaults to enabled + newEngine newEngine + cache CompilationCache + storeCustomSections bool + ensureTermination bool +} + +// engineLessConfig helps avoid copy/pasting the wrong defaults. +var engineLessConfig = &runtimeConfig{ + enabledFeatures: api.CoreFeaturesV2, + memoryLimitPages: wasm.MemoryLimitPages, + memoryCapacityFromMax: false, + dwarfDisabled: false, +} + +type engineKind int + +const ( + engineKindCompiler engineKind = iota + engineKindInterpreter + engineKindCount +) + +// NewRuntimeConfigCompiler compiles WebAssembly modules into +// runtime.GOARCH-specific assembly for optimal performance. +// +// The default implementation is AOT (Ahead of Time) compilation, applied at +// Runtime.CompileModule. This allows consistent runtime performance, as well +// the ability to reduce any first request penalty. +// +// Note: While this is technically AOT, this does not imply any action on your +// part. wazero automatically performs ahead-of-time compilation as needed when +// Runtime.CompileModule is invoked. +// +// Warning: This panics at runtime if the runtime.GOOS or runtime.GOARCH does not +// support compiler. Use NewRuntimeConfig to safely detect and fallback to +// NewRuntimeConfigInterpreter if needed. +func NewRuntimeConfigCompiler() RuntimeConfig { + ret := engineLessConfig.clone() + ret.engineKind = engineKindCompiler + ret.newEngine = wazevo.NewEngine + return ret +} + +// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly. +func NewRuntimeConfigInterpreter() RuntimeConfig { + ret := engineLessConfig.clone() + ret.engineKind = engineKindInterpreter + ret.newEngine = interpreter.NewEngine + return ret +} + +// clone makes a deep copy of this runtime config. +func (c *runtimeConfig) clone() *runtimeConfig { + ret := *c // copy except maps which share a ref + return &ret +} + +// WithCoreFeatures implements RuntimeConfig.WithCoreFeatures +func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig { + ret := c.clone() + ret.enabledFeatures = features + return ret +} + +// WithCloseOnContextDone implements RuntimeConfig.WithCloseOnContextDone +func (c *runtimeConfig) WithCloseOnContextDone(ensure bool) RuntimeConfig { + ret := c.clone() + ret.ensureTermination = ensure + 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 +} + +// WithCompilationCache implements RuntimeConfig.WithCompilationCache +func (c *runtimeConfig) WithCompilationCache(ca CompilationCache) RuntimeConfig { + ret := c.clone() + ret.cache = ca + return ret +} + +// WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax +func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig { + ret := c.clone() + ret.memoryCapacityFromMax = memoryCapacityFromMax + return ret +} + +// WithDebugInfoEnabled implements RuntimeConfig.WithDebugInfoEnabled +func (c *runtimeConfig) WithDebugInfoEnabled(dwarfEnabled bool) RuntimeConfig { + ret := c.clone() + ret.dwarfDisabled = !dwarfEnabled + return ret +} + +// WithCustomSections implements RuntimeConfig.WithCustomSections +func (c *runtimeConfig) WithCustomSections(storeCustomSections bool) RuntimeConfig { + ret := c.clone() + ret.storeCustomSections = storeCustomSections + 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 +// the name "Module" for both before and after instantiation as the name conflation has caused confusion. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - Closing the wazero.Runtime closes any CompiledModule it compiled. +type CompiledModule interface { + // Name returns the module name encoded into the binary or empty if not. + Name() string + + // ImportedFunctions returns all the imported functions + // (api.FunctionDefinition) in this module or nil if there are none. + // + // Note: Unlike ExportedFunctions, there is no unique constraint on + // imports. + ImportedFunctions() []api.FunctionDefinition + + // ExportedFunctions returns all the exported functions + // (api.FunctionDefinition) in this module keyed on export name. + ExportedFunctions() map[string]api.FunctionDefinition + + // ImportedMemories returns all the imported memories + // (api.MemoryDefinition) in this module or nil if there are none. + // + // ## Notes + // - As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + // - Unlike ExportedMemories, there is no unique constraint on imports. + ImportedMemories() []api.MemoryDefinition + + // ExportedMemories returns all the exported memories + // (api.MemoryDefinition) in this module keyed on export name. + // + // Note: As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + ExportedMemories() map[string]api.MemoryDefinition + + // CustomSections returns all the custom sections + // (api.CustomSection) in this module keyed on the section name. + CustomSections() []api.CustomSection + + // Close releases all the allocated resources for this CompiledModule. + // + // Note: It is safe to call Close while having outstanding calls from an + // api.Module instantiated from this. + Close(context.Context) error +} + +// compile-time check to ensure compiledModule implements CompiledModule +var _ CompiledModule = &compiledModule{} + +type compiledModule struct { + module *wasm.Module + // compiledEngine holds an engine on which `module` is compiled. + compiledEngine wasm.Engine + // closeWithModule prevents leaking compiled code when a module is compiled implicitly. + closeWithModule bool + typeIDs []wasm.FunctionTypeID +} + +// Name implements CompiledModule.Name +func (c *compiledModule) Name() (moduleName string) { + if ns := c.module.NameSection; ns != nil { + moduleName = ns.ModuleName + } + return +} + +// Close implements CompiledModule.Close +func (c *compiledModule) Close(context.Context) error { + c.compiledEngine.DeleteCompiledModule(c.module) + // It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close. + return nil +} + +// ImportedFunctions implements CompiledModule.ImportedFunctions +func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition { + return c.module.ImportedFunctions() +} + +// ExportedFunctions implements CompiledModule.ExportedFunctions +func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition { + return c.module.ExportedFunctions() +} + +// ImportedMemories implements CompiledModule.ImportedMemories +func (c *compiledModule) ImportedMemories() []api.MemoryDefinition { + return c.module.ImportedMemories() +} + +// ExportedMemories implements CompiledModule.ExportedMemories +func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition { + return c.module.ExportedMemories() +} + +// CustomSections implements CompiledModule.CustomSections +func (c *compiledModule) CustomSections() []api.CustomSection { + ret := make([]api.CustomSection, len(c.module.CustomSections)) + for i, d := range c.module.CustomSections { + ret[i] = &customSection{data: d.Data, name: d.Name} + } + return ret +} + +// customSection implements wasm.CustomSection +type customSection struct { + internalapi.WazeroOnlyType + name string + data []byte +} + +// Name implements wasm.CustomSection.Name +func (c *customSection) Name() string { + return c.name +} + +// Data implements wasm.CustomSection.Data +func (c *customSection) Data() []byte { + return c.data +} + +// 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. +// +// Here's an example: +// +// // Initialize base configuration: +// config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime() +// +// // Assign different configuration on each instantiation +// mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw")) +// +// 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). +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - 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. Runtime.InstantiateModule errs if any arg is empty. + // + // 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. + // + // See https://linux.die.net/man/3/argv and 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. + // Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character. + // + // 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. + // + // See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string + WithEnv(key, value string) ModuleConfig + + // WithFS is a convenience that calls WithFSConfig with an FSConfig of the + // input for the root ("/") guest path. + WithFS(fs.FS) ModuleConfig + + // WithFSConfig configures the filesystem available to each guest + // instantiated with this configuration. By default, no file access is + // allowed, so functions like `path_open` result in unsupported errors + // (e.g. syscall.ENOSYS). + WithFSConfig(FSConfig) ModuleConfig + + // WithName configures the module name. Defaults to what was decoded from + // the name section. Empty string ("") clears any name. + WithName(string) ModuleConfig + + // WithStartFunctions configures the functions to call after the module is + // instantiated. Defaults to "_start". + // + // Clearing the default is supported, via `WithStartFunctions()`. + // + // # Notes + // + // - If a start function doesn't exist, it is skipped. However, any that + // do exist are called in order. + // - Start functions are not intended to be called multiple times. + // Functions that should be called multiple times should be invoked + // manually via api.Module's `ExportedFunction` method. + // - Start functions commonly exit the module during instantiation, + // preventing use of any functions later. This is the case in "wasip1", + // which defines the default value "_start". + // - See /RATIONALE.md for motivation of this feature. + 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. + // + // # Notes + // + // - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close. + // - 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. + // + // # Notes + // + // - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close. + // - 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. + // + // # Notes + // + // - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close. + // - 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 + + // WithWalltime configures the wall clock, sometimes referred to as the + // real time clock. sys.Walltime returns the current unix/epoch time, + // seconds since midnight UTC 1 January 1970, with a nanosecond fraction. + // This defaults to a fake result that increases by 1ms on each reading. + // + // Here's an example that uses a custom clock: + // moduleConfig = moduleConfig. + // WithWalltime(func(context.Context) (sec int64, nsec int32) { + // return clock.walltime() + // }, sys.ClockResolution(time.Microsecond.Nanoseconds())) + // + // # Notes: + // - This does not default to time.Now as that violates sandboxing. + // - This is used to implement host functions such as WASI + // `clock_time_get` with the `realtime` clock ID. + // - Use WithSysWalltime for a usable implementation. + WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig + + // WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us + // (1000ns). + // + // See WithWalltime + WithSysWalltime() ModuleConfig + + // WithNanotime configures the monotonic clock, used to measure elapsed + // time in nanoseconds. Defaults to a fake result that increases by 1ms + // on each reading. + // + // Here's an example that uses a custom clock: + // moduleConfig = moduleConfig. + // WithNanotime(func(context.Context) int64 { + // return clock.nanotime() + // }, sys.ClockResolution(time.Microsecond.Nanoseconds())) + // + // # Notes: + // - This does not default to time.Since as that violates sandboxing. + // - This is used to implement host functions such as WASI + // `clock_time_get` with the `monotonic` clock ID. + // - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go). + // - If you set this, you should probably set WithNanosleep also. + // - Use WithSysNanotime for a usable implementation. + WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig + + // WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us. + // + // See WithNanotime + WithSysNanotime() ModuleConfig + + // WithNanosleep configures the how to pause the current goroutine for at + // least the configured nanoseconds. Defaults to return immediately. + // + // This example uses a custom sleep function: + // moduleConfig = moduleConfig. + // WithNanosleep(func(ns int64) { + // rel := unix.NsecToTimespec(ns) + // remain := unix.Timespec{} + // for { // loop until no more time remaining + // err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain) + // --snip-- + // + // # Notes: + // - This does not default to time.Sleep as that violates sandboxing. + // - This is used to implement host functions such as WASI `poll_oneoff`. + // - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go). + // - If you set this, you should probably set WithNanotime also. + // - Use WithSysNanosleep for a usable implementation. + WithNanosleep(sys.Nanosleep) ModuleConfig + + // WithOsyield yields the processor, typically to implement spin-wait + // loops. Defaults to return immediately. + // + // # Notes: + // - This primarily supports `sched_yield` in WASI + // - This does not default to runtime.osyield as that violates sandboxing. + WithOsyield(sys.Osyield) ModuleConfig + + // WithSysNanosleep uses time.Sleep for sys.Nanosleep. + // + // See WithNanosleep + WithSysNanosleep() ModuleConfig + + // WithRandSource configures a source of random bytes. Defaults to return a + // deterministic source. You might override this with crypto/rand.Reader + // + // This reader is most commonly used by the functions like "random_get" in + // "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and + // "getRandomData" when runtime.GOOS is "js". + // + // Note: The caller is responsible to close any io.Reader they supply: It + // is not closed on api.Module Close. + WithRandSource(io.Reader) ModuleConfig +} + +type moduleConfig struct { + name string + nameSet bool + startFunctions []string + stdin io.Reader + stdout io.Writer + stderr io.Writer + randSource io.Reader + walltime sys.Walltime + walltimeResolution sys.ClockResolution + nanotime sys.Nanotime + nanotimeResolution sys.ClockResolution + nanosleep sys.Nanosleep + osyield sys.Osyield + args [][]byte + // environ is pair-indexed to retain order similar to os.Environ. + environ [][]byte + // environKeys allow overwriting of existing values. + environKeys map[string]int + // fsConfig is the file system configuration for ABI like WASI. + fsConfig FSConfig + // sockConfig is the network listener configuration for ABI like WASI. + sockConfig *internalsock.Config +} + +// NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation. +func NewModuleConfig() ModuleConfig { + return &moduleConfig{ + startFunctions: []string{"_start"}, + environKeys: map[string]int{}, + } +} + +// clone makes a deep copy of this module config. +func (c *moduleConfig) clone() *moduleConfig { + ret := *c // copy except maps which share a ref + ret.environKeys = make(map[string]int, len(c.environKeys)) + for key, value := range c.environKeys { + ret.environKeys[key] = value + } + return &ret +} + +// WithArgs implements ModuleConfig.WithArgs +func (c *moduleConfig) WithArgs(args ...string) ModuleConfig { + ret := c.clone() + ret.args = toByteSlices(args) + return ret +} + +func toByteSlices(strings []string) (result [][]byte) { + if len(strings) == 0 { + return + } + result = make([][]byte, len(strings)) + for i, a := range strings { + result[i] = []byte(a) + } + return +} + +// WithEnv implements ModuleConfig.WithEnv +func (c *moduleConfig) WithEnv(key, value string) ModuleConfig { + ret := c.clone() + // Check to see if this key already exists and update it. + if i, ok := ret.environKeys[key]; ok { + ret.environ[i+1] = []byte(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, []byte(key), []byte(value)) + } + return ret +} + +// WithFS implements ModuleConfig.WithFS +func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig { + var config FSConfig + if fs != nil { + config = NewFSConfig().WithFSMount(fs, "") + } + return c.WithFSConfig(config) +} + +// WithFSConfig implements ModuleConfig.WithFSConfig +func (c *moduleConfig) WithFSConfig(config FSConfig) ModuleConfig { + ret := c.clone() + ret.fsConfig = config + return ret +} + +// WithName implements ModuleConfig.WithName +func (c *moduleConfig) WithName(name string) ModuleConfig { + ret := c.clone() + ret.nameSet = true + ret.name = name + return ret +} + +// WithStartFunctions implements ModuleConfig.WithStartFunctions +func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig { + ret := c.clone() + ret.startFunctions = startFunctions + return ret +} + +// WithStderr implements ModuleConfig.WithStderr +func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig { + ret := c.clone() + ret.stderr = stderr + return ret +} + +// WithStdin implements ModuleConfig.WithStdin +func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig { + ret := c.clone() + ret.stdin = stdin + return ret +} + +// WithStdout implements ModuleConfig.WithStdout +func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig { + ret := c.clone() + ret.stdout = stdout + return ret +} + +// WithWalltime implements ModuleConfig.WithWalltime +func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig { + ret := c.clone() + ret.walltime = walltime + ret.walltimeResolution = resolution + return ret +} + +// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the +// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and +// 1ns for monotonic. See RATIONALE.md for more context. + +// WithSysWalltime implements ModuleConfig.WithSysWalltime +func (c *moduleConfig) WithSysWalltime() ModuleConfig { + return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds())) +} + +// WithNanotime implements ModuleConfig.WithNanotime +func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig { + ret := c.clone() + ret.nanotime = nanotime + ret.nanotimeResolution = resolution + return ret +} + +// WithSysNanotime implements ModuleConfig.WithSysNanotime +func (c *moduleConfig) WithSysNanotime() ModuleConfig { + return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1)) +} + +// WithNanosleep implements ModuleConfig.WithNanosleep +func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig { + ret := *c // copy + ret.nanosleep = nanosleep + return &ret +} + +// WithOsyield implements ModuleConfig.WithOsyield +func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig { + ret := *c // copy + ret.osyield = osyield + return &ret +} + +// WithSysNanosleep implements ModuleConfig.WithSysNanosleep +func (c *moduleConfig) WithSysNanosleep() ModuleConfig { + return c.WithNanosleep(platform.Nanosleep) +} + +// WithRandSource implements ModuleConfig.WithRandSource +func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig { + ret := c.clone() + ret.randSource = source + return ret +} + +// toSysContext creates a baseline wasm.Context configured by ModuleConfig. +func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) { + var environ [][]byte // 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 { + key, value := c.environ[i], c.environ[i+1] + keyLen := len(key) + if keyLen == 0 { + err = errors.New("environ invalid: empty key") + return + } + valueLen := len(value) + result := make([]byte, keyLen+valueLen+1) + j := 0 + for ; j < keyLen; j++ { + if k := key[j]; k == '=' { // NUL enforced in NewContext + err = errors.New("environ invalid: key contains '=' character") + return + } else { + result[j] = k + } + } + result[j] = '=' + copy(result[j+1:], value) + environ = append(environ, result) + } + + var fs []experimentalsys.FS + var guestPaths []string + if f, ok := c.fsConfig.(*fsConfig); ok { + fs, guestPaths = f.preopens() + } + + var listeners []*net.TCPListener + if n := c.sockConfig; n != nil { + if listeners, err = n.BuildTCPListeners(); err != nil { + return + } + } + + return internalsys.NewContext( + math.MaxUint32, + c.args, + environ, + c.stdin, + c.stdout, + c.stderr, + c.randSource, + c.walltime, c.walltimeResolution, + c.nanotime, c.nanotimeResolution, + c.nanosleep, c.osyield, + fs, guestPaths, + listeners, + ) +} |