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
- Lexer —
py/lexer.cturns source into tokens (names, numbers, keywords, indent/dedent). - Parser —
py/parse.cbuilds a parse tree from a table-driven grammar inpy/grammar.h. - Compiler —
py/compile.cwalks the tree and emits bytecode (or native/viper). - Emitter — bytecode in
py/emitbc.c; native inpy/asm*.cper architecture. - VM —
py/vm.cis a computed-goto loop over opcodes inpy/bc0.h. - Runtime — objects in
py/obj*.c, GC inpy/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
- micropython/micropython —
py/is the language - ports/ — one directory per chip family
- Developer docs — adding modules, memory, and the C API