This document provides a high-level overview of CPython's multi-tier code execution architecture. CPython executes Python programs through three primary systems that vary by optimization level and runtime complexity:
This page links to detailed child pages covering each execution tier for in-depth understanding. For prior stages, see the Code Compilation Pipeline page. For runtime support and object model, see the respective sections in the wiki.
CPython's execution environment progressively optimizes Python code at runtime, starting by interpreting unoptimized bytecode and advancing towards native machine code execution through profiling, specialization, and JIT compilation.
This diagram illustrates the flow from interpreted bytecode to optimized traces and to native code via JIT when enabled. Execution can fallback or deoptimize back to interpreted bytecode at any tier upon invalidation or failed assumptions.
Sources: Python/ceval.c1-50 Python/generated_cases.c.h1-100 Python/specialize.c46-78 Python/optimizer.c129-190 Python/jit.c1-100
CPython starts execution by interpreting the bytecode housed in the PyCodeObject's co_code attribute. This tier is implemented primarily in the evaluation loop _PyEval_EvalFrameDefault() within Python/ceval.c. The loop dispatches opcodes using a large switch-case mechanism with cases generated into generated_cases.c.h from the instruction definitions in bytecodes.c.
| Component | Description | File and Location |
|---|---|---|
| Evaluation loop | Main loop fetching and executing bytecode instructions | Python/ceval.c:7-50 |
| Bytecode dispatch cases | Generated opcode handlers that implement each python bytecode instruction | Python/generated_cases.c.h:21-170 |
| Specialization entry points | Calls to adaptive specialization routines for "warming up" bytecodes | Python/bytecodes.c:175-178 |
| Stack and frame management | Handling of Python frames and stack references | Python/ceval.c |
The interpreter uses _PyStackRef abstraction to manage references on the Python value stack safely. It enforces recursion and stack limits via _PyThreadStateImpl members (e.g., c_stack_soft_limit). The interpreter supports bytecode quickening by replacing generic instructions with specialized variants at runtime.
For comprehensive details, see the child page: Bytecode Interpreter and Evaluation Loop.
Sources: Python/ceval.c1-50 Python/generated_cases.c.h1-100 Python/bytecodes.c147-178
To improve interpreter performance before advanced optimizations, CPython uses adaptive specialization or quickening. This process replaces generic opcodes with type-specialized versions using inline caches to skip repeated dynamic type checks.
_Py_Specialize_Resume() in Python/specialize.c._GUARD_TOS_FLOAT) which verify assumptions. If a guard fails, execution falls back to the generic opcode handler ensuring correctness.These logic paths are implemented within the generated opcode cases which detect counter thresholds and perform specialization, as well as in specialize.c where specialization decisions and checks occur.
For full coverage, see Adaptive Bytecode Specialization.
Sources: Python/generated_cases.c.h21-186 Python/bytecodes.c175-180 Python/specialize.c45-110
When code executes frequently and after adaptive specialization, CPython can optimize bytecode by translating it into a trace of micro-operations (UOps). These UOps represent a lower-level, more uniform, and analyzable form of the computation amenable to advanced optimizations and JIT compilation.
uop_optimize() to record sequences of micro-operations Python/optimizer.c120-130JitOptContext and symbolic tracking (JitOptSymbol), the optimizer simulates code paths to detect impossible states, constant folding, and other optimizations Python/optimizer_bytecodes.c40-180_PyExecutorObject instances representing the Tier 2 executors Python/optimizer.c130-185ENTER_EXECUTOR opcode is inserted into the bytecode to switch execution from Tier 1 to Tier 2._PyUOpInstruction holding opcode, arguments, and operands.executor_cases.c.h.pycore_uop_ids.h and pycore_uop_metadata.h.For detailed technical walkthrough, see Tier 2 Optimizer and JIT Compilation.
Sources: Python/optimizer.c120-185 Python/optimizer_bytecodes.c10-135 Python/executor_cases.c.h1-70 Include/internal/pycore_uop_ids.h10-160 Include/internal/pycore_uop_metadata.h30-40
For further performance, CPython optionally compiles Tier 2 UOp traces into native machine code using a copy-and-patch JIT technique:
Tools/jit/. They represent micro-op implementations._PyJIT_Compile() copies these stencils into executable memory, patching placeholders with actual constants, pointers, and jump addresses Python/jit.c1-100PyCodeObject for the hot trace, leading to faster execution.This method balances recompilation overhead and execution speed by reusing micro-op compiled patterns efficiently. Native execution can also deoptimize and return to the interpreter as needed.
See Tier 2 Optimizer and JIT Compilation for more.
Sources: Python/jit.c1-100 Tools/jit/template.c1-50 Include/internal/pycore_uop_metadata.h10-30
| Stage | Description | Code Entities & Files |
|---|---|---|
| Tier 1 Interpreter | Evaluate generic bytecode in a frame | _PyEval_EvalFrameDefault in Python/ceval.c |
| Adaptive Bytecode Specialization | Replace opcodes with specialized variants with guards | _Py_Specialize_Resume in Python/specialize.c, specialized opcodes in generated_cases.c.h |
| Tier 2 UOp Optimizer | Record and optimize micro-op traces | _PyOptimizer_Optimize in Python/optimizer.c, UOp analysis in optimizer_bytecodes.c |
| Tier 2 Executor | Execute micro-op optimized traces | _PyExecutorObject, executor_cases.c.h |
| JIT Compilation | Compile hot traces to native code | _PyJIT_Compile in Python/jit.c, stencil templates Tools/jit/ |
Execution starts at Tier 1, moves upward progressively on code "hotness" and optimization readiness, while always falling back on correctness through deoptimization paths.
| Data Structure / File | Role and Summary |
|---|---|
PyCodeObject | Holds bytecode and metadata, root executable unit |
_PyInterpreterFrame | Execution frame representing current function call |
_PyStackRef | Abstraction for safe reference management on stack |
_PyExecutorObject | Represents Tier 2 optimized traces and compiled code |
_PyUOpInstruction | Internal micro-op representation |
generated_cases.c.h | Generated Tier 1 opcode handlers |
executor_cases.c.h | Generated Tier 2 micro-op handlers |
optimizer_cases.c.h | Generated abstract interpretation handlers for Tier 2 optimizer |
python/ceval.c | Tier 1 interpreter main loop |
python/specialize.c | Adaptive specialization logic |
python/optimizer.c | Tier 2 optimizer entry and executor management |
python/jit.c | JIT compiler implementation |
This overview page introduces the layered architecture of CPython's code execution system bridging from bytecode interpretation to JIT compilation.
For detailed information, documentation is split into these child pages:
Bytecode Interpreter and Evaluation Loop
Covers the core evaluation loop and standard bytecode dispatch.
Adaptive Bytecode Specialization
Explains the mechanism for quickening bytecode instructions via inline caches.
Tier 2 Optimizer and JIT Compilation
Details the micro-op optimizer, executor logic, and JIT native code generation.
This modular structure allows maintainers and contributors to focus on specific execution tiers while understanding their interactions holistically.
Sources:
Python/ceval.c1-50 Python/bytecodes.c147-178 Python/generated_cases.c.h1-186 Python/specialize.c45-78 Python/optimizer.c120-190 Python/optimizer_bytecodes.c1-150 Python/executor_cases.c.h1-70 Python/jit.c1-100 Include/internal/pycore_uop_ids.h10-160 Include/internal/pycore_uop_metadata.h30-40 Tools/jit/template.c1-50
Refresh this wiki