github.com/tetratelabs/wazero@v1.2.1/RATIONALE.md (about)

     1  # Notable rationale of wazero
     2  
     3  ## Zero dependencies
     4  
     5  Wazero has zero dependencies to differentiate itself from other runtimes which
     6  have heavy impact usually due to CGO. By avoiding CGO, wazero avoids
     7  prerequisites such as shared libraries or libc, and lets users keep features
     8  like cross compilation.
     9  
    10  Avoiding go.mod dependencies reduces interference on Go version support, and
    11  size of a statically compiled binary. However, doing so brings some
    12  responsibility into the project.
    13  
    14  Go's native platform support is good: We don't need platform-specific code to
    15  get monotonic time, nor do we need much work to implement certain features
    16  needed by our compiler such as `mmap`. That said, Go does not support all
    17  common operating systems to the same degree. For example, Go 1.18 includes
    18  `Mprotect` on Linux and Darwin, but not FreeBSD.
    19  
    20  The general tradeoff the project takes from a zero dependency policy is more
    21  explicit support of platforms (in the compiler runtime), as well a larger and
    22  more technically difficult codebase.
    23  
    24  At some point, we may allow extensions to supply their own platform-specific
    25  hooks. Until then, one end user impact/tradeoff is some glitches trying
    26  untested platforms (with the Compiler runtime).
    27  
    28  ### Why do we use CGO to implement system calls on darwin?
    29  
    30  wazero is dependency and CGO free by design. In some cases, we have code that
    31  can optionally use CGO, but retain a fallback for when that's disabled. The only
    32  operating system (`GOOS`) we use CGO by default in is `darwin`.
    33  
    34  Unlike other operating systems, regardless of `CGO_ENABLED`, Go always uses
    35  "CGO" mechanisms in the runtime layer of `darwin`. This is explained in
    36  [Statically linked binaries on Mac OS X](https://developer.apple.com/library/archive/qa/qa1118/_index.html#//apple_ref/doc/uid/DTS10001666):
    37  
    38  > Apple does not support statically linked binaries on Mac OS X. A statically
    39  > linked binary assumes binary compatibility at the kernel system call
    40  > interface, and we do not make any guarantees on that front. Rather, we strive
    41  > to ensure binary compatibility in each dynamically linked system library and
    42  > framework.
    43  
    44  This plays to our advantage for system calls that aren't yet exposed in the Go
    45  standard library, notably `futimens` for nanosecond-precision timestamp
    46  manipulation.
    47  
    48  ### Why not x/sys
    49  
    50  Going beyond Go's SDK limitations can be accomplished with their [x/sys library](https://pkg.go.dev/golang.org/x/sys/unix).
    51  For example, this includes `zsyscall_freebsd_amd64.go` missing from the Go SDK.
    52  
    53  However, like all dependencies, x/sys is a source of conflict. For example,
    54  x/sys had to be in order to upgrade to Go 1.18.
    55  
    56  If we depended on x/sys, we could get more precise functionality needed for
    57  features such as clocks or more platform support for the compiler runtime.
    58  
    59  That said, formally supporting an operating system may still require testing as
    60  even use of x/sys can require platform-specifics. For example, [mmap-go](https://github.com/edsrzf/mmap-go)
    61  uses x/sys, but also mentions limitations, some not surmountable with x/sys
    62  alone.
    63  
    64  Regardless, we may at some point introduce a separate go.mod for users to use
    65  x/sys as a platform plugin without forcing all users to maintain that
    66  dependency.
    67  
    68  ## Project structure
    69  
    70  wazero uses internal packages extensively to balance API compatability desires for end users with the need to safely
    71  share internals between compilers.
    72  
    73  End-user packages include `wazero`, with `Config` structs, `api`, with shared types, and the built-in `wasi` library.
    74  Everything else is internal.
    75  
    76  We put the main program for wazero into a directory of the same name to match conventions used in `go install`,
    77  notably the name of the folder becomes the binary name. We chose to use `cmd/wazero` as it is common practice
    78  and less surprising than `wazero/wazero`.
    79  
    80  ### Internal packages
    81  
    82  Most code in wazero is internal, and it is acknowledged that this prevents external implementation of facets such as
    83  compilers or decoding. It also prevents splitting this code into separate repositories, resulting in a larger monorepo.
    84  This also adds work as more code needs to be centrally reviewed.
    85  
    86  However, the alternative is neither secure nor viable. To allow external implementation would require exporting symbols
    87  public, such as the `CodeSection`, which can easily create bugs. Moreover, there's a high drift risk for any attempt at
    88  external implementations, compounded not just by wazero's code organization, but also the fast moving Wasm and WASI
    89  specifications.
    90  
    91  For example, implementing a compiler correctly requires expertise in Wasm, Golang and assembly. This requires deep
    92  insight into how internals are meant to be structured and the various tiers of testing required for `wazero` to result
    93  in a high quality experience. Even if someone had these skills, supporting external code would introduce variables which
    94  are constants in the central one. Supporting an external codebase is harder on the project team, and could starve time
    95  from the already large burden on the central codebase.
    96  
    97  The tradeoffs of internal packages are a larger codebase and responsibility to implement all standard features. It also
    98  implies thinking about extension more as forking is not viable for reasons above also. The primary mitigation of these
    99  realities are friendly OSS licensing, high rigor and a collaborative spirit which aim to make contribution in the shared
   100  codebase productive.
   101  
   102  ### Avoiding cyclic dependencies
   103  
   104  wazero shares constants and interfaces with internal code by a sharing pattern described below:
   105  * shared interfaces and constants go in one package under root: `api`.
   106  * user APIs and structs depend on `api` and go into the root package `wazero`.
   107    * e.g. `InstantiateModule` -> `/wasm.go` depends on the type `api.Module`.
   108  * implementation code can also depend on `api` in a corresponding package under `/internal`.
   109    * Ex  package `wasm` -> `/internal/wasm/*.go` and can depend on the type `api.Module`.
   110  
   111  The above guarantees no cyclic dependencies at the cost of having to re-define symbols that exist in both packages.
   112  For example, if `wasm.Store` is a type the user needs access to, it is narrowed by a cover type in the `wazero`:
   113  
   114  ```go
   115  type runtime struct {
   116  	s *wasm.Store
   117  }
   118  ```
   119  
   120  This is not as bad as it sounds as mutations are only available via configuration. This means exported functions are
   121  limited to only a few functions.
   122  
   123  ### Avoiding security bugs
   124  
   125  In order to avoid security flaws such as code insertion, nothing in the public API is permitted to write directly to any
   126  mutable symbol in the internal package. For example, the package `api` is shared with internal code. To ensure
   127  immutability, the `api` package cannot contain any mutable public symbol, such as a slice or a struct with an exported
   128  field.
   129  
   130  In practice, this means shared functionality like memory mutation need to be implemented by interfaces.
   131  
   132  Here are some examples:
   133  * `api.Memory` protects access by exposing functions like `WriteFloat64Le` instead of exporting a buffer (`[]byte`).
   134  * There is no exported symbol for the `[]byte` representing the `CodeSection`
   135  
   136  Besides security, this practice prevents other bugs and allows centralization of validation logic such as decoding Wasm.
   137  
   138  ## API Design
   139  
   140  ### Why is `context.Context` inconsistent?
   141  
   142  It may seem strange that only certain API have an initial `context.Context`
   143  parameter. We originally had a `context.Context` for anything that might be
   144  traced, but it turned out to be only useful for lifecycle and host functions.
   145  
   146  For instruction-scoped aspects like memory updates, a context parameter is too
   147  fine-grained and also invisible in practice. For example, most users will use
   148  the compiler engine, and its memory, global or table access will never use go's
   149  context.
   150  
   151  ### Why does `api.ValueType` map to uint64?
   152  
   153  WebAssembly allows functions to be defined either by the guest or the host,
   154  with signatures expressed as WebAssembly types. For example, `i32` is a 32-bit
   155  type which might be interpreted as signed. Function signatures can have zero or
   156  more parameters or results even if WebAssembly 1.0 allows up to one result.
   157  
   158  The guest can export functions, so that the host can call it. In the case of
   159  wazero, the host is Go and an exported function can be called via
   160  `api.Function`. `api.Function` allows users to supply parameters and read
   161  results as a slice of uint64. For example, if there are no results, an empty
   162  slice is returned. The user can learn the signature via `FunctionDescription`,
   163  which returns the `api.ValueType` corresponding to each parameter or result.
   164  `api.ValueType` defines the mapping of WebAssembly types to `uint64` values for
   165  reason described in this section. The special case of `v128` is also mentioned
   166  below.
   167  
   168  wazero maps each value type to a uint64 values because it holds the largest
   169  type in WebAssembly 1.0 (i64). A slice allows you to express empty (e.g. a
   170  nullary signature), for example a start function.
   171  
   172  Here's an example of calling a function, noting this syntax works for both a
   173  signature `(param i32 i32) (result i32)` and `(param i64 i64) (result i64)`
   174  ```go
   175  x, y := uint64(1), uint64(2)
   176  results, err := mod.ExportedFunction("add").Call(ctx, x, y)
   177  if err != nil {
   178  	log.Panicln(err)
   179  }
   180  fmt.Printf("%d + %d = %d\n", x, y, results[0])
   181  ```
   182  
   183  WebAssembly does not define an encoding strategy for host defined parameters or
   184  results. This means the encoding rules above are defined by wazero instead. To
   185  address this, we clarified mapping both in `api.ValueType` and added helper
   186  functions like `api.EncodeF64`. This allows users conversions typical in Go
   187  programming, and utilities to avoid ambiguity and edge cases around casting.
   188  
   189  Alternatively, we could have defined a byte buffer based approach and a binary
   190  encoding of value types in and out. For example, an empty byte slice would mean
   191  no values, while a non-empty could use a binary encoding for supported values.
   192  This could work, but it is more difficult for the normal case of i32 and i64.
   193  It also shares a struggle with the current approach, which is that value types
   194  were added after WebAssembly 1.0 and not all of them have an encoding. More on
   195  this below.
   196  
   197  In summary, wazero chose an approach for signature mapping because there was
   198  none, and the one we chose biases towards simplicity with integers and handles
   199  the rest with documentation and utilities.
   200  
   201  #### Post 1.0 value types
   202  
   203  Value types added after WebAssembly 1.0 stressed the current model, as some
   204  have no encoding or are larger than 64 bits. While problematic, these value
   205  types are not commonly used in exported (extern) functions. However, some
   206  decisions were made and detailed below.
   207  
   208  For example `externref` has no guest representation. wazero chose to map
   209  references to uint64 as that's the largest value needed to encode a pointer on
   210  supported platforms. While there are two reference types, `externref` and
   211  `functype`, the latter is an internal detail of function tables, and the former
   212  is rarely if ever used in function signatures as of the end of 2022.
   213  
   214  The only value larger than 64 bits is used for SIMD (`v128`). Vectorizing via
   215  host functions is not used as of the end of 2022. Even if it were, it would be
   216  inefficient vs guest vectorization due to host function overhead. In other
   217  words, the `v128` value type is unlikely to be in an exported function
   218  signature. That it requires two uint64 values to encode is an internal detail
   219  and not worth changing the exported function interface `api.Function`, as doing
   220  so would break all users.
   221  
   222  ### Interfaces, not structs
   223  
   224  All exported types in public packages, regardless of configuration vs runtime, are interfaces. The primary benefits are
   225  internal flexibility and avoiding people accidentally mis-initializing by instantiating the types on their own vs using
   226  the `NewXxx` constructor functions. In other words, there's less support load when things can't be done incorrectly.
   227  
   228  Here's an example:
   229  ```go
   230  rt := &RuntimeConfig{} // not initialized properly (fields are nil which shouldn't be)
   231  rt := RuntimeConfig{} // not initialized properly (should be a pointer)
   232  rt := wazero.NewRuntimeConfig() // initialized properly
   233  ```
   234  
   235  There are a few drawbacks to this, notably some work for maintainers.
   236  * Interfaces are decoupled from the structs implementing them, which means the signature has to be repeated twice.
   237  * Interfaces have to be documented and guarded at time of use, that 3rd party implementations aren't supported.
   238  * As of Golang 1.18, interfaces are still [not well supported](https://github.com/golang/go/issues/5860) in godoc.
   239  
   240  ## Config
   241  
   242  wazero configures scopes such as Runtime and Module using `XxxConfig` types. For example, `RuntimeConfig` configures
   243  `Runtime` and `ModuleConfig` configure `Module` (instantiation). In all cases, config types begin defaults and can be
   244  customized by a user, e.g., selecting features or a module name override.
   245  
   246  ### Why don't we make each configuration setting return an error?
   247  No config types create resources that would need to be closed, nor do they return errors on use. This helps reduce
   248  resource leaks, and makes chaining easier. It makes it possible to parse configuration (ex by parsing yaml) independent
   249  of validating it.
   250  
   251  Instead of:
   252  ```
   253  cfg, err = cfg.WithFS(fs)
   254  if err != nil {
   255    return err
   256  }
   257  cfg, err = cfg.WithName(name)
   258  if err != nil {
   259    return err
   260  }
   261  mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg)
   262  if err != nil {
   263    return err
   264  }
   265  ```
   266  
   267  There's only one call site to handle errors:
   268  ```
   269  cfg = cfg.WithFS(fs).WithName(name)
   270  mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg)
   271  if err != nil {
   272    return err
   273  }
   274  ```
   275  
   276  This allows users one place to look for errors, and also the benefit that if anything internally opens a resource, but
   277  errs, there's nothing they need to close. In other words, users don't need to track which resources need closing on
   278  partial error, as that is handled internally by the only code that can read configuration fields.
   279  
   280  ### Why are configuration immutable?
   281  While it seems certain scopes like `Runtime` won't repeat within a process, they do, possibly in different goroutines.
   282  For example, some users create a new runtime for each module, and some re-use the same base module configuration with
   283  only small updates (ex the name) for each instantiation. Making configuration immutable allows them to be safely used in
   284  any goroutine.
   285  
   286  Since config are immutable, changes apply via return val, similar to `append` in a slice.
   287  
   288  For example, both of these are the same sort of error:
   289  ```go
   290  append(slice, element) // bug as only the return value has the updated slice.
   291  cfg.WithName(next) // bug as only the return value has the updated name.
   292  ```
   293  
   294  Here's an example of correct use: re-assigning explicitly or via chaining.
   295  ```go
   296  cfg = cfg.WithName(name) // explicit
   297  
   298  mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg.WithName(name)) // implicit
   299  if err != nil {
   300    return err
   301  }
   302  ```
   303  
   304  ### Why aren't configuration assigned with option types?
   305  The option pattern is a familiar one in Go. For example, someone defines a type `func (x X) err` and uses it to update
   306  the target. For example, you could imagine wazero could choose to make `ModuleConfig` from options vs chaining fields.
   307  
   308  Ex instead of:
   309  ```go
   310  type ModuleConfig interface {
   311  	WithName(string) ModuleConfig
   312  	WithFS(fs.FS) ModuleConfig
   313  }
   314  
   315  struct moduleConfig {
   316  	name string
   317  	fs fs.FS
   318  }
   319  
   320  func (c *moduleConfig) WithName(name string) ModuleConfig {
   321      ret := *c // copy
   322      ret.name = name
   323      return &ret
   324  }
   325  
   326  func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
   327      ret := *c // copy
   328      ret.setFS("/", fs)
   329      return &ret
   330  }
   331  
   332  config := r.NewModuleConfig().WithFS(fs)
   333  configDerived := config.WithName("name")
   334  ```
   335  
   336  An option function could be defined, then refactor each config method into an name prefixed option function:
   337  ```go
   338  type ModuleConfig interface {
   339  }
   340  struct moduleConfig {
   341      name string
   342      fs fs.FS
   343  }
   344  
   345  type ModuleConfigOption func(c *moduleConfig)
   346  
   347  func ModuleConfigName(name string) ModuleConfigOption {
   348      return func(c *moduleConfig) {
   349          c.name = name
   350  	}
   351  }
   352  
   353  func ModuleConfigFS(fs fs.FS) ModuleConfigOption {
   354      return func(c *moduleConfig) {
   355          c.fs = fs
   356      }
   357  }
   358  
   359  func (r *runtime) NewModuleConfig(opts ...ModuleConfigOption) ModuleConfig {
   360  	ret := newModuleConfig() // defaults
   361      for _, opt := range opts {
   362          opt(&ret.config)
   363      }
   364      return ret
   365  }
   366  
   367  func (c *moduleConfig) WithOptions(opts ...ModuleConfigOption) ModuleConfig {
   368      ret := *c // copy base config
   369      for _, opt := range opts {
   370          opt(&ret.config)
   371      }
   372      return ret
   373  }
   374  
   375  config := r.NewModuleConfig(ModuleConfigFS(fs))
   376  configDerived := config.WithOptions(ModuleConfigName("name"))
   377  ```
   378  
   379  wazero took the path of the former design primarily due to:
   380  * interfaces provide natural namespaces for their methods, which is more direct than functions with name prefixes.
   381  * parsing config into function callbacks is more direct vs parsing config into a slice of functions to do the same.
   382  * in either case derived config is needed and the options pattern is more awkward to achieve that.
   383  
   384  There are other reasons such as test and debug being simpler without options: the above list is constrained to conserve
   385  space. It is accepted that the options pattern is common in Go, which is the main reason for documenting this decision.
   386  
   387  ### Why aren't config types deeply structured?
   388  wazero's configuration types cover the two main scopes of WebAssembly use:
   389  * `RuntimeConfig`: This is the broadest scope, so applies also to compilation
   390    and instantiation. e.g. This controls the WebAssembly Specification Version.
   391  * `ModuleConfig`: This affects modules instantiated after compilation and what
   392    resources are allowed. e.g. This defines how or if STDOUT is captured. This
   393    also allows sub-configuration of `FSConfig`.
   394  
   395  These default to a flat definition each, with lazy sub-configuration only after
   396  proven to be necessary. A flat structure is easier to work with and is also
   397  easy to discover. Unlike the option pattern described earlier, more
   398  configuration in the interface doesn't taint the package namespace, only
   399  `ModuleConfig`.
   400  
   401  We default to a flat structure to encourage simplicity. If we eagerly broke out
   402  all possible configurations into sub-types (e.g. ClockConfig), it would be hard
   403  to notice configuration sprawl. By keeping the config flat, it is easy to see
   404  the cognitive load we may be adding to our users.
   405  
   406  In other words, discomfort adding more configuration is a feature, not a bug.
   407  We should only add new configuration rarely, and before doing so, ensure it
   408  will be used. In fact, this is why we support using context fields for
   409  experimental configuration. By letting users practice, we can find out if a
   410  configuration was a good idea or not before committing to it, and potentially
   411  sprawling our types.
   412  
   413  In reflection, this approach worked well for the nearly 1.5 year period leading
   414  to version 1.0. We've only had to create a single sub-configuration, `FSConfig`,
   415  and it was well understood why when it occurred.
   416  
   417  ## Why does InstantiateModule call "_start" by default?
   418  We formerly had functions like `StartWASICommand` that would verify preconditions and start WASI's "_start" command.
   419  However, this caused confusion because both many languages compiled a WASI dependency, and many did so inconsistently.
   420  
   421  That said, if "_start" isn't called, it causes issues in TinyGo, as it needs this in order to implement panic. To deal
   422  with this a different way, we have a configuration to call any start functions that exist, which defaults to "_start".
   423  
   424  ## Runtime == Engine+Store
   425  wazero defines a single user-type which combines the specification concept of `Store` with the unspecified `Engine`
   426  which manages them.
   427  
   428  ### Why not multi-store?
   429  Multi-store isn't supported as the extra tier complicates lifecycle and locking. Moreover, in practice it is unusual for
   430  there to be an engine that has multiple stores which have multiple modules. More often, it is the case that there is
   431  either 1 engine with 1 store and multiple modules, or 1 engine with many stores, each having 1 non-host module. In worst
   432  case, a user can use multiple runtimes until "multi-store" is better understood.
   433  
   434  If later, we have demand for multiple stores, that can be accomplished by overload. e.g. `Runtime.InstantiateInStore` or
   435  `Runtime.Store(name) Store`.
   436  
   437  ## wazeroir
   438  wazero's intermediate representation (IR) is called `wazeroir`. Lowering into an IR provides us a faster interpreter
   439  and a closer to assembly representation for used by our compiler.
   440  
   441  ### Intermediate Representation (IR) design
   442  `wazeroir`'s initial design borrowed heavily from the defunct `microwasm` format (a.k.a. LightbeamIR). Notably,
   443  `wazeroir` doesn't have block operations: this simplifies the implementation.
   444  
   445  Note: `microwasm` was never specified formally, and only exists in a historical codebase of wasmtime:
   446  https://github.com/bytecodealliance/wasmtime/blob/v0.29.0/crates/lightbeam/src/microwasm.rs
   447  
   448  ## Exit
   449  
   450  ### Why do we return a `sys.ExitError` on exit code zero?
   451  
   452  It may be surprising to find an error returned on success (exit code zero).
   453  This can be explained easier when you think of function returns: When results
   454  aren't empty, then you must return an error. This is trickier to explain when
   455  results are empty, such as the case in the "_start" function in WASI.
   456  
   457  The main rationale for returning an exit error even if the code is success is
   458  that the module is no longer functional. For example, function exports would
   459  error later. In cases like these, it is better to handle errors where they
   460  occur.
   461  
   462  Luckily, it is not common to exit a module during the "_start" function. For
   463  example, the only known compilation target that does this is Emscripten. Most,
   464  such as Rust, TinyGo, or normal wasi-libc, don't. If they did, it would
   465  invalidate their function exports. This means it is unlikely most compilers
   466  will change this behavior.
   467  
   468  In summary, we return a `sys.ExitError` to the caller whenever we get it, as it
   469  properly reflects the state of the module, which would be closed on this error.
   470  
   471  ### Why panic with `sys.ExitError` after a host function exits?
   472  
   473  Currently, the only portable way to stop processing code is via panic. For
   474  example, WebAssembly "trap" instructions, such as divide by zero, are
   475  implemented via panic. This ensures code isn't executed after it.
   476  
   477  When code reaches the WASI `proc_exit` instruction, we need to stop processing.
   478  Regardless of the exit code, any code invoked after exit would be in an
   479  inconsistent state. This is likely why unreachable instructions are sometimes
   480  inserted after exit: https://github.com/emscripten-core/emscripten/issues/12322
   481  
   482  ## WASI
   483  
   484  Unfortunately, (WASI Snapshot Preview 1)[https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md] is not formally defined enough, and has APIs with ambiguous semantics.
   485  This section describes how Wazero interprets and implements the semantics of several WASI APIs that may be interpreted differently by different wasm runtimes.
   486  Those APIs may affect the portability of a WASI application.
   487  
   488  ### Why don't we attempt to pass wasi-testsuite on user-defined `fs.FS`?
   489  
   490  While most cases work fine on an `os.File` based implementation, we won't
   491  promise wasi-testsuite compatibility on user defined wrappers of `os.DirFS`.
   492  The only option for real systems is to use our `sysfs.FS`.
   493  
   494  There are a lot of areas where windows behaves differently, despite the
   495  `os.File` abstraction. This goes well beyond file locking concerns (e.g.
   496  `EBUSY` errors on open files). For example, errors like `ACCESS_DENIED` aren't
   497  properly mapped to `EPERM`. There are trickier parts too. `FileInfo.Sys()`
   498  doesn't return enough information to build inodes needed for WASI. To rebuild
   499  them requires the full path to the underlying file, not just its directory
   500  name, and there's no way for us to get that information. At one point we tried,
   501  but in practice things became tangled and functionality such as read-only
   502  wrappers became untenable. Finally, there are version-specific behaviors which
   503  are difficult to maintain even in our own code. For example, go 1.20 opens
   504  files in a different way than versions before it.
   505  
   506  ### Why aren't WASI rules enforced?
   507  
   508  The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has a
   509  number of rules for a "command module", but only the memory export rule is enforced. If a "_start" function exists, it
   510  is enforced to be the correct signature and succeed, but the export itself isn't enforced. It follows that this means
   511  exports are not required to be contained to a "_start" function invocation. Finally, the "__indirect_function_table"
   512  export is also not enforced.
   513  
   514  The reason for the exceptions are that implementations aren't following the rules. For example, TinyGo doesn't export
   515  "__indirect_function_table", so crashing on this would make wazero unable to run TinyGo modules. Similarly, modules
   516  loaded by wapc-go don't always define a "_start" function. Since "snapshot-01" is not a proper version, and certainly
   517  not a W3C recommendation, there's no sense in breaking users over matters like this.
   518  
   519  ### Why is I/O configuration not coupled to WASI?
   520  
   521  WebAssembly System Interfaces (WASI) is a formalization of a practice that can be done anyway: Define a host function to
   522  access a system interface, such as writing to STDOUT. WASI stalled at snapshot-01 and as of early 2023, is being
   523  rewritten entirely.
   524  
   525  This instability implies a need to transition between WASI specs, which places wazero in a position that requires
   526  decoupling. For example, if code uses two different functions to call `fd_write`, the underlying configuration must be
   527  centralized and decoupled. Otherwise, calls using the same file descriptor number will end up writing to different
   528  places.
   529  
   530  In short, wazero defined system configuration in `ModuleConfig`, not a WASI type. This allows end-users to switch from
   531  one spec to another with minimal impact. This has other helpful benefits, as centralized resources are simpler to close
   532  coherently (ex via `Module.Close`).
   533  
   534  In reflection, this worked well as more ABI became usable in wazero. For example, `GOARCH=wasm GOOS=js` code uses the
   535  same `ModuleConfig` (and `FSConfig`) WASI uses, and in compatible ways.
   536  
   537  ### Background on `ModuleConfig` design
   538  
   539  WebAssembly 1.0 (20191205) specifies some aspects to control isolation between modules ([sandboxing](https://en.wikipedia.org/wiki/Sandbox_(computer_security))).
   540  For example, `wasm.Memory` has size constraints and each instance of it is isolated from each other. While `wasm.Memory`
   541  can be shared, by exporting it, it is not exported by default. In fact a WebAssembly Module (Wasm) has no memory by
   542  default.
   543  
   544  While memory is defined in WebAssembly 1.0 (20191205), many aspects are not. Let's use an example of `exec.Cmd` as for
   545  example, a WebAssembly System Interfaces (WASI) command is implemented as a module with a `_start` function, and in many
   546  ways acts similar to a process with a `main` function.
   547  
   548  To capture "hello world" written to the console (stdout a.k.a. file descriptor 1) in `exec.Cmd`, you would set the
   549  `Stdout` field accordingly, perhaps to a buffer. In WebAssembly 1.0 (20191205), the only way to perform something like
   550  this is via a host function (ex `HostModuleFunctionBuilder`) and internally copy memory corresponding to that string
   551  to a buffer.
   552  
   553  WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports
   554  "fd_write" from "wasi_snapshot_preview1" and calls it with the `fd` parameter set to 1 (STDOUT).
   555  
   556  The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has no
   557  means to declare configuration, although its function definitions imply configuration for example if fd 1 should exist,
   558  and if so where should it write. Moreover, snapshot-01 was last updated in late 2020 and the specification is being
   559  completely rewritten as of early 2022. This means WASI as defined by "snapshot-01" will not clarify aspects like which
   560  file descriptors are required. While it is possible a subsequent version may, it is too early to tell as no version of
   561  WASI has reached a stage near W3C recommendation. Even if it did, module authors are not required to only use WASI to
   562  write to console, as they can define their own host functions, such as they did before WASI existed.
   563  
   564  wazero aims to serve Go developers as a primary function, and help them transition between WASI specifications. In
   565  order to do this, we have to allow top-level configuration. To ensure isolation by default, `ModuleConfig` has WithXXX
   566  that override defaults to no-op or empty. One `ModuleConfig` instance is used regardless of how many times the same WASI
   567  functions are imported. The nil defaults allow safe concurrency in these situations, as well lower the cost when they
   568  are never used. Finally, a one-to-one mapping with `Module` allows the module to close the `ModuleConfig` instead of
   569  confusing users with another API to close.
   570  
   571  Naming, defaults and validation rules of aspects like `STDIN` and `Environ` are intentionally similar to other Go
   572  libraries such as `exec.Cmd` or `syscall.SetEnv`, and differences called out where helpful. For example, there's no goal
   573  to emulate any operating system primitive specific to Windows (such as a 'c:\' drive). Moreover, certain defaults
   574  working with real system calls are neither relevant nor safe to inherit: For example, `exec.Cmd` defaults to read STDIN
   575  from a real file descriptor ("/dev/null"). Defaulting to this, vs reading `io.EOF`, would be unsafe as it can exhaust
   576  file descriptors if resources aren't managed properly. In other words, blind copying of defaults isn't wise as it can
   577  violate isolation or endanger the embedding process. In summary, we try to be similar to normal Go code, but often need
   578  act differently and document `ModuleConfig` is more about emulating, not necessarily performing real system calls.
   579  
   580  ## File systems
   581  
   582  ### Why doesn't wazero implement the working directory?
   583  
   584  An early design of wazero's API included a `WithWorkDirFS` which allowed
   585  control over which file a relative path such as "./config.yml" resolved to,
   586  independent of the root file system. This intended to help separate concerns
   587  like mutability of files, but it didn't work and was removed.
   588  
   589  Compilers that target wasm act differently with regard to the working
   590  directory. For example, while `GOOS=js` uses host functions to track the
   591  working directory, WASI host functions do not. wasi-libc, used by TinyGo,
   592  tracks working directory changes in compiled wasm instead: initially "/" until
   593  code calls `chdir`. Zig assumes the first pre-opened file descriptor is the
   594  working directory.
   595  
   596  The only place wazero can standardize a layered concern is via a host function.
   597  Since WASI doesn't use host functions to track the working directory, we can't
   598  standardize the storage and initial value of it.
   599  
   600  Meanwhile, code may be able to affect the working directory by compiling
   601  `chdir` into their main function, using an argument or ENV for the initial
   602  value (possibly `PWD`). Those unable to control the compiled code should only
   603  use absolute paths in configuration.
   604  
   605  See
   606  * https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L324
   607  * https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117
   608  * https://github.com/ziglang/zig/blob/53a9ee699a35a3d245ab6d1dac1f0687a4dcb42c/src/main.zig#L32
   609  
   610  ### Why ignore the error returned by io.Reader when n > 1?
   611  
   612  Per https://pkg.go.dev/io#Reader, if we receive an error, any bytes read should
   613  be processed first. At the syscall abstraction (`fd_read`), the caller is the
   614  processor, so we can't process the bytes inline and also return the error (as
   615  `EIO`).
   616  
   617  Let's assume we want to return the bytes read on error to the caller. This
   618  implies we at least temporarily ignore the error alongside them. The choice
   619  remaining is whether to persist the error returned with the read until a
   620  possible next call, or ignore the error.
   621  
   622  If we persist an error returned, it would be coupled to a file descriptor, but
   623  effectively it is boolean as this case coerces to `EIO`. If we track a "last
   624  error" on a file descriptor, it could be complicated for a couple reasons
   625  including whether the error is transient or permanent, or if the error would
   626  apply to any FD operation, or just read. Finally, there may never be a
   627  subsequent read as perhaps the bytes leading up to the error are enough to
   628  satisfy the processor.
   629  
   630  This decision boils down to whether or not to track an error bit per file
   631  descriptor or not. If not, the assumption is that a subsequent operation would
   632  also error, this time without reading any bytes.
   633  
   634  The current opinion is to go with the simplest path, which is to return the
   635  bytes read and ignore the error the there were any. Assume a subsequent
   636  operation will err if it needs to. This helps reduce the complexity of the code
   637  in wazero and also accommodates the scenario where the bytes read are enough to
   638  satisfy its processor.
   639  
   640  ### File descriptor allocation strategy
   641  
   642  File descriptor allocation currently uses a strategy similar the one implemented
   643  by unix systems: when opening a file, the lowest unused number is picked.
   644  
   645  The WASI standard documents that programs cannot expect that file descriptor
   646  numbers will be allocated with a lowest-first strategy, and they should instead
   647  assume the values will be random. Since _random_ is a very imprecise concept in
   648  computers, we technically satisfying the implementation with the descriptor
   649  allocation strategy we use in Wazero. We could imagine adding more _randomness_
   650  to the descriptor selection process, however this should never be used as a
   651  security measure to prevent applications from guessing the next file number so
   652  there are no strong incentives to complicate the logic.
   653  
   654  ### Why does `FSConfig.WithDirMount` not match behaviour with `os.DirFS`?
   655  
   656  It may seem that we should require any feature that seems like a standard
   657  library in Go, to behave the same way as the standard library. Doing so would
   658  present least surprise to Go developers. In the case of how we handle
   659  filesystems, we break from that as it is incompatible with the expectations of
   660  WASI, the most commonly implemented filesystem ABI.
   661  
   662  The main reason is that `os.DirFS` is a virtual filesystem abstraction while
   663  WASI is an abstraction over syscalls. For example, the signature of `fs.Open`
   664  does not permit use of flags. This creates conflict on what default behaviors
   665  to take when Go implemented `os.DirFS`. On the other hand, `path_open` can pass
   666  flags, and in fact tests require them to be honored in specific ways. This
   667  extends beyond WASI as even `GOARCH=wasm GOOS=js` compiled code requires
   668  certain flags passed to `os.OpenFile` which are impossible to pass due to the
   669  signature of `fs.FS`.
   670  
   671  This conflict requires us to choose what to be more compatible with, and which
   672  type of user to surprise the least. We assume there will be more developers
   673  compiling code to wasm than developers of custom filesystem plugins, and those
   674  compiling code to wasm will be better served if we are compatible with WASI.
   675  Hence on conflict, we prefer WASI behavior vs the behavior of `os.DirFS`.
   676  
   677  Meanwhile, it is possible that Go will one day compile to `GOOS=wasi` in
   678  addition to `GOOS=js`. When there is shared stake in WASI, we expect gaps like
   679  these to be easier to close.
   680  
   681  See https://github.com/WebAssembly/wasi-testsuite
   682  See https://github.com/golang/go/issues/58141
   683  
   684  ### fd_pread: io.Seeker fallback when io.ReaderAt is not supported
   685  
   686  `ReadAt` is the Go equivalent to `pread`: it does not affect, and is not
   687  affected by, the underlying file offset. Unfortunately, `io.ReaderAt` is not
   688  implemented by all `fs.File`. For example, as of Go 1.19, `embed.openFile` does
   689  not.
   690  
   691  The initial implementation of `fd_pread` instead used `Seek`. To avoid a
   692  regression, we fall back to `io.Seeker` when `io.ReaderAt` is not supported.
   693  
   694  This requires obtaining the initial file offset, seeking to the intended read
   695  offset, and resetting the file offset the initial state. If this final seek
   696  fails, the file offset is left in an undefined state. This is not thread-safe.
   697  
   698  While seeking per read seems expensive, the common case of `embed.openFile` is
   699  only accessing a single int64 field, which is cheap.
   700  
   701  ### Pre-opened files
   702  
   703  WASI includes `fd_prestat_get` and `fd_prestat_dir_name` functions used to
   704  learn any directory paths for file descriptors open at initialization time.
   705  
   706  For example, `__wasilibc_register_preopened_fd` scans any file descriptors past
   707  STDERR (1) and invokes `fd_prestat_dir_name` to learn any path prefixes they
   708  correspond to. Zig's `preopensAlloc` does similar. These pre-open functions are
   709  not used again after initialization.
   710  
   711  wazero supports stdio pre-opens followed by any mounts e.g `.:/`. The guest
   712  path is a directory and its name, e.g. "/" is returned by `fd_prestat_dir_name`
   713  for file descriptor 3 (STDERR+1). The first longest match wins on multiple
   714  pre-opens, which allows a path like "/tmp" to match regardless of order vs "/".
   715  
   716  See
   717   * https://github.com/WebAssembly/wasi-libc/blob/a02298043ff551ce1157bc2ee7ab74c3bffe7144/libc-bottom-half/sources/preopens.c
   718   * https://github.com/ziglang/zig/blob/9cb06f3b8bf9ea6b5e5307711bc97328762d6a1d/lib/std/fs/wasi.zig#L50-L53
   719  
   720  ### fd_prestat_dir_name
   721  
   722  `fd_prestat_dir_name` is a WASI function to return the path of the pre-opened
   723  directory of a file descriptor. It has the following three parameters, and the
   724  third `path_len` has ambiguous semantics.
   725  
   726  * `fd`: a file descriptor
   727  * `path`: the offset for the result path
   728  * `path_len`: In wazero, `FdPrestatDirName` writes the result path string to
   729    `path` offset for the exact length of `path_len`.
   730  
   731  Wasmer considers `path_len` to be the maximum length instead of the exact
   732  length  that should be written.
   733  See https://github.com/wasmerio/wasmer/blob/3463c51268ed551933392a4063bd4f8e7498b0f6/lib/wasi/src/syscalls/mod.rs#L764
   734  
   735  The semantics in wazero follows that of wasmtime.
   736  See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6ab543e8c3f3b238/crates/wasi-common/src/snapshots/preview_1.rs#L578-L582
   737  
   738  Their semantics match when `path_len` == the length of `path`, so in practice
   739  this difference won't matter match.
   740  
   741  ## Why does fd_readdir not include dot (".") and dot-dot ("..") entries?
   742  
   743  When reading a directory, wazero code does not return dot (".") and dot-dot
   744  ("..") entries. The main reason is that Go does not return them from
   745  `os.ReadDir`, and materializing them is complicated (at least dot-dot is).
   746  
   747  A directory entry has stat information in it. The stat information includes
   748  inode which is used for comparing file equivalence. In the simple case of dot,
   749  we could materialize a special entry to expose the same info as stat on the fd
   750  would return. However, doing this and not doing dot-dot would cause confusion,
   751  and dot-dot is far more tricky. To back-fill inode information about a parent
   752  directory would be costly and subtle. For example, the pre-open (mount) of the
   753  directory may be different than its logical parent. This is easy to understand
   754  when considering the common case of mounting "/" and "/tmp" as pre-opens. To
   755  implement ".." from "/tmp" requires information from a separate pre-open, this
   756  includes state to even know the difference. There are easier edge cases as
   757  well, such as the decision to not return ".." from a root path. In any case,
   758  this should start to explain that faking entries when underlying stdlib doesn't
   759  return them is tricky and requires quite a lot of state.
   760  
   761  Even if we did that, it would cause expense to all users of wazero, so we'd
   762  then look to see if that would be justified or not. However, the most common
   763  compilers involved in end user questions, as of early 2023 are TinyGo, Rust and
   764  Zig. All of these compile code which ignores dot and dot-dot entries. In other
   765  words, faking these entries would not only cost our codebase with complexity,
   766  but it would also add unnecessary overhead as the values aren't commonly used.
   767  
   768  The final reason why we might do this, is an end users or a specification
   769  requiring us to. As of early 2023, no end user has raised concern over Go and
   770  by extension wazero not returning dot and dot-dot. The snapshot-01 spec of WASI
   771  does not mention anything on this point. Also, POSIX has the following to say,
   772  which summarizes to "these are optional"
   773  
   774  > The readdir() function shall not return directory entries containing empty names. If entries for dot or dot-dot exist, one entry shall be returned for dot and one entry shall be returned for dot-dot; otherwise, they shall not be returned.
   775  
   776  In summary, wazero not only doesn't return dot and dot-dot entries because Go
   777  doesn't and emulating them in spite of that would result in no difference
   778  except hire overhead to the majority of our users.
   779  
   780  See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
   781  See https://github.com/golang/go/blob/go1.20/src/os/dir_unix.go#L108-L110
   782  
   783  ## sys.Walltime and Nanotime
   784  
   785  The `sys` package has two function types, `Walltime` and `Nanotime` for real
   786  and monotonic clock exports. The naming matches conventions used in Go.
   787  
   788  ```go
   789  func time_now() (sec int64, nsec int32, mono int64) {
   790  	sec, nsec = walltime()
   791  	return sec, nsec, nanotime()
   792  }
   793  ```
   794  
   795  Splitting functions for wall and clock time allow implementations to choose
   796  whether to implement the clock once (as in Go), or split them out.
   797  
   798  Each can be configured with a `ClockResolution`, although is it usually
   799  incorrect as detailed in a sub-heading below. The only reason for exposing this
   800  is to satisfy WASI:
   801  
   802  See https://github.com/WebAssembly/wasi-clocks
   803  
   804  ### Why default to fake time?
   805  
   806  WebAssembly has an implicit design pattern of capabilities based security. By
   807  defaulting to a fake time, we reduce the chance of timing attacks, at the cost
   808  of requiring configuration to opt-into real clocks.
   809  
   810  See https://gruss.cc/files/fantastictimers.pdf for an example attacks.
   811  
   812  ### Why does fake time increase on reading?
   813  
   814  Both the fake nanotime and walltime increase by 1ms on reading. Particularly in
   815  the case of nanotime, this prevents spinning. For example, when Go compiles
   816  `time.Sleep` using `GOOS=js GOARCH=wasm`, nanotime is used in a loop. If that
   817  never increases, the gouroutine is mistaken for being busy. This would be worse
   818  if a compiler implement sleep using nanotime, yet doesn't check for spinning!
   819  
   820  ### Why not `time.Clock`?
   821  
   822  wazero can't use `time.Clock` as a plugin for clock implementation as it is
   823  only substitutable with build flags (`faketime`) and conflates wall and
   824  monotonic time in the same call.
   825  
   826  Go's `time.Clock` was added monotonic time after the fact. For portability with
   827  prior APIs, a decision was made to combine readings into the same API call.
   828  
   829  See https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md
   830  
   831  WebAssembly time imports do not have the same concern. In fact even Go's
   832  imports for clocks split walltime from nanotime readings.
   833  
   834  See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L243-L255
   835  
   836  Finally, Go's clock is not an interface. WebAssembly users who want determinism
   837  or security need to be able to substitute an alternative clock implementation
   838  from the host process one.
   839  
   840  ### `ClockResolution`
   841  
   842  A clock's resolution is hardware and OS dependent so requires a system call to retrieve an accurate value.
   843  Go does not provide a function for getting resolution, so without CGO we don't have an easy way to get an actual
   844  value. For now, we return fixed values of 1us for realtime and 1ns for monotonic, assuming that realtime clocks are
   845  often lower precision than monotonic clocks. In the future, this could be improved by having OS+arch specific assembly
   846  to make syscalls.
   847  
   848  For example, Go implements time.Now for linux-amd64 with this [assembly](https://github.com/golang/go/blob/go1.20/src/runtime/time_linux_amd64.s).
   849  Because retrieving resolution is not generally called often, unlike getting time, it could be appropriate to only
   850  implement the fallback logic that does not use VDSO (executing syscalls in user mode). The syscall for clock_getres
   851  is 229 and should be usable. https://pkg.go.dev/syscall#pkg-constants.
   852  
   853  If implementing similar for Windows, [mingw](https://github.com/mirror/mingw-w64/blob/6a0e9165008f731bccadfc41a59719cf7c8efc02/mingw-w64-libraries/winpthreads/src/clock.c#L77
   854  ) is often a good source to find the Windows API calls that correspond
   855  to a POSIX method.
   856  
   857  Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations
   858  across many combinations of OS and architecture.
   859  
   860  ## sys.Nanosleep
   861  
   862  All major programming languages have a `sleep` mechanism to block for a
   863  duration. Sleep is typically implemented by a WASI `poll_oneoff` relative clock
   864  subscription.
   865  
   866  For example, the below ends up calling `wasi_snapshot_preview1.poll_oneoff`:
   867  
   868  ```zig
   869  const std = @import("std");
   870  pub fn main() !void {
   871      std.time.sleep(std.time.ns_per_s * 5);
   872  }
   873  ```
   874  
   875  Besides Zig, this is also the case with TinyGo (`-target=wasi`) and Rust
   876  (`--target wasm32-wasi`). This isn't the case with Go (`GOOS=js GOARCH=wasm`),
   877  though. In the latter case, wasm loops on `sys.Nanotime`.
   878  
   879  We decided to expose `sys.Nanosleep` to allow overriding the implementation
   880  used in the common case, even if it isn't used by Go, because this gives an
   881  easy and efficient closure over a common program function. We also documented
   882  `sys.Nanotime` to warn users that some compilers don't optimize sleep.
   883  
   884  ## sys.Osyield
   885  
   886  We expose `sys.Osyield`, to allow users to control the behavior of WASI's
   887  `sched_yield` without a new build of wazero. This is mainly for parity with
   888  all other related features which we allow users to implement, including
   889  `sys.Nanosleep`. Unlike others, we don't provide an out-of-box implementation
   890  primarily because it will cause performance problems when accessed.
   891  
   892  For example, the below implementation uses CGO, which might result in a 1us
   893  delay per invocation depending on the platform.
   894  
   895  See https://github.com/golang/go/issues/19409#issuecomment-284788196
   896  ```go
   897  //go:noescape
   898  //go:linkname osyield runtime.osyield
   899  func osyield()
   900  ```
   901  
   902  In practice, a request to customize this is unlikely to happen until other
   903  thread based functions are implemented. That said, as of early 2023, there are
   904  a few signs of implementation interest and cross-referencing:
   905  
   906  See https://github.com/WebAssembly/stack-switching/discussions/38
   907  See https://github.com/WebAssembly/wasi-threads#what-can-be-skipped
   908  See https://slinkydeveloper.com/Kubernetes-controllers-A-New-Hope/
   909  
   910  ## poll_oneoff
   911  
   912  `poll_oneoff` is a WASI API for waiting for I/O events on multiple handles.
   913  It is conceptually similar to the POSIX `poll(2)` syscall.
   914  The name is not `poll`, because it references [“the fact that this function is not efficient
   915  when used repeatedly with the same large set of handles”][poll_oneoff].
   916  
   917  We chose to support this API in a handful of cases that work for regular files
   918  and standard input. We currently do not support other types of file descriptors such
   919  as socket handles.
   920  
   921  ### Clock Subscriptions
   922  
   923  As detailed above in [sys.Nanosleep](#sysnanosleep), `poll_oneoff` handles
   924  relative clock subscriptions. In our implementation we use `sys.Nanosleep()`
   925  for this purpose in most cases, except when polling for interactive input
   926  from `os.Stdin` (see more details below).
   927  
   928  ### FdRead and FdWrite Subscriptions
   929  
   930  When subscribing a file descriptor (except `Stdin`) for reads or writes,
   931  the implementation will generally return immediately with success, unless
   932  the file descriptor is unknown. The file descriptor is not checked further
   933  for new incoming data. Any timeout is cancelled, and the API call is able
   934  to return, unless there are subscriptions to `Stdin`: these are handled
   935  separately.
   936  
   937  ### FdRead and FdWrite Subscription to Stdin
   938  
   939  Subscribing `Stdin` for reads (writes make no sense and cause an error),
   940  requires extra care: wazero allows to configure a custom reader for `Stdin`.
   941  
   942  In general, if a custom reader is found, the behavior will be the same
   943  as for regular file descriptors: data is assumed to be present and
   944  a success is written back to the result buffer.
   945  
   946  However, if the reader is detected to read from `os.Stdin`,
   947  a special code path is followed, invoking `platform.Select()`.
   948  
   949  `platform.Select()` is a wrapper for `select(2)` on POSIX systems,
   950  and it is mocked for a handful of cases also on Windows.
   951  
   952  ### Select on POSIX
   953  
   954  On POSIX systems,`select(2)` allows to wait for incoming data on a file
   955  descriptor, and block until either data becomes available or the timeout
   956  expires. It is not surprising that `select(2)` and `poll(2)` have lot in common:
   957  the main difference is how the file descriptor parameters are passed.
   958  
   959  Usage of `platform.Select()` is only reserved for the standard input case, because
   960  
   961  1. it is really only necessary to handle interactive input: otherwise,
   962     there is no way in Go to peek from Standard Input without actually
   963     reading (and thus consuming) from it;
   964  
   965  2. if `Stdin` is connected to a pipe, it is ok in most cases to return
   966     with success immediately;
   967  
   968  3. `platform.Select()` is currently a blocking call, irrespective of goroutines,
   969     because the underlying syscall is; thus, it is better to limit its usage.
   970  
   971  So, if the subscription is for `os.Stdin` and the handle is detected
   972  to correspond to an interactive session, then `platform.Select()` will be
   973  invoked with a the `Stdin` handle *and* the timeout.
   974  
   975  This also means that in this specific case, the timeout is uninterruptible,
   976  unless data becomes available on `Stdin` itself.
   977  
   978  ### Select on Windows
   979  
   980  On Windows the `platform.Select()` is much more straightforward,
   981  and it really just replicates the behavior found in the general cases
   982  for `FdRead` subscriptions: in other words, the subscription to `Stdin`
   983  is immediately acknowledged.
   984  
   985  The implementation also support a timeout, but in this case
   986  it relies on `time.Sleep()`, which notably, as compared to the POSIX
   987  case, interruptible and compatible with goroutines.
   988  
   989  However, because `Stdin` subscriptions are always acknowledged
   990  without wait and because this code path is always followed only
   991  when at least one `Stdin` subscription is present, then the
   992  timeout is effectively always handled externally.
   993  
   994  In any case, the behavior of `platform.Select` on Windows
   995  is sensibly different from the behavior on POSIX platforms;
   996  we plan to refine and further align it in semantics in the future.
   997  
   998  [poll_oneoff]: https://github.com/WebAssembly/wasi-poll#why-is-the-function-called-poll_oneoff
   999  
  1000  ## Signed encoding of integer global constant initializers
  1001  
  1002  wazero treats integer global constant initializers signed as their interpretation is not known at declaration time. For
  1003  example, there is no signed integer [value type](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0).
  1004  
  1005  To get at the problem, let's use an example.
  1006  ```
  1007  (global (export "start_epoch") i64 (i64.const 1620216263544))
  1008  ```
  1009  
  1010  In both signed and unsigned LEB128 encoding, this value is the same bit pattern. The problem is that some numbers are
  1011  not. For example, 16256 is `807f` encoded as unsigned, but `80ff00` encoded as signed.
  1012  
  1013  While the specification mentions uninterpreted integers are in abstract [unsigned values](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A0),
  1014  the binary encoding is clear that they are encoded [signed](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A4).
  1015  
  1016  For consistency, we go with signed encoding in the special case of global constant initializers.
  1017  
  1018  ## Implementation limitations
  1019  
  1020  WebAssembly 1.0 (20191205) specification allows runtimes to [limit certain aspects of Wasm module or execution](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#a2-implementation-limitations).
  1021  
  1022  wazero limitations are imposed pragmatically and described below.
  1023  
  1024  ### Number of functions in a module
  1025  
  1026  The possible number of function instances in [a module](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#module-instances%E2%91%A0) is not specified in the WebAssembly specifications since [`funcaddr`](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr) corresponding to a function instance in a store can be arbitrary number.
  1027  wazero limits the maximum function instances to 2^27 as even that number would occupy 1GB in function pointers.
  1028  
  1029  That is because not only we _believe_ that all use cases are fine with the limitation, but also we have no way to test wazero runtimes under these unusual circumstances.
  1030  
  1031  ### Number of function types in a store
  1032  
  1033  There's no limitation on the number of function types in [a store](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0) according to the spec. In wazero implementation, we assign each function type to a unique ID, and choose to use `uint32` to represent the IDs.
  1034  Therefore the maximum number of function types a store can have is limited to 2^27 as even that number would occupy 512MB just to reference the function types.
  1035  
  1036  This is due to the same reason for the limitation on the number of functions above.
  1037  
  1038  ### Number of values on the stack in a function
  1039  
  1040  While the the spec does not clarify a limitation of function stack values, wazero limits this to 2^27 = 134,217,728.
  1041  The reason is that we internally represent all the values as 64-bit integers regardless of its types (including f32, f64), and 2^27 values means
  1042  1 GiB = (2^30). 1 GiB is the reasonable for most applications [as we see a Goroutine has 250 MB as a limit on the stack for 32-bit arch](https://github.com/golang/go/blob/go1.20/src/runtime/proc.go#L152-L159), considering that WebAssembly is (currently) 32-bit environment.
  1043  
  1044  All the functions are statically analyzed at module instantiation phase, and if a function can potentially reach this limit, an error is returned.
  1045  
  1046  ### Number of globals in a module
  1047  
  1048  Theoretically, a module can declare globals (including imports) up to 2^32 times. However, wazero limits this to  2^27(134,217,728) per module.
  1049  That is because internally we store globals in a slice with pointer types (meaning 8 bytes on 64-bit platforms), and therefore 2^27 globals
  1050  means that we have 1 GiB size of slice which seems large enough for most applications.
  1051  
  1052  ### Number of tables in a module
  1053  
  1054  While the the spec says that a module can have up to 2^32 tables, wazero limits this to 2^27 = 134,217,728.
  1055  One of the reasons is even that number would occupy 1GB in the pointers tables alone. Not only that, we access tables slice by
  1056  table index by using 32-bit signed offset in the compiler implementation, which means that the table index of 2^27 can reach 2^27 * 8 (pointer size on 64-bit machines) = 2^30 offsets in bytes.
  1057  
  1058  We _believe_ that all use cases are fine with the limitation, but also note that we have no way to test wazero runtimes under these unusual circumstances.
  1059  
  1060  If a module reaches this limit, an error is returned at the compilation phase.
  1061  
  1062  ## Compiler engine implementation
  1063  
  1064  See [compiler/RATIONALE.md](internal/engine/compiler/RATIONALE.md).
  1065  
  1066  ## Golang patterns
  1067  
  1068  ### Hammer tests
  1069  Code that uses concurrency primitives, such as locks or atomics, should include "hammer tests", which run large loops
  1070  inside a bounded amount of goroutines, run by half that many `GOMAXPROCS`. These are named consistently "hammer", so
  1071  they are easy to find. The name inherits from some existing tests in [golang/go](https://github.com/golang/go/search?q=hammer&type=code).
  1072  
  1073  Here is an annotated description of the key pieces of a hammer test:
  1074  1. `P` declares the count of goroutines to use, defaulting to 8 or 4 if `testing.Short`.
  1075     * Half this amount are the cores used, and 4 is less than a modern laptop's CPU. This allows multiple "hammer" tests to run in parallel.
  1076  2. `N` declares the scale of work (loop) per goroutine, defaulting to value that finishes in ~0.1s on a modern laptop.
  1077     * When in doubt, try 1000 or 100 if `testing.Short`
  1078     * Remember, there are multiple hammer tests and CI nodes are slow. Slower tests hurt feedback loops.
  1079  3. `defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P/2))` makes goroutines switch cores, testing visibility of shared data.
  1080  4. To ensure goroutines execute at the same time, block them with `sync.WaitGroup`, initialized to `Add(P)`.
  1081     * `sync.WaitGroup` internally uses `runtime_Semacquire` not available in any other library.
  1082     * `sync.WaitGroup.Add` with a negative value can unblock many goroutines at the same time, e.g. without a for loop.
  1083  5. Track goroutines progress via `finished := make(chan int)` where each goroutine in `P` defers `finished <- 1`.
  1084     1. Tests use `require.XXX`, so `recover()` into `t.Fail` in a `defer` function before `finished <- 1`.
  1085        * This makes it easier to spot larger concurrency problems as you see each failure, not just the first.
  1086     2. After the `defer` function, await unblocked, then run the stateful function `N` times in a normal loop.
  1087        * This loop should trigger shared state problems as locks or atomics are contended by `P` goroutines.
  1088  6. After all `P` goroutines launch, atomically release all of them with `WaitGroup.Add(-P)`.
  1089  7. Block the runner on goroutine completion, by (`<-finished`) for each `P`.
  1090  8. When all goroutines complete, `return` if `t.Failed()`, otherwise perform follow-up state checks.
  1091  
  1092  This is implemented in wazero in [hammer.go](internal/testing/hammer/hammer.go)
  1093  
  1094  ### Lock-free, cross-goroutine observations of updates
  1095  
  1096  How to achieve cross-goroutine reads of a variable are not explicitly defined in https://go.dev/ref/mem. wazero uses
  1097  atomics to implement this following unofficial practice. For example, a `Close` operation can be guarded to happen only
  1098  once via compare-and-swap (CAS) against a zero value. When we use this pattern, we consistently use atomics to both
  1099  read and update the same numeric field.
  1100  
  1101  In lieu of formal documentation, we infer this pattern works from other sources (besides tests):
  1102   * `sync.WaitGroup` by definition must support calling `Add` from other goroutines. Internally, it uses atomics.
  1103   * rsc in golang/go#5045 writes "atomics guarantee sequential consistency among the atomic variables".
  1104  
  1105  See https://github.com/golang/go/blob/go1.20/src/sync/waitgroup.go#L64
  1106  See https://github.com/golang/go/issues/5045#issuecomment-252730563
  1107  See https://www.youtube.com/watch?v=VmrEG-3bWyM