Skip to content
Open
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
2 changes: 2 additions & 0 deletions component-model/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- [Interfaces](./design/interfaces.md)
- [Worlds](./design/worlds.md)
- [Packages](./design/packages.md)
- [Async, Streams, and Futures](./design/async.md)
- [WIT By Example](./design/wit-example.md)
- [WIT Reference](./design/wit.md)
- [Migrating from WASI 0.2 to WASI 0.3](./design/migrating-to-p3.md)

# Using WebAssembly Components

Expand Down
65 changes: 64 additions & 1 deletion component-model/src/advanced/canonical-abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,69 @@

An ABI is an **application binary interface** - an agreement on how to pass data around in a binary format. ABIs are specifically concerned with data layout at the bits-and-bytes level. For example, an ABI might define how integers are represented (big-endian or little-endian?), how strings are represented (pointer to null-terminated character sequence or length-prefixed? UTF-8 or UTF-16 encoded?), and how composite types are represented (the offsets of each field from the start of the structure).

The component model defines a **canonical ABI** - an ABI to which all [components](../design/components.md) adhere. This guarantees that components can talk to each other without confusion, even if they are built in different languages. Internally, a C component might represent strings in a quite different way from a Rust component, but the canonical ABI provides a format for them to pass strings across the boundary between them.
The Component Model defines a **canonical ABI** - an ABI to which all [components](../design/components.md) adhere. This guarantees that components can talk to each other without confusion, even if they are built in different languages. Internally, a C component might represent strings in a quite different way from a Rust component, but the canonical ABI provides a format for them to pass strings across the boundary between them.

> For a more formal definition of what the Canonical ABI is, take a look at the [Canonical ABI explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md).

## Native async extensions

WASI 0.3 added [`async func`, `stream<T>`, and `future<T>`](../design/async.md) as Canonical ABI primitives.
Supporting them required extending the ABI itself, since the existing rules assumed that every interface call returned synchronously.
This section sketches what those extensions are;
for the full specification, see the upstream [Concurrency explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md)
and [Canonical ABI explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md).

### Async function ABI

A WIT function declared `async` gets a *non-blocking core function signature* in addition to the existing (synchronous) one.
Both signatures stay available,
so a sync caller can invoke an async callee and an async caller can invoke a sync callee
without either side having to adopt the other's calling convention.

In the synchronous lowering (the mapping from a WIT-level signature to actual core Wasm function parameters and results),
parameters and return values are passed as flat core Wasm types when they fit,
or via linear-memory pointers (an *in-pointer* for parameters, an *out-pointer* for the result) otherwise.
The async lowering adds an `i32` status code as the actual function return value;
the logical return value lands at the caller-provided out-pointer once the call completes.
The low bits of the status code distinguish three cases:

- The call has not yet started reading its parameters.
- The call has read its parameters but not yet written its result.
- The call has returned, with both parameters and result memory consumed.

The runtime represents each in-flight async call as a *subtask*.
The caller can wait on a single subtask or on any of a *waitable set*;
the corresponding wait built-ins are listed below.
When the runtime signals completion the caller may resume and consume the result.

### Streams and futures across the boundary

`stream<T>` and `future<T>` are Canonical ABI *values* rather than resources.
At the wire level, each end is represented by an integer index into a per-component handle table (the same general mechanism resources use), with the value-vs-resource distinction showing up in the ownership rules below rather than in the encoding.
Each has two ends: a *readable* end and a *writable* end.
Ownership rules are direct:

- A component that creates a stream or future via the new `stream.new` or `future.new` built-ins receives both ends.
- A component that *receives* a stream or future from another component or the host gets unique ownership of the *readable* end.
- A component that *passes* a stream or future across the boundary transfers ownership of the readable end.

Writable ends are sticky: they stay with the component that created the stream or future and can't be transferred across boundaries.

Core Wasm code reads from streams via the `stream.read` built-in and writes via `stream.write`, passing a linear-memory buffer.
These built-ins are *completion-based*: a call either copies values into or out of the buffer immediately,
or returns a "blocked" sentinel indicating that the operation will continue concurrently.
Futures use the analogous `future.read` and `future.write`.

This is the same shape as OS-level completion-based I/O (`io_uring` on Linux, Overlapped I/O on Windows)
which is why bindings generators can map streams and futures onto a host language's existing concurrency primitives without extra plumbing.

### Other built-ins

Async support adds several other Canonical ABI built-ins:

- `task.return` for the export side of an async function to deliver its result.
- `task.cancel` for cancelling an in-flight subtask.
- *Waitable-set* primitives for waiting on or selecting among multiple in-flight subtasks (the mechanism used to consume async call results).

The full enumeration lives in the [Canonical ABI explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md);
the [Concurrency explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md) covers the design rationale and the broader concurrency model.
65 changes: 65 additions & 0 deletions component-model/src/design/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Async, Streams, and Futures

WASI 0.3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream<T>`, and `future<T>`. Together, they let interfaces express asynchronous operations that compose across component boundaries.

For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves.

## Native async

The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI 0.3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them.

That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one.

Native primitives close the gap. With `async func`, `stream<T>`, and `future<T>` in the Canonical ABI, scheduling and wake-up propagation become the runtime's job rather than any individual component's. Components can pass futures and streams along the chain without keeping their own event loops running to relay readiness.

## The three primitives

### `async func`

A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop.

```wit
handle: async func(request: request) -> result<response, error-code>;
```

Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python.

### `stream<T>`

A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream<T>` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups.

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

