github.com/ejcx/wazero@v1.1.0/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  	"github.com/tetratelabs/wazero/api"
    13  	"github.com/tetratelabs/wazero/internal/engine/compiler"
    14  	"github.com/tetratelabs/wazero/internal/engine/interpreter"
    15  	"github.com/tetratelabs/wazero/internal/filecache"
    16  	"github.com/tetratelabs/wazero/internal/fsapi"
    17  	"github.com/tetratelabs/wazero/internal/internalapi"
    18  	"github.com/tetratelabs/wazero/internal/platform"
    19  	internalsys "github.com/tetratelabs/wazero/internal/sys"
    20  	"github.com/tetratelabs/wazero/internal/wasm"
    21  	"github.com/tetratelabs/wazero/sys"
    22  )
    23  
    24  // RuntimeConfig controls runtime behavior, with the default implementation as
    25  // NewRuntimeConfig
    26  //
    27  // The example below explicitly limits to Wasm Core 1.0 features as opposed to
    28  // relying on defaults:
    29  //
    30  //	rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1)
    31  //
    32  // # Notes
    33  //
    34  //   - This is an interface for decoupling, not third-party implementations.
    35  //     All implementations are in wazero.
    36  //   - RuntimeConfig is immutable. Each WithXXX function returns a new instance
    37  //     including the corresponding change.
    38  type RuntimeConfig interface {
    39  	// WithCoreFeatures sets the WebAssembly Core specification features this
    40  	// runtime supports. Defaults to api.CoreFeaturesV2.
    41  	//
    42  	// Example of disabling a specific feature:
    43  	//	features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false)
    44  	//	rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features)
    45  	//
    46  	// # Why default to version 2.0?
    47  	//
    48  	// Many compilers that target WebAssembly require features after
    49  	// api.CoreFeaturesV1 by default. For example, TinyGo v0.24+ requires
    50  	// api.CoreFeatureBulkMemoryOperations. To avoid runtime errors, wazero
    51  	// defaults to api.CoreFeaturesV2, even though it is not yet a Web
    52  	// Standard (REC).
    53  	WithCoreFeatures(api.CoreFeatures) RuntimeConfig
    54  
    55  	// WithMemoryLimitPages overrides the maximum pages allowed per memory. The
    56  	// default is 65536, allowing 4GB total memory per instance if the maximum is
    57  	// not encoded in a Wasm binary. Setting a value larger than default will panic.
    58  	//
    59  	// This example reduces the largest possible memory size from 4GB to 128KB:
    60  	//	rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2)
    61  	//
    62  	// Note: Wasm has 32-bit memory and each page is 65536 (2^16) bytes. This
    63  	// implies a max of 65536 (2^16) addressable pages.
    64  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
    65  	WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig
    66  
    67  	// WithMemoryCapacityFromMax eagerly allocates max memory, unless max is
    68  	// not defined. The default is false, which means minimum memory is
    69  	// allocated and any call to grow memory results in re-allocations.
    70  	//
    71  	// This example ensures any memory.grow instruction will never re-allocate:
    72  	//	rConfig = wazero.NewRuntimeConfig().WithMemoryCapacityFromMax(true)
    73  	//
    74  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
    75  	//
    76  	// Note: if the memory maximum is not encoded in a Wasm binary, this
    77  	// results in allocating 4GB. See the doc on WithMemoryLimitPages for detail.
    78  	WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig
    79  
    80  	// WithDebugInfoEnabled toggles DWARF based stack traces in the face of
    81  	// runtime errors. Defaults to true.
    82  	//
    83  	// Those who wish to disable this, can like so:
    84  	//
    85  	//	r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithDebugInfoEnabled(false)
    86  	//
    87  	// When disabled, a stack trace message looks like:
    88  	//
    89  	//	wasm stack trace:
    90  	//		.runtime._panic(i32)
    91  	//		.myFunc()
    92  	//		.main.main()
    93  	//		.runtime.run()
    94  	//		._start()
    95  	//
    96  	// When enabled, the stack trace includes source code information:
    97  	//
    98  	//	wasm stack trace:
    99  	//		.runtime._panic(i32)
   100  	//		  0x16e2: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6
   101  	//		.myFunc()
   102  	//		  0x190b: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:19:7
   103  	//		.main.main()
   104  	//		  0x18ed: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:4:3
   105  	//		.runtime.run()
   106  	//		  0x18cc: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/scheduler_none.go:26:10
   107  	//		._start()
   108  	//		  0x18b6: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_wasm_wasi.go:22:5
   109  	//
   110  	// Note: This only takes into effect when the original Wasm binary has the
   111  	// DWARF "custom sections" that are often stripped, depending on
   112  	// optimization flags passed to the compiler.
   113  	WithDebugInfoEnabled(bool) RuntimeConfig
   114  
   115  	// WithCompilationCache configures how runtime caches the compiled modules. In the default configuration, compilation results are
   116  	// only in-memory until Runtime.Close is closed, and not shareable by multiple Runtime.
   117  	//
   118  	// Below defines the shared cache across multiple instances of Runtime:
   119  	//
   120  	//	// Creates the new Cache and the runtime configuration with it.
   121  	//	cache := wazero.NewCompilationCache()
   122  	//	defer cache.Close()
   123  	//	config := wazero.NewRuntimeConfig().WithCompilationCache(c)
   124  	//
   125  	//	// Creates two runtimes while sharing compilation caches.
   126  	//	foo := wazero.NewRuntimeWithConfig(context.Background(), config)
   127  	// 	bar := wazero.NewRuntimeWithConfig(context.Background(), config)
   128  	//
   129  	// # Cache Key
   130  	//
   131  	// Cached files are keyed on the version of wazero. This is obtained from go.mod of your application,
   132  	// and we use it to verify the compatibility of caches against the currently-running wazero.
   133  	// However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct
   134  	// version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976.
   135  	// As a consequence, your cache won't contain the correct version information and always be treated as `dev` version.
   136  	// To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests.
   137  	WithCompilationCache(CompilationCache) RuntimeConfig
   138  
   139  	// WithCustomSections toggles parsing of "custom sections". Defaults to false.
   140  	//
   141  	// When enabled, it is possible to retrieve custom sections from a CompiledModule:
   142  	//
   143  	//	config := wazero.NewRuntimeConfig().WithCustomSections(true)
   144  	//	r := wazero.NewRuntimeWithConfig(ctx, config)
   145  	//	c, err := r.CompileModule(ctx, wasm)
   146  	//	customSections := c.CustomSections()
   147  	WithCustomSections(bool) RuntimeConfig
   148  
   149  	// WithCloseOnContextDone ensures the executions of functions to be closed under one of the following circumstances:
   150  	//
   151  	// 	- context.Context passed to the Call method of api.Function is canceled during execution. (i.e. ctx by context.WithCancel)
   152  	// 	- context.Context passed to the Call method of api.Function reaches timeout during execution. (i.e. ctx by context.WithTimeout or context.WithDeadline)
   153  	// 	- Close or CloseWithExitCode of api.Module is explicitly called during execution.
   154  	//
   155  	// This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of
   156  	// api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the
   157  	// entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated
   158  	// machine codes against async Goroutine preemption" section in internal/engine/compiler/RATIONALE.md for detail.
   159  	//
   160  	// Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces
   161  	// interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason,
   162  	// this is disabled by default.
   163  	//
   164  	// See examples in context_done_example_test.go for the end-to-end demonstrations.
   165  	//
   166  	// When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and
   167  	// the api.Module from which the functions are derived is made closed.
   168  	WithCloseOnContextDone(bool) RuntimeConfig
   169  }
   170  
   171  // NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment,
   172  // or the interpreter otherwise.
   173  func NewRuntimeConfig() RuntimeConfig {
   174  	return newRuntimeConfig()
   175  }
   176  
   177  type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine
   178  
   179  type runtimeConfig struct {
   180  	enabledFeatures       api.CoreFeatures
   181  	memoryLimitPages      uint32
   182  	memoryCapacityFromMax bool
   183  	engineKind            engineKind
   184  	dwarfDisabled         bool // negative as defaults to enabled
   185  	newEngine             newEngine
   186  	cache                 CompilationCache
   187  	storeCustomSections   bool
   188  	ensureTermination     bool
   189  }
   190  
   191  // engineLessConfig helps avoid copy/pasting the wrong defaults.
   192  var engineLessConfig = &runtimeConfig{
   193  	enabledFeatures:       api.CoreFeaturesV2,
   194  	memoryLimitPages:      wasm.MemoryLimitPages,
   195  	memoryCapacityFromMax: false,
   196  	dwarfDisabled:         false,
   197  }
   198  
   199  type engineKind int
   200  
   201  const (
   202  	engineKindCompiler engineKind = iota
   203  	engineKindInterpreter
   204  	engineKindCount
   205  )
   206  
   207  // NewRuntimeConfigCompiler compiles WebAssembly modules into
   208  // runtime.GOARCH-specific assembly for optimal performance.
   209  //
   210  // The default implementation is AOT (Ahead of Time) compilation, applied at
   211  // Runtime.CompileModule. This allows consistent runtime performance, as well
   212  // the ability to reduce any first request penalty.
   213  //
   214  // Note: While this is technically AOT, this does not imply any action on your
   215  // part. wazero automatically performs ahead-of-time compilation as needed when
   216  // Runtime.CompileModule is invoked.
   217  //
   218  // Warning: This panics at runtime if the runtime.GOOS or runtime.GOARCH does not
   219  // support Compiler. Use NewRuntimeConfig to safely detect and fallback to
   220  // NewRuntimeConfigInterpreter if needed.
   221  func NewRuntimeConfigCompiler() RuntimeConfig {
   222  	ret := &runtimeConfig{
   223  		enabledFeatures:       api.CoreFeaturesV2,
   224  		memoryLimitPages:      wasm.MemoryLimitPages,
   225  		memoryCapacityFromMax: false,
   226  		dwarfDisabled:         false,
   227  	}
   228  	ret.engineKind = engineKindCompiler
   229  	ret.newEngine = compiler.NewEngine
   230  	return ret
   231  }
   232  
   233  // NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
   234  func NewRuntimeConfigInterpreter() RuntimeConfig {
   235  	ret := &runtimeConfig{
   236  		enabledFeatures:       api.CoreFeaturesV2,
   237  		memoryLimitPages:      wasm.MemoryLimitPages,
   238  		memoryCapacityFromMax: false,
   239  		dwarfDisabled:         false,
   240  	}
   241  	ret.engineKind = engineKindInterpreter
   242  	ret.newEngine = interpreter.NewEngine
   243  	return ret
   244  }
   245  
   246  // clone makes a deep copy of this runtime config.
   247  func (c *runtimeConfig) clone() *runtimeConfig {
   248  	ret := *c // copy except maps which share a ref
   249  	return &ret
   250  }
   251  
   252  // WithCoreFeatures implements RuntimeConfig.WithCoreFeatures
   253  func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig {
   254  	ret := c.clone()
   255  	ret.enabledFeatures = features
   256  	return ret
   257  }
   258  
   259  // WithCloseOnContextDone implements RuntimeConfig.WithCloseOnContextDone
   260  func (c *runtimeConfig) WithCloseOnContextDone(ensure bool) RuntimeConfig {
   261  	ret := c.clone()
   262  	ret.ensureTermination = ensure
   263  	return ret
   264  }
   265  
   266  // WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
   267  func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
   268  	ret := c.clone()
   269  	// This panics instead of returning an error as it is unlikely.
   270  	if memoryLimitPages > wasm.MemoryLimitPages {
   271  		panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages))
   272  	}
   273  	ret.memoryLimitPages = memoryLimitPages
   274  	return ret
   275  }
   276  
   277  // WithCompilationCache implements RuntimeConfig.WithCompilationCache
   278  func (c *runtimeConfig) WithCompilationCache(ca CompilationCache) RuntimeConfig {
   279  	ret := c.clone()
   280  	ret.cache = ca
   281  	return ret
   282  }
   283  
   284  // WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax
   285  func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig {
   286  	ret := c.clone()
   287  	ret.memoryCapacityFromMax = memoryCapacityFromMax
   288  	return ret
   289  }
   290  
   291  // WithDebugInfoEnabled implements RuntimeConfig.WithDebugInfoEnabled
   292  func (c *runtimeConfig) WithDebugInfoEnabled(dwarfEnabled bool) RuntimeConfig {
   293  	ret := c.clone()
   294  	ret.dwarfDisabled = !dwarfEnabled
   295  	return ret
   296  }
   297  
   298  // WithCustomSections implements RuntimeConfig.WithCustomSections
   299  func (c *runtimeConfig) WithCustomSections(storeCustomSections bool) RuntimeConfig {
   300  	ret := c.clone()
   301  	ret.storeCustomSections = storeCustomSections
   302  	return ret
   303  }
   304  
   305  // CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
   306  //
   307  // In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using
   308  // the name "Module" for both before and after instantiation as the name conflation has caused confusion.
   309  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
   310  //
   311  // # Notes
   312  //
   313  //   - This is an interface for decoupling, not third-party implementations.
   314  //     All implementations are in wazero.
   315  //   - Closing the wazero.Runtime closes any CompiledModule it compiled.
   316  type CompiledModule interface {
   317  	// Name returns the module name encoded into the binary or empty if not.
   318  	Name() string
   319  
   320  	// ImportedFunctions returns all the imported functions
   321  	// (api.FunctionDefinition) in this module or nil if there are none.
   322  	//
   323  	// Note: Unlike ExportedFunctions, there is no unique constraint on
   324  	// imports.
   325  	ImportedFunctions() []api.FunctionDefinition
   326  
   327  	// ExportedFunctions returns all the exported functions
   328  	// (api.FunctionDefinition) in this module keyed on export name.
   329  	ExportedFunctions() map[string]api.FunctionDefinition
   330  
   331  	// ImportedMemories returns all the imported memories
   332  	// (api.MemoryDefinition) in this module or nil if there are none.
   333  	//
   334  	// ## Notes
   335  	//   - As of WebAssembly Core Specification 2.0, there can be at most one
   336  	//     memory.
   337  	//   - Unlike ExportedMemories, there is no unique constraint on imports.
   338  	ImportedMemories() []api.MemoryDefinition
   339  
   340  	// ExportedMemories returns all the exported memories
   341  	// (api.MemoryDefinition) in this module keyed on export name.
   342  	//
   343  	// Note: As of WebAssembly Core Specification 2.0, there can be at most one
   344  	// memory.
   345  	ExportedMemories() map[string]api.MemoryDefinition
   346  
   347  	// CustomSections returns all the custom sections
   348  	// (api.CustomSection) in this module keyed on the section name.
   349  	CustomSections() []api.CustomSection
   350  
   351  	// Close releases all the allocated resources for this CompiledModule.
   352  	//
   353  	// Note: It is safe to call Close while having outstanding calls from an
   354  	// api.Module instantiated from this.
   355  	Close(context.Context) error
   356  }
   357  
   358  // compile-time check to ensure compiledModule implements CompiledModule
   359  var _ CompiledModule = &compiledModule{}
   360  
   361  type compiledModule struct {
   362  	module *wasm.Module
   363  	// compiledEngine holds an engine on which `module` is compiled.
   364  	compiledEngine wasm.Engine
   365  	// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
   366  	closeWithModule bool
   367  	typeIDs         []wasm.FunctionTypeID
   368  }
   369  
   370  // Name implements CompiledModule.Name
   371  func (c *compiledModule) Name() (moduleName string) {
   372  	if ns := c.module.NameSection; ns != nil {
   373  		moduleName = ns.ModuleName
   374  	}
   375  	return
   376  }
   377  
   378  // Close implements CompiledModule.Close
   379  func (c *compiledModule) Close(context.Context) error {
   380  	c.compiledEngine.DeleteCompiledModule(c.module)
   381  	// It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close.
   382  	return nil
   383  }
   384  
   385  // ImportedFunctions implements CompiledModule.ImportedFunctions
   386  func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition {
   387  	return c.module.ImportedFunctions()
   388  }
   389  
   390  // ExportedFunctions implements CompiledModule.ExportedFunctions
   391  func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
   392  	return c.module.ExportedFunctions()
   393  }
   394  
   395  // ImportedMemories implements CompiledModule.ImportedMemories
   396  func (c *compiledModule) ImportedMemories() []api.MemoryDefinition {
   397  	return c.module.ImportedMemories()
   398  }
   399  
   400  // ExportedMemories implements CompiledModule.ExportedMemories
   401  func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition {
   402  	return c.module.ExportedMemories()
   403  }
   404  
   405  // CustomSections implements CompiledModule.CustomSections
   406  func (c *compiledModule) CustomSections() []api.CustomSection {
   407  	ret := make([]api.CustomSection, len(c.module.CustomSections))
   408  	for i, d := range c.module.CustomSections {
   409  		ret[i] = &customSection{data: d.Data, name: d.Name}
   410  	}
   411  	return ret
   412  }
   413  
   414  // customSection implements wasm.CustomSection
   415  type customSection struct {
   416  	internalapi.WazeroOnlyType
   417  	name string
   418  	data []byte
   419  }
   420  
   421  // Name implements wasm.CustomSection.Name
   422  func (c *customSection) Name() string {
   423  	return c.name
   424  }
   425  
   426  // Data implements wasm.CustomSection.Data
   427  func (c *customSection) Data() []byte {
   428  	return c.data
   429  }
   430  
   431  // ModuleConfig configures resources needed by functions that have low-level interactions with the host operating
   432  // system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated
   433  // multiple times.
   434  //
   435  // Here's an example:
   436  //
   437  //	// Initialize base configuration:
   438  //	config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
   439  //
   440  //	// Assign different configuration on each instantiation
   441  //	mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
   442  //
   443  // While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect.
   444  // See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
   445  //
   446  // # Notes
   447  //
   448  //   - This is an interface for decoupling, not third-party implementations.
   449  //     All implementations are in wazero.
   450  //   - ModuleConfig is immutable. Each WithXXX function returns a new instance
   451  //     including the corresponding change.
   452  type ModuleConfig interface {
   453  	// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
   454  	// none. Runtime.InstantiateModule errs if any arg is empty.
   455  	//
   456  	// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
   457  	// read by functions imported from other modules.
   458  	//
   459  	// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
   460  	// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
   461  	// argument to the same value set via WithName.
   462  	//
   463  	// Note: This does not default to os.Args as that violates sandboxing.
   464  	//
   465  	// See https://linux.die.net/man/3/argv and https://en.wikipedia.org/wiki/Null-terminated_string
   466  	WithArgs(...string) ModuleConfig
   467  
   468  	// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
   469  	// Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
   470  	//
   471  	// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
   472  	// default to the current process environment as that would violate sandboxing. This also does not preserve order.
   473  	//
   474  	// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
   475  	// they could be read by functions imported from other modules.
   476  	//
   477  	// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
   478  	// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
   479  	// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
   480  	//
   481  	// See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string
   482  	WithEnv(key, value string) ModuleConfig
   483  
   484  	// WithFS is a convenience that calls WithFSConfig with an FSConfig of the
   485  	// input for the root ("/") guest path.
   486  	WithFS(fs.FS) ModuleConfig
   487  
   488  	// WithFSConfig configures the filesystem available to each guest
   489  	// instantiated with this configuration. By default, no file access is
   490  	// allowed, so functions like `path_open` result in unsupported errors
   491  	// (e.g. syscall.ENOSYS).
   492  	WithFSConfig(FSConfig) ModuleConfig
   493  
   494  	// WithName configures the module name. Defaults to what was decoded from
   495  	// the name section. Empty string ("") clears any name.
   496  	WithName(string) ModuleConfig
   497  
   498  	// WithStartFunctions configures the functions to call after the module is
   499  	// instantiated. Defaults to "_start".
   500  	//
   501  	// # Notes
   502  	//
   503  	//   - If any function doesn't exist, it is skipped. However, all functions
   504  	//	  that do exist are called in order.
   505  	//   - Some start functions may exit the module during instantiate with a
   506  	//	  sys.ExitError (e.g. emscripten), preventing use of exported functions.
   507  	WithStartFunctions(...string) ModuleConfig
   508  
   509  	// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
   510  	//
   511  	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
   512  	// be used by functions imported from other modules.
   513  	//
   514  	// # Notes
   515  	//
   516  	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
   517  	//   - This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
   518  	//
   519  	// See https://linux.die.net/man/3/stderr
   520  	WithStderr(io.Writer) ModuleConfig
   521  
   522  	// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
   523  	//
   524  	// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could
   525  	// be used by functions imported from other modules.
   526  	//
   527  	// # Notes
   528  	//
   529  	//   - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
   530  	//   - This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
   531  	//
   532  	// See https://linux.die.net/man/3/stdin
   533  	WithStdin(io.Reader) ModuleConfig
   534  
   535  	// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
   536  	//
   537  	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
   538  	// be used by functions imported from other modules.
   539  	//
   540  	// # Notes
   541  	//
   542  	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
   543  	//   - This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
   544  	//
   545  	// See https://linux.die.net/man/3/stdout
   546  	WithStdout(io.Writer) ModuleConfig
   547  
   548  	// WithWalltime configures the wall clock, sometimes referred to as the
   549  	// real time clock. sys.Walltime returns the current unix/epoch time,
   550  	// seconds since midnight UTC 1 January 1970, with a nanosecond fraction.
   551  	// This defaults to a fake result that increases by 1ms on each reading.
   552  	//
   553  	// Here's an example that uses a custom clock:
   554  	//	moduleConfig = moduleConfig.
   555  	//		WithWalltime(func(context.Context) (sec int64, nsec int32) {
   556  	//			return clock.walltime()
   557  	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   558  	//
   559  	// # Notes:
   560  	//   - This does not default to time.Now as that violates sandboxing.
   561  	//   - This is used to implement host functions such as WASI
   562  	//     `clock_time_get` with the `realtime` clock ID.
   563  	//   - Use WithSysWalltime for a usable implementation.
   564  	WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig
   565  
   566  	// WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us
   567  	// (1000ns).
   568  	//
   569  	// See WithWalltime
   570  	WithSysWalltime() ModuleConfig
   571  
   572  	// WithNanotime configures the monotonic clock, used to measure elapsed
   573  	// time in nanoseconds. Defaults to a fake result that increases by 1ms
   574  	// on each reading.
   575  	//
   576  	// Here's an example that uses a custom clock:
   577  	//	moduleConfig = moduleConfig.
   578  	//		WithNanotime(func(context.Context) int64 {
   579  	//			return clock.nanotime()
   580  	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   581  	//
   582  	// # Notes:
   583  	//   - This does not default to time.Since as that violates sandboxing.
   584  	//   - This is used to implement host functions such as WASI
   585  	//     `clock_time_get` with the `monotonic` clock ID.
   586  	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
   587  	//   - If you set this, you should probably set WithNanosleep also.
   588  	//   - Use WithSysNanotime for a usable implementation.
   589  	WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig
   590  
   591  	// WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us.
   592  	//
   593  	// See WithNanotime
   594  	WithSysNanotime() ModuleConfig
   595  
   596  	// WithNanosleep configures the how to pause the current goroutine for at
   597  	// least the configured nanoseconds. Defaults to return immediately.
   598  	//
   599  	// This example uses a custom sleep function:
   600  	//	moduleConfig = moduleConfig.
   601  	//		WithNanosleep(func(ns int64) {
   602  	//			rel := unix.NsecToTimespec(ns)
   603  	//			remain := unix.Timespec{}
   604  	//			for { // loop until no more time remaining
   605  	//				err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain)
   606  	//			--snip--
   607  	//
   608  	// # Notes:
   609  	//   - This does not default to time.Sleep as that violates sandboxing.
   610  	//   - This is used to implement host functions such as WASI `poll_oneoff`.
   611  	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
   612  	//   - If you set this, you should probably set WithNanotime also.
   613  	//   - Use WithSysNanosleep for a usable implementation.
   614  	WithNanosleep(sys.Nanosleep) ModuleConfig
   615  
   616  	// WithOsyield yields the processor, typically to implement spin-wait
   617  	// loops. Defaults to return immediately.
   618  	//
   619  	// # Notes:
   620  	//   - This primarily supports `sched_yield` in WASI
   621  	//   - This does not default to runtime.osyield as that violates sandboxing.
   622  	WithOsyield(sys.Osyield) ModuleConfig
   623  
   624  	// WithSysNanosleep uses time.Sleep for sys.Nanosleep.
   625  	//
   626  	// See WithNanosleep
   627  	WithSysNanosleep() ModuleConfig
   628  
   629  	// WithRandSource configures a source of random bytes. Defaults to return a
   630  	// deterministic source. You might override this with crypto/rand.Reader
   631  	//
   632  	// This reader is most commonly used by the functions like "random_get" in
   633  	// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
   634  	// "getRandomData" when runtime.GOOS is "js".
   635  	//
   636  	// Note: The caller is responsible to close any io.Reader they supply: It
   637  	// is not closed on api.Module Close.
   638  	WithRandSource(io.Reader) ModuleConfig
   639  }
   640  
   641  type moduleConfig struct {
   642  	name               string
   643  	nameSet            bool
   644  	startFunctions     []string
   645  	stdin              io.Reader
   646  	stdout             io.Writer
   647  	stderr             io.Writer
   648  	randSource         io.Reader
   649  	walltime           sys.Walltime
   650  	walltimeResolution sys.ClockResolution
   651  	nanotime           sys.Nanotime
   652  	nanotimeResolution sys.ClockResolution
   653  	nanosleep          sys.Nanosleep
   654  	osyield            sys.Osyield
   655  	args               [][]byte
   656  	// environ is pair-indexed to retain order similar to os.Environ.
   657  	environ [][]byte
   658  	// environKeys allow overwriting of existing values.
   659  	environKeys map[string]int
   660  	// fsConfig is the file system configuration for ABI like WASI.
   661  	fsConfig FSConfig
   662  }
   663  
   664  // NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
   665  func NewModuleConfig() ModuleConfig {
   666  	return &moduleConfig{
   667  		startFunctions: []string{"_start"},
   668  		environKeys:    map[string]int{},
   669  	}
   670  }
   671  
   672  // clone makes a deep copy of this module config.
   673  func (c *moduleConfig) clone() *moduleConfig {
   674  	ret := *c // copy except maps which share a ref
   675  	ret.environKeys = make(map[string]int, len(c.environKeys))
   676  	for key, value := range c.environKeys {
   677  		ret.environKeys[key] = value
   678  	}
   679  	return &ret
   680  }
   681  
   682  // WithArgs implements ModuleConfig.WithArgs
   683  func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
   684  	ret := c.clone()
   685  	ret.args = toByteSlices(args)
   686  	return ret
   687  }
   688  
   689  func toByteSlices(strings []string) (result [][]byte) {
   690  	if len(strings) == 0 {
   691  		return
   692  	}
   693  	result = make([][]byte, len(strings))
   694  	for i, a := range strings {
   695  		result[i] = []byte(a)
   696  	}
   697  	return
   698  }
   699  
   700  // WithEnv implements ModuleConfig.WithEnv
   701  func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
   702  	ret := c.clone()
   703  	// Check to see if this key already exists and update it.
   704  	if i, ok := ret.environKeys[key]; ok {
   705  		ret.environ[i+1] = []byte(value) // environ is pair-indexed, so the value is 1 after the key.
   706  	} else {
   707  		ret.environKeys[key] = len(ret.environ)
   708  		ret.environ = append(ret.environ, []byte(key), []byte(value))
   709  	}
   710  	return ret
   711  }
   712  
   713  // WithFS implements ModuleConfig.WithFS
   714  func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
   715  	var config FSConfig
   716  	if fs != nil {
   717  		config = NewFSConfig().WithFSMount(fs, "")
   718  	}
   719  	return c.WithFSConfig(config)
   720  }
   721  
   722  // WithFSConfig implements ModuleConfig.WithFSConfig
   723  func (c *moduleConfig) WithFSConfig(config FSConfig) ModuleConfig {
   724  	ret := c.clone()
   725  	ret.fsConfig = config
   726  	return ret
   727  }
   728  
   729  // WithName implements ModuleConfig.WithName
   730  func (c *moduleConfig) WithName(name string) ModuleConfig {
   731  	ret := c.clone()
   732  	ret.nameSet = true
   733  	ret.name = name
   734  	return ret
   735  }
   736  
   737  // WithStartFunctions implements ModuleConfig.WithStartFunctions
   738  func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
   739  	ret := c.clone()
   740  	ret.startFunctions = startFunctions
   741  	return ret
   742  }
   743  
   744  // WithStderr implements ModuleConfig.WithStderr
   745  func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
   746  	ret := c.clone()
   747  	ret.stderr = stderr
   748  	return ret
   749  }
   750  
   751  // WithStdin implements ModuleConfig.WithStdin
   752  func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
   753  	ret := c.clone()
   754  	ret.stdin = stdin
   755  	return ret
   756  }
   757  
   758  // WithStdout implements ModuleConfig.WithStdout
   759  func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
   760  	ret := c.clone()
   761  	ret.stdout = stdout
   762  	return ret
   763  }
   764  
   765  // WithWalltime implements ModuleConfig.WithWalltime
   766  func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
   767  	ret := c.clone()
   768  	ret.walltime = walltime
   769  	ret.walltimeResolution = resolution
   770  	return ret
   771  }
   772  
   773  // We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
   774  // source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
   775  // 1ns for monotonic. See RATIONALE.md for more context.
   776  
   777  // WithSysWalltime implements ModuleConfig.WithSysWalltime
   778  func (c *moduleConfig) WithSysWalltime() ModuleConfig {
   779  	return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds()))
   780  }
   781  
   782  // WithNanotime implements ModuleConfig.WithNanotime
   783  func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
   784  	ret := c.clone()
   785  	ret.nanotime = nanotime
   786  	ret.nanotimeResolution = resolution
   787  	return ret
   788  }
   789  
   790  // WithSysNanotime implements ModuleConfig.WithSysNanotime
   791  func (c *moduleConfig) WithSysNanotime() ModuleConfig {
   792  	return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
   793  }
   794  
   795  // WithNanosleep implements ModuleConfig.WithNanosleep
   796  func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
   797  	ret := *c // copy
   798  	ret.nanosleep = nanosleep
   799  	return &ret
   800  }
   801  
   802  // WithOsyield implements ModuleConfig.WithOsyield
   803  func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig {
   804  	ret := *c // copy
   805  	ret.osyield = osyield
   806  	return &ret
   807  }
   808  
   809  // WithSysNanosleep implements ModuleConfig.WithSysNanosleep
   810  func (c *moduleConfig) WithSysNanosleep() ModuleConfig {
   811  	return c.WithNanosleep(platform.Nanosleep)
   812  }
   813  
   814  // WithRandSource implements ModuleConfig.WithRandSource
   815  func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
   816  	ret := c.clone()
   817  	ret.randSource = source
   818  	return ret
   819  }
   820  
   821  // toSysContext creates a baseline wasm.Context configured by ModuleConfig.
   822  func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
   823  	var environ [][]byte // Intentionally doesn't pre-allocate to reduce logic to default to nil.
   824  	// Same validation as syscall.Setenv for Linux
   825  	for i := 0; i < len(c.environ); i += 2 {
   826  		key, value := c.environ[i], c.environ[i+1]
   827  		keyLen := len(key)
   828  		if keyLen == 0 {
   829  			err = errors.New("environ invalid: empty key")
   830  			return
   831  		}
   832  		valueLen := len(value)
   833  		result := make([]byte, keyLen+valueLen+1)
   834  		j := 0
   835  		for ; j < keyLen; j++ {
   836  			if k := key[j]; k == '=' { // NUL enforced in NewContext
   837  				err = errors.New("environ invalid: key contains '=' character")
   838  				return
   839  			} else {
   840  				result[j] = k
   841  			}
   842  		}
   843  		result[j] = '='
   844  		copy(result[j+1:], value)
   845  		environ = append(environ, result)
   846  	}
   847  
   848  	var fs fsapi.FS
   849  	if f, ok := c.fsConfig.(*fsConfig); ok {
   850  		if fs, err = f.toFS(); err != nil {
   851  			return
   852  		}
   853  	}
   854  
   855  	return internalsys.NewContext(
   856  		math.MaxUint32,
   857  		c.args,
   858  		environ,
   859  		c.stdin,
   860  		c.stdout,
   861  		c.stderr,
   862  		c.randSource,
   863  		c.walltime, c.walltimeResolution,
   864  		c.nanotime, c.nanotimeResolution,
   865  		c.nanosleep, c.osyield,
   866  		fs,
   867  	)
   868  }