2024-05-27 17:46:15 +02:00
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
2024-06-12 14:21:34 +02:00
// WithCloseOnContextDone ensures the executions of functions to be terminated under one of the following circumstances:
2024-05-27 17:46:15 +02:00
//
// - 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.
//
2024-06-12 14:21:34 +02:00
// Upon the termination of the function executions, api.Module is closed.
//
2024-05-27 17:46:15 +02:00
// 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.
//
2024-06-12 14:21:34 +02:00
// # 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.
//
// - If you are using wazero in buildmode=c-archive or c-shared, make sure that you set up the alternate signal stack
// by using, e.g. `sigaltstack` combined with `SA_ONSTACK` flag on `sigaction` on Linux,
// before calling any api.Function. This is because the Go runtime does not set up the alternate signal stack
// for c-archive or c-shared modes, and wazero uses the different stack than the calling Goroutine.
// Hence, the signal handler might get invoked on the wazero's stack, which may cause a stack overflow.
// https://github.com/tetratelabs/wazero/blob/2092c0a879f30d49d7b37f333f4547574b8afe0d/internal/integration_test/fuzz/fuzz/tests/sigstack.rs#L19-L36
2024-05-27 17:46:15 +02:00
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 ,
)
}