wa-lang.org/wazero@v1.0.2/config.go (about)

     1  package wazero
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/fs"
     9  	"math"
    10  	"time"
    11  
    12  	"wa-lang.org/wazero/api"
    13  	"wa-lang.org/wazero/internal/engine/compiler"
    14  	"wa-lang.org/wazero/internal/engine/interpreter"
    15  	"wa-lang.org/wazero/internal/platform"
    16  	internalsys "wa-lang.org/wazero/internal/sys"
    17  	"wa-lang.org/wazero/internal/wasm"
    18  	"wa-lang.org/wazero/sys"
    19  )
    20  
    21  // RuntimeConfig controls runtime behavior, with the default implementation as
    22  // NewRuntimeConfig
    23  //
    24  // The example below explicitly limits to Wasm Core 1.0 features as opposed to
    25  // relying on defaults:
    26  //
    27  //	rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1)
    28  //
    29  // Note: RuntimeConfig is immutable. Each WithXXX function returns a new
    30  // instance including the corresponding change.
    31  type RuntimeConfig interface {
    32  	// WithCoreFeatures sets the WebAssembly Core specification features this
    33  	// runtime supports. Defaults to api.CoreFeaturesV2.
    34  	//
    35  	// Example of disabling a specific feature:
    36  	//	features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false)
    37  	//	rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features)
    38  	//
    39  	// # Why default to version 2.0?
    40  	//
    41  	// Many compilers that target WebAssembly require features after
    42  	// api.CoreFeaturesV1 by default. For example, TinyGo v0.24+ requires
    43  	// api.CoreFeatureBulkMemoryOperations. To avoid runtime errors, wazero
    44  	// defaults to api.CoreFeaturesV2, even though it is not yet a Web
    45  	// Standard (REC).
    46  	WithCoreFeatures(api.CoreFeatures) RuntimeConfig
    47  
    48  	// WithMemoryLimitPages overrides the maximum pages allowed per memory. The
    49  	// default is 65536, allowing 4GB total memory per instance. Setting a
    50  	// value larger than default will panic.
    51  	//
    52  	// This example reduces the largest possible memory size from 4GB to 128KB:
    53  	//	rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2)
    54  	//
    55  	// Note: Wasm has 32-bit memory and each page is 65536 (2^16) bytes. This
    56  	// implies a max of 65536 (2^16) addressable pages.
    57  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
    58  	WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig
    59  
    60  	// WithMemoryCapacityFromMax eagerly allocates max memory, unless max is
    61  	// not defined. The default is false, which means minimum memory is
    62  	// allocated and any call to grow memory results in re-allocations.
    63  	//
    64  	// This example ensures any memory.grow instruction will never re-allocate:
    65  	//	rConfig = wazero.NewRuntimeConfig().WithMemoryCapacityFromMax(true)
    66  	//
    67  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
    68  	WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig
    69  }
    70  
    71  // NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment,
    72  // or the interpreter otherwise.
    73  func NewRuntimeConfig() RuntimeConfig {
    74  	return newRuntimeConfig()
    75  }
    76  
    77  type runtimeConfig struct {
    78  	enabledFeatures       api.CoreFeatures
    79  	memoryLimitPages      uint32
    80  	memoryCapacityFromMax bool
    81  	isInterpreter         bool
    82  	newEngine             func(context.Context, api.CoreFeatures) wasm.Engine
    83  }
    84  
    85  // engineLessConfig helps avoid copy/pasting the wrong defaults.
    86  var engineLessConfig = &runtimeConfig{
    87  	enabledFeatures:       api.CoreFeaturesV2,
    88  	memoryLimitPages:      wasm.MemoryLimitPages,
    89  	memoryCapacityFromMax: false,
    90  }
    91  
    92  // NewRuntimeConfigCompiler compiles WebAssembly modules into
    93  // runtime.GOARCH-specific assembly for optimal performance.
    94  //
    95  // The default implementation is AOT (Ahead of Time) compilation, applied at
    96  // Runtime.CompileModule. This allows consistent runtime performance, as well
    97  // the ability to reduce any first request penalty.
    98  //
    99  // Note: While this is technically AOT, this does not imply any action on your
   100  // part. wazero automatically performs ahead-of-time compilation as needed when
   101  // Runtime.CompileModule is invoked.
   102  //
   103  // Warning: This panics at runtime if the runtime.GOOS or runtime.GOARCH does not
   104  // support Compiler. Use NewRuntimeConfig to safely detect and fallback to
   105  // NewRuntimeConfigInterpreter if needed.
   106  func NewRuntimeConfigCompiler() RuntimeConfig {
   107  	ret := engineLessConfig.clone()
   108  	ret.newEngine = compiler.NewEngine
   109  	return ret
   110  }
   111  
   112  // NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
   113  func NewRuntimeConfigInterpreter() RuntimeConfig {
   114  	ret := engineLessConfig.clone()
   115  	ret.isInterpreter = true
   116  	ret.newEngine = interpreter.NewEngine
   117  	return ret
   118  }
   119  
   120  // clone makes a deep copy of this runtime config.
   121  func (c *runtimeConfig) clone() *runtimeConfig {
   122  	ret := *c // copy except maps which share a ref
   123  	return &ret
   124  }
   125  
   126  // WithCoreFeatures implements RuntimeConfig.WithCoreFeatures
   127  func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig {
   128  	ret := c.clone()
   129  	ret.enabledFeatures = features
   130  	return ret
   131  }
   132  
   133  // WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
   134  func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
   135  	ret := c.clone()
   136  	// This panics instead of returning an error as it is unlikely.
   137  	if memoryLimitPages > wasm.MemoryLimitPages {
   138  		panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages))
   139  	}
   140  	ret.memoryLimitPages = memoryLimitPages
   141  	return ret
   142  }
   143  
   144  // WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax
   145  func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig {
   146  	ret := c.clone()
   147  	ret.memoryCapacityFromMax = memoryCapacityFromMax
   148  	return ret
   149  }
   150  
   151  // CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
   152  //
   153  // In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using
   154  // the name "Module" for both before and after instantiation as the name conflation has caused confusion.
   155  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
   156  //
   157  // Note: Closing the wazero.Runtime closes any CompiledModule it compiled.
   158  type CompiledModule interface {
   159  	// Name returns the module name encoded into the binary or empty if not.
   160  	Name() string
   161  
   162  	// ImportedFunctions returns all the imported functions
   163  	// (api.FunctionDefinition) in this module or nil if there are none.
   164  	//
   165  	// Note: Unlike ExportedFunctions, there is no unique constraint on
   166  	// imports.
   167  	ImportedFunctions() []api.FunctionDefinition
   168  
   169  	// ExportedFunctions returns all the exported functions
   170  	// (api.FunctionDefinition) in this module keyed on export name.
   171  	ExportedFunctions() map[string]api.FunctionDefinition
   172  
   173  	// ImportedMemories returns all the imported memories
   174  	// (api.MemoryDefinition) in this module or nil if there are none.
   175  	//
   176  	// ## Notes
   177  	//   - As of WebAssembly Core Specification 2.0, there can be at most one
   178  	//     memory.
   179  	//   - Unlike ExportedMemories, there is no unique constraint on imports.
   180  	ImportedMemories() []api.MemoryDefinition
   181  
   182  	// ExportedMemories returns all the exported memories
   183  	// (api.MemoryDefinition) in this module keyed on export name.
   184  	//
   185  	// Note: As of WebAssembly Core Specification 2.0, there can be at most one
   186  	// memory.
   187  	ExportedMemories() map[string]api.MemoryDefinition
   188  
   189  	// Close releases all the allocated resources for this CompiledModule.
   190  	//
   191  	// Note: It is safe to call Close while having outstanding calls from an
   192  	// api.Module instantiated from this.
   193  	Close(context.Context) error
   194  }
   195  
   196  // compile-time check to ensure compiledModule implements CompiledModule
   197  var _ CompiledModule = &compiledModule{}
   198  
   199  type compiledModule struct {
   200  	module *wasm.Module
   201  	// compiledEngine holds an engine on which `module` is compiled.
   202  	compiledEngine wasm.Engine
   203  	// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
   204  	closeWithModule bool
   205  }
   206  
   207  // Name implements CompiledModule.Name
   208  func (c *compiledModule) Name() (moduleName string) {
   209  	if ns := c.module.NameSection; ns != nil {
   210  		moduleName = ns.ModuleName
   211  	}
   212  	return
   213  }
   214  
   215  // Close implements CompiledModule.Close
   216  func (c *compiledModule) Close(context.Context) error {
   217  	c.compiledEngine.DeleteCompiledModule(c.module)
   218  	// It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close.
   219  	return nil
   220  }
   221  
   222  // ImportedFunctions implements CompiledModule.ImportedFunctions
   223  func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition {
   224  	return c.module.ImportedFunctions()
   225  }
   226  
   227  // ExportedFunctions implements CompiledModule.ExportedFunctions
   228  func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
   229  	return c.module.ExportedFunctions()
   230  }
   231  
   232  // ImportedMemories implements CompiledModule.ImportedMemories
   233  func (c *compiledModule) ImportedMemories() []api.MemoryDefinition {
   234  	return c.module.ImportedMemories()
   235  }
   236  
   237  // ExportedMemories implements CompiledModule.ExportedMemories
   238  func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition {
   239  	return c.module.ExportedMemories()
   240  }
   241  
   242  // ModuleConfig configures resources needed by functions that have low-level interactions with the host operating
   243  // system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated
   244  // multiple times.
   245  //
   246  // Here's an example:
   247  //
   248  //	// Initialize base configuration:
   249  //	config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
   250  //
   251  //	// Assign different configuration on each instantiation
   252  //	module, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
   253  //
   254  // While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect.
   255  // See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
   256  //
   257  // Note: ModuleConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
   258  type ModuleConfig interface {
   259  	// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
   260  	// none. Runtime.InstantiateModule errs if any arg is empty.
   261  	//
   262  	// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
   263  	// read by functions imported from other modules.
   264  	//
   265  	// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
   266  	// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
   267  	// argument to the same value set via WithName.
   268  	//
   269  	// Note: This does not default to os.Args as that violates sandboxing.
   270  	//
   271  	// See https://linux.die.net/man/3/argv and https://en.wikipedia.org/wiki/Null-terminated_string
   272  	WithArgs(...string) ModuleConfig
   273  
   274  	// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
   275  	// Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
   276  	//
   277  	// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
   278  	// default to the current process environment as that would violate sandboxing. This also does not preserve order.
   279  	//
   280  	// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
   281  	// they could be read by functions imported from other modules.
   282  	//
   283  	// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
   284  	// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
   285  	// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
   286  	//
   287  	// See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string
   288  	WithEnv(key, value string) ModuleConfig
   289  
   290  	// WithFS assigns the file system to use for any paths beginning at "/".
   291  	// Defaults return fs.ErrNotExist.
   292  	//
   293  	// This example sets a read-only, embedded file-system:
   294  	//
   295  	//	//go:embed testdata/index.html
   296  	//	var testdataIndex embed.FS
   297  	//
   298  	//	rooted, err := fs.Sub(testdataIndex, "testdata")
   299  	//	require.NoError(t, err)
   300  	//
   301  	//	// "index.html" is accessible as "/index.html".
   302  	//	config := wazero.NewModuleConfig().WithFS(rooted)
   303  	//
   304  	// This example sets a mutable file-system:
   305  	//
   306  	//	// Files relative to "/work/appA" are accessible as "/".
   307  	//	config := wazero.NewModuleConfig().WithFS(os.DirFS("/work/appA"))
   308  	//
   309  	// Isolation
   310  	//
   311  	// os.DirFS documentation includes important notes about isolation, which
   312  	// also applies to fs.Sub. As of Go 1.19, the built-in file-systems are not
   313  	// jailed (chroot). See https://github.com/golang/go/issues/42322
   314  	//
   315  	// Working Directory "."
   316  	//
   317  	// Relative path resolution, such as "./config.yml" to "/config.yml" or
   318  	// otherwise, is compiler-specific. See /RATIONALE.md for notes.
   319  	WithFS(fs.FS) ModuleConfig
   320  
   321  	// WithName configures the module name. Defaults to what was decoded from the name section.
   322  	WithName(string) ModuleConfig
   323  
   324  	// WithStartFunctions configures the functions to call after the module is
   325  	// instantiated. Defaults to "_start".
   326  	//
   327  	// # Notes
   328  	//
   329  	//   - If any function doesn't exist, it is skipped. However, all functions
   330  	//	  that do exist are called in order.
   331  	//   - Some start functions may exit the module during instantiate with a
   332  	//	  sys.ExitError (e.g. emscripten), preventing use of exported functions.
   333  	WithStartFunctions(...string) ModuleConfig
   334  
   335  	// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
   336  	//
   337  	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
   338  	// be used by functions imported from other modules.
   339  	//
   340  	// # Notes
   341  	//
   342  	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
   343  	//   - This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
   344  	//
   345  	// See https://linux.die.net/man/3/stderr
   346  	WithStderr(io.Writer) ModuleConfig
   347  
   348  	// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
   349  	//
   350  	// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could
   351  	// be used by functions imported from other modules.
   352  	//
   353  	// # Notes
   354  	//
   355  	//   - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
   356  	//   - This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
   357  	//
   358  	// See https://linux.die.net/man/3/stdin
   359  	WithStdin(io.Reader) ModuleConfig
   360  
   361  	// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
   362  	//
   363  	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
   364  	// be used by functions imported from other modules.
   365  	//
   366  	// # Notes
   367  	//
   368  	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
   369  	//   - This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
   370  	//
   371  	// See https://linux.die.net/man/3/stdout
   372  	WithStdout(io.Writer) ModuleConfig
   373  
   374  	// WithWalltime configures the wall clock, sometimes referred to as the
   375  	// real time clock. Defaults to a fake result that increases by 1ms on
   376  	// each reading.
   377  	//
   378  	// Here's an example that uses a custom clock:
   379  	//	moduleConfig = moduleConfig.
   380  	//		WithWalltime(func(context.Context) (sec int64, nsec int32) {
   381  	//			return clock.walltime()
   382  	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   383  	//
   384  	// Note: This does not default to time.Now as that violates sandboxing. Use
   385  	// WithSysWalltime for a usable implementation.
   386  	WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig
   387  
   388  	// WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us
   389  	// (1000ns).
   390  	//
   391  	// See WithWalltime
   392  	WithSysWalltime() ModuleConfig
   393  
   394  	// WithNanotime configures the monotonic clock, used to measure elapsed
   395  	// time in nanoseconds. Defaults to a fake result that increases by 1ms
   396  	// on each reading.
   397  	//
   398  	// Here's an example that uses a custom clock:
   399  	//	moduleConfig = moduleConfig.
   400  	//		WithNanotime(func(context.Context) int64 {
   401  	//			return clock.nanotime()
   402  	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   403  	//
   404  	// # Notes:
   405  	//   - This does not default to time.Since as that violates sandboxing.
   406  	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
   407  	//   - If you set this, you should probably set WithNanosleep also.
   408  	//   - Use WithSysNanotime for a usable implementation.
   409  	WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig
   410  
   411  	// WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us.
   412  	//
   413  	// See WithNanotime
   414  	WithSysNanotime() ModuleConfig
   415  
   416  	// WithNanosleep configures the how to pause the current goroutine for at
   417  	// least the configured nanoseconds. Defaults to return immediately.
   418  	//
   419  	// This example uses a custom sleep function:
   420  	//	moduleConfig = moduleConfig.
   421  	//		WithNanosleep(func(ctx context.Context, ns int64) {
   422  	//			rel := unix.NsecToTimespec(ns)
   423  	//			remain := unix.Timespec{}
   424  	//			for { // loop until no more time remaining
   425  	//				err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain)
   426  	//			--snip--
   427  	//
   428  	// # Notes:
   429  	//   - This primarily supports `poll_oneoff` for relative clock events.
   430  	//   - This does not default to time.Sleep as that violates sandboxing.
   431  	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
   432  	//   - If you set this, you should probably set WithNanotime also.
   433  	//   - Use WithSysNanosleep for a usable implementation.
   434  	WithNanosleep(sys.Nanosleep) ModuleConfig
   435  
   436  	// WithSysNanosleep uses time.Sleep for sys.Nanosleep.
   437  	//
   438  	// See WithNanosleep
   439  	WithSysNanosleep() ModuleConfig
   440  
   441  	// WithRandSource configures a source of random bytes. Defaults to return a
   442  	// deterministic source. You might override this with crypto/rand.Reader
   443  	//
   444  	// This reader is most commonly used by the functions like "random_get" in
   445  	// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
   446  	// "getRandomData" when runtime.GOOS is "js".
   447  	//
   448  	// Note: The caller is responsible to close any io.Reader they supply: It
   449  	// is not closed on api.Module Close.
   450  	WithRandSource(io.Reader) ModuleConfig
   451  }
   452  
   453  type moduleConfig struct {
   454  	name               string
   455  	startFunctions     []string
   456  	stdin              io.Reader
   457  	stdout             io.Writer
   458  	stderr             io.Writer
   459  	randSource         io.Reader
   460  	walltime           *sys.Walltime
   461  	walltimeResolution sys.ClockResolution
   462  	nanotime           *sys.Nanotime
   463  	nanotimeResolution sys.ClockResolution
   464  	nanosleep          *sys.Nanosleep
   465  	args               []string
   466  	// environ is pair-indexed to retain order similar to os.Environ.
   467  	environ []string
   468  	// environKeys allow overwriting of existing values.
   469  	environKeys map[string]int
   470  	// fs is the file system to open files with
   471  	fs fs.FS
   472  }
   473  
   474  // NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
   475  func NewModuleConfig() ModuleConfig {
   476  	return &moduleConfig{
   477  		startFunctions: []string{"_start"},
   478  		environKeys:    map[string]int{},
   479  	}
   480  }
   481  
   482  // clone makes a deep copy of this module config.
   483  func (c *moduleConfig) clone() *moduleConfig {
   484  	ret := *c // copy except maps which share a ref
   485  	ret.environKeys = make(map[string]int, len(c.environKeys))
   486  	for key, value := range c.environKeys {
   487  		ret.environKeys[key] = value
   488  	}
   489  	return &ret
   490  }
   491  
   492  // WithArgs implements ModuleConfig.WithArgs
   493  func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
   494  	ret := c.clone()
   495  	ret.args = args
   496  	return ret
   497  }
   498  
   499  // WithEnv implements ModuleConfig.WithEnv
   500  func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
   501  	ret := c.clone()
   502  	// Check to see if this key already exists and update it.
   503  	if i, ok := ret.environKeys[key]; ok {
   504  		ret.environ[i+1] = value // environ is pair-indexed, so the value is 1 after the key.
   505  	} else {
   506  		ret.environKeys[key] = len(ret.environ)
   507  		ret.environ = append(ret.environ, key, value)
   508  	}
   509  	return ret
   510  }
   511  
   512  // WithFS implements ModuleConfig.WithFS
   513  func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
   514  	ret := c.clone()
   515  	ret.fs = fs
   516  	return ret
   517  }
   518  
   519  // WithName implements ModuleConfig.WithName
   520  func (c *moduleConfig) WithName(name string) ModuleConfig {
   521  	ret := c.clone()
   522  	ret.name = name
   523  	return ret
   524  }
   525  
   526  // WithStartFunctions implements ModuleConfig.WithStartFunctions
   527  func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
   528  	ret := c.clone()
   529  	ret.startFunctions = startFunctions
   530  	return ret
   531  }
   532  
   533  // WithStderr implements ModuleConfig.WithStderr
   534  func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
   535  	ret := c.clone()
   536  	ret.stderr = stderr
   537  	return ret
   538  }
   539  
   540  // WithStdin implements ModuleConfig.WithStdin
   541  func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
   542  	ret := c.clone()
   543  	ret.stdin = stdin
   544  	return ret
   545  }
   546  
   547  // WithStdout implements ModuleConfig.WithStdout
   548  func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
   549  	ret := c.clone()
   550  	ret.stdout = stdout
   551  	return ret
   552  }
   553  
   554  // WithWalltime implements ModuleConfig.WithWalltime
   555  func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
   556  	ret := c.clone()
   557  	ret.walltime = &walltime
   558  	ret.walltimeResolution = resolution
   559  	return ret
   560  }
   561  
   562  // We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
   563  // source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
   564  // 1ns for monotonic. See RATIONALE.md for more context.
   565  
   566  // WithSysWalltime implements ModuleConfig.WithSysWalltime
   567  func (c *moduleConfig) WithSysWalltime() ModuleConfig {
   568  	return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   569  }
   570  
   571  // WithNanotime implements ModuleConfig.WithNanotime
   572  func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
   573  	ret := c.clone()
   574  	ret.nanotime = &nanotime
   575  	ret.nanotimeResolution = resolution
   576  	return ret
   577  }
   578  
   579  // WithSysNanotime implements ModuleConfig.WithSysNanotime
   580  func (c *moduleConfig) WithSysNanotime() ModuleConfig {
   581  	return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
   582  }
   583  
   584  // WithNanosleep implements ModuleConfig.WithNanosleep
   585  func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
   586  	ret := *c // copy
   587  	ret.nanosleep = &nanosleep
   588  	return &ret
   589  }
   590  
   591  // WithSysNanosleep implements ModuleConfig.WithSysNanosleep
   592  func (c *moduleConfig) WithSysNanosleep() ModuleConfig {
   593  	return c.WithNanosleep(platform.Nanosleep)
   594  }
   595  
   596  // WithRandSource implements ModuleConfig.WithRandSource
   597  func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
   598  	ret := c.clone()
   599  	ret.randSource = source
   600  	return ret
   601  }
   602  
   603  // toSysContext creates a baseline wasm.Context configured by ModuleConfig.
   604  func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
   605  	var environ []string // Intentionally doesn't pre-allocate to reduce logic to default to nil.
   606  	// Same validation as syscall.Setenv for Linux
   607  	for i := 0; i < len(c.environ); i += 2 {
   608  		key, value := c.environ[i], c.environ[i+1]
   609  		if len(key) == 0 {
   610  			err = errors.New("environ invalid: empty key")
   611  			return
   612  		}
   613  		for j := 0; j < len(key); j++ {
   614  			if key[j] == '=' { // NUL enforced in NewContext
   615  				err = errors.New("environ invalid: key contains '=' character")
   616  				return
   617  			}
   618  		}
   619  		environ = append(environ, key+"="+value)
   620  	}
   621  
   622  	return internalsys.NewContext(
   623  		math.MaxUint32,
   624  		c.args,
   625  		environ,
   626  		c.stdin,
   627  		c.stdout,
   628  		c.stderr,
   629  		c.randSource,
   630  		c.walltime, c.walltimeResolution,
   631  		c.nanotime, c.nanotimeResolution,
   632  		c.nanosleep,
   633  		c.fs,
   634  	)
   635  }