Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
target
go.mod
go.sum
*.wasm
wasi_*
wit_*
.crates.toml
.crates2.json

# TODO: Remove once wasip3 patch is merged into Big GO
go-*-bootstrap*
# examples/wasip2 stub
export_wasi_http_handler/wit_bindings.go
# examples/wasip3 stub
export_wasi_http_incoming_handler/wit_bindings.go

# Ignoring generated code
examples/*/go.mod
examples/*/go.sum
examples/*/wasi_*
examples/*/wit_*
examples/*/export_wasi*
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</div>

# Overview
## componentize-go
This is a tool to convert a Go application to a [WebAssembly component](https://github.com/WebAssembly/component-model). It takes the following as input:

- a [WIT](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) file or directory
Expand All @@ -21,11 +22,30 @@ This is a tool to convert a Go application to a [WebAssembly component](https://

The output is a component which may be run using e.g. [`wasmtime`](https://github.com/bytecodealliance/wasmtime).

## Installation
### Prerequisites
### Installation
#### Prerequisites
- [**Rust toolchain**](https://rust-lang.org/) - Latest version

### Run
#### Run
```sh
cargo install --git https://github.com/bytecodealliance/componentize-go
```

## go.bytecodealliance.org
This repository hosts the vanity import path redirects for `go.bytecodealliance.org`. The HTML files in the [docs/](docs/) directory are served via GitHub Pages.

### Actively Maintained Packages
| Import Path | Description |
|-------------|-------------|
| `go.bytecodealliance.org/wit_types` | WIT type definitions (Option, Result, Tuple, Future, Stream, etc.) |
| `go.bytecodealliance.org/wit_async` | Async primitives for WIT. |
| `go.bytecodealliance.org/wit_runtime` | Runtime support for WIT. |

### Legacy Packages:
These packages are located at [bytecodealliance/go-modules](https://github.com/bytecodealliance/go-modules)
| Import Path | Description |
|-------------|-------------|
| `go.bytecodealliance.org/cm` | Component Model types and utilities for use with TinyGo. |
| `go.bytecodealliance.org/wit` | TinyGo representation of the WIT (WebAssembly Interface Type) specification. |
| `go.bytecodealliance.org/cmd/wit-bindgen-go` | Legacy wit-bindgen-go for use with TinyGo. |
| `go.bytecodealliance.org/x` | Experimental packages. |
2 changes: 1 addition & 1 deletion docs/cmd.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<title>go.bytecodealliance.org/cmd</title>
<!-- Legacy: cmd package stays in go-modules -->
<!-- Legacy: for now, the cmd package stays in go-modules -->
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/go-modules" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org/cmd">
</head>
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<title>go.bytecodealliance.org</title>
<!-- Legacy: root module stays in go-modules -->
<!-- Legacy: for now, the root module stays in go-modules -->
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/go-modules" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org">
</head>
Expand Down
10 changes: 10 additions & 0 deletions docs/wit_async.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>go.bytecodealliance.org/cmd</title>
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/componentize-go" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org/wit_async">
</head>
<body>
<a href="https://pkg.go.dev/go.bytecodealliance.org/wit_async">Redirecting to documentation…</a>
</body>
</html>
10 changes: 10 additions & 0 deletions docs/wit_runtime.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>go.bytecodealliance.org/cmd</title>
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/componentize-go" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org/wit_runtime">
</head>
<body>
<a href="https://pkg.go.dev/go.bytecodealliance.org/wit_runtime">Redirecting to documentation…</a>
</body>
</html>
10 changes: 10 additions & 0 deletions docs/wit_types.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>go.bytecodealliance.org/cmd</title>
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/componentize-go" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org/wit_types">
</head>
<body>
<a href="https://pkg.go.dev/go.bytecodealliance.org/wit_types">Redirecting to documentation…</a>
</body>
</html>
2 changes: 1 addition & 1 deletion docs/x.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<title>go.bytecodealliance.org/x</title>
<!-- Legacy: x package stays in go-modules -->
<!-- Legacy: for now, the x package stays in go-modules -->
<meta name="go-import" content="go.bytecodealliance.org git https://github.com/bytecodealliance/go-modules" />
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.bytecodealliance.org/x">
</head>
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module go.bytecodealliance.org

go 1.25.5
229 changes: 229 additions & 0 deletions wit_async/wit_async.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package wit_async

import (
"fmt"
"runtime"
"unsafe"

"go.bytecodealliance.org/wit_runtime"
)

const EVENT_NONE uint32 = 0
const EVENT_SUBTASK uint32 = 1
const EVENT_STREAM_READ uint32 = 2
const EVENT_STREAM_WRITE uint32 = 3
const EVENT_FUTURE_READ uint32 = 4
const EVENT_FUTURE_WRITE uint32 = 5

const STATUS_STARTING uint32 = 0
const STATUS_STARTED uint32 = 1
const STATUS_RETURNED uint32 = 2

const CALLBACK_CODE_EXIT uint32 = 0
const CALLBACK_CODE_YIELD uint32 = 1
const CALLBACK_CODE_WAIT uint32 = 2

const RETURN_CODE_BLOCKED uint32 = 0xFFFFFFFF
const RETURN_CODE_COMPLETED uint32 = 0
const RETURN_CODE_DROPPED uint32 = 1

type unit struct{}

type taskState struct {
channel chan unit
waitableSet uint32
pending map[uint32]chan uint32
yielding chan unit
pinner runtime.Pinner
}

var state *taskState = nil

func Run(closure func()) uint32 {
state = &taskState{
make(chan unit),
0,
make(map[uint32]chan uint32),
nil,
runtime.Pinner{},
}
state.pinner.Pin(state)

defer func() {
state = nil
}()

go closure()

return callback(EVENT_NONE, 0, 0)
}

func Callback(event0, event1, event2 uint32) uint32 {
state = (*taskState)(contextGet())
contextSet(nil)

return callback(event0, event1, event2)
}

//go:linkname wasiOnIdle runtime.wasiOnIdle
func wasiOnIdle(callback func() bool)

func callback(event0, event1, event2 uint32) uint32 {
yielding := state.yielding
if state.yielding != nil {
state.yielding = nil
yielding <- unit{}
}

// Tell the Go scheduler to write to `state.channel` only after all
// goroutines have either blocked or exited. This allows us to reliably
// delay returning control to the host until there's truly nothing more
// we can do in the guest.
//
// Note that this function is _not_ currently part of upstream Go; it
// requires [this
// patch](https://github.com/dicej/go/commit/40fc123d5bce6448fc4e4601fd33bad4250b36a5)
wasiOnIdle(func() bool {
state.channel <- unit{}
return true
})
defer wasiOnIdle(func() bool {
return false
})

for {
switch event0 {
case EVENT_NONE:

case EVENT_SUBTASK:
switch event2 {
case STATUS_STARTING:
panic(fmt.Sprintf("unexpected subtask status: %v", event2))

case STATUS_STARTED:

case STATUS_RETURNED:
waitableJoin(event1, 0)
subtaskDrop(event1)
channel := state.pending[event1]
delete(state.pending, event1)
channel <- event2

default:
panic("todo")
}

case EVENT_STREAM_READ, EVENT_STREAM_WRITE, EVENT_FUTURE_READ, EVENT_FUTURE_WRITE:
waitableJoin(event1, 0)
channel := state.pending[event1]
delete(state.pending, event1)
channel <- event2

default:
panic("todo")
}

// Block this goroutine until the scheduler wakes us up.
(<-state.channel)

if state.yielding != nil {
contextSet(unsafe.Pointer(state))
if len(state.pending) == 0 {
return CALLBACK_CODE_YIELD
} else {
if state.waitableSet == 0 {
panic("unreachable")
}
event0, event1, event2 = func() (uint32, uint32, uint32) {
pinner := runtime.Pinner{}
defer pinner.Unpin()
buffer := wit_runtime.Allocate(&pinner, 8, 4)
event0 := waitableSetPoll(state.waitableSet, buffer)
return event0,
unsafe.Slice((*uint32)(buffer), 2)[0],
unsafe.Slice((*uint32)(buffer), 2)[1]
}()
if event0 == EVENT_NONE {
return CALLBACK_CODE_YIELD
}
}
} else if len(state.pending) == 0 {
state.pinner.Unpin()
if state.waitableSet != 0 {
waitableSetDrop(state.waitableSet)
}
return CALLBACK_CODE_EXIT
} else {
if state.waitableSet == 0 {
panic("unreachable")
}
contextSet(unsafe.Pointer(state))
return CALLBACK_CODE_WAIT | (state.waitableSet << 4)
}
}
}

func SubtaskWait(status uint32) {
subtask := status >> 4
status = status & 0xF

switch status {
case STATUS_STARTING, STATUS_STARTED:
if state.waitableSet == 0 {
state.waitableSet = waitableSetNew()
}
waitableJoin(subtask, state.waitableSet)
channel := make(chan uint32)
state.pending[subtask] = channel
(<-channel)

case STATUS_RETURNED:

default:
panic(fmt.Sprintf("unexpected subtask status: %v", status))
}
}

func FutureOrStreamWait(code uint32, handle int32) (uint32, uint32) {
if code == RETURN_CODE_BLOCKED {
if state.waitableSet == 0 {
state.waitableSet = waitableSetNew()
}
waitableJoin(uint32(handle), state.waitableSet)
channel := make(chan uint32)
state.pending[uint32(handle)] = channel
code = (<-channel)
}

count := code >> 4
code = code & 0xF

return code, count
}

func Yield() {
channel := make(chan unit)
state.yielding = channel
(<-channel)
}

//go:wasmimport $root [waitable-set-new]
func waitableSetNew() uint32

//go:wasmimport $root [waitable-set-poll]
func waitableSetPoll(waitableSet uint32, eventPayload unsafe.Pointer) uint32

//go:wasmimport $root [waitable-set-drop]
func waitableSetDrop(waitableSet uint32)

//go:wasmimport $root [waitable-join]
func waitableJoin(waitable, waitableSet uint32)

//go:wasmimport $root [context-get-0]
func contextGet() unsafe.Pointer

//go:wasmimport $root [context-set-0]
func contextSet(value unsafe.Pointer)

//go:wasmimport $root [subtask-drop]
func subtaskDrop(subtask uint32)
Loading
Loading