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 worksLocal variables live in the current call frame. `LOAD_LOCAL idx` and `STORE_LOCAL idx` are how compiled functions read and write slots without touching the operand stack for every access.
A closure captures variables from an enclosing scope. When an inner function references `x` from an outer function, `x` must outlive the outer frame. An upvalue is a cell pointing at that captured variable.
Two common strategies:
For example, a counter closure might capture `count` from an outer function and increment it each call, even after the outer function returns.
```c typedef struct { int code_address; int upvalue_count; Value* upvalues; } Closure; ```
Whether capture is by copy or by reference is the design choice behind JavaScript's classic loop-variable-in-closure bug.
When a closure escapes, captured variables must live on the heap, not in a stack frame that will disappear. Your flat upvalue array is the minimal version of what Lua and JavaScript runtimes manage with open and closed upvalue lists.
This exercise asks you to wire locals into frames and add flat closures with `OP_CLOSURE`. You will implement `LOAD_UPVALUE` and `STORE_UPVALUE` so a nested function reads a captured variable after the outer frame is gone.
Add closures: functions that carry captured state with them. CLOSURE label ncap pops ncap values into a new closure object's upvalue cells and pushes the closure. CALLC nargs calls a closure found below its arguments. Inside it, LOAD_UP i/STORE_UP i read and write that closure's cells, which persist between calls. That is how counters, generators and make_adder work. Stack values now have two kinds, numbers and closures, and mixing them up is a runtime error.
cLoading…
The instructions of the previous task (including CALL L nargs, RET, LOAD i, STORE i, 8 locals, frames with stack bases), plus CLOSURE L ncap (0..8), CALLC nargs (0..8), LOAD_UP i and STORE_UP i (0..7).
... closure arg0 .. argN-1. Arguments become locals as with CALL, the closure slot is removed, and the frame remembers which closure is running.LOAD_UP/STORE_UP outside a closure, or with an index >= ncap, is an error.<closure NAME#id>. Arithmetic, comparisons and JZ on a closure are errors. So is CALLC on a number.error at PC (OP): operand is a closure, not a numbererror at PC (CALLC): calling a number, not a closureerror at PC (OP): not inside a closureerror at PC (OP): closure has only N upvalue(s)call stack overflow (depth 64), return from main, step limit 100000, and missing HALThalted after K instructions, C closures created.line N: bad operands for OP, line N: unsupported instruction X, line N: duplicate label X, and line N: undefined label X.cLoading…
Input:
cLoading…
Output:
cLoading…
Hidden tests cover a closure returned from a function (make_adder), two independent counters, a closure with several captured values, STORE_UP from inside a call, upvalue access outside a closure, an out-of-range upvalue index, arithmetic on a closure, and CALLC on a number.