Skip to content

Repository files navigation

Firebird Library Mascot

Firebird 🔥

CIHex.pm

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.

Quick Start

1. Add dependency

# mix.exsdefdepsdo[{:firebird,"~> 1.0"}]end

2. Configure formatter (optional)

Add :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]]

3. Verify it works

iex>Firebird.check!():okiex>Firebird.demo()# See add, multiply, fibonacci in action

4. Call WASM functions

# 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? | Performance Deep Dive


The Declarative Way (Recommended)

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: 1end

Add to your supervision tree:

# application.exchildren=[MyApp.Math]

Call functions naturally:

{:ok,[8]}=MyApp.Math.add(5,3)[55]=MyApp.Math.fibonacci!(10)

Auto-Discovery

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 functions

Inline WAT (Prototyping)

Write 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")

Connection Pool

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])

Block API (Auto-Cleanup)

{: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}

Pipe-Friendly API

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)

Auto-Generate a Wrapper

Inspect any WASM file and generate a complete Elixir module:

mix firebird.gen priv/wasm/math.wasm --module MyApp.Math

Generates 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-shot

Scaffold a New Project

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

# 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

Creating WASM Modules

Rust

#[no_mangle]pubextern"C"fnadd(a:i32,b:i32) -> i32{ a + b }
cargo build --target wasm32-unknown-unknown --release

Go

//export addfuncadd(a, bint32) int32 { returna+b }
GOOS=wasip1 GOARCH=wasm go build -o module.wasm

Compile Elixir to WASM

Write 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)end
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 | API Reference | Elixir-to-WASM Research for full details.

Phoenix to WASM 🔥

Firebird includes WASM-accelerated Phoenix components:

ComponentDescription
RouterHTTP route matching with path params and wildcards
TemplateHTML template rendering with auto-escaping
PlugRequest parsing, response building, CSRF validation
EndpointFull request lifecycle with plug pipelines
ChannelTopic matching, message serialization, presence
JSON APIJSON encoding, API responses, pagination, errors
SessionCookie parsing, signing, session management
LiveHTML diffing, patch generation, component rendering
ValidatorForm validation: required, type, length, format, inclusion
FormHTML form helpers with CSRF, method spoofing, escaping
WebSocketFrame encode/decode, upgrade handling, Channel protocol
CSRFToken generation, signing, validation, protection middleware
RateLimiterToken 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 project

See Phoenix to WASM Guide for full documentation.

Testing WASM Modules

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}]endend

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
# 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]endend
Manual setup (without TestCase)
defmoduleMyApp.MathTestdouseExUnit.CaseimportFirebird.TestHelperssetup_wasm"priv/wasm/math.wasm"test"add works",%{wasm: wasm}doassert_wasm_callwasm,:add,[5,3],[8]endend

For quick testing with the bundled sample:

setup_sample_wasm()# Uses Firebird's included sample_math.wasm

Developer Tools

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
# 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")

Documentation

Contributing

See CONTRIBUTING.md for project setup, architecture overview, and how to submit changes.

License

MIT

About

WebAssembly toolkit for Elixir

Resources

Contributing

Stars

80 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages