PIT (Portal Interface Types) is a WebAssembly interface definition language (IDL) and toolchain for defining and implementing cross-module interfaces. It provides a structured format for describing interfaces, methods, and resource types, and a set of tools to generate language bindings and transform WebAssembly modules to conform to the PIT ABI.
The core parsing library and IDL specification live in a separate crate: pit-core.
Current status: Alpha (version 0.5.0-alpha.1). The project is under active development. ABI v1 is functional. ABI v2 (pitx) is a work in progress with no stable implementation yet.
PIT lets you define an interface as a set of typed methods operating on resources. Given an interface definition, the toolchain can:
- Generate Rust guest bindings (code a
.wasmmodule compiles against) - Generate Rust host bindings (code to instantiate and call into a PIT module)
- Generate TypeScript host adapters with JSPI when available
- Generate C WASM glue headers (
P<hex>_wasm.h) alongside more-pit canonicalP<hex>.h - Generate Scala and Java TeaVM WASM glue (
P<hex>TeaVm.*) — canonicalP<hex>interfaces come from more-pit - Transform WebAssembly binary modules: lower
externrefto i32 table indices, convert TPIT intermediate format to PIT ABI v1, canonicalize and jigger unique resource IDs - Embed interface type metadata into a module's custom sections
Public generated traits R<hex> (guest) and B<hex> (host) are removed. Implement the canonical interface from more-pit instead:
| Role | User implements | pit generates (internal) |
|---|---|---|
| Rust guest | P<hex> from pit-rust-generic / pit-gen --backend rust |
pit_ffi_<hex> module |
| Rust host | P<hex> |
wasm_<hex> import registration |
| C guest | P<hex>_t from pit-gen --backend c |
P<hex>_wasm.h glue |
| TS host | P<hex> or AP<hex> (async) from pit-gen --backend ts / ts-async |
Wasm adapter + @portal/pit-ts-host runtime |
| JVM guest | P<hex> from pit-gen --backend java / scala |
P<hex>TeaVm.* TeaVM glue |
Regenerate after upgrading:
scripts/autobuild-all.sh ../more-pitReplace impl R<hex> with impl P<hex> (or P<hex>_t in C) and re-run codegen.
Async host mode mirrors more-pit’s existing split:
- Sync:
pit-gen --backend ts→P<hex> - Async:
pit-gen --backend ts-async→AP<hex>
Generate Wasm host wiring:
pit generate ts-host [--async] interface.pit output_dir/P<hex>_host.ts
pit generate host-all [--more-pit DIR] [--async] [--out generated/host]When WebAssembly.Suspending / promising are available (Chromium 137+, Firefox), async adapters use JSPI; otherwise they fall back to sync P<hex> imports.
PIT interfaces are plain text files:
{
method_name(param_types) -> (return_types);
another_method(I32, I64) -> (F64);
}
I32,I64- 32/64-bit integersF32,F64- 32/64-bit floatsR<resource_id>- owned resource referenceR<resource_id>n- nullable resource referenceR<resource_id>&- borrowed resource reference
Resource IDs are SHA3-based hex hashes derived from the interface content.
[version=1]{
[async]get_data(I32) -> (R<buffer>);
}
For the full format specification, see SPEC.md in pit-core.
Resources are identified by a content-derived unique ID. The ABI uses externref for resource handles.
Module exports follow the pattern:
pit/<resource id>/~<unique id>/<method>
pit/<resource id>/~<unique id>.drop
Module imports follow the pattern:
pit/<resource id>.~<method>
pit.drop
All ABI v2 import modules and exports start with pitx. No stable definition yet.
Many WebAssembly toolchains (including LLVM/Rust targeting wasm32-unknown-unknown) do not natively support externref. TPIT is an intermediate binary format that replaces externref handles with integer indices into a WebAssembly table. The pit untpit command converts a TPIT module into a proper PIT ABI v1 module.
Rust source code
│ compile with TPIT bindings (tpit-rt)
▼
TPIT module (.wasm) ← uses tpit/* imports, i32 table handles
│ pit untpit
▼
PIT ABI v1 module (.wasm) ← uses pit/* imports, externref handles
-
Generate TPIT Rust bindings from a PIT interface:
pit generate rust-guest interface.pit bindings.rs
-
Compile the Rust crate (with
tpit-rtas a dependency):cargo build --target wasm32-unknown-unknown --release
-
Convert the TPIT output to PIT ABI v1:
pit module untpit target/wasm32-unknown-unknown/release/my_module.wasm output.wasm
Standard PIT interface definitions live in the sibling more-pit repository under more-pit/pit/:
| File | Resource ID | Description |
|---|---|---|
common/buffer.pit |
867207405f... |
32-bit addressable byte buffer (read8, write8, size) |
common/buffer64.pit |
68da167712... |
64-bit addressable byte buffer |
common/reader.pit |
— | Produces buffer or buffer64 resources |
common/writer.pit |
— | Consumes buffer or buffer64 resources |
Pure trait implementations (language-agnostic logic) are canonical in more-pit/impl/ and spliced into this repo via pit splice.
Checked-in generated artifacts:
generated/guest/— per-interface WASM glue (Rustpit_ffi_*, CP*_wasm.h, TeaVMP*TeaVm.*) frompit generate allgenerated/host/ts/— TypeScript host adapters frompit generate host-allgenerated/impl/— spliced copies ofmore-pit/impl/canonicals
Regenerate with:
scripts/autobuild-all.sh ../more-pit
# or guest-only:
scripts/autobuild-guest.sh ../more-pit| Crate | Description |
|---|---|
pit-cli |
Command-line interface for all PIT operations |
pit-splice |
Rice-based splicing of canonical implementations from more-pit |
pit-patch |
WebAssembly module transformation (TPIT unwrap, externref lowering, canonicalization, embedding interface metadata) |
| Crate | Description |
|---|---|
pit-rust-guest |
Generates Rust guest WASM glue (P<hex> re-export + pit_ffi_<hex>) |
pit-rust-host-core |
Generates Rust host WASM import wiring (wasm_<hex>) |
pit-ts-host-core |
Generates TypeScript host Wasm adapters |
pit-rust-host-lib |
Runtime support for hosting PIT modules (uses wasm_runtime_layer) |
pit-rust-externref |
Configures the externref crate's processor for PIT's drop function |
| Crate | Description |
|---|---|
tpit-rt |
TPIT runtime: the Tpit<D> type wrapping an i32 table handle with RAII drop via tpit.drop import |
pit-basic |
Implementations of the standard buffer interfaces for Vec<u8>, Box<[u8]>, slices |
| Crate | Description |
|---|---|
pit-c |
Generates internal P<hex>_wasm.h glue (includes canonical P<hex>.h from more-pit) |
pit-teavm |
Generates TeaVM WASM glue (P<hex>TeaVm.*) — canonical interfaces from more-pit |
pit-wit-bridge |
Bridge between PIT and WIT (WebAssembly Interface Types); currently nearly empty |
pit module untpit <input.wasm> <output.wasm> # convert TPIT to PIT ABI v1
pit module lower <input.wasm> <output.wasm> # lower externref to i32 table indices
pit module jigger <input.wasm> <output.wasm> # regenerate unique IDs based on content
pit module embed [-<interface.pit>...] <input.wasm> <output.wasm> # embed interface metadata
pit generate rust-guest <input.pit> [options] [<output.rs>]
pit generate teavm [--pkg NAME] <input.pit> <output.scala> # default pkg: pc.portal.pit.guest.scala
pit generate java [--pkg NAME] <input.pit> <output.java> # default pkg: pc.portal.pit.guest
pit generate c <input.pit> <output.h> # writes P<hex>_wasm.h glue
pit generate ts-host [--async] <input.pit> <output.ts>
pit generate host-all [--more-pit DIR] [--async] [--out DIR]
pit generate package <input.pit> [options] <output-dir> # Rust + Java + Scala + C + build files
pit generate all [--more-pit DIR] [--out DIR] # full guest autobuild
pit splice <file>... # rice splice from more-pit impl/
pit splice --all [--more-pit DIR] # refresh all known splice targets
pit hash <input.pit> [<input2.pit> ...] # print resource ID of each interfacepit generate rust-guest <input.pit> <output.rs>
pit generate rust-guest <input.pit> --extern-externref <output.rs> # use externref directly
pit generate rust-guest <input.pit> --preserve-docs <output.rs> # keep existing doc comments
pit generate rust-guest <input.pit> --salt <bytes> <output.rs> # or set PIT_SALT envWith no output path, rust-guest writes to stdout.
Legacy flat invocations (pit rust-guest, pit untpit, etc.) still work with a deprecation warning.
Canonical pure implementations live in more-pit/impl/. Refresh spliced copies in pit with:
pit splice --all --more-pit ../more-pitIn more-pit:
pit-gen splice --allThe workspace depends on:
portal-pc-waffle— fork of the waffle WebAssembly IR library, used for module analysis and transformationpit-core— the PIT parser and core data structures (git dep^0.5.0-alpha.1, locally patched viaportal-hot/.cargo/config.toml)more-pit— canonical.pitinterfaces and pure trait implementations (pit-lang-generic,pit-rust-generic,pit-gen; path dep../more-pit/crates/*)
cargo build --releaseNote: portal-pc-waffle is pulled from a git repository. The workspace Cargo.toml has a known typo in that URL (httpsd://) which must be corrected before the build will work.
Compile tests invoke real language toolchains and fail hard when a toolchain is missing. See docs/compile-tests.md for the full policy and required tools (javac, scalac, clang/cc, tsc, …).
Agents: see AGENTS.md.
CC0-1.0 (Public Domain)