Master the fundamental concepts of build a bytecode vm through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksHeap objects outlive stack slots. Mark-and-sweep finds unreachable objects by graph traversal from roots, then frees everything unmarked. Lua's collector and CPython's cycle-breaking GC both extend this family of algorithms.
Every heap object carries a mark bit. Collection runs in two passes:
Roots are anything the running program can still reach without loading from the heap. Missing a root means collecting live data; scanning too much means extra pause time.
For example, allocate three string objects, drop two references, run GC, and only the still-referenced string survives.
```c void collect_garbage() { mark_roots(); sweep(); } ```
Trigger collection when allocation fails or crosses a threshold. Print heap stats before and after to see bytes reclaimed.
Treat the operand stack, every frame's locals, and any global roots as scan starting points. Missing one root frees live objects; scanning too much only costs time. Log object counts before and after collection to verify unreachable nodes actually disappear.
This exercise asks you to add a mark-and-sweep collector to your object-allocating VM. You will implement `mark_roots`, recursive `mark_object`, and `sweep` over an allocation linked list.
Give the VM a heap and a mark-and-sweep garbage collector. Objects (ints, strings, pairs) are allocated on demand. Roots are the operand stack and the globals. Collection marks everything reachable from the roots (following pair links, including cycles), then sweeps away the rest. Like clox and Lua, collect automatically when the live-object count reaches a threshold, and double the threshold if a collection frees nothing useful.
cLoading…
heap full). If live is still >= threshold, double the threshold and print gc: still full, threshold raised to T. The operands of pair are still on the stack during that collection, so they survive.gc (heap full|explicit): marked M, freed #a #b ..., live L, listing the freed ids in increasing order (or nothing).int V, str "S", pair(#a, nil)).allocated A, live L, collections C, threshold T.pair: need two values, pop: stack empty, dup: stack empty, setg: stack empty, getg NAME: undefined global, setcar: need a pair and a value / setcdr: ..., setcar: not a pair / setcdr: not a pair, and threshold: 1..512.cLoading…
Input:
cLoading…
Output:
cLoading…
Hidden tests cover a long list built with repeated pair while automatic collections raise the threshold, moving a structure between globals, overwriting a global, and every error message.