1. Interpreter Dispatch

JVM Bytecode Stack Interpreter

midjvm-internalsinterpreter-dispatch

Every JVM method body is a flat sequence of bytecode instructions operating over two pieces of per-frame state: an operand stack (for intermediate values mid-computation) and a locals array (for method arguments and local variables). There's no register allocator at this level -- everything routes through the stack. This is the same "stack machine" shape as CPython's ceval.c (if you've done the Language Runtimes course's Bytecode Stack Machine exercise, this is its JVM-flavored counterpart: same operand-stack-and-locals model, different real VM, different instruction set) -- but the JVM's actual mnemonics and control-flow instructions.

Implement JVMInterpreter.run(instructions), a fetch-decode-execute loop over a program counter (pc) walking a flat instruction list. Support:

  • ("BIPUSH", const) -- push const onto the operand stack.
  • ("ILOAD", index) -- push self.locals[index].
  • ("ISTORE", index) -- pop the top value into self.locals[index].
  • ("IADD", None) -- pop value2, pop value1, push value1 + value2.
  • ("ISUB", None) -- pop value2, pop value1, push value1 - value2 (note the order: the value pushed FIRST is the minuend).
  • ("IFEQ", target) -- pop a value; if it's 0, jump (pc = target); otherwise fall through to the next instruction.
  • ("GOTO", target) -- unconditional jump: pc = target.
  • ("IRETURN", None) -- pop and return the value; this ends run().

Example

interp = JVMInterpreter(locals_count=0)
interp.run([
    ("BIPUSH", 3),
    ("BIPUSH", 4),
    ("IADD", None),
    ("IRETURN", None),
])
# -> 7

A loop is just IFEQ + GOTO around a body -- e.g. summing 1..n by decrementing a counter local to 0 and jumping back to the top each iteration (see the test file's _sum_loop_program for a fully worked example).

Constraints

  • Targets for IFEQ/GOTO are absolute indices into instructions, not relative offsets.
  • ISUB's operand order matters: the operand pushed onto the stack FIRST is value1 (the minuend); the one pushed SECOND (so popped first) is value2 (the subtrahend).
  • Every test program is well-formed and eventually reaches an IRETURN -- you don't need to handle malformed bytecode.

Real-world grounding

  • HotSpot's portable C++ bytecode interpreter (used by the "Zero" build, a JIT-free interpreter for platforms without a template-interpreter assembly backend -- the clearest place to read the actual fetch-decode-execute loop over an operand stack and locals array): src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp
  • The default (non-Zero) interpreter HotSpot actually ships with per platform generates real machine code per bytecode instead of switching on an enum in a C++ loop -- the platform-independent table of per-bytecode behavior it's generated from: src/hotspot/share/interpreter/templateTable.cpp

Real HotSpot bytecodes also carry type-specific variants (iadd/ladd/fadd/dadd, iload_0..3 as fast paths for the first four locals, etc.) and a full 256-entry opcode space -- this exercise implements the int-only core of the dispatch loop underneath all of it.

hints
bytecode_interpreter.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.