Skip to content

cmd/compile: allow the use of go:wasmimport globally #59149

Closed

Description

@johanbrandhorst

Background

#38248 defined a new compiler directive, go:wasmimport, for declaring a dependency on a function provided by the runtime environment. It also defined a restriction on the use of this compiler directive, such that it cannot be used in code outside of the runtime and syscall/js standard library packages. #58141 relaxed this restriction to allow the use of the directive in the syscall package to allow accessing WASI defined syscall APIs necessary to support WASI.

The go:wasmimport directive is defined as follows:

//go:wasmimport importmodule importname

Where importmodule is the Wasm module of the function and importname is the name of the function within the module. The effect of this compiler directive is that any Go function calls of the function will be replaced with a call instruction to the function identified by the module and function name during compilation. Parameters and return values to the Go function are translated to Wasm according to the following table:

Go value Wasm value
int32, uint32 i32
int64, uint64 i64
float32 f32
float64 f64
unsafe.Pointer i32

Any other parameter types are disallowed by the compiler.

For more information see the ssagen compiler package.

Proposal

We propose that the package restriction on the use of the go:wasmimport directive be removed entirely.

Rationale

Much of the discussion around the WASI proposal (#58141) revolved around the question of how and when the WASI target should move between syscall APIs (i.e. from depending on the wasm_snapshot_preview1 API to the wasm_snapshot_preview2 API and beyond). A major reason for this anxiety is because of the conflict between the desire for stability and the desire for access to newer runtime features afforded by newer syscall APIs.

One way to help avoid this conflict would be to provide access to newer APIs through purpose built third-party packages (e.g. golang.org/x/wasi/preview2/net) implementing various syscall level APIs through the use of go:wasmimport and exposing Go native types implementing interfaces such as net.Listener and net.Conn. Users who want to opt-in to new functionality can import one of these packages with the understanding that it may be unstable. Users who prefer to wait to use this functionality until it’s in the standard library can stay on the older API version. We’re exploring adding functionality to wit-bindgen to generate Go files with go:wasmimport directives and function signatures.

In addition to this, it would also allow users or vendors to create their own packages around vendor specific runtime extensions (e.g. WasmEdge defines its own sockets API, Fermyon defines a custom HTTP handler API) and expose them as easily consumable Go modules. This would help make Go more flexible and useful in different Wasm runtime environments.

Discussion

Rationale for the existing restriction

The existing restriction was added as a last minute addition to the original proposal. The primary rationale for this seems to have been an anxiety around exposing this widely without having had any experience of using it. If changes had to be made, it could break existing users.

We believe this concern should be smaller now that we have experience of using the directive while implementing go:wasmimport and working on implementing the WASI target.

Regardless, we do want to reserve the right to make breaking changes to this directive until such a point as the wasm architecture itself is declared stable. This is in line with the reservations made in #38248.

Effect on js/wasm

The js/wasm target has similar functionality exposed via the use of the syscall/js standard library package. The existing standard library interfaces will remain the preferred way of accessing functions defined on the JavaScript host. The primary use case within the js/wasm target would be as a means of accessing functions defined by other Wasm modules loaded into the same environment.

A note on wasm module names

The Wasm Core 1.0 spec defines a name as used in module and function names as a sequence of UTF-8 codepoints. The current definition of the go:wasmimport directive uses a space to separate the module name from the function name, making it impossible to specify a module or function name with a space in it. This proposal does not suggest changing the definition to support this use case.

Other language implementations

TinyGo

TinyGo uses the export pragma both for exporting to and importing methods from the environment. It also allows the user to specify the module to import from using the //go:wasm-module pragma. An example of this in the wild is the Fastly Compute@Edge SDK:

//go:wasm-module fastly_abi
//export init
//go:noescape
func fastlyABIInit(abiVersion prim.U64) FastlyStatus

TinyGo similarly restricts the types supported in parameters though I have struggled to find documentation for this exact limitation.

Rust

Rust uses the extern "C" raw interface pattern to define Wasm host imports. This is similar to our proposal in that it allows the user to specify the module and function name.

#[link(wasm_import_module = "the-wasm-import-module")]
extern "C" {
    // imports the name `foo` from `the-wasm-import-module`
    fn foo();

    // functions can have integer/float arguments/return values
    fn translate(a: i32) -> f32;

    // Note that the ABI of Rust and wasm is somewhat in flux, so while this
    // works, it's recommended to rely on raw integer/float values where
    // possible.
    fn translate_fancy(my_struct: MyStruct) -> u32;

    // you can also explicitly specify the name to import, this imports `bar`
    // instead of `baz` from `the-wasm-import-module`.
    #[link_name = "bar"]
    fn baz();
}

Rust supports using rich types in arguments but does not provide guarantees of the stability and encourages the use of integer/float values only.

Future work

Richer type support for imported functions

The current implementation of go:wasmimport supports only functions with integer, floating point or pointer parameters and a single return value. In the future, we would like to support types such as strings and structs and multiple return values. This would require defining a more elaborate calling convention for the imported functions than the one currently used.

The current mechanism follows the same semantics in use by other Wasm compilers such as Clang, which effectively treat Wasm the same as C, requiring the caller and callee to share the same ABI to be able to decode values in memory.

A 32 bit Wasm port

During the discussion of #59156, it became evident that many of the UX issues with the directive would disappear if the Wasm port was a 32 bit port, since all existing Wasm runtimes use 32 bit pointers, and the mismatch between the memory layout of Go and Wasm would disappear. There are still some concerns around whether the Go struct layout may change in the future, which would prevent us from guaranteeing direct memory mapping.

In the case of a future 32 bit Wasm port, the restriction on types allowed in the go:wasmimport parameters would be relaxed in a way that doesn't risk introducing memory corruption, but provides significant UX improvements to the compiler directive. The current wasm architecture, which is a 64 bit port, will likely retain the parameter limitations for the forseeable future.

64 bit Wasm runtimes

In the future, some runtimes may add support for 64 bit pointers. In such a runtime, the ABI translation rules for the use of the unsafe.Pointer type may change. We would evaluate the effects of this when/if adding support for 64 bit pointers.

Authors

@johanbrandhorst, @Pryz, @evanphx, @achille-roussel

Activity

  1. moved this to Incoming in Proposalson Mar 20, 2023
  2. cherrymui commented on Mar 20, 2023

    @cherrymui
    Member
    Pointer i32

    Currently the implementation uses 32-bit linear memory. But that might change in the future? Does this have any impact to this? Thanks.

  3. evanphx commented on Mar 20, 2023

    @evanphx
    Contributor

    @cherrymui There is the 64bit memory proposal for web assembly, so in the future yes. One reason we used i32 here is to remain similar to what clang and friends use for pointers. We certainly could use i64 today to avoid future concerns though.

  4. cherrymui commented on Mar 20, 2023

    @cherrymui
    Member

    Thanks. Using i32 for now is probably fine. The concern is that if later we change to 64-bit address, will this break any user? Maybe it will not if the Wasm engine is backward compatible? Do you think there is any compatibility issue? If not, this is probably okay.

  5. evanphx commented on Mar 20, 2023

    @evanphx
    Contributor

    @cherrymui Ah great question. Let me dig into the 64bit memory proposal and see what they suggest, since this would effect everyone that generates wasm if they all need to compile different 32 vs 64 bit versions.

  6. johanbrandhorst commented on Mar 20, 2023

    @johanbrandhorst
    MemberAuthor

    I've raised #59156 which should clarify some things around behavior of pointers when using go:wasmimport. This proposal should only go ahead once #59156 has been resolved.

  7. changed the title [-]cmd/compile: allow the use of go:wasmimport globally[/-] [+]proposal: cmd/compile: allow the use of go:wasmimport globally[/+] on Mar 20, 2023
  8. added this to the Proposal milestone on Mar 20, 2023
  9. johanbrandhorst commented on Apr 7, 2023

    @johanbrandhorst
    MemberAuthor

    I've added a section to the proposal that disallows any parameter types other than those that easily map to Wasm types. This restriction could be relaxed in a hypothetical future wasm32 port, but will likely remain in the current port until we have a better idea of what a 64 bit Wasm runtime means. See #59156 for more details on this discussion, but I believe this unblocks further discussion in this proposal.

  10. cherrymui commented on Apr 7, 2023

    @cherrymui
    Member

    Thanks. SGTM. Maybe add a note that if Wasm moves to 64-bit pointers, the ABI translation rule for unsafe.Pointer might change?

    A minor question: if the directive is seen when compiling to a non-Wasm platform, is it an error or just silently ignored?

  11. 25 remaining items

  12. modified the milestones: Backlog, Go1.21 on Jun 4, 2023
  13. added a commit that references this issue on Apr 29, 2024
    aff2f4a
  14. removed this from Proposalson May 15, 2024
  15. locked and limited conversation to collaborators on Jun 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions