Bytecode VM v3

hard · compilers, virtual machine, bytecode, gc

Bytecode VM v3

Implement a stack-based virtual machine with closures, arrays, maps, indexing, and mark-sweep GC.

API

func NewVM() *VM
func (vm *VM) Run(bc Bytecode) (Value, error)
func (vm *VM) GC()
func (vm *VM) HeapCount() int

Run should execute bc.Main. It should start from a clean VM state (fresh stack and environment).

Bytecode types

Use the provided types:

  • Bytecode with Main []Instr and Functions []Function
  • Function with ID, Name, Params, and Code []Instr
  • Instr with Op, Int, and Str

Value types

Use the provided Value struct with kinds:

  • number
  • string
  • bool
  • nil
  • array
  • map
  • closure

Arrays, maps, and closures are heap objects managed by GC.

Runtime rules

Truthiness

  • false and nil are falsey
  • everything else is truthy

Arithmetic / comparison

  • + - * / work on numbers
  • + also supports string concatenation when both operands are strings
  • < <= > >= work on numbers
  • == / != compare by type and value (arrays/maps/closures compare by identity)

Type mismatches should return a non-nil error.

Variables and scopes

  • DEFINE_VAR / DEFINE_CONST create bindings in the current scope
  • STORE updates the nearest existing binding (error if const)
  • LOAD reads the nearest existing binding
  • ENTER_SCOPE / EXIT_SCOPE push and pop scope frames

Functions and closures

  • MAKE_CLOSURE <id> captures the current environment and pushes a closure value.
  • CALL <arity> pops arguments and a callee. The callee must be a closure.
  • Create a new environment for each call with parent = closure environment.
  • Bind parameters left-to-right.
  • If a function returns without RETURN, its result is nil.

Arrays and maps

  • MAKE_ARRAY <n> pops n values and creates an array (preserve order).
  • MAKE_MAP <n> pops 2n values (key then value) and creates a map.
  • Map keys must be number, string, bool, or nil.
  • Pairs are evaluated left-to-right; for duplicate keys, the last source pair supplies the stored value. Reverse stack popping must preserve that policy.

Indexing

  • GET_INDEX pops index and target:
    • array: index must be number, in range
    • map: key must be hashable, missing key returns nil
  • SET_INDEX pops value, index, target:
    • array: index must be number, in range
    • map: key must be hashable, sets or overwrites

Program result

After executing Main, return the top of the stack if present; otherwise return nil.

GC

Implement mark-sweep GC over heap objects.

  • Roots include the value stack and all reachable environments.
  • Trace array elements, map values, and closure environment chains. Map keys are primitive hashKey records and contain no references to VM heap objects. Function IDs and bytecode are not GC heap objects.
  • Closures keep their captured environments alive. Mark an object before traversing its children; track visited environments to terminate cycles.
  • Clear marks on survivors after each sweep. The global environment remains a root after Run; locals in exited scopes require another reachable path.
  • HeapCount returns the number of heap objects currently tracked. Unreachable allocations remain counted until collection.

Before implementation

Use the module’s worked reconstruction and independent debugging lab to predict an intermediate artifact first. Preserve one failing counterexample while implementing the stated API. The lesson’s transfer task uses a fresh case after the guided trace.

Run tests to see results
No issues detected
    Join Discord