data-structures · advanced · ~50 min

Final Project: tiny stack VM

Dispatch loop; fixed-size stack; opcode encoding.

Challenge

Execute a sequence of opcodes against a stack. Opcodes:

  • PUSH x — push integer x.
  • ADD — pop b, pop a, push a+b.
  • MUL — pop b, pop a, push a*b.
  • NEG — pop a, push -a.
  • HALT — stop.

Represent the program as a flat int array where each opcode is a number; PUSH is followed by its argument. Use these constants:

enum { OP_PUSH=1, OP_ADD=2, OP_MUL=3, OP_NEG=4, OP_HALT=5 };

Implement int vm_run(const int *code, size_t n, int *out) returning 1 on success (with the final top-of-stack in *out), or 0 on stack underflow / overflow / bad opcode.

Why this matters

A stack VM is the simplest possible interpreter and the model behind the JVM, CPython, and WebAssembly. Building one teaches dispatch loops and the elegance of postfix evaluation.

Input format

code of n ints. Stack capacity 256.

Output format

*out set on success. Return 1/0.

Constraints

No malloc. Stack fixed at 256 ints.

Starter code

#include <stddef.h>
enum { OP_PUSH=1, OP_ADD=2, OP_MUL=3, OP_NEG=4, OP_HALT=5 };
int vm_run(const int *code, size_t n, int *out) { /* TODO */ return 0; }

Common mistakes

Popping in the wrong order (ADD/MUL are commutative; NEG is not); reading past n on a malformed program; forgetting the IP++ for PUSH's argument.

Edge cases to handle

Empty program — no HALT means error. PUSH at end with no argument — error. Stack underflow on bare ADD.

Complexity

O(n) per instruction stream.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.