_PyStackRef)Stack references are the interpreter's tagged representation of values on the evaluation stack. They carry metadata to track ownership and support optimizations such as tagged small ints.
_PyStackRef is a tagged pointer-sized value (see Include/internal/pycore_stackref.h).Py_TAG_REFCNT unset - reference count lives on the pointed-to object.Py_TAG_REFCNT set - ownership is “borrowed” (no refcount to drop on close) or the object is immortal.Py_INT_TAG set - tagged small integer stored directly in the stackref (no heap allocation).PyStackRef_NULL, PyStackRef_ERROR, and embedded None/True/False.In GIL builds, most objects carry their refcount; tagged borrowed refs skip decref on close. In free threading builds, the tag is also used to mark deferred refcounted objects so the GC can see them and to avoid refcount contention on commonly shared objects.
Three conversions control ownership:
PyStackRef_FromPyObjectNew(obj) - create a new reference (INCREF if mortal).PyStackRef_FromPyObjectSteal(obj) - take over ownership without changing the count unless the object is immortal.PyStackRef_FromPyObjectBorrow(obj) - create a borrowed stackref (never decref on close).The obj argument must not be NULL.
Going back to PyObject* mirrors this:
PyStackRef_AsPyObjectBorrow(ref) - borrow the underlying pointerPyStackRef_AsPyObjectSteal(ref) - transfer ownership from the stackref; if ref is borrowed or deferred, this creates a new owning PyObject* reference.PyStackRef_AsPyObjectNew(ref) - create a new owning referenceOnly PyStackRef_AsPyObjectBorrow allows ref to be PyStackRef_NULL.
The interpreter treats _PyStackRef as the unit of stack storage. Ownership must be managed with the stackref primitives:
PyStackRef_DUP - like Py_NewRef for stackrefs; preserves the original.PyStackRef_Borrow - create a borrowed stackref from another stackref.PyStackRef_CLOSE / PyStackRef_XCLOSE - like Py_DECREF; invalidates the stackref.PyStackRef_CLEAR - like Py_CLEAR; closes and sets the stackref to PyStackRef_NULLPyStackRef_MakeHeapSafe - converts borrowed reference to owning referenceBorrow tracking (for debug builds with Py_STACKREF_DEBUG) records who you borrowed from and reports double-close, leaked borrows, or use-after-close via fatal errors.
The interpreter can push borrowed references directly. For example, LOAD_FAST_BORROW loads a local variable as a borrowed _PyStackRef, avoiding both INCREF and DECREF for the temporary lifetime on the evaluation stack.
Small ints can be stored inline with Py_INT_TAG, so no heap object is involved. Helpers like PyStackRef_TagInt, PyStackRef_UntagInt, and PyStackRef_IncrementTaggedIntNoOverflow operate on these values. Type checks use PyStackRef_IsTaggedInt and PyStackRef_LongCheck.
Objects that support deferred reference counting can be pushed to the evaluation stack and stored in local variables without directly incrementing the reference count because they are only freed during cyclic garbage collection. This avoids reference count contention on commonly shared objects such as methods and types. The GC scans each thread's locals and evaluation stack to keep objects that use deferred reference counting alive.
Py_STACKREF_DEBUG builds replace the inline tags with table-backed IDs so the runtime can track creation sites, borrows, closes, and leaks. Enabling Py_STACKREF_CLOSE_DEBUG additionally records double closes. The tables live on PyInterpreterState and are initialized in pystate.c; helper routines reside in Python/stackrefs.c.