Pack
A WebAssembly package runtime with extended WIT support for recursive data types.
Motivation
The WebAssembly Component Model's WIT interface definition language doesn't support recursive types. This is a reasonable constraint for shared-memory scenarios where fixed-layout ABIs are desirable, but it's limiting for use cases involving tree-structured data:
- Abstract Syntax Trees (ASTs)
- S-expressions
- JSON/DOM-like structures
- File system trees
- Any recursive data structure
The standard workaround is to use resources (opaque handles) and manipulate trees through indirection. This works but is awkward for message-passing architectures where data is serialized anyway.
Pack defines a WIT+ dialect with recursion allowed by default and a graph-encoded ABI that naturally handles arbitrary-depth structures.
Design Goals
- WIT+ dialect - Recursion is allowed by default
- Simple authoring - No
reckeywords or blocks - Compatible execution - Uses standard WASM runtimes (wasmi, wasmtime)
- Single ABI - Graph-encoded schema-aware serialization for all values
Extended WIT Syntax
// Standard WIT - unchanged
record point {
x: s32,
y: s32,
}
variant color {
rgb(tuple<u8, u8, u8>),
named(string),
}
// Recursive types (implicit)
variant sexpr {
sym(string),
num(s64),
flt(f64),
str(string),
lst(list<sexpr>), // Self-reference allowed
}
// Mutually recursive types
variant expr {
literal(lit),
binary(string, expr, expr),
}
variant lit {
number(f64),
quoted(expr), // Cross-reference across types
}
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Pack Runtime │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Package Layer │ │
│ │ │ │
│ │ • WIT+ parsing (standard + recursive) │ │
│ │ • Package instantiation and linking │ │
│ │ • Host function binding │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ABI Layer │ │
│ │ │ │
│ │ Graph-encoded ABI for all values │ │
│ │ (schema-aware arena encoding) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WASM Execution (pluggable) │ │
│ │ │ │
│ │ wasmi (interpreter) / wasmtime (JIT) / other │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
ABI for WIT+
All values use a schema-aware graph encoding. The runtime:
- Encodes the value into a graph buffer
- Writes bytes to linear memory
- Passes (pointer, length) to the WASM function
- Decodes the buffer using the expected schema
This format supports shared subtrees and cycles and enables future zero/low-copy views over the arena.
Interface Hashing
Pack uses Merkle-tree hashing for O(1) interface compatibility checking. Every type, function, and interface has a content-addressed hash:
Actor A Actor B
│ │
│ import-hashes: │ export-hashes:
│ "math/ops" → a1b2c3d4... │ "math/ops" → a1b2c3d4...
│ │
└──────────── hashes match ──────────┘
✓ compatible!
Key design decisions:
- Type names excluded:
PointandVec2with same structure have same hash (structural typing) - Field names included:
{x: s32}≠{y: s32}(access patterns matter) - Interface bindings included: Interface hash includes name→type mappings
See docs/INTERFACE-HASHING.md for details.
Crates
| Crate | Description |
|---|---|
packr |
Host-side runtime (CLI + library) |
packr-abi |
Shared ABI types (Value, GraphValue derive) — no_std compatible |
packr-derive |
Derive macros for automatic Value conversion |
packr-guest |
Guest-side helpers for writing WASM packages |
packr-guest-macros |
Proc macros (#[export], #[import], pack_types!) |
Writing Packages
Packages are written in Rust with no_std and compile to WASM.
extern crate alloc;
use String;
use ;
setup_guest!;
pack_types!
Typed Actors with Derive
extern crate alloc;
use String;
use ;
setup_guest!;
pack_types!
Package Cargo.toml
[]
= "my-package"
= "0.1.0"
= "2021"
[]
= ["cdylib"]
[]
= { = "https://github.com/colinrozzi/pack.git", = "v0.3.0" }
[]
= "s"
= true
Project Structure
pack/
├── src/ # Host-side runtime
│ ├── main.rs # packr CLI
│ ├── bin/pact.rs # pact CLI (type checker)
│ ├── abi/ # Graph-encoded ABI (CGRF format)
│ └── runtime/ # WASM execution and host binding
├── crates/
│ ├── pack-abi/ # packr-abi: shared ABI types (no_std)
│ ├── pack-derive/ # packr-derive: GraphValue derive macro
│ ├── pack-guest/ # packr-guest: guest helpers
│ └── pack-guest-macros/ # packr-guest-macros: proc macros
├── packages/ # Example WASM packages
│ ├── echo/ # Echo/transform values
│ ├── logger/ # Uses host imports
│ ├── sexpr/ # S-expression evaluator
│ ├── typed-actor/ # Typed state example
│ └── ...
└── tests/ # Integration tests
Status
Working prototype. Core functionality is implemented and tested:
- WIT+ Parser — recursive and mutually recursive type definitions
- Graph ABI — CGRF format encoding/decoding with schema validation
- WASM Execution — load and run modules via wasmtime
- Guest Macros —
#[export],#[import],pack_types!,#[derive(GraphValue)] - Host Imports — packages can call back to host
- Interface Enforcement — validate WASM modules implement WIT interfaces
- Interface Hashing — Merkle-tree hashes for O(1) compatibility checking
- Static Composition — compose multiple packages into one module
Related Projects
- Theater — Actor runtime built on Pack
- Wisp — Lisp that compiles to Pack WASM modules
- WebAssembly Component Model