Bytecode VM
The Forge bytecode VM is a register-based virtual machine that compiles Forge source to 32-bit instructions and executes them in a loop. As of v0.7.0, the VM is the default execution engine. It provides significantly better performance than the tree-walking interpreter and supports the vast majority of Forge features. Programs that use VM-incompatible features (such as HTTP server decorators) are automatically detected and fall back to the interpreter.
Invocation
The VM is used by default. To explicitly use the interpreter instead:
forge run program.fg --interp
Architecture
Source -> Lexer -> Parser -> AST -> Compiler -> Bytecode Chunks -> Machine -> Result
|
Mark-Sweep GC
|
Green Threads
Key source files:
src/vm/compiler.rs(~927 lines) – AST to bytecode compilationsrc/vm/machine.rs(~2,483 lines) – bytecode execution enginesrc/vm/bytecode.rs– instruction set definitionsrc/vm/gc.rs– mark-sweep garbage collectorsrc/vm/frame.rs– call frame managementsrc/vm/value.rs– VM-specific value typesrc/vm/green.rs– green thread scheduler
Instruction Encoding
All instructions are 32 bits wide. Three encoding formats:
ABC Format: [op:8][a:8][b:8][c:8]
Used for register-to-register operations. a is typically the destination register; b and c are source registers.
ABx Format: [op:8][a:8][bx:16]
Used for instructions with a larger operand, such as constant loading. bx is an unsigned 16-bit index.
AsBx Format: [op:8][a:8][sbx:16]
Used for jump instructions. sbx is a signed 16-bit offset stored as unsigned (with bias).
Important: The VM pre-increments IP before applying jump offsets. The JIT target address is
ip + 1 + sbx, notip + sbx.
Register Machine
The VM uses a register-based architecture rather than a stack-based one. Each call frame has its own register window. Registers are addressed by 8-bit indices, allowing up to 256 registers per frame.
Benefits:
- Fewer instructions than a stack VM (no push/pop for every operand)
- Better cache locality for register access
- Natural fit for the JIT tier
Constant Pool
Each compiled function (called a “Chunk”) has a constant pool for literals, strings, and function prototypes. Constants are deduplicated via identical() comparison to avoid wasting pool slots.
Garbage Collection
The VM uses a mark-sweep garbage collector. Heap-allocated objects (strings, arrays, objects, closures) are tracked by the GC. Collection is triggered when the allocation count exceeds a threshold.
The mark phase walks from GC roots (registers, global environment, call stack). The sweep phase frees unreachable objects.
Green Threads
The VM includes a cooperative green thread scheduler (src/vm/green.rs). Green threads are multiplexed over a single OS thread with explicit yield points.
Supported Features
The VM supports the vast majority of Forge features:
- Variables, functions, closures, lambdas
- Control flow (if/else, for, while, match, when guards)
- Arrays, objects, destructuring
- Arithmetic, comparison, and logical operators
- String operations and interpolation
- All 18+ stdlib modules (math, fs, io, crypto, db, pg, mysql, http, json, csv, regex, log, env, jwt, term, exec, npc, url, toml, ws)
- Try-catch error handling
- Async/await with spawn and channels
- Schedule and watch
- Must, ask, freeze expressions
- All built-in functions (238+)
Interpreter-Only Features
The following features require the interpreter (--interp flag) and are auto-detected for fallback:
- HTTP server decorators (
@server,@get,@post,@delete,@ws) - DAP debugger integration