Fundamentals 26 min read

Deep Dive into CPython: How the Bytecode Evaluation Engine Executes Code

This article dissects CPython's _PyEval_EvalFrameDefault function, explaining how the interpreter treats itself as a CPU, uses a stack frame, switches between Python and C call stacks, and employs computed‑goto optimizations to efficiently dispatch bytecode instructions.

Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
Deep Dive into CPython: How the Bytecode Evaluation Engine Executes Code

Building on the previous overview of the Python virtual machine, we examine the core source of CPython's bytecode executor, the _PyEval_EvalFrameDefault function defined in Python/ceval.c. This function implements a CPU‑like fetch‑decode‑execute loop that operates on a stack frame, handling each opcode according to its semantics.

The interpreter maintains two call stacks: the Python call stack (managed by _PyInterpreterFrame) and the underlying C call stack (represented by _PyCFrame). Each recursive Python call creates a new C stack frame, causing both stacks to grow together.

To avoid the overhead of a massive switch statement that would compare hundreds of case labels for every opcode, CPython 3.12 introduces computed goto (enabled by the USE_COMPUTED_GOTOS macro). When this optimization is active, the macro TARGET(op) expands only to a label (e.g., TARGET_LOAD_FAST:), and the dispatcher jumps directly to the address stored in the opcode_targets table, eliminating the need for sequential case matching.

The opcode_targets array, generated in Python/opcode_targets.h, maps each opcode value to the address of its handling label. For example, opcode_targets[100] points to &&TARGET_LOAD_CONST, ensuring that the next instruction is dispatched in O(1) time.

Each opcode handler follows a common pattern: fetch operands, perform the operation, adjust reference counts, and update the runtime stack. The article walks through several key handlers:

TARGET(LOAD_CONST) loads a constant from frame->f_code->co_consts , increments its reference count, grows the stack, stores the value, and dispatches the next instruction.

TARGET(STORE_NAME) pops the top of the stack, retrieves the variable name from co_names , stores the value in the locals dictionary, decrements the reference count, shrinks the stack, and dispatches.

TARGET(LOAD_NAME) looks up a global name in the locals/globals dictionary, pushes the retrieved object onto the stack, and dispatches.

TARGET(BINARY_OP) pops the two topmost stack values, calls the appropriate binary operation function (e.g., addition or division) via binary_ops[oparg] , handles reference‑count cleanup, pushes the result, and dispatches.

The article also presents a concrete example: a small Python script that assigns three variables and computes their average. Using dis.dis, the generated bytecode sequence is shown, and each instruction (e.g., 2 LOAD_CONST, 4 STORE_NAME, 14 LOAD_NAME, 18 BINARY_OP, 30 BINARY_OP) is mapped to its corresponding C handler. The step‑by‑step evolution of the runtime stack and the locals namespace is illustrated with diagrams.

Further, the internal layout of a frame’s f_localsplus array is described. Although declared with length 1, it is dynamically sized and stores locals, cell variables, free variables, and the runtime stack in contiguous memory. Macros such as GETITEM and LOCALS() provide fast indexed access to these structures.

Finally, the article summarizes that the CPython bytecode evaluator, despite its size and complexity, follows a straightforward model: treat the interpreter as a CPU, iterate over bytecode, and use either a large switch or, in newer versions, a computed‑goto dispatch table to execute each instruction efficiently.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

bytecodeinterpreterCPythonopcodecomputed goto
Satori Komeiji's Programming Classroom
Written by

Satori Komeiji's Programming Classroom

Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.