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.exsdefdepsdo[{: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!():okiex>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:
defmoduleMyApp.MathdouseFirebird,wasm: "priv/wasm/math.wasm"wasm_fn:add,args: 2wasm_fn:multiply,args: 2wasm_fn:fibonacci,args: 1endAdd to your supervision tree:
# application.exchildren=[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:
defmoduleMyApp.MathdouseFirebird,wasm: "priv/wasm/math.wasm",auto: trueend# All WASM exports are auto-wrapped as Elixir functionsWrite WebAssembly directly in Elixir — compiled at compile time:
importFirebird.Sigilsbytes=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 treechildren=[{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",fnwasm->{:ok,[a]}=Firebird.call(wasm,:add,[5,3]){:ok,[b]}=Firebird.call(wasm,:multiply,[a,2])bend)# => {: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",fnwasm-># use wasm here, auto-stopped after blockend)# Pipe-friendly (threads instance through){[sum],wasm}=Firebird.pipe!(wasm,:add,[5,3]){[product],wasm}=Firebird.pipe!(wasm,:multiply,[sum,2])# Inspectexports=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)# Lifecycletrue=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# DiagnosticsFirebird.check!()# Verify setup worksFirebird.demo()# Interactive demoFirebird.describe(instance)# Print instance infoFirebird.describe("file.wasm")# Print file infowasm=Firebird.playground()# Pre-loaded instance for iex#[no_mangle]pubextern"C"fnadd(a:i32,b:i32) -> i32{ a + b }cargo build --target wasm32-unknown-unknown --release//export addfuncadd(a, bint32) int32 { returna+b }GOOS=wasip1 GOARCH=wasm go build -o module.wasmWrite Elixir, compile to WebAssembly:
# lib/wasm_modules/math.exdefmoduleMyMathdo@wasmtruedefadd(a,b),do: a+b@wasmtruedeffibonacci(0),do: 0deffibonacci(1),do: 1deffibonacci(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):
defmoduleMyApp.MathTestdouseFirebird.TestCase,wasm: "priv/wasm/math.wasm"test"add works",%{wasm: wasm}doassert_wasm_callwasm,:add,[5,3],[8]endtest"single value",%{wasm: wasm}doassert_wasm_resultwasm,:fibonacci,[10],55endtest"exports",%{wasm: wasm}doassert_wasm_exportswasm,[:add,:multiply,:fibonacci]endtest"type signature",%{wasm: wasm}doassert_wasm_typewasm,:add,{[:i32,:i32],[:i32]}endtest"function with arity",%{wasm: wasm}doassert_wasm_functionwasm,:add,2endtest"error on missing function",%{wasm: wasm}doassert_wasm_errorwasm,:nonexistent,[1]endtest"addition truth table",%{wasm: wasm}doassert_wasm_tablewasm,:add,[{[0,0],0},{[1,2],3},{[5,3],8},{[-1,1],0},{[100,200],300}]endendFirebird.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):defmoduleQuickTestdouseFirebird.TestCase# auto-loads sample_math.wasmtest"it works",%{wasm: wasm}doassert_wasm_callwasm,:add,[1,2],[3]endendManual setup (without TestCase)
defmoduleMyApp.MathTestdouseExUnit.CaseimportFirebird.TestHelperssetup_wasm"priv/wasm/math.wasm"test"add works",%{wasm: wasm}doassert_wasm_callwasm,:add,[5,3],[8]endendFor 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 collectionFirebird.Metrics.start_link()Firebird.Metrics.timed_call(instance,:fibonacci,[30])# Batch executionresults=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
