Skip to content

About

Complete support for WASI components for Zig / wasi 0.2 / wasi 0.3

Topics

Resources

Stars

13 stars

Watchers

3 watching

Forks

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zig-wasi-components

Build WebAssembly Component Model components in Zig.

Start with a WIT schema, write a Zig core module, and this project wraps it into a real component: a .wasm file validated by wasm-tools. Your component can call Rust hosts, and Rust hosts can call your component, through the same canonical ABI.

The build works like cargo-component for Rust. A small code generator turns WIT into Zig source, which compiles to wasm32-freestanding. Then wasm-tools component embed and component new turn that core module into a component.

Documentation

You'll find the full user documentation in docs/.

Start with getting-started to see what you'll need and run through the bundled demos. Then follow your first component to build your own, step by step.

Quick start

zig build test            # native parser, codegen, and runtime unit tests
zig build test-components # component regressions (Python 3, wasm-tools, wasmtime)

zig build demo         # builds zig-out/wasm/greeter.component.wasm
zig build dual         # builds zig-out/wasm/math.component.wasm
zig build resource     # builds zig-out/wasm/counts.component.wasm
zig build http-get     # builds zig-out/wasm/http-get.component.wasm
zig build async-basic  # builds zig-out/wasm/async-basic.component.wasm
zig build stream-demo  # builds zig-out/wasm/stream-demo.component.wasm
zig build wasi-demo    # builds zig-out/wasm/wasi-demo.component.wasm
zig build wasi-demo-p3 # builds zig-out/wasm/wasi-demo-p3.component.wasm (WASI 0.3.2)

# Run a component standalone via Wasmtime:
wasmtime run --invoke 'manhattan({x:-3, y:4})' \
  zig-out/wasm/greeter.component.wasm
# => 7

# Run the Rust↔Zig greeter demo (Rust host calls into Zig
# component; the Zig component also calls back into the host):
cargo build --release --manifest-path examples/greeter/rust-host/Cargo.toml
./examples/greeter/rust-host/target/release/host \
  zig-out/wasm/greeter.component.wasm

# Run the dual-language demo (one Rust host, two components built
# from the same WIT, one in Zig and one with cargo-component):
cargo build --release --manifest-path examples/dual/host/Cargo.toml
(cd examples/dual/rust-impl && cargo component build --release)
./examples/dual/host/target/release/dual-host \
  zig-out/wasm/math.component.wasm \
  examples/dual/rust-impl/target/wasm32-wasip1/release/math.wasm

# Run the resource demo (constructor / methods / drop lifecycle):
cargo build --release --manifest-path examples/resource/host/Cargo.toml
./examples/resource/host/target/release/counts-host \
  zig-out/wasm/counts.component.wasm

# Run the wasi:http GET example through Wasmtime directly:
wasmtime run -S http=y zig-out/wasm/http-get.component.wasm

# Run the async demo (Rust tokio host calls async Zig exports and
# the Zig guest in turn calls an async Rust import):
cargo build --release --manifest-path examples/async-basic/rust-host/Cargo.toml
./examples/async-basic/rust-host/target/release/host \
  zig-out/wasm/async-basic.component.wasm

# Run the typed-stream demo (Zig guest streams u32 and u64 values,
# the Rust host drains and verifies each readable end):
cargo build --release --manifest-path examples/stream-demo/rust-host/Cargo.toml
./examples/stream-demo/rust-host/target/release/host \
  zig-out/wasm/stream-demo.component.wasm

Expected output:

greet("Hello from Rust!") = 16
sum([1,2,3,4,5,100]) = 115
manhattan(point { x: -3, y: 4 }) = 7
format-greeting("world") = "Hi, world!"
captured logs: ["hello Hello from Rust!"]
OK: Rust ↔ Zig component interop is verified in both directions.

Layout

src/
  wit.zig       WIT parser (WASIp3-scoped grammar)
  abi.zig       Runtime helpers (cabi_realloc backing)
  codegen.zig   Zig source generator from a parsed WIT world
  wasi.zig      `Wasi(bindings)` convenience layer for WASI 0.2
  wasi3.zig     `Wasi3(bindings)` convenience layer for WASI 0.3
  wasi_common.zig  Pieces shared by both convenience layers
  main.zig      `zig-wit dump|gen` CLI
tests/
  components.py   Builds and runs component regressions in Debug and ReleaseSmall.
  *.wit, *.zig    Fixtures for generated bindings and canonical ABI behavior.
  README.md       Reproduced failures, validation commands, and coverage limits.
