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