Repository navigation
gh-110481: Implement biased reference counting #110764
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
Changes from 1 commit
94bfbde
b1cb396
425d570
8c68c78
86892d9
3c85ca1
c6c6ec6
ac2c957
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -54,11 +54,19 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *); | |||||||||
| Furthermore, we can't use designated initializers in Extensions since these | ||||||||||
| are not supported pre-C++20. Thus, keeping an internal copy here is the most | ||||||||||
| backwards compatible solution */ | ||||||||||
| #if defined(Py_NOGIL) | ||||||||||
| #define _PyObject_HEAD_INIT(type) \ | ||||||||||
| { \ | ||||||||||
| .ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL, \ | ||||||||||
| .ob_type = (type) \ | ||||||||||
| } | ||||||||||
| #else | ||||||||||
| #define _PyObject_HEAD_INIT(type) \ | ||||||||||
| { \ | ||||||||||
| .ob_refcnt = _Py_IMMORTAL_REFCNT, \ | ||||||||||
| .ob_type = (type) \ | ||||||||||
| } | ||||||||||
| #endif | ||||||||||
| #define _PyVarObject_HEAD_INIT(type, size) \ | ||||||||||
| { \ | ||||||||||
| .ob_base = _PyObject_HEAD_INIT(type), \ | ||||||||||
|
|
@@ -95,24 +103,63 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) | |||||||||
| #ifdef Py_REF_DEBUG | ||||||||||
| _Py_AddRefTotal(_PyInterpreterState_GET(), n); | ||||||||||
| #endif | ||||||||||
| #if !defined(Py_NOGIL) | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this worth the extra code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting removing |
||||||||||
| op->ob_refcnt += n; | ||||||||||
| #else | ||||||||||
| if (_Py_ThreadLocal(op)) { | ||||||||||
| uint32_t local = op->ob_ref_local; | ||||||||||
| Py_ssize_t refcnt = (Py_ssize_t)local + n; | ||||||||||
| # if PY_SSIZE_T_MAX > UINT32_MAX | ||||||||||
| if (refcnt > (Py_ssize_t)UINT32_MAX) { | ||||||||||
| // Make the object immortal if the 32-bit local reference count | ||||||||||
| // would overflow. | ||||||||||
| refcnt = _Py_IMMORTAL_REFCNT_LOCAL; | ||||||||||
| } | ||||||||||
| # endif | ||||||||||
| _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, (uint32_t)refcnt); | ||||||||||
| } | ||||||||||
| else { | ||||||||||
| _Py_atomic_add_ssize(&op->ob_ref_shared, (n << _Py_REF_SHARED_SHIFT)); | ||||||||||
| } | ||||||||||
| #endif | ||||||||||
| } | ||||||||||
| #define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) | ||||||||||
|
|
||||||||||
| static inline void _Py_SetImmortal(PyObject *op) | ||||||||||
| { | ||||||||||
| if (op) { | ||||||||||
| #ifdef Py_NOGIL | ||||||||||
| op->ob_tid = 0; | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we declare the constant variable for this? Something like But I am not sure which will be the best naming
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've named it |
||||||||||
| op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; | ||||||||||
| op->ob_ref_shared = 0; | ||||||||||
| #else | ||||||||||
| op->ob_refcnt = _Py_IMMORTAL_REFCNT; | ||||||||||
| #endif | ||||||||||
| } | ||||||||||
| } | ||||||||||
| #define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) | ||||||||||
|
|
||||||||||
| // Makes an immortal object mortal again with the specified refcnt. Should only | ||||||||||
| // be used during runtime finalization. | ||||||||||
| static inline void _Py_SetMortal(PyObject *op, Py_ssize_t refcnt) | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function shouldn't exist.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is important for the reference leak checks. It's not new functionality. Without it, we would leak immortal objects on interpreter shutdown. It's not generally safe, which is why it's in an We use this in two places. In both our intention is to deallocate immortal objects.
|
||||||||||
| { | ||||||||||
| if (op) { | ||||||||||
| assert(_Py_IsImmortal(op)); | ||||||||||
| #ifdef Py_NOGIL | ||||||||||
| op->ob_tid = 0; | ||||||||||
| op->ob_ref_local = 0; | ||||||||||
| op->ob_ref_shared = _Py_REF_SHARED(refcnt, _Py_REF_MERGED); | ||||||||||
| #else | ||||||||||
| op->ob_refcnt = refcnt; | ||||||||||
| #endif | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /* _Py_ClearImmortal() should only be used during runtime finalization. */ | ||||||||||
| static inline void _Py_ClearImmortal(PyObject *op) | ||||||||||
| { | ||||||||||
| if (op) { | ||||||||||
| assert(op->ob_refcnt == _Py_IMMORTAL_REFCNT); | ||||||||||
| op->ob_refcnt = 1; | ||||||||||
| _Py_SetMortal(op, 1); | ||||||||||
| Py_DECREF(op); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
@@ -122,6 +169,7 @@ static inline void _Py_ClearImmortal(PyObject *op) | |||||||||
| op = NULL; \ | ||||||||||
| } while (0) | ||||||||||
|
|
||||||||||
| #if !defined(Py_NOGIL) | ||||||||||
| static inline void | ||||||||||
| _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) | ||||||||||
| { | ||||||||||
|
|
@@ -161,6 +209,36 @@ _Py_DECREF_NO_DEALLOC(PyObject *op) | |||||||||
| #endif | ||||||||||
| } | ||||||||||
|
|
||||||||||
| #else | ||||||||||
| static inline void | ||||||||||
| _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not keep the specialization?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to keep the PR small so that it's easier to review. I intend to implement the specializations later. |
||||||||||
| { | ||||||||||
| Py_DECREF(op); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| static inline void | ||||||||||
| _Py_DECREF_NO_DEALLOC(PyObject *op) | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given we know that the refcount cannot reach zero, why not take advantage of that?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intend to implement this later. |
||||||||||
| { | ||||||||||
| Py_DECREF(op); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| static inline int | ||||||||||
| _Py_REF_IS_MERGED(Py_ssize_t ob_ref_shared) | ||||||||||
| { | ||||||||||
| return (ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) == _Py_REF_MERGED; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| static inline int | ||||||||||
| _Py_REF_IS_QUEUED(Py_ssize_t ob_ref_shared) | ||||||||||
| { | ||||||||||
| return (ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) == _Py_REF_QUEUED; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Merge the local and shared reference count fields and add `extra` to the | ||||||||||
| // refcount when merging. | ||||||||||
| Py_ssize_t _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra); | ||||||||||
| #endif // !defined(Py_NOGIL) | ||||||||||
|
|
||||||||||
| #ifdef Py_REF_DEBUG | ||||||||||
| # undef _Py_DEC_REFTOTAL | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -106,9 +106,26 @@ check by comparing the reference count field to the immortality reference count. | |||||||||
| #define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2) | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| // Py_NOGIL builds indicate immortal objects using `ob_ref_local`, which is | ||||||||||
| // always 32-bits. | ||||||||||
| #ifdef Py_NOGIL | ||||||||||
| #define _Py_IMMORTAL_REFCNT_LOCAL UINT32_MAX | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| // Make all internal uses of PyObject_HEAD_INIT immortal while preserving the | ||||||||||
| // C-API expectation that the refcnt will be set to 1. | ||||||||||
| #ifdef Py_BUILD_CORE | ||||||||||
| #if defined(Py_NOGIL) | ||||||||||
|
colesbury marked this conversation as resolved.
|
||||||||||
| #define PyObject_HEAD_INIT(type) \ | ||||||||||
| { \ | ||||||||||
| 0, \ | ||||||||||
| 0, \ | ||||||||||
| 0, \ | ||||||||||
| 0, \ | ||||||||||
| _Py_IMMORTAL_REFCNT_LOCAL, \ | ||||||||||
| 0, \ | ||||||||||
| (type), \ | ||||||||||
| }, | ||||||||||
| #elif defined(Py_BUILD_CORE) | ||||||||||
| #define PyObject_HEAD_INIT(type) \ | ||||||||||
| { \ | ||||||||||
| { _Py_IMMORTAL_REFCNT }, \ | ||||||||||
|
|
@@ -142,6 +159,7 @@ check by comparing the reference count field to the immortality reference count. | |||||||||
| * by hand. Similarly every pointer to a variable-size Python object can, | ||||||||||
| * in addition, be cast to PyVarObject*. | ||||||||||
| */ | ||||||||||
| #ifndef Py_NOGIL | ||||||||||
| struct _object { | ||||||||||
| #if (defined(__GNUC__) || defined(__clang__)) \ | ||||||||||
| && !(defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L) | ||||||||||
|
|
@@ -166,6 +184,31 @@ struct _object { | |||||||||
|
|
||||||||||
| PyTypeObject *ob_type; | ||||||||||
| }; | ||||||||||
| #else | ||||||||||
| // The shared reference count uses the two least-significant bits to store | ||||||||||
| // flags. The remaining bits are used to store the reference count. | ||||||||||
| #define _Py_REF_SHARED_SHIFT 2 | ||||||||||
| #define _Py_REF_SHARED_FLAG_MASK 0x3 | ||||||||||
|
|
||||||||||
| // The shared flags are initialized to zero. | ||||||||||
| #define _Py_REF_SHARED_INIT 0x0 | ||||||||||
| #define _Py_REF_MAYBE_WEAKREF 0x1 | ||||||||||
| #define _Py_REF_QUEUED 0x2 | ||||||||||
| #define _Py_REF_MERGED 0x3 | ||||||||||
|
|
||||||||||
| // Create a shared field from a refcnt and desired flags | ||||||||||
| #define _Py_REF_SHARED(refcnt, flags) (((refcnt) << _Py_REF_SHARED_SHIFT) + (flags)) | ||||||||||
|
|
||||||||||
| struct _object { | ||||||||||
| uintptr_t ob_tid; // thread id (or zero) | ||||||||||
| uint16_t _padding; | ||||||||||
| uint8_t ob_mutex; // per-object lock | ||||||||||
| uint8_t ob_gc_bits; // gc-related state | ||||||||||
| uint32_t ob_ref_local; // local reference count | ||||||||||
| Py_ssize_t ob_ref_shared; // shared (atomic) reference count | ||||||||||
| PyTypeObject *ob_type; | ||||||||||
| }; | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| /* Cast argument to PyObject* type. */ | ||||||||||
| #define _PyObject_CAST(op) _Py_CAST(PyObject*, (op)) | ||||||||||
|
|
@@ -183,9 +226,56 @@ typedef struct { | |||||||||
| PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y); | ||||||||||
| #define Py_Is(x, y) ((x) == (y)) | ||||||||||
|
|
||||||||||
| #ifndef Py_LIMITED_API | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-limited API must be in Include/cpython/object.h.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not possible here and
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote PR #111505 to fix the inter-dependency issue: move non-limited code to Include/cpython/object.h, and only keep limited code in Include/object.h. |
||||||||||
| static inline uintptr_t | ||||||||||
| _Py_ThreadId(void) | ||||||||||
| { | ||||||||||
| uintptr_t tid; | ||||||||||
| #if defined(_MSC_VER) && defined(_M_X64) | ||||||||||
| tid = __readgsqword(48); | ||||||||||
| #elif defined(_MSC_VER) && defined(_M_IX86) | ||||||||||
| tid = __readfsdword(24); | ||||||||||
| #elif defined(_MSC_VER) && defined(_M_ARM64) | ||||||||||
| tid = __getReg(18); | ||||||||||
| #elif defined(__i386__) | ||||||||||
| __asm__("movl %%gs:0, %0" : "=r" (tid)); // 32-bit always uses GS | ||||||||||
| #elif defined(__MACH__) && defined(__x86_64__) | ||||||||||
| __asm__("movq %%gs:0, %0" : "=r" (tid)); // x86_64 macOSX uses GS | ||||||||||
| #elif defined(__x86_64__) | ||||||||||
| __asm__("movq %%fs:0, %0" : "=r" (tid)); // x86_64 Linux, BSD uses FS | ||||||||||
| #elif defined(__arm__) | ||||||||||
| __asm__ ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tid)); | ||||||||||
| #elif defined(__aarch64__) && defined(__APPLE__) | ||||||||||
| __asm__ ("mrs %0, tpidrro_el0" : "=r" (tid)); | ||||||||||
| #elif defined(__aarch64__) | ||||||||||
| __asm__ ("mrs %0, tpidr_el0" : "=r" (tid)); | ||||||||||
| #else | ||||||||||
|
Comment on lines
+251
to
+257
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Caveat: I'm not an expert. Leaving aside the less common case of stamping the biased reference count block, and focusing on the more common case of comparing the current tid with object owner tid: Does the compiler optimise this move away?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is backwards: on x86/x86-64 you can do a comparison without copying On x86, many instructions, such as On ARM you can't do a comparison with a system or coprocessor register. You need to move it to a gpr first. |
||||||||||
| # error "define _Py_ThreadId for this platform" | ||||||||||
|
colesbury marked this conversation as resolved.
|
||||||||||
| #endif | ||||||||||
| return tid; | ||||||||||
| } | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| #if defined(Py_NOGIL) && !defined(Py_LIMITED_API) | ||||||||||
| static inline Py_ALWAYS_INLINE int | ||||||||||
| _Py_ThreadLocal(PyObject *ob) | ||||||||||
|
erlend-aasland marked this conversation as resolved.
Outdated
|
||||||||||
| { | ||||||||||
| return ob->ob_tid == _Py_ThreadId(); | ||||||||||
| } | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| static inline Py_ssize_t Py_REFCNT(PyObject *ob) { | ||||||||||
| #if !defined(Py_NOGIL) | ||||||||||
| return ob->ob_refcnt; | ||||||||||
| #else | ||||||||||
| uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local); | ||||||||||
| if (local == _Py_IMMORTAL_REFCNT_LOCAL) { | ||||||||||
| return _Py_IMMORTAL_REFCNT; | ||||||||||
| } | ||||||||||
| Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared); | ||||||||||
| return _Py_STATIC_CAST(Py_ssize_t, local) + | ||||||||||
| Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT); | ||||||||||
| #endif | ||||||||||
| } | ||||||||||
| #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 | ||||||||||
| # define Py_REFCNT(ob) Py_REFCNT(_PyObject_CAST(ob)) | ||||||||||
|
|
@@ -216,7 +306,9 @@ static inline Py_ssize_t Py_SIZE(PyObject *ob) { | |||||||||
|
|
||||||||||
| static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op) | ||||||||||
| { | ||||||||||
| #if SIZEOF_VOID_P > 4 | ||||||||||
| #if defined(Py_NOGIL) | ||||||||||
| return op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL; | ||||||||||
| #elif SIZEOF_VOID_P > 4 | ||||||||||
| return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0; | ||||||||||
| #else | ||||||||||
| return op->ob_refcnt == _Py_IMMORTAL_REFCNT; | ||||||||||
|
|
@@ -240,7 +332,24 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { | |||||||||
| if (_Py_IsImmortal(ob)) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| #if !defined(Py_NOGIL) | ||||||||||
| ob->ob_refcnt = refcnt; | ||||||||||
| #else | ||||||||||
| if (_Py_ThreadLocal(ob)) { | ||||||||||
| // Set local refcount to desired refcount and shared refcount to zero, | ||||||||||
| // but preserve the shared refcount flags. | ||||||||||
| assert(refcnt < UINT32_MAX); | ||||||||||
| ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt); | ||||||||||
| ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK; | ||||||||||
| } | ||||||||||
| else { | ||||||||||
| // Set local refcount to zero and shared refcount to desired refcount. | ||||||||||
| // Mark the object as merged. | ||||||||||
| ob->ob_tid = 0; | ||||||||||
| ob->ob_ref_local = 0; | ||||||||||
| ob->ob_ref_shared = _Py_REF_SHARED(refcnt, _Py_REF_MERGED); | ||||||||||
| } | ||||||||||
| #endif | ||||||||||
| } | ||||||||||
| #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 | ||||||||||
| # define Py_SET_REFCNT(ob, refcnt) Py_SET_REFCNT(_PyObject_CAST(ob), (refcnt)) | ||||||||||
|
|
@@ -618,7 +727,19 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op) | |||||||||
| #else | ||||||||||
| // Non-limited C API and limited C API for Python 3.9 and older access | ||||||||||
| // directly PyObject.ob_refcnt. | ||||||||||
| #if SIZEOF_VOID_P > 4 | ||||||||||
| #if defined(Py_NOGIL) | ||||||||||
| uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); | ||||||||||
| uint32_t new_local = local + 1; | ||||||||||
| if (new_local == 0) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| if (_Py_ThreadLocal(op)) { | ||||||||||
| _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, new_local); | ||||||||||
| } | ||||||||||
| else { | ||||||||||
| _Py_atomic_add_ssize(&op->ob_ref_shared, (1 << _Py_REF_SHARED_SHIFT)); | ||||||||||
| } | ||||||||||
| #elif SIZEOF_VOID_P > 4 | ||||||||||
| // Portable saturated add, branching on the carry flag and set low bits | ||||||||||
| PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN]; | ||||||||||
| PY_UINT32_T new_refcnt = cur_refcnt + 1; | ||||||||||
|
|
@@ -643,6 +764,19 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op) | |||||||||
| # define Py_INCREF(op) Py_INCREF(_PyObject_CAST(op)) | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
|
|
||||||||||
| #if !defined(Py_LIMITED_API) && defined(Py_NOGIL) | ||||||||||
| // Implements Py_DECREF on objects not owned by the current thread. | ||||||||||
| PyAPI_FUNC(void) _Py_DecRefShared(PyObject *); | ||||||||||
| PyAPI_FUNC(void) _Py_DecRefSharedDebug(PyObject *, const char *, int); | ||||||||||
|
|
||||||||||
| // Called from Py_DECREF by the owning thread when the local refcount reaches | ||||||||||
| // zero. The call will deallocate the object if the shared refcount is also | ||||||||||
| // zero. Otherwise, the thread gives up ownership and merges the reference | ||||||||||
| // count fields. | ||||||||||
| PyAPI_FUNC(void) _Py_MergeZeroRefcount(PyObject *); | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I read the code, the purpose of this function is to handle the object when the local ref count is zero.
Suggested change
or
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've named it |
||||||||||
| #endif | ||||||||||
|
|
||||||||||
| #if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || defined(Py_REF_DEBUG)) | ||||||||||
| // Stable ABI implements Py_DECREF() as a function call on limited C API | ||||||||||
| // version 3.12 and newer, and on Python built in debug mode. _Py_DecRef() was | ||||||||||
|
|
@@ -657,6 +791,52 @@ static inline void Py_DECREF(PyObject *op) { | |||||||||
| } | ||||||||||
| #define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op)) | ||||||||||
|
|
||||||||||
| #elif defined(Py_NOGIL) && defined(Py_REF_DEBUG) | ||||||||||
| static inline void Py_DECREF(const char *filename, int lineno, PyObject *op) | ||||||||||
| { | ||||||||||
| uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); | ||||||||||
| if (local == _Py_IMMORTAL_REFCNT_LOCAL) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| _Py_DECREF_STAT_INC(); | ||||||||||
| _Py_DECREF_DecRefTotal(); | ||||||||||
| if (_Py_ThreadLocal(op)) { | ||||||||||
| if (local == 0) { | ||||||||||
| _Py_NegativeRefcount(filename, lineno, op); | ||||||||||
| } | ||||||||||
| local--; | ||||||||||
| _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); | ||||||||||
| if (local == 0) { | ||||||||||
| _Py_MergeZeroRefcount(op); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| else { | ||||||||||
| _Py_DecRefSharedDebug(op, filename, lineno); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| #define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op)) | ||||||||||
|
|
||||||||||
| #elif defined(Py_NOGIL) | ||||||||||
| static inline void Py_DECREF(PyObject *op) | ||||||||||
| { | ||||||||||
| uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); | ||||||||||
| if (local == _Py_IMMORTAL_REFCNT_LOCAL) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| _Py_DECREF_STAT_INC(); | ||||||||||
| if (_Py_ThreadLocal(op)) { | ||||||||||
| local--; | ||||||||||
| _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); | ||||||||||
| if (local == 0) { | ||||||||||
| _Py_MergeZeroRefcount(op); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| else { | ||||||||||
| _Py_DecRefShared(op); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| #define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op)) | ||||||||||
|
|
||||||||||
| #elif defined(Py_REF_DEBUG) | ||||||||||
| static inline void Py_DECREF(const char *filename, int lineno, PyObject *op) | ||||||||||
| { | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loosing the trailing comma is definitely better style, but is bound to break some third party code.
I think you need to leave the comma.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in an internal-only header (
Include/internal/pycore_object.h), not used by third party code.There's a similarly named public macro used by third party code that is not changed.