Skip to main content

Include/internal/pycore_ceval.h

This page annotates

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_ceval.h

, the internal header that wires together the eval-frame entry point, the per-interpreter eval-state struct, pending-call scheduling, and the eval-call statistics counters. Nothing in this file is part of the stable ABI. All identifiers carry the _Py or _PyEval prefix and are consumed exclusively by Python/ceval.c and the specializing adaptive interpreter.

Map

LinesSymbolKindNotes
1-20guard macros, includespreprocessorpulls in pycore_interp.h
22-35EVAL_CALL_STAT_INC familymacrosconditional on Py_STATS
37-60struct _ceval_statestructper-interpreter eval state
62-75struct _ceval_runtime_statestructprocess-wide eval state
77-95_PyEval_EvalFrameDefaultfunction declthe main dispatch loop
97-115_Py_HandlePendingfunction declsignal/GIL-drop handling
117-140_PyEval_AddPendingCallfunction declschedule work on the eval thread
142-160_PyEval_InitState / _PyEval_FiniStatefunction declslifecycle
162-180EVAL_CALL_STAT_* counter enumenumperf instrumentation

Reading

_ceval_state: the per-interpreter eval knobs

Every PyInterpreterState embeds one _ceval_state. The field that matters most for throughput is eval_breaker, an atomic integer checked at the top of every bytecode dispatch iteration. When it is non-zero the interpreter exits the fast path and calls _Py_HandlePending.

// CPython: Include/internal/pycore_ceval.h:37 struct _ceval_state
struct _ceval_state {
/* Requests the eval loop to stop after the current opcode. */
_Py_atomic_int eval_breaker;

/* Number of pending calls waiting to run on this interpreter. */
_Py_atomic_int pending_calls_to_do;

/* Set when another OS thread wants the GIL. */
_Py_atomic_int gil_drop_request;

/* Set when a Unix signal has arrived and needs dispatch. */
_Py_atomic_int signals_pending;

struct _pending_calls pending;
/* ... gil, instrumentation fields elided ... */
};

eval_breaker is a logical OR of the other atomic flags. CPython sets it with a single atomic store so that the check inside the dispatch loop compiles to a single load-and-branch on every major architecture.

_PyEval_EvalFrameDefault: the dispatch loop entry point

The function is declared here but defined in Python/ceval.c (and on --enable-experimental-jit builds, partially in Tools/jit/). The signature has not changed since 3.11.

// CPython: Include/internal/pycore_ceval.h:77 _PyEval_EvalFrameDefault
PyObject *
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag);

throwflag is non-zero only when the frame is being resumed after a throw() on a generator. The frame pointer is not the C stack frame; it is a _PyInterpreterFrame allocated inside the frame's code object shadow stack or on the heap for generators.

_PyEval_AddPendingCall and _Py_HandlePending

These two functions form the mechanism that lets C extensions and signal handlers schedule work to run on the main thread without holding the GIL.

// CPython: Include/internal/pycore_ceval.h:117 _PyEval_AddPendingCall
int _PyEval_AddPendingCall(
PyInterpreterState *interp,
_Py_pending_call_func func,
void *arg,
int mainthreadonly);

mainthreadonly gates whether the call must run on the main OS thread (required for signal handlers) or may run on any thread that currently holds the GIL. Internally the call is appended to a fixed-size ring buffer inside _ceval_state.pending. When the buffer is full the function returns -1 without blocking.

// CPython: Include/internal/pycore_ceval.h:97 _Py_HandlePending
int _Py_HandlePending(PyThreadState *tstate);

_Py_HandlePending is called from the dispatch loop whenever eval_breaker is set. It drains the pending-call ring buffer, dispatches signals via PyErr_CheckSignals, and drops the GIL if gil_drop_request is set. The return value is -1 if a pending call raised an exception, 0 otherwise.

gopy notes

Status: not yet ported.

Planned package path: vm/ (eval-loop state fields will live on vm.Interpreter) and runtime/ (process-wide pending-call queue). The eval_breaker pattern maps naturally to a Go atomic.Int32 field on the interpreter struct. Pending calls will use a fixed-capacity ring backed by a [32]pendingCall array to match CPython's bound. _PyEval_EvalFrameDefault itself corresponds to vm.EvalFrame in the gopy eval loop.