-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
gh-124379: Document _PyStackRef #142321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+82
−7
Merged
gh-124379: Document _PyStackRef #142321
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Stack references (`_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. | ||
|
|
||
| ## Shape and tagging | ||
|
|
||
| - A `_PyStackRef` is a tagged pointer-sized value (see `Include/internal/pycore_stackref.h`). | ||
| - Tag bits distinguish three cases: | ||
| - `Py_TAG_REFCNT` clear - 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` - tagged small integer stored directly in the stackref (no heap allocation). | ||
| - Special constants: `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 for short-lived stack values. | ||
colesbury marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Converting to and from PyObject* | ||
|
|
||
| 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 pointer | ||
| - `PyStackRef_AsPyObjectSteal(ref)` - transfer ownership from the stackref | ||
colesbury marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `PyStackRef_AsPyObjectNew(ref)` - create a new owning reference | ||
|
|
||
| Only `PyStackRef_AsPyObjectBorrow` allows ref to be `PyStackRef_NULL`. | ||
|
|
||
| ## Operations on stackrefs | ||
|
|
||
| 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_NULL` | ||
| - `PyStackRef_MakeHeapSafe` - converts borrowed reference to owning reference | ||
|
|
||
| Borrow 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. | ||
|
|
||
| ## Borrow-friendly opcodes | ||
|
|
||
| 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. | ||
|
|
||
| ## Tagged integers on the 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`. | ||
|
|
||
| ## Free threading considerations | ||
|
|
||
| With `Py_GIL_DISABLED`, `Py_TAG_DEFERRED` is an alias for `Py_TAG_REFCNT`. | ||
| 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 short-lived values such as methods and types. The GC | ||
colesbury marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| scans each thread's locals and evaluation stack to keep objects that use | ||
| deferred reference counting alive. | ||
|
|
||
| ## Debugging support | ||
|
|
||
| `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`. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.