diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index f0f1e37e..90cbb740 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -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 diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md new file mode 100644 index 00000000..9ec67c1a --- /dev/null +++ b/component-model/src/design/async.md @@ -0,0 +1,84 @@ +# Native Async with WASI 0.3 + +WASI 0.3 adds new Canonical ABI primitives to the Component Model that enable async functionality. Components that target WASI 0.3 can use the new features in their WIT files: +* `async func` +* `stream` +* `future` + +These new types 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 a closer look at the WASI 0.3 release, including a 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. + +> [!NOTE] +> WASI 0.3 builds on WASI 0.2 rather than replacing it. Runtimes can host both versions side by side, and a 0.3 host can polyfill 0.2 imports at the boundary, so applications can migrate incrementally as toolchains and dependencies land 0.3 support. + +## The async problem that WASI 0.3 solves + +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 async primitives help close this expressivity gap. With updated Component ABI mechanics that enable `async func`, `stream`, and `future` available at the WIT level, scheduling and wake-up propagation become the runtime's job rather than any individual component's. + +Components can pass futures and streams along without keeping their own event loops running to relay readiness, as was necessary with WASI 0.2. + +## Async functions, Streams, and Futures + +### Async Functions (`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. + +```diff +- handle: func(request: request) -> result; ++ handle: async func(request: request) -> result; +``` + +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. + +### Streams (`stream`) + +A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource (as opposed to WASI 0.2) -- 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 one or more intermediate components without those components having to relay any wake-ups. + +```wit +read-via-stream: func() -> tuple, future>>; +``` + +### Futures (`future`) + +A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. + +Note that synchronous functions which return `future`s *cannot* block; the caller can await the result when it needs it. + +```wit +write-via-stream: func(data: stream) -> future>; +``` + +## A look at async patterns in WASI 0.3 + +### Stream plus terminal future + +Reads return both a data channel and a completion handle, packed into a tuple ([`read-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L308) in `wasi-filesystem`): + +```wit +read-via-stream: func() -> tuple, future>>; +``` + +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` 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 ([`write-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L320) in `wasi-filesystem`): + +```wit +write-via-stream: func(data: stream) -> future>; +``` + +## 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). diff --git a/component-model/src/design/component-model-concepts.md b/component-model/src/design/component-model-concepts.md index c4b67924..dd9c4f3b 100644 --- a/component-model/src/design/component-model-concepts.md +++ b/component-model/src/design/component-model-concepts.md @@ -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 + +New Component Model primitives that enable use of [`async func`, `stream`, and `future`](./async.md), were 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 diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md new file mode 100644 index 00000000..112f29eb --- /dev/null +++ b/component-model/src/design/migrating-to-p3.md @@ -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`, and `future`. 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.2 can be used in hosts just as before, WASI 0.3 is a purely additive change. + +Separately, 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#the-async-problem-that-wasi-03-solves) 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` | +| `resource input-stream` | `stream` | +| `resource output-stream` | `stream` (passed *into* the call) | +| `poll(list)` | `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` for the data and a `future>` for the outcome, packed into a tuple. + +```wit +// WASI 0.2 (filesystem read) +read-via-stream: func(offset: filesize) -> result; + +// WASI 0.3 (filesystem read) +read-via-stream: func(offset: filesize) -> tuple, future>>; +``` + +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` 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) -> future>; +``` + +### 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, remote-address: ip-socket-address) -> result<_, error-code>; +finish-connect: func() -> result, 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` 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; +``` + +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` 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 | 46+ | WASI 0.3 and `component-model-async` are on by default. | +| `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` and related packages. | +| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. | + +## 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 diff --git a/component-model/src/reference/faq.md b/component-model/src/reference/faq.md index ac87d244..5b9424c6 100644 --- a/component-model/src/reference/faq.md +++ b/component-model/src/reference/faq.md @@ -77,12 +77,16 @@ expect to access—WASI enables building interfaces on top of a shared standard [wit]: https://component-model.bytecodealliance.org/design/wit.html [wasi-http]: https://github.com/WebAssembly/WASI/tree/main/proposals/http -## Q: I see the terms Preview 1 and Preview 2 frequently. What do those refer to? +## Q: I see the terms Preview 1, Preview 2, and Preview 3 frequently. What do those refer to? Preview 1 refers to [the first iteration of the Component Model](https://github.com/WebAssembly/WASI/tree/wasi-0.1) which was based on WITX and is now deprecated. Preview 2 refers to [a newer iteration of the Component Model](https://github.com/WebAssembly/WASI/blob/main/docs/Preview2.md) which uses WebAssembly Interface Types (WIT). +Preview 3 (WASI 0.3, released June 11, 2026) adds native async to the Component Model: +`async func`, `stream`, and `future` are now Canonical ABI primitives, and the `wasi:io` package is removed. +See [Async, Streams, and Futures](../design/async.md) for the conceptual underpinnings +and [Migrating from WASI 0.2 to WASI 0.3](../design/migrating-to-p3.md) for what changes for an existing component. Many programming language toolchains may only support Preview 1 components natively, but this isn't a problem in practice as Preview 1 components can be *adapted* into Preview 2 components automatically. @@ -90,6 +94,46 @@ but this isn't a problem in practice as Preview 1 components can be *adapted* in While somewhat confusing, a WASI Preview 1 "component" is in fact a *WebAssembly core module*. More precisely, a Preview 1 "component" is a WebAssembly core module with a well-defined set of imports and exports ([legacy specification][wasi-p1]). +## Q: What is WASI 0.3? + +WASI 0.3 is the latest milestone release of WASI, published on June 11, 2026. +It rebases WASI's interfaces onto three new Canonical ABI primitives that the Component Model added for this release: +[`async func`, `stream`, and `future`](../design/async.md). +The `wasi:io` package is removed; its functionality is now provided by the Component Model directly. + +The practical effect is smaller, more composable WASI interfaces. +The `wasi:http` handler is an `async func` returning a response; +stdin, stdout, and stderr use `stream`; +and async operations propagate readiness across component boundaries without each component running its own event loop. + +For a per-interface overview of what changed, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. + +## Q: Do I need to migrate from WASI 0.2 to WASI 0.3? + +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. + +Migrating is the right call when you want composable async across component boundaries +or the newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two. + +See [Migrating from WASI 0.2 to WASI 0.3](../design/migrating-to-p3.md) for the concept mapping +and per-interface highlights. + +## Q: What happened to `wasi:io`? + +WASI 0.3 removes the `wasi:io` package entirely. +The resources it provided (`pollable`, `input-stream`, and `output-stream`) +are replaced by the Component Model's native Canonical ABI primitives +`future`, `stream`, and `stream` (used as a function parameter), respectively. + +The motivation was the so-called sandwich problem: +when async readiness was expressed as a resource scoped to a single component, +that readiness signal could not propagate cleanly across component boundaries. +With native async in the Canonical ABI, the runtime owns scheduling and wake-up propagation. + +See [Async, Streams, and Futures](../design/async.md) for the underlying concepts. + ## Q: What are component imports? WebAssembly components are self-describing: information about required external functionality