Speed up your Elixir projects by up to 42x and Phoenix projects by upward of 12x, thanks to WebAssembly.
For how more than a billion tokens with coding agents were used to create this, check out this blog post. For what these technologies are or why bring the two together, check out this blog post.
# mix.exs
def deps do
[{:firebird, "~> 1.0"}]
endAdd :firebird to import_deps so mix format respects the DSL macros:
# .formatter.exs
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:firebird]
]iex> Firebird.check!()
:ok
iex> Firebird.demo() # See add, multiply, fibonacci in action# 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/, andfixtures/for.wasmfiles. Just use the filename β no full paths needed.
π Learn more: Why WASM? | Performance Deep Dive
Wrap a WASM module in a clean Elixir module with use Firebird:
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
endAdd to your supervision tree:
# application.ex
children = [MyApp.Math]Call functions naturally:
{:ok, [8]} = MyApp.Math.add(5, 3)
[55] = MyApp.Math.fibonacci!(10)Don't want to declare each function? Use auto: true:
defmodule MyApp.Math do
use Firebird, wasm: "priv/wasm/math.wasm", auto: true
end
# All WASM exports are auto-wrapped as Elixir functionsWrite WebAssembly directly in Elixir β compiled at compile time:
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:
{:ok, [42]} = Firebird.Quick.eval_wat("""
(module
(func (export "answer") (result i32) i32.const 42))
""", "answer")For concurrent workloads, use Firebird.Pool:
# 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]){: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}Chain operations without losing the instance reference:
wasm = Firebird.load!("math.wasm")
{[sum], wasm} = Firebird.pipe!(wasm, :add, [5, 3])
{[product], wasm} = Firebird.pipe!(wasm, :multiply, [sum, 2])
Firebird.stop(wasm)Inspect any WASM file and generate a complete Elixir module:
mix firebird.gen priv/wasm/math.wasm --module MyApp.MathGenerates a ready-to-use module with all exported functions wrapped. Styles:
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-shotmix 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# 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#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }cargo build --target wasm32-unknown-unknown --release//export add
func add(a, b int32) int32 { return a + b }GOOS=wasip1 GOARCH=wasm go build -o module.wasmWrite Elixir, compile to WebAssembly:
# 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)
endmix 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 BEAMSupports: arithmetic, comparisons, if/else/unless/cond/case, pattern matching, recursion, guards, pipe operator, variable bindings.
See WASM Target Guide | API Reference | Elixir-to-WASM Research for full details.
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.
mix firebird.phoenix.gen my_app # Generate Phoenix WASM projectSee Phoenix to WASM Guide for full documentation.
Use Firebird.TestCase for zero-boilerplate WASM testing (like Phoenix's ConnCase):
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
endFirebird.TestCase handles ExUnit setup, WASM loading, and cleanup automatically.
Options:
- No args β loads bundled
sample_math.wasm wasm: "path.wasm"β loads your modulewasm: falseβ just imports helpers, no auto-loadingwasi: trueβ enable WASI support
# 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
endManual setup (without TestCase)
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
endFor quick testing with the bundled sample:
setup_sample_wasm() # Uses Firebird's included sample_math.wasmmix 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# 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")- Cheatsheet β Single-page quick reference
- Why WASM? β When to use WASM vs BEAM
- Performance Deep Dive β 75+ benchmarks with methodology
- Decision Guide β Which API to use? WASM vs BEAM? Pool vs Module? Start here
- Getting Started Guide β Full walkthrough
- Performance Guide β Profiling, benchmarking, deciding what to WASM-ify
- Testing Guide β Testing WASM modules with
Firebird.TestCase, assertions, patterns - Cookbook β Real-world recipes: plugins, ETL, sandboxing, batch jobs
- API Reference β Complete API documentation
- Architecture Guide β Module layers, data flow, compiler pipeline, design decisions
- Troubleshooting β Common issues and solutions
- Phoenix to WASM β Full Phoenix WASM guide
- Examples β Working example scripts
- Elixir-to-WASM Guide β Compile Elixir to WebAssembly
See CONTRIBUTING.md for project setup, architecture overview, and how to submit changes.
MIT
