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