Skip to content

Commit

Permalink
Bring in a subset of biased reference counting:
Browse files Browse the repository at this point in the history
colesbury/nogil@b6b12a9a94e

Co-Authored-By: Sam Gross <colesbury@gmail.com>
  • Loading branch information
DinoV and colesbury committed Jan 24, 2024
1 parent 191531f commit 818fc5f
Showing 1 changed file with 151 additions and 0 deletions.
151 changes: 151 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,157 @@ static inline void _PyObject_GC_UNTRACK(
_PyObject_GC_UNTRACK(__FILE__, __LINE__, _PyObject_CAST(op))
#endif

#ifdef Py_GIL_DISABLED

/* Tries to increment an object's reference count
*
* This is a specialized version of _Py_TryIncref that only succeeds if the
* object is immortal or local to this thread. It does not handle the case
* where the reference count modification requires an atomic operation. This
* allows call sites to specialize for the immortal/local case.
*/
static inline Py_ALWAYS_INLINE int
_Py_TryIncrefFast(PyObject *op) {
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
local += 1;
if (local == 0) {
// immortal
return 1;
}
if (_Py_IsOwnedByCurrentThread(op)) {
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
#ifdef Py_REF_DEBUG
_Py_IncRefTotal(_PyInterpreterState_GET());
#endif
return 1;
}
return 0;
}

static inline Py_ALWAYS_INLINE int
_Py_TryIncRefShared(PyObject *op)
{
for (;;) {
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);

// If the shared refcount is zero and the object is either merged
// or may not have weak references, then we cannot incref it.
if (shared == 0 || shared == _Py_REF_MERGED) {
return 0;
}

if (_Py_atomic_compare_exchange_ssize(
&op->ob_ref_shared,
&shared,
shared + (1 << _Py_REF_SHARED_SHIFT))) {
#ifdef Py_REF_DEBUG
_Py_IncRefTotal(_PyInterpreterState_GET());
#endif
return 1;
}
}
}

/* Tries to incref the object op and ensures that *src still points to it. */
static inline int
_Py_TryAcquireObject(PyObject **src, PyObject *op)
{
if (_Py_TryIncrefFast(op)) {
return 1;
}
if (!_Py_TryIncRefShared(op)) {
return 0;
}
if (op != _Py_atomic_load_ptr(src)) {
Py_DECREF(op);
return 0;
}
return 1;
}

/* Loads and increfs an object from ptr, which may contain a NULL value.
Safe with concurrent (atomic) updates to ptr.
NOTE: The writer must set maybe-weakref on the stored object! */
static inline Py_ALWAYS_INLINE PyObject *
_Py_XFetchRef(PyObject **ptr)
{
#ifdef Py_NOGIL
for (;;) {
PyObject *value = _Py_atomic_load_ptr(ptr);
if (value == NULL) {
return value;
}
if (_Py_TryAcquireObject(ptr, value)) {
return value;
}
}
#else
return Py_XNewRef(*ptr);
#endif
}

/* Attempts to loads and increfs an object from ptr. Returns NULL
on failure, which may be due to a NULL value or a concurrent update. */
static inline Py_ALWAYS_INLINE PyObject *
_Py_TryXFetchRef(PyObject **ptr)
{
PyObject *value = _Py_atomic_load_ptr(ptr);
if (value == NULL) {
return value;
}
if (_Py_TryAcquireObject(ptr, value)) {
return value;
}
return NULL;
}

/* Like Py_NewRef but also optimistically sets _Py_REF_MAYBE_WEAKREF
on objects owned by a different thread. */
static inline PyObject *
_Py_NewRefWithLock(PyObject *op)
{
_Py_INCREF_STAT_INC();
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
local += 1;
if (local == 0) {
return op;
}

#ifdef Py_REF_DEBUG
_Py_IncRefTotal(_PyInterpreterState_GET());
#endif
if (_Py_IsOwnedByCurrentThread(op)) {
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
}
else {
for (;;) {
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
Py_ssize_t new_shared = shared + (1 << _Py_REF_SHARED_SHIFT);
if ((shared & _Py_REF_SHARED_FLAG_MASK) == 0) {
new_shared |= _Py_REF_MAYBE_WEAKREF;
}
if (_Py_atomic_compare_exchange_ssize(
&op->ob_ref_shared,
&shared,
new_shared)) {
return op;
}
}
}
return op;
}

static inline PyObject *
_Py_XNewRefWithLock(PyObject *obj)
{
if (obj == NULL) {
return NULL;
}
return _Py_NewRefWithLock(obj);
}

#endif

#ifdef Py_REF_DEBUG
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
Expand Down

0 comments on commit 818fc5f

Please sign in to comment.