### `future<T>`

A typed handle for a single value that will become available later. Like `stream<T>`, `future<T>` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future<T>` does not block; the caller awaits the result when it needs it.

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## How the primitives work in WASI 0.3

### Stream plus terminal future

Reads return both a data channel and a completion handle, packed into a tuple:

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

The two halves are independent. The caller can consume the stream eagerly, sample it, or drop it part-way through; either way the future resolves once the operation has terminated, carrying the success-or-failure outcome. The same shape appears in stdin, filesystem reads, TCP receives, and directory listings.

### Stream parameter, future return

Writes use the symmetric shape: the guest supplies the data as a `stream<u8>` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape:

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## Where to go next

For an end-to-end Rust example that uses these primitives in practice, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For runtime support and CLI flags, see [Wasmtime](../running-components/wasmtime.md). For the WIT syntax in detail, see [WIT Reference](./wit.md).
4 changes: 4 additions & 0 deletions component-model/src/design/component-model-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ For example, the [wasi-http](https://github.com/WebAssembly/WASI/blob/main/propo
an `imports` world encapsulating the interfaces that an HTTP proxy depends on,
and a `proxy` world that depends on `imports`.

### Async, Streams, and Futures

The Component Model includes [`async func`, `stream<T>`, and `future<T>`](./async.md) as native Canonical ABI primitives, introduced alongside WASI 0.3. Together, they let interfaces express asynchronous operations that compose across component boundaries.

### Platforms

In the context of WebAssembly, a _host_ refers to a WebAssembly runtime
Expand Down
107 changes: 107 additions & 0 deletions component-model/src/design/migrating-to-p3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Migrating from WASI 0.2 to WASI 0.3

WASI 0.3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream<T>`, and `future<T>`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives.

This page covers the mapping between concepts in WASI 0.2 and WASI 0.3. For a WIT-level comparison of every WASI 0.3 interface, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev.

## Do you need to migrate?

Not immediately. WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want:

- Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away).
- The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two.
- First-class support in 0.3-targeted toolchains as they continue to land.

## Concept mapping

WASI 0.3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one:

| WASI 0.2 (`wasi:io`) | WASI 0.3 (Component Model) |
| -------------------------------- | ---------------------------------------- |
| `resource pollable` | `future<T>` |
| `resource input-stream` | `stream<u8>` |
| `resource output-stream` | `stream<u8>` (passed *into* the call) |
| `poll(list<pollable>)` | `await` on a future |
| `subscribe()` on a resource | return a `future` from the call |
| `start-foo` / `finish-foo` | a single `func` or `async func` |

## What changed in WIT

### Stream-plus-future for reads

A WASI 0.2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. WASI 0.3 splits those concerns: the call returns a `stream<u8>` for the data and a `future<result<_, error-code>>` for the outcome, packed into a tuple.

```wit
// WASI 0.2 (filesystem read)
read-via-stream: func(offset: filesize) -> result<input-stream, error-code>;

// WASI 0.3 (filesystem read)
read-via-stream: func(offset: filesize) -> tuple<stream<u8>, future<result<_, error-code>>>;
```

In WASI 0.3, the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way.

### Write-direction flip

WASI 0.2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. WASI 0.3 inverts that: the guest supplies the data as a `stream<u8>` value, and the host returns a `future` that resolves once it has finished consuming the stream.

```wit
// WASI 0.2: receive an output-stream resource, write into it
get-stdout: func() -> output-stream;

// WASI 0.3: pass a stream value in, receive a completion future
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

### Two-step calls collapsed

WASI 0.2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. WASI 0.3 collapses each pair into a single call:

```wit
// WASI 0.2
start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;

// WASI 0.3
connect: async func(remote-address: ip-socket-address) -> result<_, error-code>;
```

The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`).

## Interface highlights

The complete per-interface diff lives on [WASI 0.3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are:

- **`wasi:io` is gone.** The package has no 0.3.0 release. Every resource it exposed (`pollable`, `input-stream`, `output-stream`) is replaced by a Component Model primitive, per the [concept mapping](#concept-mapping) above.
- **`wasi:http` collapses from nine resources to two.** The incoming/outgoing × request/response/body matrix plus `future-trailers`, `future-incoming-response`, and `response-outparam` all become `request` and `response`, with `stream<u8>` bodies and a `future` for trailers. The handler is now an `async func`:

```wit
// WASI 0.2
handle: func(request: incoming-request, response-out: response-outparam);

// WASI 0.3
handle: async func(request: request) -> result<response, error-code>;
```

The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler.
- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven WASI 0.2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream<tcp-socket>` directly instead of requiring a separate `accept` loop.

Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above.

## Tooling requirements

| Tool | Minimum | Notes |
| ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
| Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. |
| `wit-bindgen` | 0.46+ | Use the `async` feature for 0.3 binding generation. |
| jco | latest | 0.3 host bindings ship in the `preview3-shim` package. |
| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. |
| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. |

> **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh.

## Further reading

- [Async, Streams, and Futures](./async.md) — the conceptual foundation
- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the 0.3 `async fn run()` pattern
- [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface
Loading