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}