Skip to content

Commit a782c6b

Browse files
committed
gh-124379: Document _PyStackRef
1 parent dcac498 commit a782c6b

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

Include/internal/pycore_stackref.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,13 +479,6 @@ PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)
479479

480480
#define PyStackRef_IsDeferred(ref) (((ref).bits & Py_TAG_BITS) == Py_TAG_DEFERRED)
481481

482-
static inline PyObject *
483-
PyStackRef_NotDeferred_AsPyObject(_PyStackRef stackref)
484-
{
485-
assert(!PyStackRef_IsDeferred(stackref));
486-
return (PyObject *)stackref.bits;
487-
}
488-
489482
static inline PyObject *
490483
PyStackRef_AsPyObjectSteal(_PyStackRef stackref)
491484
{

InternalDocs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Program Execution
3636

3737
- [The Bytecode Interpreter](interpreter.md)
3838

39+
- [Stack references (_PyStackRef)](stackrefs.md)
40+
3941
- [The JIT](jit.md)
4042

4143
- [Garbage Collector Design](garbage_collector.md)

InternalDocs/stackrefs.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Stack references (`_PyStackRef`)
2+
3+
Stack references are the interpreter's tagged representation of values on the evaluation stack.
4+
They carry metadata to track ownership and support optimizations such as tagged small ints.
5+
6+
## Shape and tagging
7+
8+
- A `_PyStackRef` is a tagged pointer-sized value (see `Include/internal/pycore_stackref.h`).
9+
- Tag bits distinguish three cases:
10+
- `Py_TAG_REFCNT` clear - reference count lives on the pointed-to object.
11+
- `Py_TAG_REFCNT` set - ownership is "borrowed" (no refcount to drop on close) or the object is immortal.
12+
- `Py_INT_TAG` - tagged small integer stored directly in the stackref (no heap allocation).
13+
- Special constants: `PyStackRef_NULL`, `PyStackRef_ERROR`, and embedded `None`/`True`/`False`.
14+
15+
In GIL builds, most objects carry their refcount; tagged borrowed refs skip decref on close. In free
16+
threading builds, the tag is also used to mark deferred refcounted objects so the GC can see them and
17+
to avoid refcount contention for short-lived stack values.
18+
19+
## Converting to and from PyObject*
20+
21+
Three conversions control ownership:
22+
23+
- `PyStackRef_FromPyObjectNew(obj)` - create a new reference (INCREF if mortal).
24+
- `PyStackRef_FromPyObjectSteal(obj)` - take over ownership without changing the count unless the
25+
object is immortal.
26+
- `PyStackRef_FromPyObjectBorrow(obj)` - create a borrowed stackref (never decref on close).
27+
28+
The `obj` argument must not be `NULL`.
29+
30+
Going back to `PyObject*` mirrors this:
31+
32+
- `PyStackRef_AsPyObjectBorrow(ref)` - borrow the underlying pointer
33+
- `PyStackRef_AsPyObjectSteal(ref)` - transfer ownership from the stackref
34+
- `PyStackRef_AsPyObjectNew(ref)` - create a new owning reference
35+
36+
Only `PyStackRef_AsPyObjectBorrow` allows ref to be `PyStackRef_NULL`.
37+
38+
## Operations on stackrefs
39+
40+
The interpreter treats `_PyStackRef` as the unit of stack storage. Ownership must be managed with
41+
the stackref primitives:
42+
43+
- `PyStackRef_DUP` - like `Py_NewRef` for stackrefs; preserves the original.
44+
- `PyStackRef_Borrow` - create a borrowed stackref from another stackref.
45+
- `PyStackRef_CLOSE` / `PyStackRef_XCLOSE` - like `Py_DECREF`; invalidates the stackref.
46+
- `PyStackRef_CLEAR` - like `Py_CLEAR`; closes and sets the stackref to `PyStackRef_NULL`
47+
- `PyStackRef_MakeHeapSafe` - converts borrowed reference to owning reference
48+
49+
Borrow tracking (for debug builds with `Py_STACKREF_DEBUG`) records who you borrowed from and reports
50+
double-close, leaked borrows, or use-after-close via fatal errors.
51+
52+
## Borrow-friendly opcodes
53+
54+
The interpreter can push borrowed references directly. For example, `LOAD_FAST_BORROW` loads a local
55+
variable as a borrowed `_PyStackRef`, avoiding both INCREF and DECREF for the temporary lifetime on
56+
the evaluation stack.
57+
58+
## Tagged integers on the stack
59+
60+
Small ints can be stored inline with `Py_INT_TAG`, so no heap object is involved. Helpers like
61+
`PyStackRef_TagInt`, `PyStackRef_UntagInt`, and `PyStackRef_IncrementTaggedIntNoOverflow` operate on
62+
these values. Type checks use `PyStackRef_IsTaggedInt` and `PyStackRef_LongCheck`.
63+
64+
## Free threading considerations
65+
66+
With `Py_GIL_DISABLED`, `Py_TAG_DEFERRED` is an alias for `Py_TAG_REFCNT`.
67+
Objects that support deferred reference counting can be pushed to the evaluation
68+
stack and stored in local variables without directly incrementing the reference
69+
count because they are only freed during cyclic garbage collection. This avoids
70+
reference count contention on short-lived values such as methods and types. The GC
71+
scans each thread's locals and evaluation stack to keep objects that use
72+
deferred reference counting alive.
73+
74+
## Debugging support
75+
76+
`Py_STACKREF_DEBUG` builds replace the inline tags with table-backed IDs so the runtime can track
77+
creation sites, borrows, closes, and leaks. Enabling `Py_STACKREF_CLOSE_DEBUG` additionally records
78+
double closes. The tables live on `PyInterpreterState` and are initialized in `pystate.c`; helper
79+
routines reside in `Python/stackrefs.c`.

0 commit comments

Comments
 (0)