The Bytecode VM
High-performance execution
The tree-walking interpreter is easy to understand but not the fastest. For performance-critical code, Bloom has an optional bytecode virtual machine (VM) that can be much faster.
Why Bytecode?
Walking a tree involves lots of pointer chasing and method dispatch. Bytecode is a flat array of numbers — the VM just reads instructions sequentially, which is much faster.
How It Works
The bytecode system has two parts:
- Compiler — Converts AST to bytecode instructions
- Virtual Machine — Executes those instructions
The Stack
The VM uses a stack to hold values. Operations pop values from the stack, compute, and push results back.
CONST 1 // Push 1 onto stack Stack: [1]
CONST 2 // Push 2 onto stack Stack: [1, 2]
ADD // Pop 2, pop 1, push 1+2 Stack: [3]
Bytecode Instructions
Each instruction is an opcode (operation code), sometimes followed by arguments:
The CONST instruction takes an index into a constant pool — an array of literal values. This keeps the bytecode compact.
Key Opcodes
Compiling a For Loop
Let's see how a loop becomes bytecode:
for i in 0..3 {
print(i)
}
The green rows are the loop body — executed 3 times. LOOP is a backward jump that repeats the body.
Superinstructions
Common instruction sequences are fused into single superinstructions for speed:
The compiler recognizes these patterns and emits the optimized version.
Local Variables
Inside functions, variables use numbered slots instead of names. No hash table lookup needed!
fn example(a, b) { // a = slot 0, b = slot 1
let c = a + b // c = slot 2
return c
}
// Compiled to:
LOAD_LOCAL 0 // push a
LOAD_LOCAL 1 // push b
ADD // a + b
STORE_LOCAL 2 // store to c
LOAD_LOCAL 2 // push c
RETURN // return top of stack
Calling Native Functions
Native functions (like circle) use a dispatch table for O(1) lookup:
// In the VM:
case OpCode.CALL_NATIVE: {
const funcIndex = code[ip++]
const argCount = code[ip++]
const func = nativeFunctions[funcIndex] // Direct array access!
// ... pop args, call func, push result
}
Performance
The bytecode VM can be 10-60x faster than the tree-walking interpreter for computation-heavy code.
In the Source Code
The bytecode system lives in src/lang/bytecode.ts:
Compilerclass — Converts AST to bytecodeVMclass — Executes bytecodeOpCodeenum — All instruction typesVMNativeBridge— Adapts interpreter natives for VM