README.md

![Firebird Library Mascot](mascot.png)

# Firebird 🔥

[![CI](https://github.com/hdresearch/firebird/actions/workflows/ci.yml/badge.svg)](https://github.com/hdresearch/firebird/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/firebird.svg)](https://hex.pm/packages/firebird)

**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, "~> 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