Skip to main content
Arc is a JavaScript runtime that runs on the BEAM VM (Erlang’s virtual machine). It brings JavaScript’s familiar syntax to Erlang’s robust actor model, enabling lightweight concurrency with message-passing semantics.

What is Arc?

Arc executes JavaScript code as BEAM processes, giving you:
  • Actor-based concurrency — Spawn thousands of isolated processes, each running JavaScript
  • Fault tolerance — Process crashes are isolated and recoverable
  • ES modules — Modern import/export syntax with compile-time bundling
  • REPL — Interactive development environment with live code evaluation

Architecture overview

Arc implements a complete JavaScript execution pipeline:

Key components

Parser

Parses JavaScript source into an Abstract Syntax Tree (AST). Supports both Script and Module modes.

Compiler

Three-phase bytecode compiler: emit symbolic operations, resolve variable scopes, then resolve jump addresses.

VM

Stack-based bytecode interpreter with heap-allocated objects. Executes compiled templates on the BEAM.

Builtins

Standard JavaScript globals (Array, Object, Promise, etc.) plus Arc-specific functions for concurrency.

Compilation pipeline

The compiler transforms AST into executable bytecode through three phases:
1

Phase 1: Emit

Convert AST to symbolic EmitterOp instructions with variable names and label IDs.
2

Phase 2: Scope

Resolve variable names to local indices and collect captured variables for closures.
3

Phase 3: Resolve

Replace label IDs with absolute bytecode addresses for jumps and branches.
The result is a FuncTemplate containing:
  • Bytecode operations (array of Op)
  • Constant pool (literals, strings, numbers)
  • Nested function templates (closures, arrow functions)
  • Metadata (parameter count, strict mode, async/generator flags)
All compilation happens ahead-of-time. The VM never sees source code or AST — only bytecode.

JavaScript on Erlang

Arc bridges two worlds:
// JavaScript values and operations
const obj = { x: 1 };
const arr = [1, 2, 3];
const fn = (x) => x + 1;

Heap management

All JavaScript objects live on Arc’s heap — an immutable dictionary arena with garbage collection:
// From src/arc/vm/heap.gleam
pub opaque type Heap {
  Heap(
    data: dict.Dict(Int, HeapSlot),  // Object storage
    free: List(Int),                 // Recycled IDs
    next: Int,                       // Next allocation ID
    roots: Set(Int),                 // GC roots
  )
}
Objects are stored by reference (Ref(id)) and retrieved via heap.read(). The heap is immutable — every mutation returns a new heap instance.

REPL and module execution

Arc supports two execution modes:

REPL mode

Interactive evaluation with persistent state:
arc
> const x = 10
> x * 2
20
> /help  # Built-in commands
Top-level var/let/const declarations create global bindings that persist across evaluations.

Module execution

Run ES modules from files:
arc examples/counter_actor.js
Modules use the two-phase lifecycle:
  1. Compile bundle — Parse and compile all dependencies into a ModuleBundle
  2. Evaluate bundle — Execute modules in dependency order, link imports/exports
The ModuleBundle is a pure Erlang term, serializable with term_to_binary. You can precompile modules and load them later without parsing.

Arc namespace

The Arc global provides concurrency primitives:
Arc.spawn(fn)        // Spawn a new BEAM process
Arc.send(pid, msg)   // Send a message to a process
Arc.receive(timeout) // Block waiting for a message
Arc.self()           // Get current process ID
Arc.log(...args)     // Print to stdout (works in any process)
Arc.sleep(ms)        // Sleep current process
These functions bridge JavaScript code to the BEAM’s actor model.

Next steps

JavaScript on BEAM

Deep dive into the execution model and compilation pipeline

Actor model

Learn actor-based concurrency with Arc.spawn, send, and receive

Modules

ES module system and import/export mechanics

Build docs developers (and LLMs) love