Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 compilation
  • src/vm/machine.rs (~2,483 lines) – bytecode execution engine
  • src/vm/bytecode.rs – instruction set definition
  • src/vm/gc.rs – mark-sweep garbage collector
  • src/vm/frame.rs – call frame management
  • src/vm/value.rs – VM-specific value type
  • src/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, not ip + 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