Firebird can compile a subset of Elixir directly to WebAssembly. This enables running Elixir-authored logic as native WASM modules — useful for edge computing, sandboxed execution, and high-performance numeric workloads.
Elixir Source (.ex)
→ Elixir AST (Code.string_to_quoted)
→ Firebird IR (normalized intermediate representation)
→ Optimization (constant folding, dead code elimination)
→ Tail Call Opt (loop-based TCO for tail-recursive functions)
→ Type Inference (i64/f64 type assignment)
→ Validation (check compilable subset)
→ WAT (WebAssembly Text Format)
→ WASM binary (via wat2wasm)
# 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# Compile all @wasm annotated modules
mix firebird.target
# Or compile specific files
mix firebird.compile lib/wasm_modules/math.ex
# WAT only (no binary)
mix firebird.target --wat-only{:ok, instance} = Firebird.load("_build/wasm/MyMath.wasm")
{:ok, [8]} = Firebird.call(instance, "add", [5, 3])
{:ok, [55]} = Firebird.call(instance, "fibonacci", [10])+,-,*(standard operators)div/2,rem/2(integer division and remainder)- Unary
-(negation)
==,!=,<,>,<=,>=
and,or,not
if/elseexpressionsunless/elseexpressionscondexpressionscaseexpressions with literal patterns
- Multi-clause function definitions
- Pattern matching on integer literals
- Guard clauses (
when n > 0,when n >= 0, etc.) - Recursive functions
- Tail-recursive functions (with TCO optimization)
- Local function calls between exported functions
- Variable assignment (
x = expr) - Block expressions (multiple statements, last one returned)
a |> f(b)compiles tof(a, b)
| Flag | Description |
|---|---|
--output DIR |
Output directory (default: _build/wasm) |
--files FILE,... |
Compile specific files |
--dirs DIR,... |
Scan specific directories |
--wat-only |
Generate WAT only, skip binary |
--verbose |
Show WAT output and sizes |
--verify |
Load and verify compiled WASM |
--stats |
Show compilation statistics |
--clean |
Remove WASM build artifacts |
--optimize |
Enable IR optimizations |
--tco |
Enable tail call optimization |
--inline |
Enable function inlining |
def project do
[
firebird: [
wasm_output: "_build/wasm",
wasm_sources: ["lib/wasm_modules"],
wat_only: false
]
]
end# Compile source string
{:ok, result} = Firebird.Compiler.compile_source(source,
optimize: true, # Enable IR optimizations
tco: true, # Enable tail call optimization
inline: true, # Enable function inlining
wat_only: false # Generate WASM binary
)
# result.wat - WAT text
# result.wasm - WASM binary
# result.module - Module name atomEvaluates constant expressions at compile time:
# Before: a + (2 * 3)
# After: a + 6Removes unreachable branches:
# Before: if true, do: x, else: y
# After: xReplaces expensive operations:
# x * 8 → x << 3 (shift left)
# x * 4 → x << 2Removes no-op operations:
# x + 0 → x
# x * 1 → x
# x * 0 → 0Replaces calls to small helper functions with their body:
# Before: quad(n) calls double(double(n))
# After: quad(n) = (n * 2) * 2 (no function call overhead)
def double(n), do: n * 2
def quad(n), do: double(double(n))Converts tail-recursive functions to loops:
# This won't stack overflow for large n:
def sum_acc(0, acc), do: acc
def sum_acc(n, acc), do: sum_acc(n - 1, acc + n)
# Compiles to a WASM loop instead of recursive calls- Only
i64(64-bit integers) andf64(64-bit floats) - No lists, maps, tuples, strings, or atoms
- No binary/bitstring operations
- No processes, GenServers, or OTP
- No message passing (send/receive)
- No IO operations
- No standard library functions (Enum, String, etc.)
- No hot code reloading
- Only integer literal patterns and variable/wildcard patterns
- No tuple, list, or map patterns
- No binary patterns
- No pin operator
- No closures or anonymous functions
- No module attributes at runtime
- No macros (they expand before compilation)
- Integer range limited to i64 (-2^63 to 2^63-1)
Compiled WASM modules have different performance characteristics than BEAM:
- WASM strengths: Tight numeric loops, recursive algorithms, pure computation
- BEAM strengths: Concurrency, pattern matching on complex data, IO, hot reloading
The mix firebird.bench task can compare WASM vs BEAM performance:
mix firebird.bench
mix firebird.bench --functions fibonacci,factorialInstall the WebAssembly Binary Toolkit:
# macOS
brew install wabt
# Ubuntu/Debian
apt-get install wabtAdd @wasm true annotations before functions you want to compile:
@wasm true
def my_function(a, b), do: a + bThe expression uses features outside the compilable subset. Check the limitations above.
The IR validator found constructs that can't be compiled to WASM. Common causes:
- Using lists, maps, or tuples
- Calling functions from other modules
- Using string operations