Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started

Prerequisites

  • Rust (stable, edition 2024)
  • LLVM 18 — the compiler uses inkwell (LLVM 18 bindings)
    • macOS: brew install llvm@18 then export LLVM_SYS_181_PREFIX=/opt/homebrew/opt/llvm@18
    • Ubuntu: apt install llvm-18-dev
  • Bun (for running integration tests and the JAM runner) — bun.sh

Build

git clone https://github.com/tomusdrw/wasm-pvm.git
cd wasm-pvm
cargo build --release

Hello World: Compile & Run

Create a simple WAT program that adds two numbers:

;; add.wat
(module
  (memory 1)
  (func (export "main") (param $args_ptr i32) (param $args_len i32) (result i64)
    ;; Read two i32 args, add them, write result to memory
    (i32.store (i32.const 0)
      (i32.add
        (i32.load (local.get $args_ptr))
        (i32.load (i32.add (local.get $args_ptr) (i32.const 4)))))
    (i64.const 17179869184)))  ;; packed ptr=0, len=4

Compile it to a JAM blob and run it:

# Compile WAT → JAM
cargo run -p wasm-pvm-cli -- compile add.wat -o add.jam

# Run with two u32 arguments: 5 and 7 (little-endian hex)
npx @fluffylabs/anan-as run add.jam 0500000007000000
# Output: 0c000000  (12 in little-endian)

Inspect the Output

Upload the resulting .jam file to the PVM Debugger for step-by-step execution, disassembly, register inspection, and gas metering visualization.

AssemblyScript Example

You can also write programs in AssemblyScript:

// fibonacci.ts
export function main(args_ptr: i32, args_len: i32): i64 {
  const buf = heap.alloc(256);
  let n = load<i32>(args_ptr);
  let a: i32 = 0;
  let b: i32 = 1;

  while (n > 0) {
    b = a + b;
    a = b - a;
    n = n - 1;
  }

  store<i32>(buf, a);
  return (buf as i64) | ((4 as i64) << 32);  // packed ptr + len
}

Compile via the AssemblyScript compiler to WASM, then use wasm-pvm-cli to produce a JAM blob. See the tests/fixtures/assembly/ directory for more examples.

Using as a Library

You can use wasm-pvm as a Rust dependency in two modes:

Full compiler (default)

Requires LLVM 18 installed on the system.

[dependencies]
wasm-pvm = "0.5.2"

This gives you access to the full compiler pipeline (compile(), compile_with_options()) plus all PVM types.

PVM types only

No LLVM dependency — compiles to any target including wasm32-unknown-unknown.

[dependencies]
wasm-pvm = { version = "0.5.2", default-features = false }

Available types: Instruction, Opcode, ProgramBlob, SpiProgram, abi::*, memory_layout::*, and Error. This is useful for PVM interpreters, debuggers, and bytecode analyzers that don’t need the WASM compiler.

Entry Function ABI

All entry functions must use the signature main(args_ptr: i32, args_len: i32) -> i64. The i64 return value packs a result pointer (lower 32 bits) and result length (upper 32 bits). The compiler unpacks this into PVM’s SPI convention (r7 = start address, r8 = end address).

For WAT programs, the common “return 4 bytes at address 0” constant is (i64.const 17179869184) (= 4 << 32).

For AssemblyScript, use: return (ptr as i64) | ((len as i64) << 32).