Repository: go
Issue: cmd/compile: relax wasm/wasm32 function import signature type constraints
Labels: Proposal,Proposal-Accepted,compiler/runtime

## Background

#59149 removed the package restrictions on the use of `go:wasmimport`, but established strict constraints on the types that can be used as input and result parameters. The motivation for this was that supporting rich types between the host and the client would require sophisticated and expensive runtime type conversions because of the mismatch between the 64 bit architecture of the client and the 32 bit architecture of the host.

With the upcoming [32 bit wasm port](https://github.com/golang/go/issues/63131), this problem goes away, as both client and host will use 32 bit pointers. However, we can also support a limited set of types on 64 bit platforms, where no runtime conversion is necessary.


## Proposal

Relax the constraints on types that can be used as input and result parameters with the `go:wasmimport` compiler directive. The exact allowed types would depend on whether `wasm` or `wasm32` is used.

We define the "small integer types" group as the set of types described by `[u]int[8|16]`. The following types would be allowed as input parameters to any `wasmimport`/`wasmexport` function:

* `bool`
* `int32`, `uint32`, `int64`, `uint64`
* `float32`, `float64`
* `string`
* `uintptr`, `unsafe.Pointer`, `*T` where `T` is an allowed parameter type or one of the small integer types.
* `*struct`. All fields of the `*struct` must be allowed parameter types, `struct`, `[...]T`, or one of the small integer types.
    * If the struct has any fields, it must embed `structs.HostLayout` (see #66408).
    * Any `struct` fields must also embed `structs.HostLayout` (recursively).
    * `*T`, `unsafe.Pointer`, `uintptr`, and `string` types are only allowed in `*struct` on `wasm32`.
* `*[...]T` where `T` is an allowed type or one of the small integer types.

All input parameter types except `string` are also allowed as result parameter types.

The following types would remain disallowed as input and output parameter types:

* `chan T`
* `complex64`, `complex128`
* `func`
* `interface`
* `map[T]U`
* `[]T`
* `struct`
* `[...]T`

The [conventions established for use of pointers in CGO](https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers) will be required when using pointers with `wasmimport`/`wasmexport`, e.g. the host can read Go memory, can write pointerless data (like the content of a byte buffer) but cannot write Go pointers to Go memory, and cannot hold on to Go pointers unless they are pinned.

## Discussion


### Compatibility guarantees

The Go spec does not specify the struct layout and leaves it up to implementations to decide. As such, we cannot provide a guaranteed ABI without having to change the spec or force future layout changes to provide runtime conversion of data. This proposal suggests making it clear to users through documentation that there are no guarantees of compatibility across versions of the Go compiler.


### Type conversion rules

The following conversion rules would be automatically applied by the compiler for the respective parameter type:

<table>
  <tr>
   <td>Go Type
   </td>
   <td> Parameter type (per <a href="https://webassembly.github.io/spec/core/syntax/types.html">Wasm spec</a>)
   </td>
  </tr>
  <tr>
   <td><code>bool</code>
   </td>
   <td><code>i32</code>
   </td>
  </tr>
  <tr>
   <td><code>int32, uint32</code>
<p>
<code>int64, uint64</code>
   </td>
   <td><code>i32, i32</code>
<p>
<code>i64, i64</code>
   </td>
  </tr>
  <tr>
   <td><code>float32, float64</code>
   </td>
   <td><code>f32, f64</code>
   </td>
  </tr>
  <tr>
   <td><code>string</code>
   </td>
   <td>Assigned to two call parameters as a <code>(i32, i32)</code> tuple of (pointer, len). Only allowed for input parameters.
   </td>
  </tr>
  <tr>
   <td><code>uintptr</code>, <code>unsafe.Pointer</code>, <code>*T</code>, <code>*struct</code>, <code>*[...]T</code>
   </td>
   <td><code>i32, i32, i32, i32, i32</code>
   </td>
  </tr>
</table>

### Strings

Strings are not allowed as result parameters as Wasm practically [does not allow more than 1 result parameter](https://go.googlesource.com/go/+/refs/tags/go1.23.0/src/cmd/internal/obj/wasm/wasmobj.go#219).

### Supporting `GOARCH=wasm`

The `wasm` architecture uses 64 bit pointers and integer sizes. As the host uses 32 bit pointers, this makes it impossible to allow certain types without costly runtime conversions, such as `*struct` types containing pointer fields. Since `string` types are also pointer types, `*struct` types containing `string` fields are also disallowed.

### Supporting `[u]int`, `[u]int8`, `[u]int16` as concrete parameters

The `[u]int` types are problematic as the size of them are not precisely defined, and may cause confusion when used with strictly 32 bit or 64 bit integers. The `[u]int8` and `[u]int16` types are problematic because we would be forced to automatically convert them to/from the `i32` wasm representation, with potential loss of precision or overflow. They are still allowed as pointer type, array elements and struct fields.

### Supporting slices, maps

Both slices and maps are disallowed because of the uncertainty around the memory underlying these types and interactions with struct and array rules. Users who wish to use slices can manually use `(&slice, len(slice))` or `unsafe.Pointer`. There is no clear way to support passing or returning map data from the host other than by using `unsafe.Pointer` and making assumptions about the underlying data.

## Related proposals

### struct.Hostlayout

#66408 proposes a way for users to request that struct layout is _host compatible_. Our proposal depends on the definitions put forward in this proposal for `struct` parameters.

## Future work


### WASI Preview 2 (AKA WASI 0.2)

[WASI Preview 2](https://github.com/WebAssembly/WASI/blob/main/preview2/README.md) defines its API in terms of the [Component Model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md), with a rich [type system](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) and an IDL language, [WIT](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md). The Component Model also defines a [Canonical ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md) with a specification for lifting and lowering Component Model types into and out of linear memory. This proposal does not attempt to define the ABI for any hypothetical wasip2 target, and would leave such decisions for any future wasip2 proposal.

### Supporting `struct` and `[...]T` by value

A previous version of this proposal included support for passing `struct` and `[...]T` types by value by expanding each field recursively into call parameters. This was removed in favor of a simpler initial implementation but could be re-added if users require it.

## Contributors

@johanbrandhorst, @evanphx, @achille-roussel, @dgryski, @ydnar 

CC @cherrymui @golang/wasm 

<!-- Generated by Oscar. DO NOT EDIT. {"bot":"gabyhelp","kind":"overview"} -->
@gabyhelp's overview of this issue: https://github.com/golang/go/issues/66984#issuecomment-2593973519
<!-- oscar-end -->