summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero/RATIONALE.md
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/RATIONALE.md')
-rw-r--r--vendor/github.com/tetratelabs/wazero/RATIONALE.md1587
1 files changed, 0 insertions, 1587 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/RATIONALE.md b/vendor/github.com/tetratelabs/wazero/RATIONALE.md
deleted file mode 100644
index ffb1a299e..000000000
--- a/vendor/github.com/tetratelabs/wazero/RATIONALE.md
+++ /dev/null
@@ -1,1587 +0,0 @@
-# Notable rationale of wazero
-
-## Zero dependencies
-
-Wazero has zero dependencies to differentiate itself from other runtimes which
-have heavy impact usually due to CGO. By avoiding CGO, wazero avoids
-prerequisites such as shared libraries or libc, and lets users keep features
-like cross compilation.
-
-Avoiding go.mod dependencies reduces interference on Go version support, and
-size of a statically compiled binary. However, doing so brings some
-responsibility into the project.
-
-Go's native platform support is good: We don't need platform-specific code to
-get monotonic time, nor do we need much work to implement certain features
-needed by our compiler such as `mmap`. That said, Go does not support all
-common operating systems to the same degree. For example, Go 1.18 includes
-`Mprotect` on Linux and Darwin, but not FreeBSD.
-
-The general tradeoff the project takes from a zero dependency policy is more
-explicit support of platforms (in the compiler runtime), as well a larger and
-more technically difficult codebase.
-
-At some point, we may allow extensions to supply their own platform-specific
-hooks. Until then, one end user impact/tradeoff is some glitches trying
-untested platforms (with the Compiler runtime).
-
-### Why do we use CGO to implement system calls on darwin?
-
-wazero is dependency and CGO free by design. In some cases, we have code that
-can optionally use CGO, but retain a fallback for when that's disabled. The only
-operating system (`GOOS`) we use CGO by default in is `darwin`.
-
-Unlike other operating systems, regardless of `CGO_ENABLED`, Go always uses
-"CGO" mechanisms in the runtime layer of `darwin`. This is explained in
-[Statically linked binaries on Mac OS X](https://developer.apple.com/library/archive/qa/qa1118/_index.html#//apple_ref/doc/uid/DTS10001666):
-
-> Apple does not support statically linked binaries on Mac OS X. A statically
-> linked binary assumes binary compatibility at the kernel system call
-> interface, and we do not make any guarantees on that front. Rather, we strive
-> to ensure binary compatibility in each dynamically linked system library and
-> framework.
-
-This plays to our advantage for system calls that aren't yet exposed in the Go
-standard library, notably `futimens` for nanosecond-precision timestamp
-manipulation.
-
-### Why not x/sys
-
-Going beyond Go's SDK limitations can be accomplished with their [x/sys library](https://pkg.go.dev/golang.org/x/sys/unix).
-For example, this includes `zsyscall_freebsd_amd64.go` missing from the Go SDK.
-
-However, like all dependencies, x/sys is a source of conflict. For example,
-x/sys had to be in order to upgrade to Go 1.18.
-
-If we depended on x/sys, we could get more precise functionality needed for
-features such as clocks or more platform support for the compiler runtime.
-
-That said, formally supporting an operating system may still require testing as
-even use of x/sys can require platform-specifics. For example, [mmap-go](https://github.com/edsrzf/mmap-go)
-uses x/sys, but also mentions limitations, some not surmountable with x/sys
-alone.
-
-Regardless, we may at some point introduce a separate go.mod for users to use
-x/sys as a platform plugin without forcing all users to maintain that
-dependency.
-
-## Project structure
-
-wazero uses internal packages extensively to balance API compatibility desires for end users with the need to safely
-share internals between compilers.
-
-End-user packages include `wazero`, with `Config` structs, `api`, with shared types, and the built-in `wasi` library.
-Everything else is internal.
-
-We put the main program for wazero into a directory of the same name to match conventions used in `go install`,
-notably the name of the folder becomes the binary name. We chose to use `cmd/wazero` as it is common practice
-and less surprising than `wazero/wazero`.
-
-### Internal packages
-
-Most code in wazero is internal, and it is acknowledged that this prevents external implementation of facets such as
-compilers or decoding. It also prevents splitting this code into separate repositories, resulting in a larger monorepo.
-This also adds work as more code needs to be centrally reviewed.
-
-However, the alternative is neither secure nor viable. To allow external implementation would require exporting symbols
-public, such as the `CodeSection`, which can easily create bugs. Moreover, there's a high drift risk for any attempt at
-external implementations, compounded not just by wazero's code organization, but also the fast moving Wasm and WASI
-specifications.
-
-For example, implementing a compiler correctly requires expertise in Wasm, Golang and assembly. This requires deep
-insight into how internals are meant to be structured and the various tiers of testing required for `wazero` to result
-in a high quality experience. Even if someone had these skills, supporting external code would introduce variables which
-are constants in the central one. Supporting an external codebase is harder on the project team, and could starve time
-from the already large burden on the central codebase.
-
-The tradeoffs of internal packages are a larger codebase and responsibility to implement all standard features. It also
-implies thinking about extension more as forking is not viable for reasons above also. The primary mitigation of these
-realities are friendly OSS licensing, high rigor and a collaborative spirit which aim to make contribution in the shared
-codebase productive.
-
-### Avoiding cyclic dependencies
-
-wazero shares constants and interfaces with internal code by a sharing pattern described below:
-* shared interfaces and constants go in one package under root: `api`.
-* user APIs and structs depend on `api` and go into the root package `wazero`.
- * e.g. `InstantiateModule` -> `/wasm.go` depends on the type `api.Module`.
-* implementation code can also depend on `api` in a corresponding package under `/internal`.
- * Ex package `wasm` -> `/internal/wasm/*.go` and can depend on the type `api.Module`.
-
-The above guarantees no cyclic dependencies at the cost of having to re-define symbols that exist in both packages.
-For example, if `wasm.Store` is a type the user needs access to, it is narrowed by a cover type in the `wazero`:
-
-```go
-type runtime struct {
- s *wasm.Store
-}
-```
-
-This is not as bad as it sounds as mutations are only available via configuration. This means exported functions are
-limited to only a few functions.
-
-### Avoiding security bugs
-
-In order to avoid security flaws such as code insertion, nothing in the public API is permitted to write directly to any
-mutable symbol in the internal package. For example, the package `api` is shared with internal code. To ensure
-immutability, the `api` package cannot contain any mutable public symbol, such as a slice or a struct with an exported
-field.
-
-In practice, this means shared functionality like memory mutation need to be implemented by interfaces.
-
-Here are some examples:
-* `api.Memory` protects access by exposing functions like `WriteFloat64Le` instead of exporting a buffer (`[]byte`).
-* There is no exported symbol for the `[]byte` representing the `CodeSection`
-
-Besides security, this practice prevents other bugs and allows centralization of validation logic such as decoding Wasm.
-
-## API Design
-
-### Why is `context.Context` inconsistent?
-
-It may seem strange that only certain API have an initial `context.Context`
-parameter. We originally had a `context.Context` for anything that might be
-traced, but it turned out to be only useful for lifecycle and host functions.
-
-For instruction-scoped aspects like memory updates, a context parameter is too
-fine-grained and also invisible in practice. For example, most users will use
-the compiler engine, and its memory, global or table access will never use go's
-context.
-
-### Why does `api.ValueType` map to uint64?
-
-WebAssembly allows functions to be defined either by the guest or the host,
-with signatures expressed as WebAssembly types. For example, `i32` is a 32-bit
-type which might be interpreted as signed. Function signatures can have zero or
-more parameters or results even if WebAssembly 1.0 allows up to one result.
-
-The guest can export functions, so that the host can call it. In the case of
-wazero, the host is Go and an exported function can be called via
-`api.Function`. `api.Function` allows users to supply parameters and read
-results as a slice of uint64. For example, if there are no results, an empty
-slice is returned. The user can learn the signature via `FunctionDescription`,
-which returns the `api.ValueType` corresponding to each parameter or result.
-`api.ValueType` defines the mapping of WebAssembly types to `uint64` values for
-reason described in this section. The special case of `v128` is also mentioned
-below.
-
-wazero maps each value type to a uint64 values because it holds the largest
-type in WebAssembly 1.0 (i64). A slice allows you to express empty (e.g. a
-nullary signature), for example a start function.
-
-Here's an example of calling a function, noting this syntax works for both a
-signature `(param i32 i32) (result i32)` and `(param i64 i64) (result i64)`
-```go
-x, y := uint64(1), uint64(2)
-results, err := mod.ExportedFunction("add").Call(ctx, x, y)
-if err != nil {
- log.Panicln(err)
-}
-fmt.Printf("%d + %d = %d\n", x, y, results[0])
-```
-
-WebAssembly does not define an encoding strategy for host defined parameters or
-results. This means the encoding rules above are defined by wazero instead. To
-address this, we clarified mapping both in `api.ValueType` and added helper
-functions like `api.EncodeF64`. This allows users conversions typical in Go
-programming, and utilities to avoid ambiguity and edge cases around casting.
-
-Alternatively, we could have defined a byte buffer based approach and a binary
-encoding of value types in and out. For example, an empty byte slice would mean
-no values, while a non-empty could use a binary encoding for supported values.
-This could work, but it is more difficult for the normal case of i32 and i64.
-It also shares a struggle with the current approach, which is that value types
-were added after WebAssembly 1.0 and not all of them have an encoding. More on
-this below.
-
-In summary, wazero chose an approach for signature mapping because there was
-none, and the one we chose biases towards simplicity with integers and handles
-the rest with documentation and utilities.
-
-#### Post 1.0 value types
-
-Value types added after WebAssembly 1.0 stressed the current model, as some
-have no encoding or are larger than 64 bits. While problematic, these value
-types are not commonly used in exported (extern) functions. However, some
-decisions were made and detailed below.
-
-For example `externref` has no guest representation. wazero chose to map
-references to uint64 as that's the largest value needed to encode a pointer on
-supported platforms. While there are two reference types, `externref` and
-`functype`, the latter is an internal detail of function tables, and the former
-is rarely if ever used in function signatures as of the end of 2022.
-
-The only value larger than 64 bits is used for SIMD (`v128`). Vectorizing via
-host functions is not used as of the end of 2022. Even if it were, it would be
-inefficient vs guest vectorization due to host function overhead. In other
-words, the `v128` value type is unlikely to be in an exported function
-signature. That it requires two uint64 values to encode is an internal detail
-and not worth changing the exported function interface `api.Function`, as doing
-so would break all users.
-
-### Interfaces, not structs
-
-All exported types in public packages, regardless of configuration vs runtime, are interfaces. The primary benefits are
-internal flexibility and avoiding people accidentally mis-initializing by instantiating the types on their own vs using
-the `NewXxx` constructor functions. In other words, there's less support load when things can't be done incorrectly.
-
-Here's an example:
-```go
-rt := &RuntimeConfig{} // not initialized properly (fields are nil which shouldn't be)
-rt := RuntimeConfig{} // not initialized properly (should be a pointer)
-rt := wazero.NewRuntimeConfig() // initialized properly
-```
-
-There are a few drawbacks to this, notably some work for maintainers.
-* Interfaces are decoupled from the structs implementing them, which means the signature has to be repeated twice.
-* Interfaces have to be documented and guarded at time of use, that 3rd party implementations aren't supported.
-* As of Golang 1.21, interfaces are still [not well supported](https://github.com/golang/go/issues/5860) in godoc.
-
-## Config
-
-wazero configures scopes such as Runtime and Module using `XxxConfig` types. For example, `RuntimeConfig` configures
-`Runtime` and `ModuleConfig` configure `Module` (instantiation). In all cases, config types begin defaults and can be
-customized by a user, e.g., selecting features or a module name override.
-
-### Why don't we make each configuration setting return an error?
-No config types create resources that would need to be closed, nor do they return errors on use. This helps reduce
-resource leaks, and makes chaining easier. It makes it possible to parse configuration (ex by parsing yaml) independent
-of validating it.
-
-Instead of:
-```
-cfg, err = cfg.WithFS(fs)
-if err != nil {
- return err
-}
-cfg, err = cfg.WithName(name)
-if err != nil {
- return err
-}
-mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg)
-if err != nil {
- return err
-}
-```
-
-There's only one call site to handle errors:
-```
-cfg = cfg.WithFS(fs).WithName(name)
-mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg)
-if err != nil {
- return err
-}
-```
-
-This allows users one place to look for errors, and also the benefit that if anything internally opens a resource, but
-errs, there's nothing they need to close. In other words, users don't need to track which resources need closing on
-partial error, as that is handled internally by the only code that can read configuration fields.
-
-### Why are configuration immutable?
-While it seems certain scopes like `Runtime` won't repeat within a process, they do, possibly in different goroutines.
-For example, some users create a new runtime for each module, and some re-use the same base module configuration with
-only small updates (ex the name) for each instantiation. Making configuration immutable allows them to be safely used in
-any goroutine.
-
-Since config are immutable, changes apply via return val, similar to `append` in a slice.
-
-For example, both of these are the same sort of error:
-```go
-append(slice, element) // bug as only the return value has the updated slice.
-cfg.WithName(next) // bug as only the return value has the updated name.
-```
-
-Here's an example of correct use: re-assigning explicitly or via chaining.
-```go
-cfg = cfg.WithName(name) // explicit
-
-mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg.WithName(name)) // implicit
-if err != nil {
- return err
-}
-```
-
-### Why aren't configuration assigned with option types?
-The option pattern is a familiar one in Go. For example, someone defines a type `func (x X) err` and uses it to update
-the target. For example, you could imagine wazero could choose to make `ModuleConfig` from options vs chaining fields.
-
-Ex instead of:
-```go
-type ModuleConfig interface {
- WithName(string) ModuleConfig
- WithFS(fs.FS) ModuleConfig
-}
-
-struct moduleConfig {
- name string
- fs fs.FS
-}
-
-func (c *moduleConfig) WithName(name string) ModuleConfig {
- ret := *c // copy
- ret.name = name
- return &ret
-}
-
-func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
- ret := *c // copy
- ret.setFS("/", fs)
- return &ret
-}
-
-config := r.NewModuleConfig().WithFS(fs)
-configDerived := config.WithName("name")
-```
-
-An option function could be defined, then refactor each config method into an name prefixed option function:
-```go
-type ModuleConfig interface {
-}
-struct moduleConfig {
- name string
- fs fs.FS
-}
-
-type ModuleConfigOption func(c *moduleConfig)
-
-func ModuleConfigName(name string) ModuleConfigOption {
- return func(c *moduleConfig) {
- c.name = name
- }
-}
-
-func ModuleConfigFS(fs fs.FS) ModuleConfigOption {
- return func(c *moduleConfig) {
- c.fs = fs
- }
-}
-
-func (r *runtime) NewModuleConfig(opts ...ModuleConfigOption) ModuleConfig {
- ret := newModuleConfig() // defaults
- for _, opt := range opts {
- opt(&ret.config)
- }
- return ret
-}
-
-func (c *moduleConfig) WithOptions(opts ...ModuleConfigOption) ModuleConfig {
- ret := *c // copy base config
- for _, opt := range opts {
- opt(&ret.config)
- }
- return ret
-}
-
-config := r.NewModuleConfig(ModuleConfigFS(fs))
-configDerived := config.WithOptions(ModuleConfigName("name"))
-```
-
-wazero took the path of the former design primarily due to:
-* interfaces provide natural namespaces for their methods, which is more direct than functions with name prefixes.
-* parsing config into function callbacks is more direct vs parsing config into a slice of functions to do the same.
-* in either case derived config is needed and the options pattern is more awkward to achieve that.
-
-There are other reasons such as test and debug being simpler without options: the above list is constrained to conserve
-space. It is accepted that the options pattern is common in Go, which is the main reason for documenting this decision.
-
-### Why aren't config types deeply structured?
-wazero's configuration types cover the two main scopes of WebAssembly use:
-* `RuntimeConfig`: This is the broadest scope, so applies also to compilation
- and instantiation. e.g. This controls the WebAssembly Specification Version.
-* `ModuleConfig`: This affects modules instantiated after compilation and what
- resources are allowed. e.g. This defines how or if STDOUT is captured. This
- also allows sub-configuration of `FSConfig`.
-
-These default to a flat definition each, with lazy sub-configuration only after
-proven to be necessary. A flat structure is easier to work with and is also
-easy to discover. Unlike the option pattern described earlier, more
-configuration in the interface doesn't taint the package namespace, only
-`ModuleConfig`.
-
-We default to a flat structure to encourage simplicity. If we eagerly broke out
-all possible configurations into sub-types (e.g. ClockConfig), it would be hard
-to notice configuration sprawl. By keeping the config flat, it is easy to see
-the cognitive load we may be adding to our users.
-
-In other words, discomfort adding more configuration is a feature, not a bug.
-We should only add new configuration rarely, and before doing so, ensure it
-will be used. In fact, this is why we support using context fields for
-experimental configuration. By letting users practice, we can find out if a
-configuration was a good idea or not before committing to it, and potentially
-sprawling our types.
-
-In reflection, this approach worked well for the nearly 1.5 year period leading
-to version 1.0. We've only had to create a single sub-configuration, `FSConfig`,
-and it was well understood why when it occurred.
-
-## Why does `ModuleConfig.WithStartFunctions` default to `_start`?
-
-We formerly had functions like `StartWASICommand` that would verify
-preconditions and start WASI's `_start` command. However, this caused confusion
-because both many languages compiled a WASI dependency, and many did so
-inconsistently.
-
-The conflict is that exported functions need to use features the language
-runtime provides, such as garbage collection. There's a "chicken-egg problem"
-where `_start` needs to complete in order for exported behavior to work.
-
-For example, unlike `GOOS=wasip1` in Go 1.21, TinyGo's "wasi" target supports
-function exports. So, the only way to use FFI style is via the "wasi" target.
-Not explicitly calling `_start` before an ABI such as wapc-go, would crash, due
-to setup not happening (e.g. to implement `panic`). Other embedders such as
-Envoy also called `_start` for the same reason. To avoid a common problem for
-users unaware of WASI, and also to simplify normal use of WASI (e.g. `main`),
-we added `_start` to `ModuleConfig.WithStartFunctions`.
-
-In cases of multiple initializers, such as in wapc-go, users can override this
-to add the others *after* `_start`. Users who want to explicitly control
-`_start`, such as some of our unit tests, can clear the start functions and
-remove it.
-
-This decision was made in 2022, and holds true in 2023, even with the
-introduction of "wasix". It holds because "wasix" is backwards compatible with
-"wasip1". In the future, there will be other ways to start applications, and
-may not be backwards compatible with "wasip1".
-
-Most notably WASI "Preview 2" is not implemented in a way compatible with
-wasip1. Its start function is likely to be different, and defined in the
-wasi-cli "world". When the design settles, and it is implemented by compilers,
-wazero will attempt to support "wasip2". However, it won't do so in a way that
-breaks existing compilers.
-
-In other words, we won't remove `_start` if "wasip2" continues a path of an
-alternate function name. If we did, we'd break existing users despite our
-compatibility promise saying we don't. The most likely case is that when we
-build-in something incompatible with "wasip1", that start function will be
-added to the start functions list in addition to `_start`.
-
-See http://wasix.org
-See https://github.com/WebAssembly/wasi-cli
-
-## Runtime == Engine+Store
-wazero defines a single user-type which combines the specification concept of `Store` with the unspecified `Engine`
-which manages them.
-
-### Why not multi-store?
-Multi-store isn't supported as the extra tier complicates lifecycle and locking. Moreover, in practice it is unusual for
-there to be an engine that has multiple stores which have multiple modules. More often, it is the case that there is
-either 1 engine with 1 store and multiple modules, or 1 engine with many stores, each having 1 non-host module. In worst
-case, a user can use multiple runtimes until "multi-store" is better understood.
-
-If later, we have demand for multiple stores, that can be accomplished by overload. e.g. `Runtime.InstantiateInStore` or
-`Runtime.Store(name) Store`.
-
-## Exit
-
-### Why do we only return a `sys.ExitError` on a non-zero exit code?
-
-It is reasonable to think an exit error should be returned, even if the code is
-success (zero). Even on success, the module is no longer functional. For
-example, function exports would error later. However, wazero does not. The only
-time `sys.ExitError` is on error (non-zero).
-
-This decision was to improve performance and ergonomics for guests that both
-use WASI (have a `_start` function), and also allow custom exports.
-Specifically, Rust, TinyGo and normal wasi-libc, don't exit the module during
-`_start`. If they did, it would invalidate their function exports. This means
-it is unlikely most compilers will change this behavior.
-
-`GOOS=waspi1` from Go 1.21 does exit during `_start`. However, it doesn't
-support other exports besides `_start`, and `_start` is not defined to be
-called multiple times anyway.
-
-Since `sys.ExitError` is not always returned, we added `Module.IsClosed` for
-defensive checks. This helps integrators avoid calling functions which will
-always fail.
-
-### Why panic with `sys.ExitError` after a host function exits?
-
-Currently, the only portable way to stop processing code is via panic. For
-example, WebAssembly "trap" instructions, such as divide by zero, are
-implemented via panic. This ensures code isn't executed after it.
-
-When code reaches the WASI `proc_exit` instruction, we need to stop processing.
-Regardless of the exit code, any code invoked after exit would be in an
-inconsistent state. This is likely why unreachable instructions are sometimes
-inserted after exit: https://github.com/emscripten-core/emscripten/issues/12322
-
-## WASI
-
-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.
-This section describes how Wazero interprets and implements the semantics of several WASI APIs that may be interpreted differently by different wasm runtimes.
-Those APIs may affect the portability of a WASI application.
-
-### Why don't we attempt to pass wasi-testsuite on user-defined `fs.FS`?
-
-While most cases work fine on an `os.File` based implementation, we won't
-promise wasi-testsuite compatibility on user defined wrappers of `os.DirFS`.
-The only option for real systems is to use our `sysfs.FS`.
-
-There are a lot of areas where windows behaves differently, despite the
-`os.File` abstraction. This goes well beyond file locking concerns (e.g.
-`EBUSY` errors on open files). For example, errors like `ACCESS_DENIED` aren't
-properly mapped to `EPERM`. There are trickier parts too. `FileInfo.Sys()`
-doesn't return enough information to build inodes needed for WASI. To rebuild
-them requires the full path to the underlying file, not just its directory
-name, and there's no way for us to get that information. At one point we tried,
-but in practice things became tangled and functionality such as read-only
-wrappers became untenable. Finally, there are version-specific behaviors which
-are difficult to maintain even in our own code. For example, go 1.20 opens
-files in a different way than versions before it.
-
-### Why aren't WASI rules enforced?
-
-The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has a
-number of rules for a "command module", but only the memory export rule is enforced. If a "_start" function exists, it
-is enforced to be the correct signature and succeed, but the export itself isn't enforced. It follows that this means
-exports are not required to be contained to a "_start" function invocation. Finally, the "__indirect_function_table"
-export is also not enforced.
-
-The reason for the exceptions are that implementations aren't following the rules. For example, TinyGo doesn't export
-"__indirect_function_table", so crashing on this would make wazero unable to run TinyGo modules. Similarly, modules
-loaded by wapc-go don't always define a "_start" function. Since "snapshot-01" is not a proper version, and certainly
-not a W3C recommendation, there's no sense in breaking users over matters like this.
-
-### Why is I/O configuration not coupled to WASI?
-
-WebAssembly System Interfaces (WASI) is a formalization of a practice that can be done anyway: Define a host function to
-access a system interface, such as writing to STDOUT. WASI stalled at snapshot-01 and as of early 2023, is being
-rewritten entirely.
-
-This instability implies a need to transition between WASI specs, which places wazero in a position that requires
-decoupling. For example, if code uses two different functions to call `fd_write`, the underlying configuration must be
-centralized and decoupled. Otherwise, calls using the same file descriptor number will end up writing to different
-places.
-
-In short, wazero defined system configuration in `ModuleConfig`, not a WASI type. This allows end-users to switch from
-one spec to another with minimal impact. This has other helpful benefits, as centralized resources are simpler to close
-coherently (ex via `Module.Close`).
-
-In reflection, this worked well as more ABI became usable in wazero.
-
-### Background on `ModuleConfig` design
-
-WebAssembly 1.0 (20191205) specifies some aspects to control isolation between modules ([sandboxing](https://en.wikipedia.org/wiki/Sandbox_(computer_security))).
-For example, `wasm.Memory` has size constraints and each instance of it is isolated from each other. While `wasm.Memory`
-can be shared, by exporting it, it is not exported by default. In fact a WebAssembly Module (Wasm) has no memory by
-default.
-
-While memory is defined in WebAssembly 1.0 (20191205), many aspects are not. Let's use an example of `exec.Cmd` as for
-example, a WebAssembly System Interfaces (WASI) command is implemented as a module with a `_start` function, and in many
-ways acts similar to a process with a `main` function.
-
-To capture "hello world" written to the console (stdout a.k.a. file descriptor 1) in `exec.Cmd`, you would set the
-`Stdout` field accordingly, perhaps to a buffer. In WebAssembly 1.0 (20191205), the only way to perform something like
-this is via a host function (ex `HostModuleFunctionBuilder`) and internally copy memory corresponding to that string
-to a buffer.
-
-WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports
-"fd_write" from "wasi_snapshot_preview1" and calls it with the `fd` parameter set to 1 (STDOUT).
-
-The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has no
-means to declare configuration, although its function definitions imply configuration for example if fd 1 should exist,
-and if so where should it write. Moreover, snapshot-01 was last updated in late 2020 and the specification is being
-completely rewritten as of early 2022. This means WASI as defined by "snapshot-01" will not clarify aspects like which
-file descriptors are required. While it is possible a subsequent version may, it is too early to tell as no version of
-WASI has reached a stage near W3C recommendation. Even if it did, module authors are not required to only use WASI to
-write to console, as they can define their own host functions, such as they did before WASI existed.
-
-wazero aims to serve Go developers as a primary function, and help them transition between WASI specifications. In
-order to do this, we have to allow top-level configuration. To ensure isolation by default, `ModuleConfig` has WithXXX
-that override defaults to no-op or empty. One `ModuleConfig` instance is used regardless of how many times the same WASI
-functions are imported. The nil defaults allow safe concurrency in these situations, as well lower the cost when they
-are never used. Finally, a one-to-one mapping with `Module` allows the module to close the `ModuleConfig` instead of
-confusing users with another API to close.
-
-Naming, defaults and validation rules of aspects like `STDIN` and `Environ` are intentionally similar to other Go
-libraries such as `exec.Cmd` or `syscall.SetEnv`, and differences called out where helpful. For example, there's no goal
-to emulate any operating system primitive specific to Windows (such as a 'c:\' drive). Moreover, certain defaults
-working with real system calls are neither relevant nor safe to inherit: For example, `exec.Cmd` defaults to read STDIN
-from a real file descriptor ("/dev/null"). Defaulting to this, vs reading `io.EOF`, would be unsafe as it can exhaust
-file descriptors if resources aren't managed properly. In other words, blind copying of defaults isn't wise as it can
-violate isolation or endanger the embedding process. In summary, we try to be similar to normal Go code, but often need
-act differently and document `ModuleConfig` is more about emulating, not necessarily performing real system calls.
-
-## File systems
-
-### Motivation on `sys.FS`
-
-The `sys.FS` abstraction in wazero was created because of limitations in
-`fs.FS`, and `fs.File` in Go. Compilers targeting `wasip1` may access
-functionality that writes new files. The ability to overcome this was requested
-even before wazero was named this, via issue #21 in March 2021.
-
-A month later, golang/go#45757 was raised by someone else on the same topic. As
-of July 2023, this has not resolved to a writeable file system abstraction.
-
-Over the next year more use cases accumulated, consolidated in March 2022 into
-#390. This closed in January 2023 with a milestone of providing more
-functionality, limited to users giving a real directory. This didn't yet expose
-a file abstraction for general purpose use. Internally, this used `os.File`.
-However, a wasm module instance is a virtual machine. Only supporting `os.File`
-breaks sand-boxing use cases. Moreover, `os.File` is not an interface. Even
-though this abstracts functionality, it does allow interception use cases.
-
-Hence, a few days later in January 2023, we had more issues asking to expose an
-abstraction, #1013 and later #1532, on use cases like masking access to files.
-In other words, the use case requests never stopped, and aren't solved by
-exposing only real files.
-
-In summary, the primary motivation for exposing a replacement for `fs.FS` and
-`fs.File` was around repetitive use case requests for years, around
-interception and the ability to create new files, both virtual and real files.
-While some use cases are solved with real files, not all are. Regardless, an
-interface approach is necessary to ensure users can intercept I/O operations.
-
-### Why doesn't `sys.File` have a `Fd()` method?
-
-There are many features we could expose. We could make File expose underlying
-file descriptors in case they are supported, for integration of system calls
-that accept multiple ones, namely `poll` for multiplexing. This special case is
-described in a subsequent section.
-
-As noted above, users have been asking for a file abstraction for over two
-years, and a common answer was to wait. Making users wait is a problem,
-especially so long. Good reasons to make people wait are stabilization. Edge
-case features are not a great reason to hold abstractions from users.
-
-Another reason is implementation difficulty. Go did not attempt to abstract
-file descriptors. For example, unlike `fs.ReadFile` there is no `fs.FdFile`
-interface. Most likely, this is because file descriptors are an implementation
-detail of common features. Programming languages, including Go, do not require
-end users to know about file descriptors. Types such as `fs.File` can be used
-without any knowledge of them. Implementations may or may not have file
-descriptors. For example, in Go, `os.DirFS` has underlying file descriptors
-while `embed.FS` does not.
-
-Despite this, some may want to expose a non-standard interface because
-`os.File` has `Fd() uintptr` to return a file descriptor. Mainly, this is
-handy to integrate with `syscall` package functions (on `GOOS` values that
-declare them). Notice, though that `uintptr` is unsafe and not an abstraction.
-Close inspection will find some `os.File` types internally use `poll.FD`
-instead, yet this is not possible to use abstractly because that type is not
-exposed. For example, `plan9` uses a different type than `poll.FD`. In other
-words, even in real files, `Fd()` is not wholly portable, despite it being
-useful on many operating systems with the `syscall` package.
-
-The reasons above, why Go doesn't abstract `FdFile` interface are a subset of
-reasons why `sys.File` does not. If we exposed `File.Fd()` we not only would
-have to declare all the edge cases that Go describes including impact of
-finalizers, we would have to describe these in terms of virtualized files.
-Then, we would have to reason with this value vs our existing virtualized
-`sys.FileTable`, mapping whatever type we return to keys in that table, also
-in consideration of garbage collection impact. The combination of issues like
-this could lead down a path of not implementing a file system abstraction at
-all, and instead a weak key mapped abstraction of the `syscall` package. Once
-we finished with all the edge cases, we would have lost context of the original
-reason why we started.. simply to allow file write access!
-
-When wazero attempts to do more than what the Go programming language team, it
-has to be carefully evaluated, to:
-* Be possible to implement at least for `os.File` backed files
-* Not be confusing or cognitively hard for virtual file systems and normal use.
-* Affordable: custom code is solely the responsible by the core team, a much
- smaller group of individuals than who maintain the Go programming language.
-
-Due to problems well known in Go, consideration of the end users who constantly
-ask for basic file system functionality, and the difficulty virtualizing file
-descriptors at multiple levels, we don't expose `Fd()` and likely won't ever
-expose `Fd()` on `sys.File`.
-
-### Why does `sys.File` have a `Poll()` method, while `sys.FS` does not?
-
-wazero exposes `File.Poll` which allows one-at-a-time poll use cases,
-requested by multiple users. This not only includes abstract tests such as
-Go 1.21 `GOOS=wasip1`, but real use cases including python and container2wasm
-repls, as well listen sockets. The main use cases is non-blocking poll on a
-single file. Being a single file, this has no risk of problems such as
-head-of-line blocking, even when emulated.
-
-The main use case of multi-poll are bidirectional network services, something
-not used in `GOOS=wasip1` standard libraries, but could be in the future.
-Moving forward without a multi-poller allows wazero to expose its file system
-abstraction instead of continuing to hold back it back for edge cases. We'll
-continue discussion below regardless, as rationale was requested.
-
-You can loop through multiple `sys.File`, using `File.Poll` to see if an event
-is ready, but there is a head-of-line blocking problem. If a long timeout is
-used, bad luck could have a file that has nothing to read or write before one
-that does. This could cause more blocking than necessary, even if you could
-poll the others just after with a zero timeout. What's worse than this is if
-unlimited blocking was used (`timeout=-1`). The host implementations could use
-goroutines to avoid this, but interrupting a "forever" poll is problematic. All
-of these are reasons to consider a multi-poll API, but do not require exporting
-`File.Fd()`.
-
-Should multi-poll becomes critical, `sys.FS` could expose a `Poll` function
-like below, despite it being the non-portable, complicated if possible to
-implement on all platforms and virtual file systems.
-```go
-ready, errno := fs.Poll([]sys.PollFile{{f1, sys.POLLIN}, {f2, sys.POLLOUT}}, timeoutMillis)
-```
-
-A real filesystem could handle this by using an approach like the internal
-`unix.Poll` function in Go, passing file descriptors on unix platforms, or
-returning `sys.ENOSYS` for unsupported operating systems. Implementation for
-virtual files could have a strategy around timeout to avoid the worst case of
-head-of-line blocking (unlimited timeout).
-
-Let's remember that when designing abstractions, it is not best to add an
-interface for everything. Certainly, Go doesn't, as evidenced by them not
-exposing `poll.FD` in `os.File`! Such a multi-poll could be limited to
-built-in filesystems in the wazero repository, avoiding complexity of trying to
-support and test this abstractly. This would still permit multiplexing for CLI
-users, and also permit single file polling as exists now.
-
-### Why doesn't wazero implement the working directory?
-
-An early design of wazero's API included a `WithWorkDirFS` which allowed
-control over which file a relative path such as "./config.yml" resolved to,
-independent of the root file system. This intended to help separate concerns
-like mutability of files, but it didn't work and was removed.
-
-Compilers that target wasm act differently with regard to the working
-directory. For example, wasi-libc, used by TinyGo,
-tracks working directory changes in compiled wasm instead: initially "/" until
-code calls `chdir`. Zig assumes the first pre-opened file descriptor is the
-working directory.
-
-The only place wazero can standardize a layered concern is via a host function.
-Since WASI doesn't use host functions to track the working directory, we can't
-standardize the storage and initial value of it.
-
-Meanwhile, code may be able to affect the working directory by compiling
-`chdir` into their main function, using an argument or ENV for the initial
-value (possibly `PWD`). Those unable to control the compiled code should only
-use absolute paths in configuration.
-
-See
-* https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L324
-* https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117
-* https://github.com/ziglang/zig/blob/53a9ee699a35a3d245ab6d1dac1f0687a4dcb42c/src/main.zig#L32
-
-### Why ignore the error returned by io.Reader when n > 1?
-
-Per https://pkg.go.dev/io#Reader, if we receive an error, any bytes read should
-be processed first. At the syscall abstraction (`fd_read`), the caller is the
-processor, so we can't process the bytes inline and also return the error (as
-`EIO`).
-
-Let's assume we want to return the bytes read on error to the caller. This
-implies we at least temporarily ignore the error alongside them. The choice
-remaining is whether to persist the error returned with the read until a
-possible next call, or ignore the error.
-
-If we persist an error returned, it would be coupled to a file descriptor, but
-effectively it is boolean as this case coerces to `EIO`. If we track a "last
-error" on a file descriptor, it could be complicated for a couple reasons
-including whether the error is transient or permanent, or if the error would
-apply to any FD operation, or just read. Finally, there may never be a
-subsequent read as perhaps the bytes leading up to the error are enough to
-satisfy the processor.
-
-This decision boils down to whether or not to track an error bit per file
-descriptor or not. If not, the assumption is that a subsequent operation would
-also error, this time without reading any bytes.
-
-The current opinion is to go with the simplest path, which is to return the
-bytes read and ignore the error the there were any. Assume a subsequent
-operation will err if it needs to. This helps reduce the complexity of the code
-in wazero and also accommodates the scenario where the bytes read are enough to
-satisfy its processor.
-
-### File descriptor allocation strategy
-
-File descriptor allocation currently uses a strategy similar the one implemented
-by unix systems: when opening a file, the lowest unused number is picked.
-
-The WASI standard documents that programs cannot expect that file descriptor
-numbers will be allocated with a lowest-first strategy, and they should instead
-assume the values will be random. Since _random_ is a very imprecise concept in
-computers, we technically satisfying the implementation with the descriptor
-allocation strategy we use in Wazero. We could imagine adding more _randomness_
-to the descriptor selection process, however this should never be used as a
-security measure to prevent applications from guessing the next file number so
-there are no strong incentives to complicate the logic.
-
-### Why does `FSConfig.WithDirMount` not match behaviour with `os.DirFS`?
-
-It may seem that we should require any feature that seems like a standard
-library in Go, to behave the same way as the standard library. Doing so would
-present least surprise to Go developers. In the case of how we handle
-filesystems, we break from that as it is incompatible with the expectations of
-WASI, the most commonly implemented filesystem ABI.
-
-The main reason is that `os.DirFS` is a virtual filesystem abstraction while
-WASI is an abstraction over syscalls. For example, the signature of `fs.Open`
-does not permit use of flags. This creates conflict on what default behaviors
-to take when Go implemented `os.DirFS`. On the other hand, `path_open` can pass
-flags, and in fact tests require them to be honored in specific ways.
-
-This conflict requires us to choose what to be more compatible with, and which
-type of user to surprise the least. We assume there will be more developers
-compiling code to wasm than developers of custom filesystem plugins, and those
-compiling code to wasm will be better served if we are compatible with WASI.
-Hence on conflict, we prefer WASI behavior vs the behavior of `os.DirFS`.
-
-See https://github.com/WebAssembly/wasi-testsuite
-See https://github.com/golang/go/issues/58141
-
-## Why is our `Readdir` function more like Go's `os.File` than POSIX `readdir`?
-
-At one point we attempted to move from a bulk `Readdir` function to something
-more like the POSIX `DIR` struct, exposing functions like `telldir`, `seekdir`
-and `readdir`. However, we chose the design more like `os.File.Readdir`,
-because it performs and fits wasip1 better.
-
-### wasip1/wasix
-
-`fd_readdir` in wasip1 (and so also wasix) is like `getdents` in Linux, not
-`readdir` in POSIX. `getdents` is more like Go's `os.File.Readdir`.
-
-We currently have an internal type `sys.DirentCache` which only is used by
-wasip1 or wasix. When `HostModuleBuilder` adds support for instantiation state,
-we could move this to the `wasi_snapshot_preview1` package. Meanwhile, all
-filesystem code is internal anyway, so this special-case is acceptable.
-
-### wasip2
-
-`directory-entry-stream` in wasi-filesystem preview2 is defined in component
-model, not an ABI, but in wasmtime it is a consuming iterator. A consuming
-iterator is easy to support with anything (like `Readdir(1)`), even if it is
-inefficient as you can neither bulk read nor skip. The implementation of the
-preview1 adapter (uses preview2) confirms this. They use a dirent cache similar
-in some ways to our `sysfs.DirentCache`. As there is no seek concept in
-preview2, they interpret the cookie as numeric and read on repeat entries when
-a cache wasn't available. Note: we currently do not skip-read like this as it
-risks buffering large directories, and no user has requested entries before the
-cache, yet.
-
-Regardless, wasip2 is not complete until the end of 2023. We can defer design
-discussion until after it is stable and after the reference impl wasmtime
-implements it.
-
-See
- * https://github.com/WebAssembly/wasi-filesystem/blob/ef9fc87c07323a6827632edeb6a7388b31266c8e/example-world.md#directory_entry_stream
- * https://github.com/bytecodealliance/wasmtime/blob/b741f7c79d72492d17ab8a29c8ffe4687715938e/crates/wasi/src/preview2/preview2/filesystem.rs#L286-L296
- * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L2131-L2137
- * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L936
-
-### wasip3
-
-`directory-entry-stream` is documented to change significantly in wasip3 moving
-from synchronous to synchronous streams. This is dramatically different than
-POSIX `readdir` which is synchronous.
-
-Regardless, wasip3 is not complete until after wasip2, which means 2024 or
-later. We can defer design discussion until after it is stable and after the
-reference impl wasmtime implements it.
-
-See
- * https://github.com/WebAssembly/WASI/blob/ddfe3d1dda5d1473f37ecebc552ae20ce5fd319a/docs/WitInWasi.md#Streams
- * https://docs.google.com/presentation/d/1MNVOZ8hdofO3tI0szg_i-Yoy0N2QPU2C--LzVuoGSlE/edit#slide=id.g1270ef7d5b6_0_662
-
-### How do we implement `Pread` with an `fs.File`?
-
-`ReadAt` is the Go equivalent to `pread`: it does not affect, and is not
-affected by, the underlying file offset. Unfortunately, `io.ReaderAt` is not
-implemented by all `fs.File`. For example, as of Go 1.19, `embed.openFile` does
-not.
-
-The initial implementation of `fd_pread` instead used `Seek`. To avoid a
-regression, we fall back to `io.Seeker` when `io.ReaderAt` is not supported.
-
-This requires obtaining the initial file offset, seeking to the intended read
-offset, and resetting the file offset the initial state. If this final seek
-fails, the file offset is left in an undefined state. This is not thread-safe.
-
-While seeking per read seems expensive, the common case of `embed.openFile` is
-only accessing a single int64 field, which is cheap.
-
-### Pre-opened files
-
-WASI includes `fd_prestat_get` and `fd_prestat_dir_name` functions used to
-learn any directory paths for file descriptors open at initialization time.
-
-For example, `__wasilibc_register_preopened_fd` scans any file descriptors past
-STDERR (1) and invokes `fd_prestat_dir_name` to learn any path prefixes they
-correspond to. Zig's `preopensAlloc` does similar. These pre-open functions are
-not used again after initialization.
-
-wazero supports stdio pre-opens followed by any mounts e.g `.:/`. The guest
-path is a directory and its name, e.g. "/" is returned by `fd_prestat_dir_name`
-for file descriptor 3 (STDERR+1). The first longest match wins on multiple
-pre-opens, which allows a path like "/tmp" to match regardless of order vs "/".
-
-See
- * https://github.com/WebAssembly/wasi-libc/blob/a02298043ff551ce1157bc2ee7ab74c3bffe7144/libc-bottom-half/sources/preopens.c
- * https://github.com/ziglang/zig/blob/9cb06f3b8bf9ea6b5e5307711bc97328762d6a1d/lib/std/fs/wasi.zig#L50-L53
-
-### fd_prestat_dir_name
-
-`fd_prestat_dir_name` is a WASI function to return the path of the pre-opened
-directory of a file descriptor. It has the following three parameters, and the
-third `path_len` has ambiguous semantics.
-
-* `fd`: a file descriptor
-* `path`: the offset for the result path
-* `path_len`: In wazero, `FdPrestatDirName` writes the result path string to
- `path` offset for the exact length of `path_len`.
-
-Wasmer considers `path_len` to be the maximum length instead of the exact
-length that should be written.
-See https://github.com/wasmerio/wasmer/blob/3463c51268ed551933392a4063bd4f8e7498b0f6/lib/wasi/src/syscalls/mod.rs#L764
-
-The semantics in wazero follows that of wasmtime.
-See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6ab543e8c3f3b238/crates/wasi-common/src/snapshots/preview_1.rs#L578-L582
-
-Their semantics match when `path_len` == the length of `path`, so in practice
-this difference won't matter match.
-
-## fd_readdir
-
-### Why does "wasi_snapshot_preview1" require dot entries when POSIX does not?
-
-In October 2019, WASI project knew requiring dot entries ("." and "..") was not
-documented in preview1, not required by POSIX and problematic to synthesize.
-For example, Windows runtimes backed by `FindNextFileW` could not return these.
-A year later, the tag representing WASI preview 1 (`snapshot-01`) was made.
-This did not include the requested change of making dot entries optional.
-
-The `phases/snapshot/docs.md` document was altered in subsequent years in
-significant ways, often in lock-step with wasmtime or wasi-libc. In January
-2022, `sock_accept` was added to `phases/snapshot/docs.md`, a document later
-renamed to later renamed to `legacy/preview1/docs.md`.
-
-As a result, the ABI and behavior remained unstable: The `snapshot-01` tag was
-not an effective basis of portability. A test suite was requested well before
-this tag, in April 2019. Meanwhile, compliance had no meaning. Developers had
-to track changes to the latest doc, while clarifying with wasi-libc or wasmtime
-behavior. This lack of stability could have permitted a fix to the dot entries
-problem, just as it permitted changes desired by other users.
-
-In November 2022, the wasi-testsuite project began and started solidifying
-expectations. This quickly led to changes in runtimes and the spec doc. WASI
-began importing tests from wasmtime as required behaviors for all runtimes.
-Some changes implied changes to wasi-libc. For example, `readdir` began to
-imply inode fan-outs, which caused performance regressions. Most notably a
-test merged in January required dot entries. Tests were merged without running
-against any runtime, and even when run ad-hoc only against Linux. Hence,
-portability issues mentioned over three years earlier did not trigger any
-failure until wazero (which tests Windows) noticed.
-
-In the same month, wazero requested to revert this change primarily because
-Go does not return them from `os.ReadDir`, and materializing them is
-complicated due to tests also requiring inodes. Moreover, they are discarded by
-not just Go, but other common programming languages. This was rejected by the
-WASI lead for preview1, but considered for the completely different ABI named
-preview2.
-
-In February 2023, the WASI chair declared that new rule requiring preview1 to
-return dot entries "was decided by the subgroup as a whole", citing meeting
-notes. According to these notes, the WASI lead stated incorrectly that POSIX
-conformance required returning dot entries, something it explicitly says are
-optional. In other words, he said filtering them out would make Preview1
-non-conforming, and asked if anyone objects to this. The co-chair was noted to
-say "Because there are existing P1 programs, we shouldn’t make changes like
-this." No other were recorded to say anything.
-
-In summary, preview1 was changed retrospectively to require dot entries and
-preview2 was changed to require their absence. This rule was reverse engineered
-from wasmtime tests, and affirmed on two false premises:
-
-* POSIX compliance requires dot entries
- * POSIX literally says these are optional
-* WASI cannot make changes because there are existing P1 programs.
- * Changes to Preview 1 happened before and after this topic.
-
-As of June 2023, wasi-testsuite still only runs on Linux, so compliance of this
-rule on Windows is left to runtimes to decide to validate. The preview2 adapter
-uses fake cookies zero and one to refer to dot dirents, uses a real inode for
-the dot(".") entry and zero inode for dot-dot("..").
-
-See https://github.com/WebAssembly/wasi-filesystem/issues/3
-See https://github.com/WebAssembly/WASI/tree/snapshot-01
-See https://github.com/WebAssembly/WASI/issues/9
-See https://github.com/WebAssembly/WASI/pull/458
-See https://github.com/WebAssembly/wasi-testsuite/pull/32
-See https://github.com/WebAssembly/wasi-libc/pull/345
-See https://github.com/WebAssembly/wasi-testsuite/issues/52
-See https://github.com/WebAssembly/WASI/pull/516
-See https://github.com/WebAssembly/meetings/blob/main/wasi/2023/WASI-02-09.md#should-preview1-fd_readdir-filter-out--and-
-See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
-
-### Why are dot (".") and dot-dot ("..") entries problematic?
-
-When reading a directory, dot (".") and dot-dot ("..") entries are problematic.
-For example, Go does not return them from `os.ReadDir`, and materializing them
-is complicated (at least dot-dot is).
-
-A directory entry has stat information in it. The stat information includes
-inode which is used for comparing file equivalence. In the simple case of dot,
-we could materialize a special entry to expose the same info as stat on the fd
-would return. However, doing this and not doing dot-dot would cause confusion,
-and dot-dot is far more tricky. To back-fill inode information about a parent
-directory would be costly and subtle. For example, the pre-open (mount) of the
-directory may be different than its logical parent. This is easy to understand
-when considering the common case of mounting "/" and "/tmp" as pre-opens. To
-implement ".." from "/tmp" requires information from a separate pre-open, this
-includes state to even know the difference. There are easier edge cases as
-well, such as the decision to not return ".." from a root path. In any case,
-this should start to explain that faking entries when underlying stdlib doesn't
-return them is tricky and requires quite a lot of state.
-
-Another issue is around the `Dirent.Off` value of a directory entry, sometimes
-called a "cookie" in Linux man pagers. When the host operating system or
-library function does not return dot entries, to support functions such as
-`seekdir`, you still need a value for `Dirent.Off`. Naively, you can synthesize
-these by choosing sequential offsets zero and one. However, POSIX strictly says
-offsets should be treated opaquely. The backing filesystem could use these to
-represent real entries. For example, a directory with one entry could use zero
-as the `Dirent.Off` value. If you also used zero for the "." dirent, there
-would be a clash. This means if you synthesize `Dirent.Off` for any entry, you
-need to synthesize this value for all entries. In practice, the simplest way is
-using an incrementing number, such as done in the WASI preview2 adapter.
-
-Working around these issues causes expense to all users of wazero, so we'd
-then look to see if that would be justified or not. However, the most common
-compilers involved in end user questions, as of early 2023 are TinyGo, Rust and
-Zig. All of these compile code which ignores dot and dot-dot entries. In other
-words, faking these entries would not only cost our codebase with complexity,
-but it would also add unnecessary overhead as the values aren't commonly used.
-
-The final reason why we might do this, is an end users or a specification
-requiring us to. As of early 2023, no end user has raised concern over Go and
-by extension wazero not returning dot and dot-dot. The snapshot-01 spec of WASI
-does not mention anything on this point. Also, POSIX has the following to say,
-which summarizes to "these are optional"
-
-> 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.
-
-Unfortunately, as described above, the WASI project decided in early 2023 to
-require dot entries in both the spec and the wasi-testsuite. For only this
-reason, wazero adds overhead to synthesize dot entries despite it being
-unnecessary for most users.
-
-See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
-See https://github.com/golang/go/blob/go1.20/src/os/dir_unix.go#L108-L110
-See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
-
-### Why don't we pre-populate an inode for the dot-dot ("..") entry?
-
-We only populate an inode for dot (".") because wasi-testsuite requires it, and
-we likely already have it (because we cache it). We could attempt to populate
-one for dot-dot (".."), but chose not to.
-
-Firstly, wasi-testsuite does not require the inode of dot-dot, possibly because
-the wasip2 adapter doesn't populate it (but we don't really know why).
-
-The only other reason to populate it would be to avoid wasi-libc's stat fanout
-when it is missing. However, wasi-libc explicitly doesn't fan-out to lstat on
-the ".." entry on a zero ino.
-
-Fetching dot-dot's inode despite the above not only doesn't help wasi-libc, but
-it also hurts languages that don't use it, such as Go. These languages would
-pay a stat syscall penalty even if they don't need the inode. In fact, Go
-discards both dot entries!
-
-In summary, there are no significant upsides in attempting to pre-fetch
-dot-dot's inode, and there are downsides to doing it anyway.
-
-See
- * https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
- * https://github.com/WebAssembly/wasi-testsuite/blob/main/tests/rust/src/bin/fd_readdir.rs#L108
- * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1037
-
-### Why don't we require inodes to be non-zero?
-
-We don't require a non-zero value for `Dirent.Ino` because doing so can prevent
-a real one from resolving later via `Stat_t.Ino`.
-
-We define `Ino` like `d_ino` in POSIX which doesn't special-case zero. It can
-be zero for a few reasons:
-
-* The file is not a regular file or directory.
-* The underlying filesystem does not support inodes. e.g. embed:fs
-* A directory doesn't include inodes, but a later stat can. e.g. Windows
-* The backend is based on wasi-filesystem (a.k.a wasip2), which has
- `directory_entry.inode` optional, and might remove it entirely.
-
-There are other downsides to returning a zero inode in widely used compilers:
-
-* File equivalence utilities, like `os.SameFile` will not work.
-* wasi-libc's `wasip1` mode will call `lstat` and attempt to retrieve a
- non-zero value (unless the entry is named "..").
-
-A new compiler may accidentally skip a `Dirent` with a zero `Ino` if emulating
-a non-POSIX function and re-using `Dirent.Ino` for `d_fileno`.
-
-* Linux `getdents` doesn't define `d_fileno` must be non-zero
-* BSD `getdirentries` is implementation specific. For example, OpenBSD will
- return dirents with a zero `d_fileno`, but Darwin will skip them.
-
-The above shouldn't be a problem, even in the case of BSD, because `wasip1` is
-defined more in terms of `getdents` than `getdirentries`. The bottom half of
-either should treat `wasip1` (or any similar ABI such as wasix or wasip2) as a
-different operating system and either use different logic that doesn't skip, or
-synthesize a fake non-zero `d_fileno` when `d_ino` is zero.
-
-However, this has been a problem. Go's `syscall.ParseDirent` utility is shared
-for all `GOOS=unix`. For simplicity, this abstracts `direntIno` with data from
-`d_fileno` or `d_ino`, and drops if either are zero, even if `d_fileno` is the
-only field with zero explicitly defined. This led to a change to special case
-`GOOS=wasip1` as otherwise virtual files would be unconditionally skipped.
-
-In practice, this problem is rather unique due to so many compilers relying on
-wasi-libc, which tolerates a zero inode. For example, while issues were
-reported about the performance regression when wasi-libc began doing a fan-out
-on zero `Dirent.Ino`, no issues were reported about dirents being dropped as a
-result.
-
-In summary, rather than complicating implementation and forcing non-zero inodes
-for a rare case, we permit zero. We instead document this topic thoroughly, so
-that emerging compilers can re-use the research and reference it on conflict.
-We also document that `Ino` should be non-zero, so that users implementing that
-field will attempt to get it.
-
-See
- * https://github.com/WebAssembly/wasi-filesystem/pull/81
- * https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
- * https://linux.die.net/man/3/getdents
- * https://www.unix.com/man-page/osx/2/getdirentries/
- * https://man.openbsd.org/OpenBSD-5.4/getdirentries.2
- * https://github.com/golang/go/blob/go1.20/src/syscall/dirent.go#L60-L102
- * https://go-review.googlesource.com/c/go/+/507915
-
-## sys.Walltime and Nanotime
-
-The `sys` package has two function types, `Walltime` and `Nanotime` for real
-and monotonic clock exports. The naming matches conventions used in Go.
-
-```go
-func time_now() (sec int64, nsec int32, mono int64) {
- sec, nsec = walltime()
- return sec, nsec, nanotime()
-}
-```
-
-Splitting functions for wall and clock time allow implementations to choose
-whether to implement the clock once (as in Go), or split them out.
-
-Each can be configured with a `ClockResolution`, although is it usually
-incorrect as detailed in a sub-heading below. The only reason for exposing this
-is to satisfy WASI:
-
-See https://github.com/WebAssembly/wasi-clocks
-
-### Why default to fake time?
-
-WebAssembly has an implicit design pattern of capabilities based security. By
-defaulting to a fake time, we reduce the chance of timing attacks, at the cost
-of requiring configuration to opt-into real clocks.
-
-See https://gruss.cc/files/fantastictimers.pdf for an example attacks.
-
-### Why does fake time increase on reading?
-
-Both the fake nanotime and walltime increase by 1ms on reading. Particularly in
-the case of nanotime, this prevents spinning.
-
-### Why not `time.Clock`?
-
-wazero can't use `time.Clock` as a plugin for clock implementation as it is
-only substitutable with build flags (`faketime`) and conflates wall and
-monotonic time in the same call.
-
-Go's `time.Clock` was added monotonic time after the fact. For portability with
-prior APIs, a decision was made to combine readings into the same API call.
-
-See https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md
-
-WebAssembly time imports do not have the same concern. In fact even Go's
-imports for clocks split walltime from nanotime readings.
-
-See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L243-L255
-
-Finally, Go's clock is not an interface. WebAssembly users who want determinism
-or security need to be able to substitute an alternative clock implementation
-from the host process one.
-
-### `ClockResolution`
-
-A clock's resolution is hardware and OS dependent so requires a system call to retrieve an accurate value.
-Go does not provide a function for getting resolution, so without CGO we don't have an easy way to get an actual
-value. For now, we return fixed values of 1us for realtime and 1ns for monotonic, assuming that realtime clocks are
-often lower precision than monotonic clocks. In the future, this could be improved by having OS+arch specific assembly
-to make syscalls.
-
-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).
-Because retrieving resolution is not generally called often, unlike getting time, it could be appropriate to only
-implement the fallback logic that does not use VDSO (executing syscalls in user mode). The syscall for clock_getres
-is 229 and should be usable. https://pkg.go.dev/syscall#pkg-constants.
-
-If implementing similar for Windows, [mingw](https://github.com/mirror/mingw-w64/blob/6a0e9165008f731bccadfc41a59719cf7c8efc02/mingw-w64-libraries/winpthreads/src/clock.c#L77
-) is often a good source to find the Windows API calls that correspond
-to a POSIX method.
-
-Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations
-across many combinations of OS and architecture.
-
-## sys.Nanosleep
-
-All major programming languages have a `sleep` mechanism to block for a
-duration. Sleep is typically implemented by a WASI `poll_oneoff` relative clock
-subscription.
-
-For example, the below ends up calling `wasi_snapshot_preview1.poll_oneoff`:
-
-```zig
-const std = @import("std");
-pub fn main() !void {
- std.time.sleep(std.time.ns_per_s * 5);
-}
-```
-
-Besides Zig, this is also the case with TinyGo (`-target=wasi`) and Rust
-(`--target wasm32-wasi`).
-
-We decided to expose `sys.Nanosleep` to allow overriding the implementation
-used in the common case, even if it isn't used by Go, because this gives an
-easy and efficient closure over a common program function. We also documented
-`sys.Nanotime` to warn users that some compilers don't optimize sleep.
-
-## sys.Osyield
-
-We expose `sys.Osyield`, to allow users to control the behavior of WASI's
-`sched_yield` without a new build of wazero. This is mainly for parity with
-all other related features which we allow users to implement, including
-`sys.Nanosleep`. Unlike others, we don't provide an out-of-box implementation
-primarily because it will cause performance problems when accessed.
-
-For example, the below implementation uses CGO, which might result in a 1us
-delay per invocation depending on the platform.
-
-See https://github.com/golang/go/issues/19409#issuecomment-284788196
-```go
-//go:noescape
-//go:linkname osyield runtime.osyield
-func osyield()
-```
-
-In practice, a request to customize this is unlikely to happen until other
-thread based functions are implemented. That said, as of early 2023, there are
-a few signs of implementation interest and cross-referencing:
-
-See https://github.com/WebAssembly/stack-switching/discussions/38
-See https://github.com/WebAssembly/wasi-threads#what-can-be-skipped
-See https://slinkydeveloper.com/Kubernetes-controllers-A-New-Hope/
-
-## sys.Stat_t
-
-We expose `stat` information as `sys.Stat_t`, like `syscall.Stat_t` except
-defined without build constraints. For example, you can use `sys.Stat_t` on
-`GOOS=windows` which doesn't define `syscall.Stat_t`.
-
-The first use case of this is to return inodes from `fs.FileInfo` without
-relying on platform-specifics. For example, a user could return `*sys.Stat_t`
-from `info.Sys()` and define a non-zero inode for a virtual file, or map a
-real inode to a virtual one.
-
-Notable choices per field are listed below, where `sys.Stat_t` is unlike
-`syscall.Stat_t` on `GOOS=linux`, or needs clarification. One common issue
-not repeated below is that numeric fields are 64-bit when at least one platform
-defines it that large. Also, zero values are equivalent to nil or absent.
-
-* `Dev` and `Ino` (`Inode`) are both defined unsigned as they are defined
- opaque, and most `syscall.Stat_t` also defined them unsigned. There are
- separate sections in this document discussing the impact of zero in `Ino`.
-* `Mode` is defined as a `fs.FileMode` even though that is not defined in POSIX
- and will not map to all possible values. This is because the current use is
- WASI, which doesn't define any types or features not already supported. By
- using `fs.FileMode`, we can re-use routine experience in Go.
-* `NLink` is unsigned because it is defined that way in `syscall.Stat_t`: there
- can never be less than zero links to a file. We suggest defaulting to 1 in
- conversions when information is not knowable because at least that many links
- exist.
-* `Size` is signed because it is defined that way in `syscall.Stat_t`: while
- regular files and directories will always be non-negative, irregular files
- are possibly negative or not defined. Notably sparse files are known to
- return negative values.
-* `Atim`, `Mtim` and `Ctim` are signed because they are defined that way in
- `syscall.Stat_t`: Negative values are time before 1970. The resolution is
- nanosecond because that's the maximum resolution currently supported in Go.
-
-### Why do we use `sys.EpochNanos` instead of `time.Time` or similar?
-
-To simplify documentation, we defined a type alias `sys.EpochNanos` for int64.
-`time.Time` is a data structure, and we could have used this for
-`syscall.Stat_t` time values. The most important reason we do not is conversion
-penalty deriving time from common types.
-
-The most common ABI used in `wasip2`. This, and compatible ABI such as `wasix`,
-encode timestamps in memory as a 64-bit number. If we used `time.Time`, we
-would have to convert an underlying type like `syscall.Timespec` to `time.Time`
-only to later have to call `.UnixNano()` to convert it back to a 64-bit number.
-
-In the future, the component model module "wasi-filesystem" may represent stat
-timestamps with a type shared with "wasi-clocks", abstractly structured similar
-to `time.Time`. However, component model intentionally does not define an ABI.
-It is likely that the canonical ABI for timestamp will be in two parts, but it
-is not required for it to be intermediately represented this way. A utility
-like `syscall.NsecToTimespec` could split an int64 so that it could be written
-to memory as 96 bytes (int64, int32), without allocating a struct.
-
-Finally, some may confuse epoch nanoseconds with 32-bit epoch seconds. While
-32-bit epoch seconds has "The year 2038" problem, epoch nanoseconds has
-"The Year 2262" problem, which is even less concerning for this library. If
-the Go programming language and wazero exist in the 2200's, we can make a major
-version increment to adjust the `sys.EpochNanos` approach. Meanwhile, we have
-faster code.
-
-## poll_oneoff
-
-`poll_oneoff` is a WASI API for waiting for I/O events on multiple handles.
-It is conceptually similar to the POSIX `poll(2)` syscall.
-The name is not `poll`, because it references [“the fact that this function is not efficient
-when used repeatedly with the same large set of handles”][poll_oneoff].
-
-We chose to support this API in a handful of cases that work for regular files
-and standard input. We currently do not support other types of file descriptors such
-as socket handles.
-
-### Clock Subscriptions
-
-As detailed above in [sys.Nanosleep](#sysnanosleep), `poll_oneoff` handles
-relative clock subscriptions. In our implementation we use `sys.Nanosleep()`
-for this purpose in most cases, except when polling for interactive input
-from `os.Stdin` (see more details below).
-
-### FdRead and FdWrite Subscriptions
-
-When subscribing a file descriptor (except `Stdin`) for reads or writes,
-the implementation will generally return immediately with success, unless
-the file descriptor is unknown. The file descriptor is not checked further
-for new incoming data. Any timeout is cancelled, and the API call is able
-to return, unless there are subscriptions to `Stdin`: these are handled
-separately.
-
-### FdRead and FdWrite Subscription to Stdin
-
-Subscribing `Stdin` for reads (writes make no sense and cause an error),
-requires extra care: wazero allows to configure a custom reader for `Stdin`.
-
-In general, if a custom reader is found, the behavior will be the same
-as for regular file descriptors: data is assumed to be present and
-a success is written back to the result buffer.
-
-However, if the reader is detected to read from `os.Stdin`,
-a special code path is followed, invoking `sysfs.poll()`.
-
-`sysfs.poll()` is a wrapper for `poll(2)` on POSIX systems,
-and it is emulated on Windows.
-
-### Poll on POSIX
-
-On POSIX systems, `poll(2)` allows to wait for incoming data on a file
-descriptor, and block until either data becomes available or the timeout
-expires.
-
-Usage of `syfs.poll()` is currently only reserved for standard input, because
-
-1. it is really only necessary to handle interactive input: otherwise,
- there is no way in Go to peek from Standard Input without actually
- reading (and thus consuming) from it;
-
-2. if `Stdin` is connected to a pipe, it is ok in most cases to return
- with success immediately;
-
-3. `syfs.poll()` is currently a blocking call, irrespective of goroutines,
- because the underlying syscall is; thus, it is better to limit its usage.
-
-So, if the subscription is for `os.Stdin` and the handle is detected
-to correspond to an interactive session, then `sysfs.poll()` will be
-invoked with a the `Stdin` handle *and* the timeout.
-
-This also means that in this specific case, the timeout is uninterruptible,
-unless data becomes available on `Stdin` itself.
-
-### Select on Windows
-
-On Windows `sysfs.poll()` cannot be delegated to a single
-syscall, because there is no single syscall to handle sockets,
-pipes and regular files.
-
-Instead, we emulate its behavior for the cases that are currently
-of interest.
-
-- For regular files, we _always_ report them as ready, as
-[most operating systems do anyway][async-io-windows].
-
-- For pipes, we invoke [`PeekNamedPipe`][peeknamedpipe]
-for each file handle we detect is a pipe open for reading.
-We currently ignore pipes open for writing.
-
-- Notably, we include also support for sockets using the [WinSock
-implementation of `poll`][wsapoll], but instead
-of relying on the timeout argument of the `WSAPoll` function,
-we set a 0-duration timeout so that it behaves like a peek.
-
-This way, we can check for regular files all at once,
-at the beginning of the function, then we poll pipes and
-sockets periodically using a cancellable `time.Tick`,
-which plays nicely with the rest of the Go runtime.
-
-### Impact of blocking
-
-Because this is a blocking syscall, it will also block the carrier thread of
-the goroutine, preventing any means to support context cancellation directly.
-
-There are ways to obviate this issue. We outline here one idea, that is however
-not currently implemented. A common approach to support context cancellation is
-to add a signal file descriptor to the set, e.g. the read-end of a pipe or an
-eventfd on Linux. When the context is canceled, we may unblock a Select call by
-writing to the fd, causing it to return immediately. This however requires to
-do a bit of housekeeping to hide the "special" FD from the end-user.
-
-[poll_oneoff]: https://github.com/WebAssembly/wasi-poll#why-is-the-function-called-poll_oneoff
-[async-io-windows]: https://tinyclouds.org/iocp_links
-[peeknamedpipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
-[wsapoll]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll
-
-## Signed encoding of integer global constant initializers
-
-wazero treats integer global constant initializers signed as their interpretation is not known at declaration time. For
-example, there is no signed integer [value type](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0).
-
-To get at the problem, let's use an example.
-```
-(global (export "start_epoch") i64 (i64.const 1620216263544))
-```
-
-In both signed and unsigned LEB128 encoding, this value is the same bit pattern. The problem is that some numbers are
-not. For example, 16256 is `807f` encoded as unsigned, but `80ff00` encoded as signed.
-
-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),
-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).
-
-For consistency, we go with signed encoding in the special case of global constant initializers.
-
-## Implementation limitations
-
-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).
-
-wazero limitations are imposed pragmatically and described below.
-
-### Number of functions in a module
-
-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.
-wazero limits the maximum function instances to 2^27 as even that number would occupy 1GB in function pointers.
-
-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.
-
-### Number of function types in a store
-
-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.
-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.
-
-This is due to the same reason for the limitation on the number of functions above.
-
-### Number of values on the stack in a function
-
-While the the spec does not clarify a limitation of function stack values, wazero limits this to 2^27 = 134,217,728.
-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
-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.
-
-All the functions are statically analyzed at module instantiation phase, and if a function can potentially reach this limit, an error is returned.
-
-### Number of globals in a module
-
-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.
-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
-means that we have 1 GiB size of slice which seems large enough for most applications.
-
-### Number of tables in a module
-
-While the the spec says that a module can have up to 2^32 tables, wazero limits this to 2^27 = 134,217,728.
-One of the reasons is even that number would occupy 1GB in the pointers tables alone. Not only that, we access tables slice by
-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.
-
-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.
-
-If a module reaches this limit, an error is returned at the compilation phase.
-
-## Compiler engine implementation
-
-### Why it's safe to execute runtime-generated machine codes against async Goroutine preemption
-
-Goroutine preemption is the mechanism of the Go runtime to switch goroutines contexts on an OS thread.
-There are two types of preemption: cooperative preemption and async preemption. The former happens, for example,
-when making a function call, and it is not an issue for our runtime-generated functions as they do not make
-direct function calls to Go-implemented functions. On the other hand, the latter, async preemption, can be problematic
-since it tries to interrupt the execution of Goroutine at any point of function, and manipulates CPU register states.
-
-Fortunately, our runtime-generated machine codes do not need to take the async preemption into account.
-All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)),
-and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption:
-- https://github.com/golang/go/blob/go1.20rc1/src/runtime/preempt.go#L406-L407
-- https://github.com/golang/go/blob/9f0234214473dfb785a5ad84a8fc62a6a395cbc3/src/runtime/traceback.go#L227
-
-From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of
-that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption.
-
-## Why context cancellation is handled in Go code rather than native code
-
-Since [wazero v1.0.0-pre.9](https://github.com/tetratelabs/wazero/releases/tag/v1.0.0-pre.9), the runtime
-supports integration with Go contexts to interrupt execution after a timeout, or in response to explicit cancellation.
-This support is internally implemented as a special opcode `builtinFunctionCheckExitCode` that triggers the execution of
-a Go function (`ModuleInstance.FailIfClosed`) that atomically checks a sentinel value at strategic points in the code.
-
-[It _is indeed_ possible to check the sentinel value directly, without leaving the native world][native_check], thus sparing some cycles;
-however, because native code never preempts (see section above), this may lead to a state where the other goroutines
-never get the chance to run, and thus never get the chance to set the sentinel value; effectively preventing
-cancellation from taking place.
-
-[native_check]: https://github.com/tetratelabs/wazero/issues/1409
-
-## Golang patterns
-
-### Hammer tests
-Code that uses concurrency primitives, such as locks or atomics, should include "hammer tests", which run large loops
-inside a bounded amount of goroutines, run by half that many `GOMAXPROCS`. These are named consistently "hammer", so
-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).
-
-Here is an annotated description of the key pieces of a hammer test:
-1. `P` declares the count of goroutines to use, defaulting to 8 or 4 if `testing.Short`.
- * 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.
-2. `N` declares the scale of work (loop) per goroutine, defaulting to value that finishes in ~0.1s on a modern laptop.
- * When in doubt, try 1000 or 100 if `testing.Short`
- * Remember, there are multiple hammer tests and CI nodes are slow. Slower tests hurt feedback loops.
-3. `defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P/2))` makes goroutines switch cores, testing visibility of shared data.
-4. To ensure goroutines execute at the same time, block them with `sync.WaitGroup`, initialized to `Add(P)`.
- * `sync.WaitGroup` internally uses `runtime_Semacquire` not available in any other library.
- * `sync.WaitGroup.Add` with a negative value can unblock many goroutines at the same time, e.g. without a for loop.
-5. Track goroutines progress via `finished := make(chan int)` where each goroutine in `P` defers `finished <- 1`.
- 1. Tests use `require.XXX`, so `recover()` into `t.Fail` in a `defer` function before `finished <- 1`.
- * This makes it easier to spot larger concurrency problems as you see each failure, not just the first.
- 2. After the `defer` function, await unblocked, then run the stateful function `N` times in a normal loop.
- * This loop should trigger shared state problems as locks or atomics are contended by `P` goroutines.
-6. After all `P` goroutines launch, atomically release all of them with `WaitGroup.Add(-P)`.
-7. Block the runner on goroutine completion, by (`<-finished`) for each `P`.
-8. When all goroutines complete, `return` if `t.Failed()`, otherwise perform follow-up state checks.
-
-This is implemented in wazero in [hammer.go](internal/testing/hammer/hammer.go)
-
-### Lock-free, cross-goroutine observations of updates
-
-How to achieve cross-goroutine reads of a variable are not explicitly defined in https://go.dev/ref/mem. wazero uses
-atomics to implement this following unofficial practice. For example, a `Close` operation can be guarded to happen only
-once via compare-and-swap (CAS) against a zero value. When we use this pattern, we consistently use atomics to both
-read and update the same numeric field.
-
-In lieu of formal documentation, we infer this pattern works from other sources (besides tests):
- * `sync.WaitGroup` by definition must support calling `Add` from other goroutines. Internally, it uses atomics.
- * rsc in golang/go#5045 writes "atomics guarantee sequential consistency among the atomic variables".
-
-See https://github.com/golang/go/blob/go1.20/src/sync/waitgroup.go#L64
-See https://github.com/golang/go/issues/5045#issuecomment-252730563
-See https://www.youtube.com/watch?v=VmrEG-3bWyM