Skip to main content

Python/ceval.c (part 78)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers generator resumption and yield opcodes. See python_ceval77_detail for CALL, CALL_PY_EXACT_ARGS, and CALL_BUILTIN_FAST.

Map

LinesSymbolRole
1-80RESUMEFrame resumption point after suspension
81-160YIELD_VALUEYield a value and suspend the frame
161-240RESUME specializationsRESUME_CHECK and profiling hooks
241-360Frame suspension mechanicsHow the frame state is saved
361-500AWAITawait expr — enter a coroutine

Reading

RESUME

// CPython: Python/ceval.c:1220 RESUME
inst(RESUME, (--)) {
/* Check for pending signals/calls at function entry and after suspension */
uintptr_t v = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker);
if (v & _Py_EVAL_EVENTS_MASK) {
int err = _Py_HandlePending(tstate);
ERROR_IF(err != 0, error);
}
/* For generators: after a SEND, oparg tells why we're resuming:
0 = entering function, 1 = yield, 2 = yield from / await */
}

RESUME is the first opcode in every function and the resumption point after every yield/await. It checks eval_breaker for pending signals, profiling events, or GIL release requests. For generators, oparg distinguishes first entry from resume.

YIELD_VALUE

// CPython: Python/ceval.c:4480 YIELD_VALUE
inst(YIELD_VALUE, (retval --)) {
assert(oparg == frame->f_code->co_nlocalsplus);
/* Save the frame's stack pointer so we can resume later */
frame->f_lasti = INSTR_OFFSET() - 1;
_PyFrame_SetStackPointer(frame, stack_pointer);
/* Suspend the frame */
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
PyGenObject *gen = _PyFrame_GetGenerator(frame);
gen->gi_frame_state = FRAME_SUSPENDED;
/* Return the yielded value to the caller */
_PyInterpreterFrame *prev_frame = frame->previous;
tstate->current_frame = prev_frame;
return retval;
}

YIELD_VALUE saves the current instruction pointer (f_lasti) and stack pointer, marks the generator frame as FRAME_SUSPENDED, and returns the yielded value to the caller. The frame is not freed — it stays alive until the generator is GC'd or exhausted.

Frame suspension mechanics

// CPython: Python/ceval.c:4520 frame state after YIELD_VALUE
/* frame->f_lasti: points to YIELD_VALUE itself (to resume at RESUME after it) */
/* frame->localsplus: holds all local variables and the value stack */
/* gen->gi_frame_state = FRAME_SUSPENDED: marks it as resumable */
/* tstate->current_frame: reverts to the caller's frame */

A suspended generator frame is a complete snapshot of the function's execution state. Locals, cells, and the value stack are all preserved in frame->localsplus. Only the program counter needs to be restored on resume.

AWAIT

// CPython: Python/ceval.c:4560 AWAIT
inst(AWAIT, (iterable -- iter)) {
/* oparg: 0 for first await, 1 for subsequent */
unaryfunc getter = Py_TYPE(iterable)->tp_as_async->am_await;
if (getter == NULL) {
/* Non-coroutine awaitable (has __await__) */
iter = PyObject_GetIter(iterable);
} else {
iter = (*getter)(iterable);
}
ERROR_IF(iter == NULL, error);
Py_DECREF(iterable);
}

await expr compiles to GET_AWAITABLE (which calls __await__ or am_await) followed by SEND/RESUME pairs that drive the coroutine. AWAIT is the GET_AWAITABLE call site for non-top-level awaits.

RESUME specializations

// CPython: Python/ceval.c:1260 RESUME_CHECK
inst(RESUME_CHECK, (--)) {
/* Specialized RESUME: only check tracing/profiling, skip signal check */
if (tstate->tracing) {
_PyEval_SetTrace(tstate, ...);
}
}

RESUME_CHECK is the specialization of RESUME used when profiling or tracing is active. It calls the profiling hooks without the signal-check overhead of the generic RESUME.

gopy notes

RESUME is in vm/eval_gen.go as evalResume. YIELD_VALUE calls vm.yieldValue which saves the frame state into objects.Generator.Frame and returns to the caller. AWAIT calls objects.GetAwaitable. Frame suspension is implemented via Go's goroutines in gopy's async model.