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 worksFunctions need a call stack separate from the operand stack. Each invocation pushes a frame storing the return address, locals, and a link to the caller's frame. CPython's `PyFrameObject` and the JVM's per-method frame are production versions of what you build here.
Recursion works because each call gets fresh locals. Python's default recursion limit of 1000 frames exists because this stack is finite.
For example, `factorial(5)` nests five frames, each with its own `n` local, before unwinding via `RETURN`.
Frame layout:
Size each frame's local array to the function's needs and zero it on entry. Stale locals from a previous invocation are a subtle bug that only shows up when callers reuse overlapping stack slots without clearing state.
This exercise asks you to add `CALL` and `RETURN` with a `CallFrame` struct. You will implement recursive factorial in bytecode to prove frames push and pop correctly under nested calls.
Give the VM functions. CALL label nargs pushes a call frame: it moves the top nargs operands into the new frame's locals 0..nargs−1, records the return address and the current stack height, and jumps. RET pops the return value, drops anything else the callee left on the stack, pushes the value back for the caller, and resumes after the call. LOAD i/STORE i access the current frame's 8 locals. Recursion must work, and runaway recursion must hit a depth limit.
cLoading…
Instructions: HALT PUSH n POP ADD SUB MUL DIV NEG DUP SWAP PRINT EQ LT GT JMP L JZ L CALL L nargs RET LOAD i STORE i.
end).main is frame 0. A new frame's locals start at 0. Each frame has its own stack base: an instruction that needs more operands than exist above the base is a stack underflow, and a CALL needs nargs operands.error at PC (CALL L): call stack overflow (depth limit N). RET in main prints error at PC (RET): return from main.halted after K instructions, C calls, max call depth D (main is depth 0).call NAME(arg, ...) and NAME returns V, indented 2 spaces per depth − 1.line N: bad operand for OP (PUSH needs an int32; LOAD/STORE need 0..7)line N: CALL needs a label and 0..8 argumentsline N: JMP needs a label / JZ needs a labelline N: unsupported instruction Xline N: duplicate label Xline N: undefined label Xerror at PC (OP): stack underflow | division by zero | result does not fit in 32 bits, plus error: step limit 100000 reached at PC and error: ran off the end of the program without HALT.cLoading…
Input:
cLoading…
Output:
cLoading…
Hidden tests cover recursive Fibonacci, a two-argument function, a callee that leaves junk on the stack, the depth limit, RET from main, a callee reading below its base, and bad operands.