static inline Py_ssize_t
_PyDict_UniqueId(PyDictObject *mp)
{
- // Offset by one so that _ma_watcher_tag=0 represents an unassigned id
- return (Py_ssize_t)(mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) - 1;
+ return (Py_ssize_t)(mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT);
}
static inline void
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- // Unsigned comparison so that `unique_id=-1`, which indicates that
- // per-thread refcounting has been disabled on this object, is handled by
- // the "else".
- if ((size_t)unique_id < (size_t)tstate->refcounts.size) {
+ // The table index is `unique_id - 1` because 0 is not a valid unique id.
+ // Unsigned comparison so that `idx=-1` is handled by the "else".
+ size_t idx = (size_t)(unique_id - 1);
+ if (idx < (size_t)tstate->refcounts.size) {
# ifdef Py_REF_DEBUG
_Py_INCREF_IncRefTotal();
# endif
_Py_INCREF_STAT_INC();
- tstate->refcounts.values[unique_id]++;
+ tstate->refcounts.values[idx]++;
}
else {
// The slow path resizes the per-thread refcount array if necessary.
- // It handles the unique_id=-1 case to keep the inlinable function smaller.
- _PyObject_ThreadIncrefSlow(obj, unique_id);
+ // It handles the unique_id=0 case to keep the inlinable function smaller.
+ _PyObject_ThreadIncrefSlow(obj, idx);
}
}
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- // Unsigned comparison so that `unique_id=-1`, which indicates that
- // per-thread refcounting has been disabled on this object, is handled by
- // the "else".
- if ((size_t)unique_id < (size_t)tstate->refcounts.size) {
+ // The table index is `unique_id - 1` because 0 is not a valid unique id.
+ // Unsigned comparison so that `idx=-1` is handled by the "else".
+ size_t idx = (size_t)(unique_id - 1);
+ if (idx < (size_t)tstate->refcounts.size) {
# ifdef Py_REF_DEBUG
_Py_DECREF_DecRefTotal();
# endif
_Py_DECREF_STAT_INC();
- tstate->refcounts.values[unique_id]--;
+ tstate->refcounts.values[idx]--;
}
else {
// Directly decref the object if the id is not assigned or if
// Per-thread reference counting is used along with deferred reference
// counting to avoid scaling bottlenecks due to reference count contention.
//
-// An id of -1 is used to indicate that an object doesn't use per-thread
+// An id of 0 is used to indicate that an object doesn't use per-thread
// refcounting. This value is used when the object is finalized by the GC
// and during interpreter shutdown to allow the object to be
// deallocated promptly when the object's refcount reaches zero.
Py_ssize_t size;
};
+#define _Py_INVALID_UNIQUE_ID 0
+
// Assigns the next id from the pool of ids.
extern Py_ssize_t _PyObject_AssignUniqueId(PyObject *obj);
extern void _PyObject_FinalizeUniqueIdPool(PyInterpreterState *interp);
// Increfs the object, resizing the thread-local refcount array if necessary.
-PyAPI_FUNC(void) _PyObject_ThreadIncrefSlow(PyObject *obj, Py_ssize_t unique_id);
+PyAPI_FUNC(void) _PyObject_ThreadIncrefSlow(PyObject *obj, size_t idx);
#endif /* Py_GIL_DISABLED */
Base.value = 3
type_freeze(FreezeThis)
self.assertEqual(FreezeThis.value, 2)
+
+ def test_manual_heap_type(self):
+ # gh-128923: test that a manually allocated and initailized heap type
+ # works correctly
+ ManualHeapType = _testcapi.ManualHeapType
+ for i in range(100):
+ self.assertIsInstance(ManualHeapType(), ManualHeapType)
.tp_new = ContainerNoGC_new,
};
+/* Manually allocated heap type */
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *dict;
+} ManualHeapType;
+
+static int
+ManualHeapType_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ ManualHeapType *mht = (ManualHeapType *)self;
+ Py_VISIT(mht->dict);
+ return 0;
+}
+
+static void
+ManualHeapType_dealloc(PyObject *self)
+{
+ ManualHeapType *mht = (ManualHeapType *)self;
+ PyObject_GC_UnTrack(self);
+ Py_XDECREF(mht->dict);
+ PyTypeObject *type = Py_TYPE(self);
+ Py_TYPE(self)->tp_free(self);
+ Py_DECREF(type);
+}
+
+static PyObject *
+create_manual_heap_type(void)
+{
+ // gh-128923: Ensure that a heap type allocated through PyType_Type.tp_alloc
+ // with minimal initialization works correctly.
+ PyHeapTypeObject *heap_type = (PyHeapTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0);
+ if (heap_type == NULL) {
+ return NULL;
+ }
+ PyTypeObject* type = &heap_type->ht_type;
+ type->tp_basicsize = sizeof(ManualHeapType);
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC;
+ type->tp_new = PyType_GenericNew;
+ type->tp_name = "ManualHeapType";
+ type->tp_dictoffset = offsetof(ManualHeapType, dict);
+ type->tp_traverse = ManualHeapType_traverse;
+ type->tp_dealloc = ManualHeapType_dealloc;
+ heap_type->ht_name = PyUnicode_FromString(type->tp_name);
+ if (!heap_type->ht_name) {
+ Py_DECREF(type);
+ return NULL;
+ }
+ heap_type->ht_qualname = Py_NewRef(heap_type->ht_name);
+ if (PyType_Ready(type) < 0) {
+ Py_DECREF(type);
+ return NULL;
+ }
+ return (PyObject *)type;
+}
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
(PyObject *) &ContainerNoGC_type) < 0)
return NULL;
+ PyObject *manual_heap_type = create_manual_heap_type();
+ if (manual_heap_type == NULL) {
+ return NULL;
+ }
+ if (PyModule_Add(m, "ManualHeapType", manual_heap_type) < 0) {
+ return NULL;
+ }
+
+
/* Include tests from the _testcapi/ directory */
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
return NULL;
Py_XDECREF(co->co_linetable);
Py_XDECREF(co->co_exceptiontable);
#ifdef Py_GIL_DISABLED
- assert(co->_co_unique_id == -1);
+ assert(co->_co_unique_id == _Py_INVALID_UNIQUE_ID);
#endif
if (co->_co_cached != NULL) {
Py_XDECREF(co->_co_cached->_co_code);
assert(PyDict_Check(op));
#ifdef Py_GIL_DISABLED
Py_ssize_t id = _PyObject_AssignUniqueId(op);
+ if (id == _Py_INVALID_UNIQUE_ID) {
+ return;
+ }
if ((uint64_t)id >= (uint64_t)DICT_UNIQUE_ID_MAX) {
_PyObject_ReleaseUniqueId(id);
return;
PyDictObject *mp = (PyDictObject *)op;
assert((mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) == 0);
- // Plus 1 so that _ma_watcher_tag=0 represents an unassigned id
- mp->_ma_watcher_tag += ((uint64_t)id + 1) << DICT_UNIQUE_ID_SHIFT;
+ mp->_ma_watcher_tag += (uint64_t)id << DICT_UNIQUE_ID_SHIFT;
#endif
}
Py_XDECREF(et->ht_module);
PyMem_Free(et->_ht_tpname);
#ifdef Py_GIL_DISABLED
- assert(et->unique_id == -1);
+ assert(et->unique_id == _Py_INVALID_UNIQUE_ID);
#endif
et->ht_token = NULL;
Py_TYPE(type)->tp_free((PyObject *)type);
if (pool->freelist == NULL) {
if (resize_interp_type_id_pool(pool) < 0) {
UNLOCK_POOL(pool);
- return -1;
+ return _Py_INVALID_UNIQUE_ID;
}
}
pool->freelist = entry->next;
entry->obj = obj;
_PyObject_SetDeferredRefcount(obj);
- Py_ssize_t unique_id = (entry - pool->table);
+ // The unique id is one plus the index of the entry in the table.
+ Py_ssize_t unique_id = (entry - pool->table) + 1;
+ assert(unique_id > 0);
UNLOCK_POOL(pool);
return unique_id;
}
struct _Py_unique_id_pool *pool = &interp->unique_ids;
LOCK_POOL(pool);
- assert(unique_id >= 0 && unique_id < pool->size);
- _Py_unique_id_entry *entry = &pool->table[unique_id];
+ assert(unique_id > 0 && unique_id <= pool->size);
+ Py_ssize_t idx = unique_id - 1;
+ _Py_unique_id_entry *entry = &pool->table[idx];
entry->next = pool->freelist;
pool->freelist = entry;
UNLOCK_POOL(pool);
static Py_ssize_t
clear_unique_id(PyObject *obj)
{
- Py_ssize_t id = -1;
+ Py_ssize_t id = _Py_INVALID_UNIQUE_ID;
if (PyType_Check(obj)) {
if (PyType_HasFeature((PyTypeObject *)obj, Py_TPFLAGS_HEAPTYPE)) {
PyHeapTypeObject *ht = (PyHeapTypeObject *)obj;
id = ht->unique_id;
- ht->unique_id = -1;
+ ht->unique_id = _Py_INVALID_UNIQUE_ID;
}
}
else if (PyCode_Check(obj)) {
PyCodeObject *co = (PyCodeObject *)obj;
id = co->_co_unique_id;
- co->_co_unique_id = -1;
+ co->_co_unique_id = _Py_INVALID_UNIQUE_ID;
}
else if (PyDict_Check(obj)) {
PyDictObject *mp = (PyDictObject *)obj;
_PyObject_DisablePerThreadRefcounting(PyObject *obj)
{
Py_ssize_t id = clear_unique_id(obj);
- if (id >= 0) {
+ if (id != _Py_INVALID_UNIQUE_ID) {
_PyObject_ReleaseUniqueId(id);
}
}
void
-_PyObject_ThreadIncrefSlow(PyObject *obj, Py_ssize_t unique_id)
+_PyObject_ThreadIncrefSlow(PyObject *obj, size_t idx)
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- if (unique_id < 0 || resize_local_refcounts(tstate) < 0) {
+ if (((Py_ssize_t)idx) < 0 || resize_local_refcounts(tstate) < 0) {
// just incref the object directly.
Py_INCREF(obj);
return;
}
- assert(unique_id < tstate->refcounts.size);
- tstate->refcounts.values[unique_id]++;
+ assert(idx < (size_t)tstate->refcounts.size);
+ tstate->refcounts.values[idx]++;
#ifdef Py_REF_DEBUG
_Py_IncRefTotal((PyThreadState *)tstate);
#endif
if (obj != NULL) {
Py_ssize_t id = clear_unique_id(obj);
(void)id;
- assert(id == i);
+ assert(id == i + 1);
}
}
PyMem_Free(pool->table);