Skip to content

Conversation

@dicej
Copy link
Collaborator

@dicej dicej commented Dec 3, 2025

This re-introduces Go support, with a new implementation written from scratch. While the previous incarnation targeted TinyGo and reused the C generator for lifting and lowering, this one targets "big" Go and handles lifting and lowering itself. In addition, it supports idiomatic, goroutine-based concurrency on top of the component model async ABI, including streams and futures.

This implementation is also distinct from the
go-modules project, which has a number of limitations (e.g. no async support, not safe for use with "big" Go) and is no longer being actively maintained.

Note that the async support currently requires a small patch to the Go runtime library. I plan to work with the upstream project to make that patch unecessary in the future. Components that don't use async features should work with stock, unpatched Go.

One of the tricky parts about lowering values and passing pointers to e.g. {stream,future}.{read,write} is that we must tell the Go garbage collector to pin the pointers (i.e. mark the pointee as both immovable and uncollectable) for as long as the host has access to them, but then unpin them promptly afterward to avoid leaks. We do this using runtime.Pinner instances, most of which are locally scoped and easy to reason about. However, we must use a couple of globally-scoped pinners as well: one for cabi_realloc allocations by the host when it lowers parameters (which we unpin after lifting those parameters) and another for returning values from sync-lifted exports (which we unpin in post-return functions).

There are a couple of known missing features which I'll open GitHub issues for:

  1. Resource handles are not being restored for unwritten items in the case of an incomplete stream or future write.
  2. Importing and/or exporting multiple versions of the same package will cause name clashes.

In addition, I plan to expand the test coverage beyond the basics covered in this commit.

@dicej dicej requested a review from alexcrichton December 3, 2025 23:50
@dicej
Copy link
Collaborator Author

dicej commented Dec 3, 2025

I just realized I need to make CI run the go tests. I'll push an update.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed we'll want to ge this running on CI, but overall looking great! I only skimmed codegen though and would mostly lean on tests to exercise bits and bobs

@dicej
Copy link
Collaborator Author

dicej commented Dec 4, 2025

I was so focused on the runtime tests that I never ran the codegen ones. Looks like I have more work to do. I'll switch this to draft mode while I do that.

@dicej dicej marked this pull request as draft December 4, 2025 19:47
alexcrichton added a commit to alexcrichton/wit-bindgen that referenced this pull request Dec 4, 2025
This commit switches the way tests are built such that all binaries
produced are "reactors" or those without a `main` function. This ensures
that all imports/exports are fully described with WIT in this repository
as opposed to implicitly relying on `wasi:cli/run`. This additionally
means that testing in this repository no longer relies on toolchain
conventions for the `wasi:cli/run` world making testing a bit more
self-contained here, especially in the context of async.

This notably came up on bytecodealliance#1447 which should make some of the tests there
easier to write and more idiomatic.
github-merge-queue bot pushed a commit that referenced this pull request Dec 5, 2025
This commit switches the way tests are built such that all binaries
produced are "reactors" or those without a `main` function. This ensures
that all imports/exports are fully described with WIT in this repository
as opposed to implicitly relying on `wasi:cli/run`. This additionally
means that testing in this repository no longer relies on toolchain
conventions for the `wasi:cli/run` world making testing a bit more
self-contained here, especially in the context of async.

This notably came up on #1447 which should make some of the tests there
easier to write and more idiomatic.
@dicej dicej force-pushed the go branch 2 times, most recently from 77b27d5 to 03bd027 Compare December 5, 2025 23:11
@dicej dicej marked this pull request as ready for review December 5, 2025 23:11
@dicej dicej force-pushed the go branch 2 times, most recently from 990c86b to 5602d38 Compare December 5, 2025 23:15
This re-introduces Go support, with a new implementation written from scratch.
While the previous incarnation targeted TinyGo and reused the C generator for
lifting and lowering, this one targets "big" Go and handles lifting and lowering
itself.  In addition, it supports idiomatic, goroutine-based concurrency on top
of the component model async ABI, including streams and futures.

