A step-by-step guide to building a BEAM-like virtual machine in JavaScript, based on The BEAM Book by Erik Stenman.
File: 01-memory.js
Implements tagged memory representation using a 2-bit tagging scheme:
Run:
node 01-memory.js
Concepts: Memory allocation, heap pointer, tagged pointers, immediate values vs boxed values
File: 02-pattern-matching.js
Implements unification over tagged 32-bit words:
Run:
node 02-pattern-matching.js
Concepts: Unification, pattern matching, environment binding, structural recursion
File: 03-process.js
Implements the Process Control Block (PCB) with:
Run:
node 03-process.js
Concepts: Process isolation, private heaps, garbage collection, register machines
File: 04-scheduler.js
Implements the preemptive scheduler:
Run:
timeout 2s node 04-scheduler.js
Concepts: Preemptive scheduling, round-robin, context switching, process states
File: 05-isa.js
Implements the BEAM-like instruction set:
Includes a simple assembler that parses text assembly into bytecode.
Run:
node 05-isa.js
Concepts: Register machines, operand types, assembler, BIFs, stack frames
File: 06-vm.js
Ties everything together into a complete virtual machine:
Run:
timeout 2s node 06-vm.js
Concepts: Linking, label resolution, complete VM architecture
Execute the full test suite:
./run-tests.sh
Or run individual steps:
# Step 1: Memory
node 01-memory.js
# Step 2: Pattern Matching
node 02-pattern-matching.js
# Step 3: Process
node 03-process.js
# Step 4: Scheduler (ctrl+c to stop)
node 04-scheduler.js
# Step 5: ISA
node 05-isa.js
# Step 6: Integrated VM (ctrl+c to stop)
node 06-vm.js
┌─────────────────────────────────────────┐
│ Code Server (Scrolls) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Module1 │ │ Module2 │ │ Module3 │ │
│ │Bytecode │ │Bytecode │ │Bytecode │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Scheduler (Village) │
│ ┌───────────────────────────────────┐ │
│ │ Run Queue (FIFO) │ │
│ │ [P1] → [P2] → [P3] → [P4] │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│Process 1│ │Process 2│
├─────────┤ ├─────────┤
│ PC: 42 │ │ PC: 103 │
│ X0-X31 │ │ X0-X31 │
│ Stack │ │ Stack │
│ Heap │ │ Heap │
│ Mailbox │ │ Mailbox │
└─────────┘ └─────────┘
Each process has its own heap. Messages are copied between process heaps for isolation.
All values are 32-bit words. The bottom 2 bits indicate the type:
00: Integer01: Atom10: Boxed (pointer to heap)11: List (pointer to cons cell)Processes run for a fixed number of reductions (function calls) before yielding to other processes. This prevents any single process from monopolizing the CPU.
Jump instructions don't consume stack space, allowing infinite recursion. They still cost 1 reduction for fair scheduling.
Educational purposes. Based on The BEAM Book and Erlang/OTP documentation.