config.go

  1package wazero
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7	"io"
  8	"io/fs"
  9	"math"
 10	"net"
 11	"time"
 12
 13	"github.com/tetratelabs/wazero/api"
 14	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
 15	"github.com/tetratelabs/wazero/internal/filecache"
 16	"github.com/tetratelabs/wazero/internal/internalapi"
 17	"github.com/tetratelabs/wazero/internal/platform"
 18	internalsock "github.com/tetratelabs/wazero/internal/sock"
 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.
 38type 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 terminated 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 RATIONALE.md for detail.
159	//
160	// Upon the termination of the function executions, api.Module is closed.
161	//
162	// Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces
163	// interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason,
164	// this is disabled by default.
165	//
166	// See examples in context_done_example_test.go for the end-to-end demonstrations.
167	//
168	// When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and
169	// the api.Module from which the functions are derived is made closed.
170	WithCloseOnContextDone(bool) RuntimeConfig
171}
172
173// NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment,
174// or the interpreter otherwise.
175func NewRuntimeConfig() RuntimeConfig {
176	ret := engineLessConfig.clone()
177	ret.engineKind = engineKindAuto
178	return ret
179}
180
181type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine
182
183type runtimeConfig struct {
184	enabledFeatures       api.CoreFeatures
185	memoryLimitPages      uint32
186	memoryCapacityFromMax bool
187	engineKind            engineKind
188	dwarfDisabled         bool // negative as defaults to enabled
189	newEngine             newEngine
190	cache                 CompilationCache
191	storeCustomSections   bool
192	ensureTermination     bool
193}
194
195// engineLessConfig helps avoid copy/pasting the wrong defaults.
196var engineLessConfig = &runtimeConfig{
197	enabledFeatures:       api.CoreFeaturesV2,
198	memoryLimitPages:      wasm.MemoryLimitPages,
199	memoryCapacityFromMax: false,
200	dwarfDisabled:         false,
201}
202
203type engineKind int
204
205const (
206	engineKindAuto engineKind = iota - 1
207	engineKindCompiler
208	engineKindInterpreter
209	engineKindCount
210)
211
212// NewRuntimeConfigCompiler compiles WebAssembly modules into
213// runtime.GOARCH-specific assembly for optimal performance.
214//
215// The default implementation is AOT (Ahead of Time) compilation, applied at
216// Runtime.CompileModule. This allows consistent runtime performance, as well
217// the ability to reduce any first request penalty.
218//
219// Note: While this is technically AOT, this does not imply any action on your
220// part. wazero automatically performs ahead-of-time compilation as needed when
221// Runtime.CompileModule is invoked.
222//
223// # Warning
224//
225//   - This panics at runtime if the runtime.GOOS or runtime.GOARCH does not
226//     support compiler. Use NewRuntimeConfig to safely detect and fallback to
227//     NewRuntimeConfigInterpreter if needed.
228//
229//   - If you are using wazero in buildmode=c-archive or c-shared, make sure that you set up the alternate signal stack
230//     by using, e.g. `sigaltstack` combined with `SA_ONSTACK` flag on `sigaction` on Linux,
231//     before calling any api.Function. This is because the Go runtime does not set up the alternate signal stack
232//     for c-archive or c-shared modes, and wazero uses the different stack than the calling Goroutine.
233//     Hence, the signal handler might get invoked on the wazero's stack, which may cause a stack overflow.
234//     https://github.com/tetratelabs/wazero/blob/2092c0a879f30d49d7b37f333f4547574b8afe0d/internal/integration_test/fuzz/fuzz/tests/sigstack.rs#L19-L36
235func NewRuntimeConfigCompiler() RuntimeConfig {
236	ret := engineLessConfig.clone()
237	ret.engineKind = engineKindCompiler
238	return ret
239}
240
241// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
242func NewRuntimeConfigInterpreter() RuntimeConfig {
243	ret := engineLessConfig.clone()
244	ret.engineKind = engineKindInterpreter
245	return ret
246}
247
248// clone makes a deep copy of this runtime config.
249func (c *runtimeConfig) clone() *runtimeConfig {
250	ret := *c // copy except maps which share a ref
251	return &ret
252}
253
254// WithCoreFeatures implements RuntimeConfig.WithCoreFeatures
255func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig {
256	ret := c.clone()
257	ret.enabledFeatures = features
258	return ret
259}
260
261// WithCloseOnContextDone implements RuntimeConfig.WithCloseOnContextDone
262func (c *runtimeConfig) WithCloseOnContextDone(ensure bool) RuntimeConfig {
263	ret := c.clone()
264	ret.ensureTermination = ensure
265	return ret
266}
267
268// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
269func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
270	ret := c.clone()
271	// This panics instead of returning an error as it is unlikely.
272	if memoryLimitPages > wasm.MemoryLimitPages {
273		panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages))
274	}
275	ret.memoryLimitPages = memoryLimitPages
276	return ret
277}
278
279// WithCompilationCache implements RuntimeConfig.WithCompilationCache
280func (c *runtimeConfig) WithCompilationCache(ca CompilationCache) RuntimeConfig {
281	ret := c.clone()
282	ret.cache = ca
283	return ret
284}
285
286// WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax
287func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig {
288	ret := c.clone()
289	ret.memoryCapacityFromMax = memoryCapacityFromMax
290	return ret
291}
292
293// WithDebugInfoEnabled implements RuntimeConfig.WithDebugInfoEnabled
294func (c *runtimeConfig) WithDebugInfoEnabled(dwarfEnabled bool) RuntimeConfig {
295	ret := c.clone()
296	ret.dwarfDisabled = !dwarfEnabled
297	return ret
298}
299
300// WithCustomSections implements RuntimeConfig.WithCustomSections
301func (c *runtimeConfig) WithCustomSections(storeCustomSections bool) RuntimeConfig {
302	ret := c.clone()
303	ret.storeCustomSections = storeCustomSections
304	return ret
305}
306
307// CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
308//
309// In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using
310// the name "Module" for both before and after instantiation as the name conflation has caused confusion.
311// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
312//
313// # Notes
314//
315//   - This is an interface for decoupling, not third-party implementations.
316//     All implementations are in wazero.
317//   - Closing the wazero.Runtime closes any CompiledModule it compiled.
318type CompiledModule interface {
319	// Name returns the module name encoded into the binary or empty if not.
320	Name() string
321
322	// ImportedFunctions returns all the imported functions
323	// (api.FunctionDefinition) in this module or nil if there are none.
324	//
325	// Note: Unlike ExportedFunctions, there is no unique constraint on
326	// imports.
327	ImportedFunctions() []api.FunctionDefinition
328
329	// ExportedFunctions returns all the exported functions
330	// (api.FunctionDefinition) in this module keyed on export name.
331	ExportedFunctions() map[string]api.FunctionDefinition
332
333	// ImportedMemories returns all the imported memories
334	// (api.MemoryDefinition) in this module or nil if there are none.
335	//
336	// ## Notes
337	//   - As of WebAssembly Core Specification 2.0, there can be at most one
338	//     memory.
339	//   - Unlike ExportedMemories, there is no unique constraint on imports.
340	ImportedMemories() []api.MemoryDefinition
341
342	// ExportedMemories returns all the exported memories
343	// (api.MemoryDefinition) in this module keyed on export name.
344	//
345	// Note: As of WebAssembly Core Specification 2.0, there can be at most one
346	// memory.
347	ExportedMemories() map[string]api.MemoryDefinition
348
349	// CustomSections returns all the custom sections
350	// (api.CustomSection) in this module keyed on the section name.
351	CustomSections() []api.CustomSection
352
353	// Close releases all the allocated resources for this CompiledModule.
354	//
355	// Note: It is safe to call Close while having outstanding calls from an
356	// api.Module instantiated from this.
357	Close(context.Context) error
358}
359
360// compile-time check to ensure compiledModule implements CompiledModule
361var _ CompiledModule = &compiledModule{}
362
363type compiledModule struct {
364	module *wasm.Module
365	// compiledEngine holds an engine on which `module` is compiled.
366	compiledEngine wasm.Engine
367	// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
368	closeWithModule bool
369	typeIDs         []wasm.FunctionTypeID
370}
371
372// Name implements CompiledModule.Name
373func (c *compiledModule) Name() (moduleName string) {
374	if ns := c.module.NameSection; ns != nil {
375		moduleName = ns.ModuleName
376	}
377	return
378}
379
380// Close implements CompiledModule.Close
381func (c *compiledModule) Close(context.Context) error {
382	c.compiledEngine.DeleteCompiledModule(c.module)
383	// It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close.
384	return nil
385}
386
387// ImportedFunctions implements CompiledModule.ImportedFunctions
388func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition {
389	return c.module.ImportedFunctions()
390}
391
392// ExportedFunctions implements CompiledModule.ExportedFunctions
393func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
394	return c.module.ExportedFunctions()
395}
396
397// ImportedMemories implements CompiledModule.ImportedMemories
398func (c *compiledModule) ImportedMemories() []api.MemoryDefinition {
399	return c.module.ImportedMemories()
400}
401
402// ExportedMemories implements CompiledModule.ExportedMemories
403func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition {
404	return c.module.ExportedMemories()
405}
406
407// CustomSections implements CompiledModule.CustomSections
408func (c *compiledModule) CustomSections() []api.CustomSection {
409	ret := make([]api.CustomSection, len(c.module.CustomSections))
410	for i, d := range c.module.CustomSections {
411		ret[i] = &customSection{data: d.Data, name: d.Name}
412	}
413	return ret
414}
415
416// customSection implements wasm.CustomSection
417type customSection struct {
418	internalapi.WazeroOnlyType
419	name string
420	data []byte
421}
422
423// Name implements wasm.CustomSection.Name
424func (c *customSection) Name() string {
425	return c.name
426}
427
428// Data implements wasm.CustomSection.Data
429func (c *customSection) Data() []byte {
430	return c.data
431}
432
433// ModuleConfig configures resources needed by functions that have low-level interactions with the host operating
434// system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated
435// multiple times.
436//
437// Here's an example:
438//
439//	// Initialize base configuration:
440//	config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
441//
442//	// Assign different configuration on each instantiation
443//	mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
444//
445// While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect.
446// See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
447//
448// # Notes
449//
450//   - This is an interface for decoupling, not third-party implementations.
451//     All implementations are in wazero.
452//   - ModuleConfig is immutable. Each WithXXX function returns a new instance
453//     including the corresponding change.
454type ModuleConfig interface {
455	// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
456	// none. Runtime.InstantiateModule errs if any arg is empty.
457	//
458	// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
459	// read by functions imported from other modules.
460	//
461	// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
462	// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
463	// argument to the same value set via WithName.
464	//
465	// Note: This does not default to os.Args as that violates sandboxing.
466	//
467	// See https://linux.die.net/man/3/argv and https://en.wikipedia.org/wiki/Null-terminated_string
468	WithArgs(...string) ModuleConfig
469
470	// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
471	// Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
472	//
473	// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
474	// default to the current process environment as that would violate sandboxing. This also does not preserve order.
475	//
476	// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
477	// they could be read by functions imported from other modules.
478	//
479	// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
480	// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
481	// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
482	//
483	// See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string
484	WithEnv(key, value string) ModuleConfig
485
486	// WithFS is a convenience that calls WithFSConfig with an FSConfig of the
487	// input for the root ("/") guest path.
488	WithFS(fs.FS) ModuleConfig
489
490	// WithFSConfig configures the filesystem available to each guest
491	// instantiated with this configuration. By default, no file access is
492	// allowed, so functions like `path_open` result in unsupported errors
493	// (e.g. syscall.ENOSYS).
494	WithFSConfig(FSConfig) ModuleConfig
495
496	// WithName configures the module name. Defaults to what was decoded from
497	// the name section. Duplicate names are not allowed in a single Runtime.
498	//
499	// Calling this with the empty string "" makes the module anonymous.
500	// That is useful when you want to instantiate the same CompiledModule multiple times like below:
501	//
502	// 	for i := 0; i < N; i++ {
503	//		// Instantiate a new Wasm module from the already compiled `compiledWasm` anonymously without a name.
504	//		instance, err := r.InstantiateModule(ctx, compiledWasm, wazero.NewModuleConfig().WithName(""))
505	//		// ....
506	//	}
507	//
508	// See the `concurrent-instantiation` example for a complete usage.
509	//
510	// Non-empty named modules are available for other modules to import by name.
511	WithName(string) ModuleConfig
512
513	// WithStartFunctions configures the functions to call after the module is
514	// instantiated. Defaults to "_start".
515	//
516	// Clearing the default is supported, via `WithStartFunctions()`.
517	//
518	// # Notes
519	//
520	//   - If a start function doesn't exist, it is skipped. However, any that
521	//     do exist are called in order.
522	//   - Start functions are not intended to be called multiple times.
523	//     Functions that should be called multiple times should be invoked
524	//     manually via api.Module's `ExportedFunction` method.
525	//   - Start functions commonly exit the module during instantiation,
526	//     preventing use of any functions later. This is the case in "wasip1",
527	//     which defines the default value "_start".
528	//   - See /RATIONALE.md for motivation of this feature.
529	WithStartFunctions(...string) ModuleConfig
530
531	// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
532	//
533	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
534	// be used by functions imported from other modules.
535	//
536	// # Notes
537	//
538	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
539	//   - This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
540	//
541	// See https://linux.die.net/man/3/stderr
542	WithStderr(io.Writer) ModuleConfig
543
544	// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
545	//
546	// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could
547	// be used by functions imported from other modules.
548	//
549	// # Notes
550	//
551	//   - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
552	//   - This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
553	//
554	// See https://linux.die.net/man/3/stdin
555	WithStdin(io.Reader) ModuleConfig
556
557	// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
558	//
559	// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
560	// be used by functions imported from other modules.
561	//
562	// # Notes
563	//
564	//   - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
565	//   - This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
566	//
567	// See https://linux.die.net/man/3/stdout
568	WithStdout(io.Writer) ModuleConfig
569
570	// WithWalltime configures the wall clock, sometimes referred to as the
571	// real time clock. sys.Walltime returns the current unix/epoch time,
572	// seconds since midnight UTC 1 January 1970, with a nanosecond fraction.
573	// This defaults to a fake result that increases by 1ms on each reading.
574	//
575	// Here's an example that uses a custom clock:
576	//	moduleConfig = moduleConfig.
577	//		WithWalltime(func(context.Context) (sec int64, nsec int32) {
578	//			return clock.walltime()
579	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
580	//
581	// # Notes:
582	//   - This does not default to time.Now as that violates sandboxing.
583	//   - This is used to implement host functions such as WASI
584	//     `clock_time_get` with the `realtime` clock ID.
585	//   - Use WithSysWalltime for a usable implementation.
586	WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig
587
588	// WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us
589	// (1000ns).
590	//
591	// See WithWalltime
592	WithSysWalltime() ModuleConfig
593
594	// WithNanotime configures the monotonic clock, used to measure elapsed
595	// time in nanoseconds. Defaults to a fake result that increases by 1ms
596	// on each reading.
597	//
598	// Here's an example that uses a custom clock:
599	//	moduleConfig = moduleConfig.
600	//		WithNanotime(func(context.Context) int64 {
601	//			return clock.nanotime()
602	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
603	//
604	// # Notes:
605	//   - This does not default to time.Since as that violates sandboxing.
606	//   - This is used to implement host functions such as WASI
607	//     `clock_time_get` with the `monotonic` clock ID.
608	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
609	//   - If you set this, you should probably set WithNanosleep also.
610	//   - Use WithSysNanotime for a usable implementation.
611	WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig
612
613	// WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us.
614	//
615	// See WithNanotime
616	WithSysNanotime() ModuleConfig
617
618	// WithNanosleep configures the how to pause the current goroutine for at
619	// least the configured nanoseconds. Defaults to return immediately.
620	//
621	// This example uses a custom sleep function:
622	//	moduleConfig = moduleConfig.
623	//		WithNanosleep(func(ns int64) {
624	//			rel := unix.NsecToTimespec(ns)
625	//			remain := unix.Timespec{}
626	//			for { // loop until no more time remaining
627	//				err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain)
628	//			--snip--
629	//
630	// # Notes:
631	//   - This does not default to time.Sleep as that violates sandboxing.
632	//   - This is used to implement host functions such as WASI `poll_oneoff`.
633	//   - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
634	//   - If you set this, you should probably set WithNanotime also.
635	//   - Use WithSysNanosleep for a usable implementation.
636	WithNanosleep(sys.Nanosleep) ModuleConfig
637
638	// WithOsyield yields the processor, typically to implement spin-wait
639	// loops. Defaults to return immediately.
640	//
641	// # Notes:
642	//   - This primarily supports `sched_yield` in WASI
643	//   - This does not default to runtime.osyield as that violates sandboxing.
644	WithOsyield(sys.Osyield) ModuleConfig
645
646	// WithSysNanosleep uses time.Sleep for sys.Nanosleep.
647	//
648	// See WithNanosleep
649	WithSysNanosleep() ModuleConfig
650
651	// WithRandSource configures a source of random bytes. Defaults to return a
652	// deterministic source. You might override this with crypto/rand.Reader
653	//
654	// This reader is most commonly used by the functions like "random_get" in
655	// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
656	// "getRandomData" when runtime.GOOS is "js".
657	//
658	// Note: The caller is responsible to close any io.Reader they supply: It
659	// is not closed on api.Module Close.
660	WithRandSource(io.Reader) ModuleConfig
661}
662
663type moduleConfig struct {
664	name               string
665	nameSet            bool
666	startFunctions     []string
667	stdin              io.Reader
668	stdout             io.Writer
669	stderr             io.Writer
670	randSource         io.Reader
671	walltime           sys.Walltime
672	walltimeResolution sys.ClockResolution
673	nanotime           sys.Nanotime
674	nanotimeResolution sys.ClockResolution
675	nanosleep          sys.Nanosleep
676	osyield            sys.Osyield
677	args               [][]byte
678	// environ is pair-indexed to retain order similar to os.Environ.
679	environ [][]byte
680	// environKeys allow overwriting of existing values.
681	environKeys map[string]int
682	// fsConfig is the file system configuration for ABI like WASI.
683	fsConfig FSConfig
684	// sockConfig is the network listener configuration for ABI like WASI.
685	sockConfig *internalsock.Config
686}
687
688// NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
689func NewModuleConfig() ModuleConfig {
690	return &moduleConfig{
691		startFunctions: []string{"_start"},
692		environKeys:    map[string]int{},
693	}
694}
695
696// clone makes a deep copy of this module config.
697func (c *moduleConfig) clone() *moduleConfig {
698	ret := *c // copy except maps which share a ref
699	ret.environKeys = make(map[string]int, len(c.environKeys))
700	for key, value := range c.environKeys {
701		ret.environKeys[key] = value
702	}
703	return &ret
704}
705
706// WithArgs implements ModuleConfig.WithArgs
707func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
708	ret := c.clone()
709	ret.args = toByteSlices(args)
710	return ret
711}
712
713func toByteSlices(strings []string) (result [][]byte) {
714	if len(strings) == 0 {
715		return
716	}
717	result = make([][]byte, len(strings))
718	for i, a := range strings {
719		result[i] = []byte(a)
720	}
721	return
722}
723
724// WithEnv implements ModuleConfig.WithEnv
725func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
726	ret := c.clone()
727	// Check to see if this key already exists and update it.
728	if i, ok := ret.environKeys[key]; ok {
729		ret.environ[i+1] = []byte(value) // environ is pair-indexed, so the value is 1 after the key.
730	} else {
731		ret.environKeys[key] = len(ret.environ)
732		ret.environ = append(ret.environ, []byte(key), []byte(value))
733	}
734	return ret
735}
736
737// WithFS implements ModuleConfig.WithFS
738func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
739	var config FSConfig
740	if fs != nil {
741		config = NewFSConfig().WithFSMount(fs, "")
742	}
743	return c.WithFSConfig(config)
744}
745
746// WithFSConfig implements ModuleConfig.WithFSConfig
747func (c *moduleConfig) WithFSConfig(config FSConfig) ModuleConfig {
748	ret := c.clone()
749	ret.fsConfig = config
750	return ret
751}
752
753// WithName implements ModuleConfig.WithName
754func (c *moduleConfig) WithName(name string) ModuleConfig {
755	ret := c.clone()
756	ret.nameSet = true
757	ret.name = name
758	return ret
759}
760
761// WithStartFunctions implements ModuleConfig.WithStartFunctions
762func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
763	ret := c.clone()
764	ret.startFunctions = startFunctions
765	return ret
766}
767
768// WithStderr implements ModuleConfig.WithStderr
769func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
770	ret := c.clone()
771	ret.stderr = stderr
772	return ret
773}
774
775// WithStdin implements ModuleConfig.WithStdin
776func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
777	ret := c.clone()
778	ret.stdin = stdin
779	return ret
780}
781
782// WithStdout implements ModuleConfig.WithStdout
783func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
784	ret := c.clone()
785	ret.stdout = stdout
786	return ret
787}
788
789// WithWalltime implements ModuleConfig.WithWalltime
790func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
791	ret := c.clone()
792	ret.walltime = walltime
793	ret.walltimeResolution = resolution
794	return ret
795}
796
797// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
798// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
799// 1ns for monotonic. See RATIONALE.md for more context.
800
801// WithSysWalltime implements ModuleConfig.WithSysWalltime
802func (c *moduleConfig) WithSysWalltime() ModuleConfig {
803	return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds()))
804}
805
806// WithNanotime implements ModuleConfig.WithNanotime
807func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
808	ret := c.clone()
809	ret.nanotime = nanotime
810	ret.nanotimeResolution = resolution
811	return ret
812}
813
814// WithSysNanotime implements ModuleConfig.WithSysNanotime
815func (c *moduleConfig) WithSysNanotime() ModuleConfig {
816	return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
817}
818
819// WithNanosleep implements ModuleConfig.WithNanosleep
820func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
821	ret := *c // copy
822	ret.nanosleep = nanosleep
823	return &ret
824}
825
826// WithOsyield implements ModuleConfig.WithOsyield
827func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig {
828	ret := *c // copy
829	ret.osyield = osyield
830	return &ret
831}
832
833// WithSysNanosleep implements ModuleConfig.WithSysNanosleep
834func (c *moduleConfig) WithSysNanosleep() ModuleConfig {
835	return c.WithNanosleep(platform.Nanosleep)
836}
837
838// WithRandSource implements ModuleConfig.WithRandSource
839func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
840	ret := c.clone()
841	ret.randSource = source
842	return ret
843}
844
845// toSysContext creates a baseline wasm.Context configured by ModuleConfig.
846func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
847	var environ [][]byte // Intentionally doesn't pre-allocate to reduce logic to default to nil.
848	// Same validation as syscall.Setenv for Linux
849	for i := 0; i < len(c.environ); i += 2 {
850		key, value := c.environ[i], c.environ[i+1]
851		keyLen := len(key)
852		if keyLen == 0 {
853			err = errors.New("environ invalid: empty key")
854			return
855		}
856		valueLen := len(value)
857		result := make([]byte, keyLen+valueLen+1)
858		j := 0
859		for ; j < keyLen; j++ {
860			if k := key[j]; k == '=' { // NUL enforced in NewContext
861				err = errors.New("environ invalid: key contains '=' character")
862				return
863			} else {
864				result[j] = k
865			}
866		}
867		result[j] = '='
868		copy(result[j+1:], value)
869		environ = append(environ, result)
870	}
871
872	var fs []experimentalsys.FS
873	var guestPaths []string
874	if f, ok := c.fsConfig.(*fsConfig); ok {
875		fs, guestPaths = f.preopens()
876	}
877
878	var listeners []*net.TCPListener
879	if n := c.sockConfig; n != nil {
880		if listeners, err = n.BuildTCPListeners(); err != nil {
881			return
882		}
883	}
884
885	return internalsys.NewContext(
886		math.MaxUint32,
887		c.args,
888		environ,
889		c.stdin,
890		c.stdout,
891		c.stderr,
892		c.randSource,
893		c.walltime, c.walltimeResolution,
894		c.nanotime, c.nanotimeResolution,
895		c.nanosleep, c.osyield,
896		fs, guestPaths,
897		listeners,
898	)
899}