Repository navigation
all: add GOOS=wasip1 GOARCH=wasm port #58141
Description
Background
The WebAssembly System Interface (WASI, https://wasi.dev/) is gaining popularity as a compile-once-run-anywhere target for developer and cloud native applications. Many cloud providers are offering services that make it possible to execute WASI directly inside familiar orchestration frameworks like Kubernetes (https://learn.microsoft.com/en-us/azure/aks/use-wasi-node-pools, https://docs.krustlet.dev/howto/), or on edge compute platforms (https://developer.fastly.com/learning/compute/, https://blog.cloudflare.com/announcing-wasi-on-workers/) and the popular developer tool Docker has beta support for executing wasi directly (https://docs.docker.com/desktop/wasm/). For Go to remain relevant in a hypothetical world where this becomes a significant part of software delivery, it must support compiling code to the Wasm binary format and the WASI syscall API.
Proposal
We propose adding a new port, GOOS=wasip1 GOARCH=wasm, that targets the wasi_snapshot_preview1 syscall API. We further propose allowing the use of the go:wasmimport compiler directive in the syscall package, in addition to the currently allowed runtime and syscall/js packages.
Discussion
Go already supports WebAssembly (Wasm) through the existing GOOS=js GOARCH=wasm port, and the implementation of this proposal would reuse the existing Wasm architecture code and change the interface with which the compiled code interacts with the outside world. It builds on the accepted proposal (#38248) for a go:wasmimport compiler directive for defining Wasm host function imports. The compiled code would be a “Command”, executing func main and running until exit, similar to the existing js/wasm port.
Syscall API target
Today, implementing WASI means implementing the wasi_snapshot_preview1 API described in the spec. However, this interface is evolving without the insurance of backward compatibility. A “preview2” version is already being worked on. Should the Go compiler support the old one for now, and switch to the new one in the future, or should we name the new GOOS such that we can add new GOOS’s for new WASI APIs? We propose that we assume the wasi_snapshot_preview1 API for now and that future releases of Go may add support for newer syscall APIs under a different GOOS (e.g. GOOS=wasip2 for wasi_snapshot_preview2).
Maintainers
Since this is a new port and the porting policy requires at least two maintainers, Evan Phoenix (@evanphx), Julien Fabre (@Pryz) and I (@johanbrandhorst) are volunteering to be maintainers of this port.
Testing
The wasi/wasm port will be tested by executing the standard library tests using an established WASI VM, such as Wasmtime. This software has precompiled binaries available for download, which can be used to set up a builder for the trybots, similar to how NodeJS is used for the js/wasm port.
What happens to the js/wasm port?
The existing js/wasm port will remain relevant for the purposes of compiling Go Wasm for running in a JavaScript VM and using the syscall/js interface for interacting with the JS world. Both ports will coexist, and should eventually require minimal differences in compiler and syscall code. See the discussion on rewriting wasm_exec.js for more information.
A note on capabilities
wasi_snapshot_preview1 is limited in ways that may be surprising to users, for example, it is not possible to open a network socket with the APIs defined in the spec. The initial implementation of the wasi target will aim to implement as much of the standard library as possible, but there will be big gaps.
Related issues
This would close #31105, which has mostly been a discussion issue.
Future work
WASI Preview2
As the second snapshot of the WASI standard matures, we will aim to add support for the new standard. This will unlock new functionality such as networking sockets and ensure that Go’s WASI support remains relevant for users. This could be done in any new major release of the Go toolchain, but not in a minor revision.
Considering the upcoming changes in preview 2, it is a legitimate question to ask whether the work to add support for wasi_snapshot_preview1 is worth doing; could we simply wait for the next standard iteration? We believe the work to be useful because at this time, all compilers are targeting preview 1, and preview 2 seems far from being fully completed. We also believe that runtimes will provide polyfills to preview 1 while preview 2 is in the process of being implemented (see this Wasmtime issue). Finally, the next version of the WASI standard is split into multiple components, and it is possible that specifications for each component will be finalized at different times, with preview 1 remaining the de-facto fallback for components that are not yet fully specified or implemented by runtimes yet.
Rewriting wasm_exec.js as WASI and unifying syscall interfaces
The existing js/wasm port has a custom syscall interface implemented by wasm_exec.js and run on any JavaScript VM. Now that a standard is emerging, the js/wasm target should reuse the same syscall interface, allowing parts of the syscall interface between wasi and js to be unified to reduce maintenance burden. This would require implementing a WASI interface shim in wasm_exec.js, which is a significant undertaking, and thus out of scope of this initial WASI work.
WASI Libraries (AKA Reactors)
The WASI concept of libraries allow compiled binaries to expose single functions for consumption from the host. This is not something that will be supported in the initial WASI port, as it requires a concept of marking Go functions as exported (i.e. //go:wasmexport), and somehow facilitating the execution of a single function. For more discussions on why this is complicated, see #42372.
GOOS=none GOARCH=wasm
Binary wasm can run without any particular knowledge of its host, perhaps using something like GOOS=none, similar to Rust’s wasm32-unknown-unknown target. This proposal does not propose any such port be added, but it may be something to consider in the future. The name “none” is, of course, not decided.
Activity
prattmic commentedon Jan 30, 2023
I like the idea of using
GOOS=wasito distinguish fromGOOS=js. It seems like a natural place to select wasi.The obvious concern here to me is the instability of the API of
wasi_snapshot_preview1. Iswasi_snapshot_preview2planning to be backwards compatible withwasi_snapshot_preview1binaries? (It sounds like no?). If not, I am concerned byGOOS=wasichanging APIs between versions because I suspect (a) some users will want preview2 ASAP to use new feature, and (b) some users will want to keep preview1 because their wasm runtime doesn't support preview2 yet. These are in direct conflict with one another.As a workaround, we could have
GOWASI=preview1, or something like that, similar toGOARM,GOAMD64, etc.What is the timeline for a "stable" version of WASI? The other end of the spectrum would be to say that
GOOS=wasiis not stable (hidden behind aGOEXPERIMENTmaybe?) and will change compatibility arbitrarily from release-to-release until there is a stable version of WASI to target.prattmic commentedon Jan 30, 2023
From https://github.com/WebAssembly/WASI: "The WebAssembly System Interface is not a monolithic standard system interface, but is instead a modular collection of standardized APIs. None of the APIs are required to be implemented to have a compliant runtime. Instead, host environments can choose which APIs make sense for their use cases."
Will there be some minimum requirements that the Go runtime will require from the host environment? Or will Go still work even if the host environment provides no APIs?
codefromthecrypt commentedon Jan 30, 2023
@prattmic while not documented really, the usual way features are disabled is via syscall.ENOSYS errors.
For example, when source is compiled with
GOARCH=wasm GOOS=jsand let's say wazero is running it, if that code tries to access the filesystem and the filesystem is disabled, the wazero host functions return an ENOSYS error which the compiled code expects and returns an error message like "not implemented in js"In the case of WASI, and specifically the most implemented version of it (snapshot-01), there are error codes which map to syscall.Errno here https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16
johanbrandhorst commentedon Jan 30, 2023
The existing js/wasm port has generally taken a conservative approach to including new features, and we would seek to emulate that. We don't yet know what would be the threshold for switching over to preview2, but it would likely be year(s) in the future. A
GOWASIenvironment variable as suggested may be considered, but it shouldn't be necessary in the near term.I will note also that the existing js/wasm port is still considered experimental and can introduce breaking changes at any time. The wasi/wasm port would similarly not provide any backwards compatibility guarantees. It would seem appropriate for this to remain the case until a stable WASI API spec is available and implemented in runtimes at least.
Go binaries compiled with GOOS=wasi would require the host to provide the full
wasi_snapshot_preview1API. A hypothetical GOOS=none GOARCH=wasm could be introduced to avoid any host dependencies, but it's not currently part of our planning.prattmic commentedon Jan 30, 2023
@codefromthecrypt:
@johanbrandhorst:
These seem a bit contradictory?
I'm specifically wondering about APIs which the Go runtime cannot run without at all. e.g., we may require
clock_get_timeto implementruntime.nanotime, because the runtime more-or-less can't run without a source of time [1]. I'm wondering if there are other cases.It doesn't seem like we'd need to require all APIs. e.g.,
fd_syncwouldn't be called by Go unless the program explicitly callsos.File.Sync/syscall.Sync. Those returning an error seems fine to me.[1] OK, maybe not a perfect example, since technically
-tags=faketimedoesn't require a time source.johanbrandhorst commentedon Jan 30, 2023
I'm not sure what you're asking exactly, the way I see this being implemented is by translating syscalls in the code to the relevant host API calls. Sure you could build a wasi binary that doesn't use all of the API and it'd work fine on a host that only implements the part of the API that's used, but I don't think the implementation should need to do any sort of feature capability negotiation with the host - if the API returns ENOSYS then the function call fails up the stack. Does that sound okay?
For your specific example, I guess if
clock_get_timeisn't implemented it would fail very quickly at runtime, not try to get by without a clock source.ianlancetaylor commentedon Jan 30, 2023
At the very least for documentation purposes I think we want to be able to write down which APIs must be implemented in order to run simple Go programs.
johanbrandhorst commentedon Jan 30, 2023
Not that I disagree, but I'm a little confused by this inquiry - are we expecting users to implement their own partial implementations of
wasi_snapshot_preview1in the hopes of running simple Go programs? It's tempting to say that any Go compiled wasi binary requires the host to provide the API defined by the spec. What is the purpose of defining a minimal API used by simple Go programs?codefromthecrypt commentedon Jan 30, 2023
@ianlancetaylor
I think initially it would look like TinyGo, which implements a subset of wasi. Here's a list of functions that are used and who uses them https://wazero.io/specs/#wasi and here's an example simple cat program. Hope it helps!
dmitshur commentedon Jan 30, 2023
CC @golang/release.
prattmic commentedon Jan 30, 2023
I haven't been following wasm/wasi super closely, so maybe I've misunderstood, but I thought that wasm/wasi was commonly using in "sandboxing"-type scenarios, where presumably the operator wants to limit the APIs that the sandboxed program has access to. Documenting the minimum requirements for the Go runtime itself makes it clear to those building a sandbox what the most restricted set of APIs they can provide is (and if that is still too permissive, maybe Go programs aren't a good sandboxee target).
265 remaining items
mar1n3r0 commentedon Jul 23, 2023
So theoretically it would be possible if
syscall/jsis ported?johanbrandhorst commentedon Jul 23, 2023
Wasm runtimes can define any APIs they like, and with
go:wasmimportyou can write Go wrappers around the APIs (a third partysyscall/js, if you will). It seems impractical to me. In any case, this issue is not the right forum for this discussion. Please do not bump it.interp: add wasip1 to known os list
interp: add wasip1 to known os list
interp: Add wasip1 to known OS list
gopherbot commentedon Nov 7, 2023
Change https://go.dev/cl/540220 mentions this issue:
internal/version: add wasip1 supportinternal/version: add wasip1 support
internal/version: add wasip1 support
Add WASI wasip1 support
all: merge upstream changes (#11)
singleflight: make the check for exec support in TestPanicDoChan plat…
time: add wasip1 support