Skip to main content

Python/ceval.c (part 84)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers generator and coroutine opcodes. See python_ceval83_detail for GET_ITER, FOR_ITER, and SEND.

Map

LinesSymbolRole
1-80RETURN_GENERATORConvert a function call into a generator object
81-160COPY_FREE_VARSCopy free variables from the closure into the frame
161-260RESUMEEntry point guard — re-check for tracing/interrupts
261-360YIELD_VALUESuspend the generator and return a value
361-500Generator frame lifecycleSuspension, resumption, and finalization

Reading

RETURN_GENERATOR

// CPython: Python/ceval.c:4020 RETURN_GENERATOR
inst(RETURN_GENERATOR, (-- )) {
/* Called at entry to a generator function.
Creates a PyGenObject wrapping the current frame, then
returns it to the caller WITHOUT executing any further bytecode. */
PyGenObject *gen = _PyGen_NewWithQualName(frame, qualname, name);
frame->f_frame->owner = FRAME_OWNED_BY_GENERATOR;
_PyFrame_SetStackPointer(frame, stack_pointer);
/* Detach the frame from the eval loop */
tstate->current_frame = frame->previous;
Py_INCREF(gen);
return (PyObject *)gen;
}

When a function containing yield is called, the interpreter runs RETURN_GENERATOR before any user code. It creates a generator object that owns the frame, transfers ownership, and returns the generator to the caller. No user code has run yet.

COPY_FREE_VARS

// CPython: Python/ceval.c:4060 COPY_FREE_VARS
inst(COPY_FREE_VARS, (--)) {
/* oparg = number of free variables */
PyObject *closure = ((PyFunctionObject *)frame->f_funcobj)->func_closure;
for (int i = 0; i < oparg; ++i) {
PyObject *o = PyTuple_GET_ITEM(closure, i);
frame->localsplus[frame->f_code->co_nlocalsplus - oparg + i] = Py_NewRef(o);
}
}

COPY_FREE_VARS runs at the start of any function with a closure. It copies PyCellObject references from the function's func_closure tuple into the frame's localsplus array, where LOAD_DEREF/STORE_DEREF can find them.

RESUME

// CPython: Python/ceval.c:4100 RESUME
inst(RESUME, (--)) {
/* oparg:
0 = function entry
1 = yield
2 = yield from / await (send)
3 = yield from / await (throw)
*/
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) &&
oparg < 2) {
/* Check for signals, GIL drop requests, etc. */
if (_Py_HandlePending(tstate) != 0)
ERROR_IF(true, error);
}
}

RESUME is the first instruction of every function and the re-entry point after a yield. It checks eval_breaker for pending signals, GIL drops, and tracing hooks. The oparg distinguishes first entry from generator resumption so that tracing callbacks fire correctly.

YIELD_VALUE

// CPython: Python/ceval.c:4140 YIELD_VALUE
inst(YIELD_VALUE, (retval -- )) {
/* Suspend: save stack pointer, return retval to the caller. */
assert(oparg == FRAME_SUSPENDED_YIELD_FROM || oparg == 0);
frame->f_frame->instr_ptr++; /* advance past YIELD_VALUE */
_PyFrame_SetStackPointer(frame, stack_pointer);
tstate->current_frame = frame->previous;
frame->f_frame->owner = FRAME_SUSPENDED;
return retval;
}

YIELD_VALUE stores the current stack pointer in the frame, marks the frame as FRAME_SUSPENDED, unlinks it from the thread state, and returns retval to whoever called next() or send(). The frame is preserved intact for the next resumption.

gopy notes

RETURN_GENERATOR creates a vm.Generator in vm/eval_gen.go. COPY_FREE_VARS fills frame.FreeVars from function.Closure. RESUME checks vm.evalBreaker. YIELD_VALUE suspends the goroutine-backed generator by sending on a channel and blocking until the next Send.