This implementation is also distinct from the
[go-modules](https://github.com/bytecodealliance/go-modules) project, which has
a number of limitations (e.g. no async support, not safe for use with "big" Go)
and is no longer being actively maintained.

Note that the async support currently requires [a small
patch](dicej/go@a1c8322)
to the Go `runtime` library.  I plan to work with the upstream project to make
that patch unecessary in the future.  Components that don't use async features
should work with stock, unpatched Go.

One of the tricky parts about lowering values and passing pointers to
e.g. `{stream,future}.{read,write}` is that we must tell the Go garbage
collector to pin the pointers (i.e. mark the pointee as both immovable and
uncollectable) for as long as the host has access to them, but then unpin them
promptly afterward to avoid leaks.  We do this using `runtime.Pinner` instances,
most of which are locally scoped and easy to reason about.  However, we must use
a couple of globally-scoped pinners as well: one for `cabi_realloc` allocations
by the host when it lowers parameters (which we unpin after lifting those
parameters) and another for returning values from sync-lifted exports (which we
unpin in post-return functions).

There are a couple of known missing features which I'll open GitHub issues for:

1. Resource handles are not being restored for unwritten items in the case of an incomplete stream or future write.
2. Importing and/or exporting multiple versions of the same package will cause name clashes.

In addition, I plan to expand the test coverage beyond the basics covered in
this commit.

Signed-off-by: Joel Dice <[email protected]>

add ping-pong Go tests

Signed-off-by: Joel Dice <[email protected]>

address review feedback

Signed-off-by: Joel Dice <[email protected]>

add wit-bindgen-go to ci/publish.rs

Signed-off-by: Joel Dice <[email protected]>

enable Go in test CI matrix

I'm assuming the default version of Go on the GitHub worker image will be
sufficient; if not, we can install a different one explicitly.

I'm not enabling the async tests for Go yet, since that will require publishing
and installing a build of Go with [this
patch](dicej/go@a1c8322);
I'll need to do some homework to find out the best way to do that.

Signed-off-by: Joel Dice <[email protected]>

fix codegen tests

Signed-off-by: Joel Dice <[email protected]>

update Go runtime tests to use `run` export

Signed-off-by: Joel Dice <[email protected]>

run cargo fmt

Signed-off-by: Joel Dice <[email protected]>

remove `publish = false` from crates/go/Cargo.toml

Signed-off-by: Joel Dice <[email protected]>

temporarily skip async codegen tests for Go

Signed-off-by: Joel Dice <[email protected]>

add `async = true` config directive to `async-trait-function.wit`

Signed-off-by: Joel Dice <[email protected]>

indicate that `async-trait-function.wit-no-std` works for Rust

Signed-off-by: Joel Dice <[email protected]>

add error contexts to `Go::compile`

Signed-off-by: Joel Dice <[email protected]>

add `actions/setup-go` to workflow

Signed-off-by: Joel Dice <[email protected]>

add finalizers where appropriate; tweak `StreamReader` API

Signed-off-by: Joel Dice <[email protected]>

update workspace to 2024 edition

Signed-off-by: Joel Dice <[email protected]>
@dicej dicej enabled auto-merge December 6, 2025 00:46
@dicej dicej added this pull request to the merge queue Dec 6, 2025
Merged via the queue into bytecodealliance:main with commit 4284ea8 Dec 6, 2025
25 checks passed
@dicej dicej deleted the go branch December 6, 2025 01:14
@ydnar
Copy link

ydnar commented Dec 11, 2025

Thanks to @dgryski for pointing me to this!

@dicej this looks great. We solved the multiple versions issue by optionally generating a versioned Go package, e.g. wasi/http/v0.2.1.

Does this require the WASI 0.1 adapter to function in big Go?

@dicej
Copy link
Collaborator Author

dicej commented Dec 11, 2025

@dicej this looks great. We solved the multiple versions issue by optionally generating a versioned Go package, e.g. wasi/http/v0.2.1.

Good idea. Currently we generate package names like wasi_http_0_2_1_types because I'm a Go novice, but versioning the Go package makes more sense.

Does this require the WASI 0.1 adapter to function in big Go?

Yes, currently. I've opened golang/go#76775 as a baby step towards adding GOOS=wasip3 upstream. Meanwhile, you can use any p2 or p3 features you like by generating bindings from the WASI WIT files and using them directly.

BTW, here's an example that shows off concurrent outbound requests, etc. Eventually we'll want to do something like you did here to make that use net/http interfaces, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants