Internals

The runtime is a small pipeline. Understanding it is how you write a teaching interpreter, a debugger, or a new emitter — work we will add as tools later. This page is the map.

Pipeline

  1. Lexerpy/lexer.c turns source into tokens (names, numbers, keywords, indent/dedent).
  2. Parserpy/parse.c builds a parse tree from a table-driven grammar in py/grammar.h.
  3. Compilerpy/compile.c walks the tree and emits bytecode (or native/viper).
  4. Emitter — bytecode in py/emitbc.c; native in py/asm*.c per architecture.
  5. VMpy/vm.c is a computed-goto loop over opcodes in py/bc0.h.
  6. Runtime — objects in py/obj*.c, GC in py/gc.c.

Bytecode

A compiled function is a prelude (stack size, exception stack, argument counts) plus a stream of opcodes: load/store local, binary ops, calls, jumps, FOR_ITER, and so on. .mpy files are a versioned container for that bytecode plus constants. mpy-cross produces them on a desktop so a tiny board never has to parse source.

The VM is portable. Ports do not reimplement Python; they implement the HAL that machine and the system layer call into. That is why a teaching VM can reuse mpy-cross and only interpret the opcodes.

Objects and GC

Small integers are tagged pointers. Everything else is a heap object with a type pointer. The collector is a mark-and-sweep GC over a fixed heap the port declares at startup. gc.collect() is explicit; allocation may also trigger a collection. Fragmentation matters more than on CPython — prefer bytearray reuse and const for tables.

Configuration

py/mpconfig.h defaults. MICROPY_CONFIG_ROM_LEVEL turns whole groups of features on or off. Individual flags override: MICROPY_PY_SYS_SETTRACE for sys.settrace, MICROPY_PERSISTENT_CODE_LOAD for .mpy import, and so on. A port that wants a debugger enables settrace and keeps names with MICROPY_PY_SYS_SETTRACE_SAVE_NAMES.

Read the source