gh-133164: Add PyUnstable_Object_IsUniqueReferencedTemporary C API (gh-133170)

After gh-130704, the interpreter replaces some uses of `LOAD_FAST` with
`LOAD_FAST_BORROW` which avoid incref/decrefs by "borrowing" references
on the interpreter stack when the bytecode compiler can determine that
it's safe.

This change broke some checks in C API extensions that relied on
`Py_REFCNT()` of `1` to determine if it's safe to modify an object
in-place. Objects may have a reference count of one, but still be
referenced further up the interpreter stack due to borrowing of
references.

This provides a replacement function for those checks.
`PyUnstable_Object_IsUniqueReferencedTemporary` is more conservative:
it checks that the object has a reference count of one and that it exists as a
unique strong reference in the interpreter's stack of temporary
variables in the top most frame.

See also:

* https://github.com/numpy/numpy/issues/28681

Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
Co-authored-by: T. Wouters <thomas@python.org>
Co-authored-by: mpage <mpage@cs.stanford.edu>
Co-authored-by: Mark Shannon <mark@hotpy.org>
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Sam Gross 2025-05-02 09:24:57 -04:00 committed by GitHub
parent 4701ff92d7
commit f2379535fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 109 additions and 0 deletions

View file

@ -15,6 +15,7 @@
#include "pycore_hamt.h" // _PyHamtItems_Type
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_Type
#include "pycore_interpframe.h" // _PyFrame_Stackbase()
#include "pycore_interpolation.h" // _PyInterpolation_Type
#include "pycore_list.h" // _PyList_DebugMallocStats()
#include "pycore_long.h" // _PyLong_GetZero()
@ -2621,6 +2622,29 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op)
#endif
}
int
PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *op)
{
if (!_PyObject_IsUniquelyReferenced(op)) {
return 0;
}
_PyInterpreterFrame *frame = _PyEval_GetFrame();
if (frame == NULL) {
return 0;
}
_PyStackRef *base = _PyFrame_Stackbase(frame);
_PyStackRef *stackpointer = frame->stackpointer;
while (stackpointer > base) {
stackpointer--;
if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) {
return PyStackRef_IsHeapSafe(*stackpointer);
}
}
return 0;
}
int
PyUnstable_TryIncRef(PyObject *op)
{