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