
# Firebird 🔥
[](https://github.com/hdresearch/firebird/actions/workflows/ci.yml)
**Speed up your Elixir projects by up to [42x](docs/performance.md#single-operations-per-call-syncnif-call_fast-path) and Phoenix projects by upward of [12x](docs/performance.md#batch-operations-amortized-across-n-requests), thanks to WebAssembly.**
## Quick Start
### 1. Add dependency
```elixir
# mix.exs
def deps do
[{:firebird, "~> 0.1.0"}]
end
```
### 2. Configure formatter (optional)
Add `:firebird` to `import_deps` so `mix format` respects the DSL macros:
```elixir
# .formatter.exs
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:firebird]
]
```
### 3. Verify it works
```elixir
iex> Firebird.check!()
:ok
iex> Firebird.demo() # See add, multiply, fibonacci in action
```
### 4. Call WASM functions
```elixir
# Absolute quickest way — one expression:
8 = Firebird.quick(:sample, :add, [5, 3])
# One-liner: load, call, and stop in one step
{:ok, 8} = Firebird.run_one("math.wasm", :add, [5, 3])
8 = Firebird.run_one!("math.wasm", :add, [5, 3])
# Or load once, call many times
{:ok, wasm} = Firebird.load("math.wasm")
{:ok, 8} = Firebird.call_one(wasm, :add, [5, 3])
55 = Firebird.call_one!(wasm, :fibonacci, [10])
Firebird.stop(wasm)
```
**That's it.** No config files, no boilerplate, no setup ceremony.
> **Smart Path Resolution**: Firebird auto-searches `priv/wasm/`, `wasm/`, and `fixtures/` for `.wasm` files. Just use the filename — no full paths needed.
---
📖 **Learn more:** [Why WASM?](docs/why-wasm.md) | [Performance Deep Dive](docs/performance.md)
---
## The Declarative Way (Recommended)
Wrap a WASM module in a clean Elixir module with `use Firebird`:
```elixir
defmodule MyApp.Math do
use Firebird, wasm: "priv/wasm/math.wasm"
wasm_fn :add, args: 2
wasm_fn :multiply, args: 2
wasm_fn :fibonacci, args: 1
end
```
Add to your supervision tree:
```elixir
# application.ex
children = [MyApp.Math]
```
Call functions naturally:
```elixir
{:ok, [8]} = MyApp.Math.add(5, 3)
[55] = MyApp.Math.fibonacci!(10)
```
### Auto-Discovery
Don't want to declare each function? Use `auto: true`:
```elixir
defmodule MyApp.Math do
use Firebird, wasm: "priv/wasm/math.wasm", auto: true
end
# All WASM exports are auto-wrapped as Elixir functions
```
## Inline WAT (Prototyping)
Write WebAssembly directly in Elixir — compiled at compile time:
```elixir
import Firebird.Sigils
bytes = wat!("""
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0 local.get 1 i32.add))
""")
{:ok, wasm} = Firebird.load(bytes)
{:ok, [8]} = Firebird.call(wasm, :add, [5, 3])
```
Or use `Firebird.Quick` for one-off evaluations:
```elixir
{:ok, [42]} = Firebird.Quick.eval_wat("""
(module
(func (export "answer") (result i32) i32.const 42))
""", "answer")
```
## Connection Pool
For concurrent workloads, use `Firebird.Pool`:
```elixir
# In your supervision tree
children = [
{Firebird.Pool, wasm: "priv/wasm/math.wasm", size: 4, name: :math_pool}
]
# Calls distributed across instances automatically
{:ok, [8]} = Firebird.Pool.call(:math_pool, :add, [5, 3])
[8] = Firebird.Pool.call!(:math_pool, :add, [5, 3])
```
## Block API (Auto-Cleanup)
```elixir
{:ok, result} = Firebird.with_instance("math.wasm", fn wasm ->
{:ok, [a]} = Firebird.call(wasm, :add, [5, 3])
{:ok, [b]} = Firebird.call(wasm, :multiply, [a, 2])
b
end)
# => {:ok, 16}
```
## Pipe-Friendly API
Chain operations without losing the instance reference:
```elixir
wasm = Firebird.load!("math.wasm")
{[sum], wasm} = Firebird.pipe!(wasm, :add, [5, 3])
{[product], wasm} = Firebird.pipe!(wasm, :multiply, [sum, 2])
Firebird.stop(wasm)
```
## Auto-Generate a Wrapper
Inspect any WASM file and generate a complete Elixir module:
```bash
mix firebird.gen priv/wasm/math.wasm --module MyApp.Math
```
Generates a ready-to-use module with all exported functions wrapped. Styles:
```bash
mix firebird.gen module.wasm --style module # GenServer (default)
mix firebird.gen module.wasm --style pool # Pool-backed
mix firebird.gen module.wasm --style basic # Stateless one-shot
```
## Scaffold a New Project
```bash
mix firebird.init # Basic setup in existing project
mix firebird.init --rust # + Rust WASM scaffold
mix firebird.init --phoenix # + Phoenix helpers
mix firebird.new my_app # New project from scratch
mix firebird.new my_app --rust # With Rust scaffold
```
## Core API
```elixir
# One-shot (load → call → stop) — single value
{:ok, result} = Firebird.run_one("module.wasm", :function, [args])
result = Firebird.run_one!("module.wasm", :function, [args])
# One-shot — list return
{:ok, [result]} = Firebird.run("module.wasm", :function, [args])
[result] = Firebird.run!("module.wasm", :function, [args])
# Load
{:ok, instance} = Firebird.load("module.wasm")
instance = Firebird.load!("module.wasm")
{:ok, instance} = Firebird.load(wasm_bytes)
{:ok, instance} = Firebird.load("app.wasm", wasi: true)
# Call — single value (recommended for most functions)
{:ok, result} = Firebird.call_one(instance, :function_name, [arg1, arg2])
result = Firebird.call_one!(instance, :function_name, [arg1, arg2])
# Call — list return (for multi-value or explicit matching)
{:ok, [result]} = Firebird.call(instance, :function_name, [arg1, arg2])
[result] = Firebird.call!(instance, :function_name, [arg1, arg2])
# Batch calls
{:ok, results} = Firebird.call_many(instance, [
{:add, [1, 2]},
{:multiply, [3, 4]}
])
# Block API (auto-cleanup)
{:ok, result} = Firebird.with_instance("module.wasm", fn wasm ->
# use wasm here, auto-stopped after block
end)
# Pipe-friendly (threads instance through)
{[sum], wasm} = Firebird.pipe!(wasm, :add, [5, 3])
{[product], wasm} = Firebird.pipe!(wasm, :multiply, [sum, 2])
# Inspect
exports = Firebird.exports(instance)
exists? = Firebird.function_exists?(instance, :add)
{:ok, {[:i32, :i32], [:i32]}} = Firebird.function_type(instance, :add)
info = Firebird.info(instance)
# Memory
{:ok, size} = Firebird.memory_size(instance)
:ok = Firebird.write_memory(instance, 0, <<1, 2, 3>>)
{:ok, bytes} = Firebird.read_memory(instance, 0, 3)
# Lifecycle
true = Firebird.alive?(instance)
:ok = Firebird.stop(instance)
# WAT (inline WASM for prototyping)
{:ok, wasm} = Firebird.from_wat("(module (func (export \"f\") (result i32) i32.const 42))")
{:ok, bytes} = Firebird.compile_wat(wat_source) # WAT → bytes
# Diagnostics
Firebird.check!() # Verify setup works
Firebird.demo() # Interactive demo
Firebird.describe(instance) # Print instance info
Firebird.describe("file.wasm") # Print file info
wasm = Firebird.playground() # Pre-loaded instance for iex
```
## Creating WASM Modules
### Rust
```rust
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
```
```bash
cargo build --target wasm32-unknown-unknown --release
```
### Go
```go
//export add
func add(a, b int32) int32 { return a + b }
```
```bash
GOOS=wasip1 GOARCH=wasm go build -o module.wasm
```
## Compile Elixir to WASM
Write Elixir, compile to WebAssembly:
```elixir
# lib/wasm_modules/math.ex
defmodule MyMath do
@wasm true
def add(a, b), do: a + b
@wasm true
def fibonacci(0), do: 0
def fibonacci(1), do: 1
def fibonacci(n), do: fibonacci(n - 1) + fibonacci(n - 2)
end
```
```bash
mix firebird.target # Compile to WASM
mix firebird.target --optimize --tco # With optimizations
mix firebird.target --watch # Watch for changes
mix firebird.bench --format markdown # Benchmark WASM vs BEAM
```
Supports: arithmetic, comparisons, if/else/unless/cond/case, pattern matching,
recursion, guards, pipe operator, variable bindings.
See **[WASM Target Guide](WASM_TARGET.md)** | **[API Reference](WASM_TARGET_API.md)** | **[Elixir-to-WASM Research](docs/ELIXIR_TO_WASM.md)** for full details.
## Phoenix to WASM 🔥
Firebird includes WASM-accelerated Phoenix components:
| Component | Description |
|-----------|-------------|
| **Router** | HTTP route matching with path params and wildcards |
| **Template** | HTML template rendering with auto-escaping |
| **Plug** | Request parsing, response building, CSRF validation |
| **Endpoint** | Full request lifecycle with plug pipelines |
| **Channel** | Topic matching, message serialization, presence |
| **JSON API** | JSON encoding, API responses, pagination, errors |
| **Session** | Cookie parsing, signing, session management |
| **Live** | HTML diffing, patch generation, component rendering |
| **Validator** | Form validation: required, type, length, format, inclusion |
| **Form** | HTML form helpers with CSRF, method spoofing, escaping |
| **WebSocket** | Frame encode/decode, upgrade handling, Channel protocol |
| **CSRF** | Token generation, signing, validation, protection middleware |
| **RateLimiter** | Token bucket rate limiting with per-client tracking |
Plus Elixir-level framework modules: Conn, Pipeline, Middleware, RouterDSL,
RequestHandler, LiveComponent, ErrorHandler, Application, Scaffold, Testing,
Form, WebSocket, CSRF, RateLimiter.
```bash
mix firebird.phoenix.gen my_app # Generate Phoenix WASM project
```
See **[Phoenix to WASM Guide](docs/PHOENIX_TO_WASM.md)** for full documentation.
## Testing WASM Modules
Use `Firebird.TestCase` for zero-boilerplate WASM testing (like Phoenix's `ConnCase`):
```elixir
defmodule MyApp.MathTest do
use Firebird.TestCase, wasm: "priv/wasm/math.wasm"
test "add works", %{wasm: wasm} do
assert_wasm_call wasm, :add, [5, 3], [8]
end
test "single value", %{wasm: wasm} do
assert_wasm_result wasm, :fibonacci, [10], 55
end
test "exports", %{wasm: wasm} do
assert_wasm_exports wasm, [:add, :multiply, :fibonacci]
end
test "type signature", %{wasm: wasm} do
assert_wasm_type wasm, :add, {[:i32, :i32], [:i32]}
end
test "function with arity", %{wasm: wasm} do
assert_wasm_function wasm, :add, 2
end
test "error on missing function", %{wasm: wasm} do
assert_wasm_error wasm, :nonexistent, [1]
end
test "addition truth table", %{wasm: wasm} do
assert_wasm_table wasm, :add, [
{[0, 0], 0},
{[1, 2], 3},
{[5, 3], 8},
{[-1, 1], 0},
{[100, 200], 300}
]
end
end
```
`Firebird.TestCase` handles ExUnit setup, WASM loading, and cleanup automatically.
Options:
- No args — loads bundled `sample_math.wasm`
- `wasm: "path.wasm"` — loads your module
- `wasm: false` — just imports helpers, no auto-loading
- `wasi: true` — enable WASI support
```elixir
# Quick test with bundled sample (add/2, multiply/2, fibonacci/1):
defmodule QuickTest do
use Firebird.TestCase # auto-loads sample_math.wasm
test "it works", %{wasm: wasm} do
assert_wasm_call wasm, :add, [1, 2], [3]
end
end
```
<details>
<summary>Manual setup (without TestCase)</summary>
```elixir
defmodule MyApp.MathTest do
use ExUnit.Case
import Firebird.TestHelpers
setup_wasm "priv/wasm/math.wasm"
test "add works", %{wasm: wasm} do
assert_wasm_call wasm, :add, [5, 3], [8]
end
end
```
For quick testing with the bundled sample:
```elixir
setup_sample_wasm() # Uses Firebird's included sample_math.wasm
```
</details>
## Developer Tools
```bash
mix firebird.inspect fixtures/math.wasm # View exports and types
mix firebird.gen module.wasm --module MyApp.M # Generate wrapper module
mix firebird.init # Setup in existing project
mix firebird.new my_app # New project from scratch
mix firebird.analyze lib/wasm_modules/ # Analyze before compiling
```
```elixir
# Type-safe calls with clear error messages
{:ok, [8]} = Firebird.TypedCall.call(instance, :add, [5, 3])
# Metrics collection
Firebird.Metrics.start_link()
Firebird.Metrics.timed_call(instance, :fibonacci, [30])
# Batch execution
results = Firebird.Batch.map(pool, :fibonacci, Enum.map(1..100, &[&1]))
# Hot reload (development)
{:ok, w} = Firebird.HotReload.start_link(path: "math.wasm")
```
## Documentation
- **[Cheatsheet](CHEATSHEET.md)** — Single-page quick reference
- **[Why WASM?](docs/why-wasm.md)** — When to use WASM vs BEAM
- **[Performance Deep Dive](docs/performance.md)** — 75+ benchmarks with methodology
- **[Decision Guide](docs/DECISION_GUIDE.md)** — Which API to use? WASM vs BEAM? Pool vs Module? Start here
- **[Getting Started Guide](docs/GETTING_STARTED.md)** — Full walkthrough
- **[Performance Guide](docs/PERFORMANCE_GUIDE.md)** — Profiling, benchmarking, deciding what to WASM-ify
- **[Testing Guide](docs/TESTING_GUIDE.md)** — Testing WASM modules with `Firebird.TestCase`, assertions, patterns
- **[Cookbook](docs/COOKBOOK.md)** — Real-world recipes: plugins, ETL, sandboxing, batch jobs
- **[API Reference](docs/API.md)** — Complete API documentation
- **[Architecture Guide](docs/ARCHITECTURE.md)** — Module layers, data flow, compiler pipeline, design decisions
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** — Common issues and solutions
- **[Phoenix to WASM](docs/PHOENIX_TO_WASM.md)** — Full Phoenix WASM guide
- **[Examples](examples/)** — Working example scripts
- **[Elixir-to-WASM Guide](docs/ELIXIR_TO_WASM.md)** — Compile Elixir to WebAssembly
## Contributing
See **[CONTRIBUTING.md](CONTRIBUTING.md)** for project setup, architecture overview, and how to submit changes.
## License
MIT