From 311f629de0016e7c01c0b2c5ac5df7824cb8377f Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 00:47:52 +0500 Subject: [PATCH 01/12] Use _PyObject_IsUniquelyReferenced to check if object is uniquely referenced --- Modules/_ctypes/_ctypes.c | 2 +- Modules/_elementtree.c | 5 +++-- Modules/_functoolsmodule.c | 4 ++-- Modules/_testcapi/object.c | 4 ++-- Modules/_testcapimodule.c | 2 +- Modules/itertoolsmodule.c | 14 +++++++------- Objects/bytesobject.c | 2 +- Objects/dictobject.c | 10 ++-------- Objects/listobject.c | 2 +- Objects/longobject.c | 16 ++++++++-------- Objects/setobject.c | 4 ++-- Objects/unicodeobject.c | 4 ++-- Python/marshal.c | 2 +- 13 files changed, 33 insertions(+), 38 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 91fd23d413de21..965df757de2854 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3251,7 +3251,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info) * used in constructors and therefore does not have concurrent * access. */ - assert (Py_REFCNT(obj) == 1); + assert (_PyObject_IsUniquelyReferenced(obj)); assert(stginfo_get_dict_final(info) == 1); if ((size_t)info->size <= sizeof(obj->b_value)) { diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 9263f14b57f972..8883319265a6ea 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -912,7 +912,7 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo) return Py_NewRef(object); } - if (Py_REFCNT(object) == 1) { + if (_PyObject_IsUniquelyReferenced(object)) { if (PyDict_CheckExact(object)) { PyObject *key, *value; Py_ssize_t pos = 0; @@ -2794,7 +2794,8 @@ treebuilder_handle_data(TreeBuilderObject* self, PyObject* data) self->data = Py_NewRef(data); } else { /* more than one item; use a list to collect items */ - if (PyBytes_CheckExact(self->data) && Py_REFCNT(self->data) == 1 && + if (PyBytes_CheckExact(self->data) && + _PyObject_IsUniquelyReferenced(self->data) && PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) { /* XXX this code path unused in Python 3? */ /* expat often generates single character data sections; handle diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 257d5c6d53611c..e9ca4a18613636 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -291,7 +291,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) if (kw == NULL) { pto->kw = PyDict_New(); } - else if (Py_REFCNT(kw) == 1) { + else if (_PyObject_IsUniquelyReferenced(kw)) { pto->kw = Py_NewRef(kw); } else { @@ -1093,7 +1093,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, result = op2; else { /* Update the args tuple in-place */ - assert(Py_REFCNT(args) == 1); + assert(_PyObject_IsUniquelyReferenced(args)); Py_XSETREF(_PyTuple_ITEMS(args)[0], result); Py_XSETREF(_PyTuple_ITEMS(args)[1], op2); if ((result = PyObject_Call(func, args, NULL)) == NULL) { diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 798ef97c495aeb..e917b9a96ee2dd 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -258,7 +258,7 @@ negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(Py_REFCNT(obj) == 1); + assert(_PyObject_IsUniquelyReferenced(obj)); Py_SET_REFCNT(obj, 0); /* Py_DECREF() must call _Py_NegativeRefcount() and abort Python */ @@ -275,7 +275,7 @@ decref_freed_object(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(Py_REFCNT(obj) == 1); + assert(_PyObject_IsUniquelyReferenced(obj)); // Deallocate the memory Py_DECREF(obj); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4e73be20e1b709..94426042617597 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2249,7 +2249,7 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(ref == obj); // delete the referenced object: clear the weakref - assert(Py_REFCNT(obj) == 1); + assert(_PyObject_IsUniquelyReferenced(obj)); Py_DECREF(obj); assert(PyWeakref_IsDead(weakref)); diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 60ef6f9ff4cd98..9d8de0e748dd85 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -376,7 +376,7 @@ pairwise_next(PyObject *op) } result = po->result; - if (Py_REFCNT(result) == 1) { + if (_PyObject_IsUniquelyReferenced(result)) { Py_INCREF(result); PyObject *last_old = PyTuple_GET_ITEM(result, 0); PyObject *last_new = PyTuple_GET_ITEM(result, 1); @@ -802,7 +802,7 @@ teedataobject_traverse(PyObject *op, visitproc visit, void * arg) static void teedataobject_safe_decref(PyObject *obj) { - while (obj && Py_REFCNT(obj) == 1) { + while (obj && _PyObject_IsUniquelyReferenced(obj)) { teedataobject *tmp = teedataobject_CAST(obj); PyObject *nextlink = tmp->nextlink; tmp->nextlink = NULL; @@ -2143,7 +2143,7 @@ product_next_lock_held(PyObject *op) _PyTuple_Recycle(result); } /* Now, we've got the only copy so we can update it in-place */ - assert (npools==0 || Py_REFCNT(result) == 1); + assert (npools==0 || _PyObject_IsUniquelyReferenced(result)); /* Update the pool indices right-to-left. Only advance to the next pool when the previous one rolls-over */ @@ -2381,7 +2381,7 @@ combinations_next_lock_held(PyObject *op) * CPython's empty tuple is a singleton and cached in * PyTuple's freelist. */ - assert(r == 0 || Py_REFCNT(result) == 1); + assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); /* Scan indices right-to-left until finding one that is not at its maximum (i + n - r). */ @@ -2633,7 +2633,7 @@ cwr_next(PyObject *op) } /* Now, we've got the only copy so we can update it in-place CPython's empty tuple is a singleton and cached in PyTuple's freelist. */ - assert(r == 0 || Py_REFCNT(result) == 1); + assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); /* Scan indices right-to-left until finding one that is not * at its maximum (n-1). */ @@ -2893,7 +2893,7 @@ permutations_next(PyObject *op) _PyTuple_Recycle(result); } /* Now, we've got the only copy so we can update it in-place */ - assert(r == 0 || Py_REFCNT(result) == 1); + assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); /* Decrement rightmost cycle, moving leftward upon zero rollover */ for (i=r-1 ; i>=0 ; i--) { @@ -3847,7 +3847,7 @@ zip_longest_next(PyObject *op) return NULL; if (lz->numactive == 0) return NULL; - if (Py_REFCNT(result) == 1) { + if (_PyObject_IsUniquelyReferenced(result)) { Py_INCREF(result); for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index de8ab26db1e966..b54642356fd566 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3171,7 +3171,7 @@ PyBytes_Concat(PyObject **pv, PyObject *w) return; } - if (Py_REFCNT(*pv) == 1 && PyBytes_CheckExact(*pv)) { + if (_PyObject_IsUniquelyReferenced(*pv) && PyBytes_CheckExact(*pv)) { /* Only one reference, so we can resize in place */ Py_ssize_t oldsize; Py_buffer wb; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ddf9bde63f31bb..e6d509a7b38f36 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5670,13 +5670,7 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, static bool has_unique_reference(PyObject *op) { -#ifdef Py_GIL_DISABLED - return (_Py_IsOwnedByCurrentThread(op) && - op->ob_ref_local == 1 && - _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); -#else - return Py_REFCNT(op) == 1; -#endif + return _PyObject_IsUniquelyReferenced(op) == 1; } static bool @@ -5828,7 +5822,7 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) } else if (Py_IS_TYPE(di, &PyDictRevIterItem_Type)) { result = di->di_result; - if (Py_REFCNT(result) == 1) { + if (_PyObject_IsUniquelyReferenced(result)) { PyObject *oldkey = PyTuple_GET_ITEM(result, 0); PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); PyTuple_SET_ITEM(result, 0, Py_NewRef(key)); diff --git a/Objects/listobject.c b/Objects/listobject.c index 1722ea60cdc68f..5f5da77bac4b96 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -79,7 +79,7 @@ ensure_shared_on_resize(PyListObject *self) // We can't use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here because // the `CALL_LIST_APPEND` bytecode handler may lock the list without // a critical section. - assert(Py_REFCNT(self) == 1 || PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); + assert(_PyObject_IsUniquelyReferenced(self) || PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); // Ensure that the list array is freed using QSBR if we are not the // owning thread. diff --git a/Objects/longobject.c b/Objects/longobject.c index 5eb4063f861f7a..c69aa80845dd24 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -352,7 +352,7 @@ _PyLong_Negate(PyLongObject **x_p) PyLongObject *x; x = (PyLongObject *)*x_p; - if (Py_REFCNT(x) == 1) { + if (_PyObject_IsUniquelyReferenced(x)) { _PyLong_FlipSign(x); return; } @@ -3848,7 +3848,7 @@ long_add(PyLongObject *a, PyLongObject *b) and thus z must be a multiple-digit int. That also means z is not an element of small_ints, so negating it in-place is safe. */ - assert(Py_REFCNT(z) == 1); + assert(_PyObject_IsUniquelyReferenced(z)); _PyLong_FlipSign(z); } } @@ -3895,7 +3895,7 @@ long_sub(PyLongObject *a, PyLongObject *b) else { z = x_add(a, b); if (z != NULL) { - assert(_PyLong_IsZero(z) || Py_REFCNT(z) == 1); + assert(_PyLong_IsZero(z) || _PyObject_IsUniquelyReferenced(z)); _PyLong_FlipSign(z); } } @@ -5487,7 +5487,7 @@ long_lshift1(PyLongObject *a, Py_ssize_t wordshift, digit remshift) if (z == NULL) return NULL; if (_PyLong_IsNegative(a)) { - assert(Py_REFCNT(z) == 1); + assert(_PyObject_IsUniquelyReferenced(z)); _PyLong_FlipSign(z); } for (i = 0; i < wordshift; i++) @@ -5849,7 +5849,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg) assert(size_a >= 0); _PyLong_SetSignAndDigitCount(c, 1, size_a); } - else if (Py_REFCNT(a) == 1) { + else if (_PyObject_IsUniquelyReferenced(a)) { c = (PyLongObject*)Py_NewRef(a); } else { @@ -5863,7 +5863,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg) assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a); } - else if (Py_REFCNT(b) == 1 && size_a <= alloc_b) { + else if (_PyObject_IsUniquelyReferenced(b) && size_a <= alloc_b) { d = (PyLongObject*)Py_NewRef(b); assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a); @@ -6951,7 +6951,7 @@ PyLongWriter_Discard(PyLongWriter *writer) } PyLongObject *obj = (PyLongObject *)writer; - assert(Py_REFCNT(obj) == 1); + assert(_PyObject_IsUniquelyReferenced(obj)); Py_DECREF(obj); } @@ -6960,7 +6960,7 @@ PyObject* PyLongWriter_Finish(PyLongWriter *writer) { PyLongObject *obj = (PyLongObject *)writer; - assert(Py_REFCNT(obj) == 1); + assert(_PyObject_IsUniquelyReferenced(obj)); // Normalize and get singleton if possible obj = maybe_small_long(long_normalize(obj)); diff --git a/Objects/setobject.c b/Objects/setobject.c index d8340499be5aae..cd18dc78591446 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1058,7 +1058,7 @@ set_update_lock_held(PySetObject *so, PyObject *other) static int set_update_local(PySetObject *so, PyObject *other) { - assert(Py_REFCNT(so) == 1); + assert(_PyObject_IsUniquelyReferenced(so)); if (PyAnySet_Check(other)) { int rv; Py_BEGIN_CRITICAL_SECTION(other); @@ -2444,7 +2444,7 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds) if (!PyArg_UnpackTuple(args, Py_TYPE(self)->tp_name, 0, 1, &iterable)) return -1; - if (Py_REFCNT(self) == 1 && self->fill == 0) { + if (_PyObject_IsUniquelyReferenced(self) && self->fill == 0) { self->hash = -1; if (iterable == NULL) { return 0; diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index d4549b70d4dabc..53e232e7d44375 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1162,7 +1162,7 @@ static int resize_inplace(PyObject *unicode, Py_ssize_t length) { assert(!PyUnicode_IS_COMPACT(unicode)); - assert(Py_REFCNT(unicode) == 1); + assert(_PyObject_IsUniquelyReferenced(unicode)); Py_ssize_t new_size; Py_ssize_t char_size; @@ -1717,7 +1717,7 @@ unicode_dealloc(PyObject *unicode) // Successfully popped. assert(popped == unicode); // Only our `popped` reference should be left; remove it too. - assert(Py_REFCNT(unicode) == 1); + assert(_PyObject_IsUniquelyReferenced(unicode)); Py_SET_REFCNT(unicode, 0); #ifdef Py_REF_DEBUG /* let's be pedantic with the ref total */ diff --git a/Python/marshal.c b/Python/marshal.c index 4f444d4671cbff..35ced942630956 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -388,7 +388,7 @@ w_ref(PyObject *v, char *flag, WFILE *p) * But we use TYPE_REF always for interned string, to PYC file stable * as possible. */ - if (Py_REFCNT(v) == 1 && + if (_PyObject_IsUniquelyReferenced(v) && !(PyUnicode_CheckExact(v) && PyUnicode_CHECK_INTERNED(v))) { return 0; } From d381a91a9703e34f69501deaaf8f37d52f26ca55 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 01:22:59 +0500 Subject: [PATCH 02/12] Fix build on linux --- Modules/_ctypes/_ctypes.c | 2 +- Modules/_testcapi/object.c | 4 ++-- Modules/_testcapimodule.c | 2 +- Objects/longobject.c | 18 ++++++++++-------- Objects/setobject.c | 4 ++-- Python/marshal.c | 1 + 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 965df757de2854..8e05810c80c6ad 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3251,7 +3251,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info) * used in constructors and therefore does not have concurrent * access. */ - assert (_PyObject_IsUniquelyReferenced(obj)); + assert (_PyObject_IsUniquelyReferenced((PyObject *)obj)); assert(stginfo_get_dict_final(info) == 1); if ((size_t)info->size <= sizeof(obj->b_value)) { diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index e917b9a96ee2dd..93dc3760957d8a 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -258,7 +258,7 @@ negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(_PyObject_IsUniquelyReferenced(obj)); + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); Py_SET_REFCNT(obj, 0); /* Py_DECREF() must call _Py_NegativeRefcount() and abort Python */ @@ -275,7 +275,7 @@ decref_freed_object(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(_PyObject_IsUniquelyReferenced(obj)); + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); // Deallocate the memory Py_DECREF(obj); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 94426042617597..fbf2929b9a9d3b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2249,7 +2249,7 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(ref == obj); // delete the referenced object: clear the weakref - assert(_PyObject_IsUniquelyReferenced(obj)); + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); Py_DECREF(obj); assert(PyWeakref_IsDead(weakref)); diff --git a/Objects/longobject.c b/Objects/longobject.c index c69aa80845dd24..e96ec30525f667 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -352,7 +352,7 @@ _PyLong_Negate(PyLongObject **x_p) PyLongObject *x; x = (PyLongObject *)*x_p; - if (_PyObject_IsUniquelyReferenced(x)) { + if (_PyObject_IsUniquelyReferenced((PyObject *)x)) { _PyLong_FlipSign(x); return; } @@ -3848,7 +3848,7 @@ long_add(PyLongObject *a, PyLongObject *b) and thus z must be a multiple-digit int. That also means z is not an element of small_ints, so negating it in-place is safe. */ - assert(_PyObject_IsUniquelyReferenced(z)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)z)); _PyLong_FlipSign(z); } } @@ -3895,7 +3895,8 @@ long_sub(PyLongObject *a, PyLongObject *b) else { z = x_add(a, b); if (z != NULL) { - assert(_PyLong_IsZero(z) || _PyObject_IsUniquelyReferenced(z)); + assert(_PyLong_IsZero(z) || + _PyObject_IsUniquelyReferenced((PyObject *)z)); _PyLong_FlipSign(z); } } @@ -5487,7 +5488,7 @@ long_lshift1(PyLongObject *a, Py_ssize_t wordshift, digit remshift) if (z == NULL) return NULL; if (_PyLong_IsNegative(a)) { - assert(_PyObject_IsUniquelyReferenced(z)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)z)); _PyLong_FlipSign(z); } for (i = 0; i < wordshift; i++) @@ -5849,7 +5850,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg) assert(size_a >= 0); _PyLong_SetSignAndDigitCount(c, 1, size_a); } - else if (_PyObject_IsUniquelyReferenced(a)) { + else if (_PyObject_IsUniquelyReferenced((PyObject *)a)) { c = (PyLongObject*)Py_NewRef(a); } else { @@ -5863,7 +5864,8 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg) assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a); } - else if (_PyObject_IsUniquelyReferenced(b) && size_a <= alloc_b) { + else if (_PyObject_IsUniquelyReferenced((PyObject *)b) && + size_a <= alloc_b) { d = (PyLongObject*)Py_NewRef(b); assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a); @@ -6951,7 +6953,7 @@ PyLongWriter_Discard(PyLongWriter *writer) } PyLongObject *obj = (PyLongObject *)writer; - assert(_PyObject_IsUniquelyReferenced(obj)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)obj)); Py_DECREF(obj); } @@ -6960,7 +6962,7 @@ PyObject* PyLongWriter_Finish(PyLongWriter *writer) { PyLongObject *obj = (PyLongObject *)writer; - assert(_PyObject_IsUniquelyReferenced(obj)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)obj)); // Normalize and get singleton if possible obj = maybe_small_long(long_normalize(obj)); diff --git a/Objects/setobject.c b/Objects/setobject.c index cd18dc78591446..e9beb3cfa91cac 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1058,7 +1058,7 @@ set_update_lock_held(PySetObject *so, PyObject *other) static int set_update_local(PySetObject *so, PyObject *other) { - assert(_PyObject_IsUniquelyReferenced(so)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)so)); if (PyAnySet_Check(other)) { int rv; Py_BEGIN_CRITICAL_SECTION(other); @@ -2444,7 +2444,7 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds) if (!PyArg_UnpackTuple(args, Py_TYPE(self)->tp_name, 0, 1, &iterable)) return -1; - if (_PyObject_IsUniquelyReferenced(self) && self->fill == 0) { + if (_PyObject_IsUniquelyReferenced((PyObject *)self) && self->fill == 0) { self->hash = -1; if (iterable == NULL) { return 0; diff --git a/Python/marshal.c b/Python/marshal.c index 35ced942630956..8b56de6575559c 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -11,6 +11,7 @@ #include "pycore_code.h" // _PyCode_New() #include "pycore_hashtable.h" // _Py_hashtable_t #include "pycore_long.h" // _PyLong_IsZero() +#include "pycore_object.h" // _PyObject_IsUniquelyReferenced #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_setobject.h" // _PySet_NextEntryRef() #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal() From d416ac64f9a8fbd4877510b37cd6f09054e26ba5 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 10:53:17 +0500 Subject: [PATCH 03/12] Return Py_REFCNT to test_weakref_capi --- Modules/_testcapimodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index fbf2929b9a9d3b..4e73be20e1b709 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2249,7 +2249,7 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(ref == obj); // delete the referenced object: clear the weakref - assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + assert(Py_REFCNT(obj) == 1); Py_DECREF(obj); assert(PyWeakref_IsDead(weakref)); From 45593e835c9401b453a971b7cf51a9635f478824 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 10:53:37 +0500 Subject: [PATCH 04/12] Fix warning in ensure_shared_on_resize --- Objects/listobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 5f5da77bac4b96..f3cfbbc4b57b41 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -79,7 +79,8 @@ ensure_shared_on_resize(PyListObject *self) // We can't use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here because // the `CALL_LIST_APPEND` bytecode handler may lock the list without // a critical section. - assert(_PyObject_IsUniquelyReferenced(self) || PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); + assert(_PyObject_IsUniquelyReferenced((PyObject *)self) || + PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); // Ensure that the list array is freed using QSBR if we are not the // owning thread. From 29899181c6a6a4bef297f262abb76c1e17cbcf3d Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 23:40:25 +0500 Subject: [PATCH 05/12] Address review suggestions --- Modules/_functoolsmodule.c | 2 +- Modules/_testcapi/object.c | 4 ++-- Objects/dictobject.c | 8 +------- Objects/listobject.c | 3 +-- Objects/longobject.c | 4 ++-- Objects/unicodeobject.c | 2 +- 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index e9ca4a18613636..97c0ade88c70a8 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1076,7 +1076,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, for (;;) { PyObject *op2; - if (Py_REFCNT(args) > 1) { + if (_PyObject_IsUniquelyReferenced(args)) { Py_DECREF(args); if ((args = PyTuple_New(2)) == NULL) goto Fail; diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 93dc3760957d8a..798ef97c495aeb 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -258,7 +258,7 @@ negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + assert(Py_REFCNT(obj) == 1); Py_SET_REFCNT(obj, 0); /* Py_DECREF() must call _Py_NegativeRefcount() and abort Python */ @@ -275,7 +275,7 @@ decref_freed_object(PyObject *self, PyObject *Py_UNUSED(args)) if (obj == NULL) { return NULL; } - assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + assert(Py_REFCNT(obj) == 1); // Deallocate the memory Py_DECREF(obj); diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e6d509a7b38f36..40825e86ae6c05 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5667,16 +5667,10 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, #endif -static bool -has_unique_reference(PyObject *op) -{ - return _PyObject_IsUniquelyReferenced(op) == 1; -} - static bool acquire_iter_result(PyObject *result) { - if (has_unique_reference(result)) { + if (_PyObject_IsUniquelyReferenced(result)) { Py_INCREF(result); return true; } diff --git a/Objects/listobject.c b/Objects/listobject.c index f3cfbbc4b57b41..1722ea60cdc68f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -79,8 +79,7 @@ ensure_shared_on_resize(PyListObject *self) // We can't use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here because // the `CALL_LIST_APPEND` bytecode handler may lock the list without // a critical section. - assert(_PyObject_IsUniquelyReferenced((PyObject *)self) || - PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); + assert(Py_REFCNT(self) == 1 || PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex)); // Ensure that the list array is freed using QSBR if we are not the // owning thread. diff --git a/Objects/longobject.c b/Objects/longobject.c index e96ec30525f667..208b16b853cb21 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6953,7 +6953,7 @@ PyLongWriter_Discard(PyLongWriter *writer) } PyLongObject *obj = (PyLongObject *)writer; - assert(_PyObject_IsUniquelyReferenced((PyObject *)obj)); + assert(Py_REFCNT(obj) == 1); Py_DECREF(obj); } @@ -6962,7 +6962,7 @@ PyObject* PyLongWriter_Finish(PyLongWriter *writer) { PyLongObject *obj = (PyLongObject *)writer; - assert(_PyObject_IsUniquelyReferenced((PyObject *)obj)); + assert(Py_REFCNT(obj) == 1); // Normalize and get singleton if possible obj = maybe_small_long(long_normalize(obj)); diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 53e232e7d44375..36d55ed9c5c821 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1717,7 +1717,7 @@ unicode_dealloc(PyObject *unicode) // Successfully popped. assert(popped == unicode); // Only our `popped` reference should be left; remove it too. - assert(_PyObject_IsUniquelyReferenced(unicode)); + assert(Py_REFCNT(unicode) == 1); Py_SET_REFCNT(unicode, 0); #ifdef Py_REF_DEBUG /* let's be pedantic with the ref total */ From d3b0e31e6c38a223a94aad5a835692e69ac5d2f6 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 23:45:00 +0500 Subject: [PATCH 06/12] Revert for asserts --- Modules/_ctypes/_ctypes.c | 2 +- Modules/_functoolsmodule.c | 2 +- Modules/itertoolsmodule.c | 8 ++++---- Objects/longobject.c | 7 +++---- Objects/setobject.c | 2 +- Objects/unicodeobject.c | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 8e05810c80c6ad..56c52471fc198b 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -987,7 +987,7 @@ CDataType_from_buffer_copy_impl(PyObject *type, PyTypeObject *cls, result = generic_pycdata_new(st, (PyTypeObject *)type, NULL, NULL); if (result != NULL) { - assert(_PyObject_IsUniquelyReferenced(result)); + assert(Py_REFCNT(result) == 1); memcpy(((CDataObject *) result)->b_ptr, (char *)buffer->buf + offset, info->size); } return result; diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 97c0ade88c70a8..cb801b8cdb1d59 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1093,7 +1093,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, result = op2; else { /* Update the args tuple in-place */ - assert(_PyObject_IsUniquelyReferenced(args)); + assert(Py_REFCNT(args) == 1); Py_XSETREF(_PyTuple_ITEMS(args)[0], result); Py_XSETREF(_PyTuple_ITEMS(args)[1], op2); if ((result = PyObject_Call(func, args, NULL)) == NULL) { diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 9d8de0e748dd85..302679922572df 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -2143,7 +2143,7 @@ product_next_lock_held(PyObject *op) _PyTuple_Recycle(result); } /* Now, we've got the only copy so we can update it in-place */ - assert (npools==0 || _PyObject_IsUniquelyReferenced(result)); + assert (npools==0 || Py_REFCNT(result) == 1); /* Update the pool indices right-to-left. Only advance to the next pool when the previous one rolls-over */ @@ -2381,7 +2381,7 @@ combinations_next_lock_held(PyObject *op) * CPython's empty tuple is a singleton and cached in * PyTuple's freelist. */ - assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); + assert(r == 0 || Py_REFCNT(result) == 1); /* Scan indices right-to-left until finding one that is not at its maximum (i + n - r). */ @@ -2633,7 +2633,7 @@ cwr_next(PyObject *op) } /* Now, we've got the only copy so we can update it in-place CPython's empty tuple is a singleton and cached in PyTuple's freelist. */ - assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); + assert(r == 0 || Py_REFCNT(result) == 1); /* Scan indices right-to-left until finding one that is not * at its maximum (n-1). */ @@ -2893,7 +2893,7 @@ permutations_next(PyObject *op) _PyTuple_Recycle(result); } /* Now, we've got the only copy so we can update it in-place */ - assert(r == 0 || _PyObject_IsUniquelyReferenced(result)); + assert(r == 0 || Py_REFCNT(result) == 1); /* Decrement rightmost cycle, moving leftward upon zero rollover */ for (i=r-1 ; i>=0 ; i--) { diff --git a/Objects/longobject.c b/Objects/longobject.c index 208b16b853cb21..14fb3275474837 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3848,7 +3848,7 @@ long_add(PyLongObject *a, PyLongObject *b) and thus z must be a multiple-digit int. That also means z is not an element of small_ints, so negating it in-place is safe. */ - assert(_PyObject_IsUniquelyReferenced((PyObject *)z)); + assert(Py_REFCNT(z) == 1); _PyLong_FlipSign(z); } } @@ -3895,8 +3895,7 @@ long_sub(PyLongObject *a, PyLongObject *b) else { z = x_add(a, b); if (z != NULL) { - assert(_PyLong_IsZero(z) || - _PyObject_IsUniquelyReferenced((PyObject *)z)); + assert(_PyLong_IsZero(z) || Py_REFCNT(z) == 1); _PyLong_FlipSign(z); } } @@ -5488,7 +5487,7 @@ long_lshift1(PyLongObject *a, Py_ssize_t wordshift, digit remshift) if (z == NULL) return NULL; if (_PyLong_IsNegative(a)) { - assert(_PyObject_IsUniquelyReferenced((PyObject *)z)); + assert(Py_REFCNT(z) == 1); _PyLong_FlipSign(z); } for (i = 0; i < wordshift; i++) diff --git a/Objects/setobject.c b/Objects/setobject.c index e9beb3cfa91cac..87bd0651b27878 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1058,7 +1058,7 @@ set_update_lock_held(PySetObject *so, PyObject *other) static int set_update_local(PySetObject *so, PyObject *other) { - assert(_PyObject_IsUniquelyReferenced((PyObject *)so)); + assert(Py_REFCNT(so) == 1); if (PyAnySet_Check(other)) { int rv; Py_BEGIN_CRITICAL_SECTION(other); diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 36d55ed9c5c821..d4549b70d4dabc 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1162,7 +1162,7 @@ static int resize_inplace(PyObject *unicode, Py_ssize_t length) { assert(!PyUnicode_IS_COMPACT(unicode)); - assert(_PyObject_IsUniquelyReferenced(unicode)); + assert(Py_REFCNT(unicode) == 1); Py_ssize_t new_size; Py_ssize_t char_size; From 375bf2cbc9a2d12a19092810c1b9c5c7ff246e50 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 23:47:13 +0500 Subject: [PATCH 07/12] Revert for ctypes --- Modules/_ctypes/_ctypes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 56c52471fc198b..91fd23d413de21 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -987,7 +987,7 @@ CDataType_from_buffer_copy_impl(PyObject *type, PyTypeObject *cls, result = generic_pycdata_new(st, (PyTypeObject *)type, NULL, NULL); if (result != NULL) { - assert(Py_REFCNT(result) == 1); + assert(_PyObject_IsUniquelyReferenced(result)); memcpy(((CDataObject *) result)->b_ptr, (char *)buffer->buf + offset, info->size); } return result; @@ -3251,7 +3251,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info) * used in constructors and therefore does not have concurrent * access. */ - assert (_PyObject_IsUniquelyReferenced((PyObject *)obj)); + assert (Py_REFCNT(obj) == 1); assert(stginfo_get_dict_final(info) == 1); if ((size_t)info->size <= sizeof(obj->b_value)) { From 86079c4d62bfa4f364fbfec0254921bc40830749 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 23:50:51 +0500 Subject: [PATCH 08/12] Fix functools --- Modules/_functoolsmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index cb801b8cdb1d59..44b1a302d49ed2 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1076,7 +1076,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, for (;;) { PyObject *op2; - if (_PyObject_IsUniquelyReferenced(args)) { + if (!_PyObject_IsUniquelyReferenced(args)) { Py_DECREF(args); if ((args = PyTuple_New(2)) == NULL) goto Fail; From b8b54c74796e21690bdce7f7d76632f3e245d7a5 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 14 Oct 2025 23:59:41 +0500 Subject: [PATCH 09/12] Fix Py_REFCNT > 1 --- Modules/itertoolsmodule.c | 8 ++++---- Objects/bytesobject.c | 2 +- Objects/setobject.c | 2 +- Objects/tupleobject.c | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 302679922572df..8685eff8be65c3 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -2129,7 +2129,7 @@ product_next_lock_held(PyObject *op) Py_ssize_t *indices = lz->indices; /* Copy the previous result tuple or re-use it if available */ - if (Py_REFCNT(result) > 1) { + if (!_PyObject_IsUniquelyReferenced(result)) { PyObject *old_result = result; result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), npools); if (result == NULL) @@ -2364,7 +2364,7 @@ combinations_next_lock_held(PyObject *op) } } else { /* Copy the previous result tuple or re-use it if available */ - if (Py_REFCNT(result) > 1) { + if (!_PyObject_IsUniquelyReferenced(result)) { PyObject *old_result = result; result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r); if (result == NULL) @@ -2618,7 +2618,7 @@ cwr_next(PyObject *op) } } else { /* Copy the previous result tuple or re-use it if available */ - if (Py_REFCNT(result) > 1) { + if (!_PyObject_IsUniquelyReferenced(result)) { PyObject *old_result = result; result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r); if (result == NULL) @@ -2879,7 +2879,7 @@ permutations_next(PyObject *op) goto empty; /* Copy the previous result tuple or re-use it if available */ - if (Py_REFCNT(result) > 1) { + if (!_PyObject_IsUniquelyReferenced(result)) { PyObject *old_result = result; result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r); if (result == NULL) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index b54642356fd566..9c807b3dd166ee 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3256,7 +3256,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) Py_DECREF(v); return 0; } - if (Py_REFCNT(v) != 1) { + if (!_PyObject_IsUniquelyReferenced(v)) { if (oldsize < newsize) { *pv = _PyBytes_FromSize(newsize, 0); if (*pv) { diff --git a/Objects/setobject.c b/Objects/setobject.c index 87bd0651b27878..213bd821d8a1b9 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2774,7 +2774,7 @@ int PySet_Add(PyObject *anyset, PyObject *key) { if (!PySet_Check(anyset) && - (!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) { + (!PyFrozenSet_Check(anyset) || !_PyObject_IsUniquelyReferenced(anyset))) { PyErr_BadInternalCall(); return -1; } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 94b7ae7e642283..12dba29b0da160 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -118,7 +118,7 @@ int PyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) { PyObject **p; - if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) { + if (!PyTuple_Check(op) || !_PyObject_IsUniquelyReferenced(op)) { Py_XDECREF(newitem); PyErr_BadInternalCall(); return -1; @@ -923,7 +923,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) v = (PyTupleObject *) *pv; if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) || - (Py_SIZE(v) != 0 && Py_REFCNT(v) != 1)) { + (Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(v))) { *pv = 0; Py_XDECREF(v); PyErr_BadInternalCall(); From 307be2cd4d9230c9a4e09ba01bc09ec51eeb6135 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 15 Oct 2025 00:11:41 +0500 Subject: [PATCH 10/12] Fix compilation --- Objects/tupleobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 12dba29b0da160..cd90b06d499faf 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -923,7 +923,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) v = (PyTupleObject *) *pv; if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) || - (Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(v))) { + (Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(*pv))) { *pv = 0; Py_XDECREF(v); PyErr_BadInternalCall(); From 83f4b841b7c84444785beed3a6581c4dbb6f4230 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 15 Oct 2025 00:23:10 +0500 Subject: [PATCH 11/12] Add news entry --- .../2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst new file mode 100644 index 00000000000000..7c3924195eb85f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst @@ -0,0 +1,2 @@ +Fixing the checking of whether an object is uniquely referenced to ensure +free-threaded compatibility. Patch by Sergey Miryanov. From 1b2193f4d641f6bd924ad5c04af022d9608074ab Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 15 Oct 2025 17:50:00 +0500 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Victor Stinner --- Modules/_elementtree.c | 6 +++--- Objects/longobject.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 8883319265a6ea..3173b52afb31b6 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -2794,9 +2794,9 @@ treebuilder_handle_data(TreeBuilderObject* self, PyObject* data) self->data = Py_NewRef(data); } else { /* more than one item; use a list to collect items */ - if (PyBytes_CheckExact(self->data) && - _PyObject_IsUniquelyReferenced(self->data) && - PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) { + if (PyBytes_CheckExact(self->data) + && _PyObject_IsUniquelyReferenced(self->data) + && PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) { /* XXX this code path unused in Python 3? */ /* expat often generates single character data sections; handle the most common case by resizing the existing string... */ diff --git a/Objects/longobject.c b/Objects/longobject.c index 14fb3275474837..298399210afd58 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -5863,8 +5863,8 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg) assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a); } - else if (_PyObject_IsUniquelyReferenced((PyObject *)b) && - size_a <= alloc_b) { + else if (_PyObject_IsUniquelyReferenced((PyObject *)b) + && size_a <= alloc_b) { d = (PyLongObject*)Py_NewRef(b); assert(size_a >= 0); _PyLong_SetSignAndDigitCount(d, 1, size_a);