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, ) }