CPython Evaluation Loop: Inside _PyEval_EvalFrameDefault

The _PyEval_EvalFrameDefault function is the core execution engine of CPython, responsible for interpreting compiled Python bytecode into runtime actions. Located primarily within Python/ceval.c (and modularized execution tables in modern Python versions), this function manages the virtual machine's stack, dispatches opcodes, handles function calls, manages exceptions, and coordinates system-level interrupts and thread switches.

Frame Initialization and Context Setup

When a Python function is invoked, CPython creates an execution frame—represented in modern CPython (3.11+) as an _PyInterpreterFrame. When _PyEval_EvalFrameDefault receives this frame, it initializes several register-level C variables for maximum performance:

The Fetch-Decode-Execute Cycle

The core of _PyEval_EvalFrameDefault is an infinite loop that repeatedly fetches, decodes, and executes bytecode instructions.

1. Instruction Fetching

Bytecode is stored as an array of 16-bit code units (in Python 3.10+). Each code unit consists of an 8-bit opcode and an 8-bit argument (oparg). If an argument exceeds 8 bits, an EXTENDED_ARG prefix instruction is used to shift and accumulate bits before reaching the true operation.

2. Opcode Dispatching

CPython uses two primary strategies to jump to the code that handles a specific instruction:

3. Execution and Stack Manipulation

Each instruction block contains raw C code that alters the state of the machine. For instance:

Periodic Checks and Interrupt Handling

Inside the loop, CPython periodically checks whether it must service external requests. This mechanism is controlled by an evaluation breaker flag (eval_breaker):

Exception Handling and Stack Unwinding

If any C API call within an instruction returns NULL (indicating an error), the evaluation loop transitions to exception-handling mode:

  1. Exception Table Lookup: Modern CPython checks the code object's exception table to find an offset matching the current instruction pointer.
  2. Stack Adjustment: If an enclosing try...except or try...finally block is found, the value stack is unwound to the depth expected by the handler, the exception state is pushed, and next_instr jumps to the handler's bytecode offset.
  3. Frame Unwinding: If no handler exists within the current frame, _PyEval_EvalFrameDefault cleans up the current frame, sets the exception in the thread state (PyThreadState), and returns NULL to the caller, propagating the exception up the call stack.

Adaptive Specialization (Python 3.11+)

In modern CPython architectures, _PyEval_EvalFrameDefault incorporates the specializing adaptive interpreter (PEP 659).

Instructions monitor their execution patterns at runtime. For example, a generic LOAD_ATTR instruction tracks the types of objects passing through it. Once a type stabilizes, the interpreter overwrites the instruction in-place with a specialized opcode (such as LOAD_ATTR_INSTANCE_VALUE) alongside an inline cache entry. In subsequent iterations of the loop, the specialized opcode bypasses generic dictionary lookups, directly fetching attributes via precomputed memory offsets. If type assumptions are invalidated, the instruction de-optimizes back to its generic form.

Exiting the Frame

The loop terminates when it encounters instructions like RETURN_VALUE or when an unhandled exception causes an exit. The function decrements references to remaining objects on the value stack, adjusts the interpreter frame hierarchy to point back to the caller frame, and returns the final PyObject* to the calling context.