Description
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, 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
whereT
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 structs: add HostLayout "directive" type #66408). - Any
struct
fields must also embedstructs.HostLayout
(recursively). *T
,unsafe.Pointer
,uintptr
, andstring
types are only allowed in*struct
onwasm32
.
- If the struct has any fields, it must embed
*[...]T
whereT
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 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:
Go Type | Parameter type (per Wasm spec) |
bool
|
i32
|
int32, uint32
|
i32, i32
|
float32, float64
|
f32, f64
|
string
|
Assigned to two call parameters as a (i32, i32) tuple of (pointer, len). Only allowed for input parameters.
|
uintptr , unsafe.Pointer , *T , *struct , *[...]T
|
i32, i32, i32, i32, i32
|
Strings
Strings are not allowed as result parameters as Wasm practically does not allow more than 1 result parameter.
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 defines its API in terms of the Component Model, with a rich type system and an IDL language, WIT. The Component Model also defines a Canonical ABI 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
@gabyhelp's overview of this issue: #66984 (comment)
Metadata
Metadata
Assignees
Type
Projects
Status
Status