Skip to main content

Include/internal/pycore_frame.h (part 2)

Source:

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

This annotation covers the frame struct layout and inline helpers. See include_internal_pycore_frame_h_detail for frame creation (_PyFrame_PushUnchecked) and the shadow frame stack.

Map

LinesSymbolRole
1-60_PyInterpreterFrame fieldsf_code, f_locals, f_globals, previous, f_lasti, owner
61-120frame_owner enumFRAME_OWNED_BY_THREAD, FRAME_OWNED_BY_CSTACK, FRAME_OWNED_BY_GENERATOR
121-180_PyFrame_IsIncompleteTrue if frame is between push and first instruction
181-240_PyFrame_GetLocalsArrayPointer to the fast locals array
241-350Inline fast-local helpersGETLOCAL, SETLOCAL, _PyFrame_HasCompleted

Reading

_PyInterpreterFrame layout

// CPython: Include/internal/pycore_frame.h:56 _PyInterpreterFrame
struct _PyInterpreterFrame {
PyObject *f_executable; /* code object or NULL */
struct _PyInterpreterFrame *previous;
PyObject *f_funcobj; /* function that created this frame */
PyObject *f_globals;
PyObject *f_builtins;
PyObject *f_locals; /* NULL in optimized frames */
PyFrameObject *frame_obj; /* backing PyFrameObject, created lazily */
_Py_CODEUNIT *prev_instr; /* last executed instruction */
int f_lasti; /* last instruction offset */
int return_offset;
char owner; /* frame_owner enum */
/* Followed by fast locals array: PyObject *localsplus[nlocalsplus] */
};

frame_owner

// CPython: Include/internal/pycore_frame.h:62 frame_owner
typedef enum _frameowner {
FRAME_OWNED_BY_THREAD = 0, /* on the C stack (normal call) */
FRAME_OWNED_BY_GENERATOR = 1, /* inside a generator/coroutine */
FRAME_OWNED_BY_FRAME_OBJECT = 2, /* frame object outlived the C frame */
FRAME_OWNED_BY_CSTACK = 3, /* on the shadow (C extension) stack */
} _PyFrameOwner;

_PyFrame_IsIncomplete

// CPython: Include/internal/pycore_frame.h:138 _PyFrame_IsIncomplete
static inline int
_PyFrame_IsIncomplete(struct _PyInterpreterFrame *frame)
{
/* A frame is incomplete if it has been pushed but
RESUME has not yet executed (i.e. prev_instr still points before it). */
if (frame->f_executable == NULL) return 1;
PyCodeObject *code = (PyCodeObject *)frame->f_executable;
return frame->prev_instr <
_PyCode_CODE(code) + code->_co_firsttraceable;
}

Incomplete frames are skipped by traceback and inspect.currentframe().

_PyFrame_GetLocalsArray

// CPython: Include/internal/pycore_frame.h:195 _PyFrame_GetLocalsArray
static inline PyObject **
_PyFrame_GetLocalsArray(struct _PyInterpreterFrame *frame)
{
/* The fast locals array lives immediately after the frame header */
return (PyObject **)((char *)frame + sizeof(struct _PyInterpreterFrame));
}

LOAD_FAST and STORE_FAST index into this array by variable slot number.

GETLOCAL / SETLOCAL

// CPython: Include/internal/pycore_frame.h:260 GETLOCAL
#define GETLOCAL(i) (frame->localsplus[i])
#define SETLOCAL(i, v) do { \
PyObject *tmp = GETLOCAL(i); \
GETLOCAL(i) = (v); \
Py_XDECREF(tmp); \
} while (0)

SETLOCAL unconditionally decrefs the old value before replacing it. Variables that were never assigned hold NULL; LOAD_FAST checks for NULL and raises UnboundLocalError.

gopy notes

gopy's frame is vm.Frame in vm/frame.go. localsplus is a []PyObject slice sized to code.Co_nlocalsplus. GETLOCAL / SETLOCAL map to frame.Locals[i] with the same NULL-check semantics. frame_owner maps to vm.FrameOwner (int8). Incomplete-frame detection uses frame.PrevInstr < frame.Code.FirstTraceable.