examples/
  greeter/          Records, lists, variants, options, results, tuples,
                    flags, char, indirect params/results.
  dual/             Same WIT, one impl in Zig and one in Rust.
  resource/         A `counter` resource with the full
                    constructor/methods/drop lifecycle.
  http-get/         A wasi:http GET client. The whole wasi:http /
                    wasi:io / wasi:cli surface is fed through the
                    codegen via `wasm-tools component wit`; the
                    guest contains zero hand-written `@extern`s.
  async-basic/      Async-with-callback canonical ABI end-to-end:
                    `[async-lift]` exports (`succ`, `measure`,
                    `greet`, `promise`, `relay`) and an
                    `[async-lower]` import (`clock.tick`). The Rust
                    host uses `instantiate_async` + `run_concurrent`.
  stream-demo/      Typed `stream<T>` end-to-end: the guest streams
                    `stream<u32>` squares and `stream<u64>` Fibonacci
                    numbers through one generic producer, exercising
                    the non-byte stream path that `async-basic`'s
                    `stream<u8>` skips.
  wasi-demo/        Uses the `zig_wasi_components.wasi` convenience
                    module to exercise clocks, randomness, stdio,
                    environment, terminal probes, a filesystem
                    round-trip, a DNS lookup, and an HTTPS GET in a
                    single guest.
  wasi-demo-p3/     The same tour against WASI 0.3.2 via the
                    `zig_wasi_components.wasi3` module: stream-based
                    stdio and file I/O, async clock sleeps, a
                    `stream<directory-entry>` listing, DNS, plus a
                    `wasi:http/service@0.3.2` guest that serves real
                    requests under `wasmtime serve` and makes an
                    outbound fetch through the 0.3 http client
                    wrappers on each one.

What works

The parser handles all the WIT syntax used by real WASI 0.2.x and 0.3.x packages:

  • packages with ns:name@x.y.z[-suffix] versions, file-scope use
  • interfaces and worlds, with include ... with { a as b }
  • cross-package use foo:bar/baz@1.0.0.{x, y}
  • record, variant, enum, flags, type aliases
  • resource with constructors, methods, static methods, own<T> / borrow<T>
  • stream<T>, future<T>, error-context
  • map<K, V>, represented as a slice of key/value pairs using its canonical form, list<tuple<K, V>>
  • async functions
  • @since(version = ...) / @unstable(feature = ...) / @deprecated(version = ...) gates
  • @external-id("...") annotations
  • inline interfaces in world imports/exports
  • plain-named interface imports/exports: import users: store; and import catalog: store; bind the same interface twice under different names, each with its own generated namespace

The project has been checked against the published WIT files for the full set of these packages at 0.2.12:

  • wasi:cli
  • wasi:io
  • wasi:clocks
  • wasi:random
  • wasi:sockets
  • wasi:filesystem
  • wasi:http

This includes the exit-with-code call and the unstable wasi:clocks/timezone interface.

WASI 0.3.2 is fully supported. Every world in its six packages generates bindings that compile and pass wasm-tools component new.

  • wasmtime run -S p3: run the command world.
  • wasmtime serve -S p3,cli: run the HTTP service world.

Both run end to end under wasmtime 49.

The generator creates stream and future canon intrinsics for every function that uses stream<T> or future<T>. That includes async and sync functions, both at the top level and in resource methods.

The lift and lower helpers use the canonical layout and support compound payloads such as directory-entry. So you can use the wasi3 convenience layer from plain Zig, including the 0.3 stdio, filesystem, and HTTP APIs.

The code generator handles every WIT type in the table below, in both directions. For indirect parameters and returns, it uses the specification's canonical memory layout.

It also generates the required cabi_realloc and cabi_post_* entry points. The latter follows wit-component's current naming convention, so wasmtime can connect each function to canon lift.

WIT type covered demo
primitives ✓ greeter
char ✓ greeter/upper-char
string ✓ greeter/greet, format-greeting, log import
list<T> ✓ greeter/sum, total-distance
record ✓ greeter/manhattan, origin import
variant ✓ greeter/classify
enum, flags (≤32 labels) ✓ greeter/perms-popcount
option<T> ✓ greeter/maybe-double
result<T, E> (return and parameter) ✓ greeter/safe-divide, greeter/choose
tuple<...> ✓ greeter/pair, greeter/divmod
indirect params (>16 flats) ✓ greeter/sum-many
indirect results (export and import) ✓ greeter/format-greeting, origin import
resource ✓ resource/counter
stream<T>, future<T>, error-context ✓ (handle pass-through) tmp/streamworld.wit
async func(...) exports ✓ async-basic/{succ,measure,greet,promise}
async func(...) imports ✓ async-basic/relay (calls host's clock.tick)
per-func [stream-*-i] / [future-*-i] ✓ async-basic/{greet,promise}
root async builtins (waitable-set, ...) ✓ used implicitly by async imports

What's still missing

The project covers the canonical ABI for WIT 0.2.x and 0.3.x in both directions, but two gaps remain:

  • Stream/future intrinsics for sync exports. The per-function intrinsics namespaces are generated for:

    • imports
    • imported-resource methods
    • async exports

    But if a sync export or exported resource method mentions stream<T> or future<T>, it doesn't get an intrinsics namespace yet. No WASI world ships such a function, so there's no consumer to check it against.

    Async exports are fully supported, including:

    • eager functions
    • typed state machines

    See docs/bindings.md.

  • HTTP request bodies in the wasi3 blocking wrapper. The 0.3 host only answers after the request body stream is dropped, but you can only write the body while client.send is in flight. A blocking helper can't interleave those operations.

    To send a body, you need the state-machine async export form. wasi3.http.Request deliberately has no body field, so trying to use one gives you a compile error.

About

Complete support for WASI components for Zig / wasi 0.2 / wasi 0.3

Topics

Resources

Stars

13 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages