Repository: go
Issue: cmd/compile: create GOARCH=wasm32
Labels: Proposal,Proposal-Accepted,arch-wasm

# Background

The `GOARCH=wasm` architecture was first introduced alongside `GOOS=js` in 2019. The choice was made to use a 64 bit architecture because of WebAssembly’s (Wasm) native support for 64 bit integer types and the [existing Wasm proposal](https://github.com/WebAssembly/memory64) to introduce a 64 bit memory address space, as detailed in the [original Go Wasm design doc](https://docs.google.com/document/d/131vjr4DH6JFnb-blm_uRdaC0_Nv3OUwjEY5qVCxCup4/edit#heading=h.pi4kwpb166zn). The assumption at the time was that the Wasm ecosystem was going to switch to a 64 bit address space over time, and that Go would benefit from having used a 64 bit architecture from the start.

On the browser side, [Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1727084) and [Chrome](https://bugs.chromium.org/p/v8/issues/detail?id=10949) both have experimental support for 64 bit memory, and it [seems poised](https://github.com/WebAssembly/memory64/issues/36#issuecomment-1469800748) to become stable in the coming year. However, on the server side, all existing hosts use a 32 bit architecture, and it has become clear that it is here to stay.

In most uses of Wasm with Go, the architecture is transparent to the user, but with the introduction of `go:wasmimport` in #38248 and #59149, knowing the memory layout of input and output parameters becomes very important, as described in #59156. A short term solution of restricting the types of input and output parameters to scalar types and `unsafe.Pointer` was adopted to make this clear to users, but this user experience is not a desirable long term solution.

As an example, the [fd_write](https://github.com/WebAssembly/WASI/blob/01255fd333137c902f74e6b0ad9693ae4694cce3/legacy/preview1/docs.md#fd_write) import from `wasi_snapshot_preview1` accepts an [*iovec](https://github.com/WebAssembly/WASI/blob/01255fd333137c902f74e6b0ad9693ae4694cce3/legacy/preview1/docs.md#ciovec_array) of this type:

```
type iovec struct {
    buf *byte
    len uint32
}
```

Because `GOARCH=wasm` has 64 bit pointers we have [currently defined](https://github.com/golang/go/blob/d110d7c42dd8025465153e4008ba807f1e69b359/src/runtime/os_wasip1.go#L40-L44C2) this type as:

```
type iovec struct {
    buf uint32
    len uint32
}
```

which gives the correct alignment but has issues:

* The conversion from `*byte` to `uint32` causes the compiler to lose track of the pointer, potentially causing the GC to reclaim the memory before the imported function is called. It requires using `runtime.KeepAlive` on inner pointers passed to imported function calls to ensure that they remain alive.

* It creates a poor and error prone user experience, as the conversion of the pointer type looks like this:

    ```
    iov := &iovec{
        buf: uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buf)))),
        len: uint32(len(buf)),
    }

    ```

This issue is already affecting early adopters of the wasip1 port: [Fastly SDK](https://github.com/fastly/compute-sdk-go/blob/c3a63de93dcb2cf090f431846d601c1302886c3e/internal/abi/prim/prim.go#L29-L40).

### Performance

A 32 bit architecture would allow the compiled Go code to be more performant, due to the effects of locality, the larger size of certain structs and the difficulty of avoiding bounds checking. It would also use less memory.

# Proposal

Create a new `GOARCH=wasm32` architecture which uses 32 bit pointers and integers. The architecture would only be supported in a new `wasip1/wasm32` port. `int`, `uint` and `uintptr` would all be 32 bit in length.

The [maintainers](https://github.com/orgs/golang/teams/wasm) of the existing `wasip1/wasm` port (@johanbrandhorst , @Pryz, @evanphx) volunteer to become maintainers of this new port.


# Discussion

The introduction of `GOARCH=wasm32` would allow us to write safer code, because the pointer size matches what the host expects:

```
type iovec struct {
    buf *byte // pointers are 32 bits
    len int   // int is 32 bits
}

iov := &iovec{
    buf: unsafe.SliceData(buf),
    len: len(buf),
}
```

In this case, the compiler can track the use of the slice pointer, and there is no risk that the GC will collect the objects before calling the imported function. There is also stronger type safety since there is no need to bypass the compiler to perform unsafe type conversions.

This provides a much improved user experience for users writing wrappers for host provided functions.

The existing `wasip1/wasm` port would remain and retain the `go:wasmimport` type restrictions. It may eventually become deprecated and removed, in accordance with the [Go porting policy](https://github.com/golang/go/wiki/PortingPolicy#removing-old-operating-system-and-architecture-versions). Such a change would be subject to a separate proposal.

There are no plans for introducing a `js/wasm32` port.