Skip to main content

Include/internal/pycore_frame.h

CPython 3.11 replaced heap-allocated PyFrameObject instances with a compact _PyInterpreterFrame that normally lives on the C stack, threaded through a linked list via previous. This header defines the struct layout, the stack-pointer accessors, the owner enum that records where the frame was allocated, and the incomplete-frame predicate used during frame setup.

Map

LinesSymbolRole
1-30_PyInterpreterFrameInline frame struct holding all per-call execution state
31-50_PyInterpreterFrame.f_executableStrong reference to the code object (or other executable) being run
51-70_PyInterpreterFrame.f_globalsBorrowed reference to the global namespace dict
71-80_PyInterpreterFrame.f_localsBorrowed reference to the local namespace (NULL for optimized frames)
81-90_PyInterpreterFrame.f_builtinsBorrowed reference to the builtins dict for fast LOAD_GLOBAL lookup
91-120_PyInterpreterFrame.prev_instrPointer to the last executed instruction, used as the program counter
121-140_PyInterpreterFrame.localsplusFlexible array of PyObject * holding locals, cells, free vars, and the value stack
141-155_PyInterpreterFrame.frame_objBackpointer to the heap PyFrameObject if one has been materialised, else NULL
156-170_PyInterpreterFrame.owner_PyFrameOwner enum value recording the frame's allocation site
171-180FRAME_OWNED_BY_CSTACKOwner constant for frames allocated on the C stack inside _PyEval_EvalFrameDefault
181-185FRAME_OWNED_BY_GENERATOROwner constant for frames embedded inside a PyGenObject
186-190FRAME_OWNED_BY_FRAME_OBJECTOwner constant for frames that were moved to the heap when a PyFrameObject was requested
191-195_PyFrame_SetStackPointerWrite the current value-stack top back into the frame
196-200_PyFrame_GetStackPointerDerive the current stack top from prev_instr and the code object
201-210_PyFrame_IsIncompletePredicate returning true while the frame is still being set up and is not yet safe to inspect

Reading

_PyInterpreterFrame struct

Before CPython 3.11, every function call allocated a PyFrameObject on the heap. The "lazy frames" redesign moved the execution state into _PyInterpreterFrame, which is typically allocated in the large C-stack array that _PyEval_EvalFrameDefault reserves at the start of each call to the eval loop. The heap PyFrameObject is only created on demand, for example when user code calls sys._getframe() or when a debugger requests the frame.

f_executable holds a strong reference to the code object so that the code object cannot be freed while the frame is live. All other dict references (f_globals, f_locals, f_builtins) are borrowed because the module or function object is guaranteed to outlive the frame.

localsplus and the value stack

localsplus is a flexible-array member at the end of the struct. Its layout for a given code object is: co_nlocals local variable slots, followed by co_ncellvars cell slots, followed by co_nfreevars free variable slots, followed by the value stack growing upward. The total capacity is co_nlocalsplus + co_stacksize entries. The eval loop uses _PyFrame_GetStackPointer to find the current stack top without storing a redundant field in the struct.

Owner enum

The owner field uses the _PyFrameOwner enum to record where the frame struct lives. FRAME_OWNED_BY_CSTACK means the memory is inside the eval loop's on-stack frame array and must not be freed explicitly. FRAME_OWNED_BY_GENERATOR means the struct is embedded in a PyGenObject on the heap; the generator's dealloc path owns the memory. FRAME_OWNED_BY_FRAME_OBJECT means the struct was copied to the heap as part of materialising a PyFrameObject; the frame object's dealloc path frees it.

The distinction matters during finalization. When a generator is garbage-collected without being exhausted, the GC calls gen_dealloc, which checks owner to decide how to clean up the embedded frame without double-freeing.

Stack pointer accessors and _PyFrame_IsIncomplete

_PyFrame_SetStackPointer(frame, sp) stores sp back into the frame by encoding it relative to prev_instr. This indirection lets the eval loop avoid a redundant pointer field while still supporting frame inspection.

_PyFrame_IsIncomplete(frame) returns true when prev_instr points before the first real instruction of the code object, which happens during the RESUME opcode phase. Debuggers and sys.settrace must not inspect a frame that is incomplete because fields like f_locals and localsplus may not yet be populated.

gopy notes

gopy's equivalent is vm.Frame in vm/frame.go, which is heap-allocated and reference-counted rather than stack-allocated. The localsplus flexible array maps to vm.Frame.Locals (a Go slice covering local variables and cells) plus the value stack embedded in the eval loop as a Go slice. The owner enum has no direct counterpart because gopy frames are always heap-allocated. _PyFrame_IsIncomplete is not ported; gopy's RESUME handling initialises all frame fields before the frame becomes visible to any observer. The prev_instr program counter maps to vm.Frame.PC, an integer index into the instruction slice.