From 818628c2da99ba0376313971816d472c65c9a9fc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 1 Jul 2021 02:30:46 +0200 Subject: [PATCH] bpo-44531: Add _PyType_AllocNoTrack() function (GH-26947) Add an internal _PyType_AllocNoTrack() function to allocate an object without tracking it in the GC. Modify dict_new() to use _PyType_AllocNoTrack(): dict subclasses are now only tracked once all PyDictObject members are initialized. Calling _PyObject_GC_UNTRACK() is no longer needed for the dict type. Similar change in tuple_subtype_new() for tuple subclasses. Replace tuple_gc_track() with _PyObject_GC_TRACK(). --- Include/internal/pycore_object.h | 2 ++ Objects/dictobject.c | 30 +++++++++++++++++---------- Objects/tupleobject.c | 35 +++++++++++++++++--------------- Objects/typeobject.c | 12 ++++++++++- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 9dfc8c62bab..4091f5178ee 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -178,6 +178,8 @@ extern int _Py_CheckSlotResult( // See also the Py_TPFLAGS_READY flag. #define _PyType_IsReady(type) ((type)->tp_dict != NULL) +extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems); + #ifdef __cplusplus } #endif diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 3a1dbc994b4..7f1d38dd5f4 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3324,19 +3324,16 @@ static PyNumberMethods dict_as_number = { static PyObject * dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - PyObject *self; - PyDictObject *d; + assert(type != NULL); + assert(type->tp_alloc != NULL); + // dict subclasses must implement the GC protocol + assert(_PyType_IS_GC(type)); - assert(type != NULL && type->tp_alloc != NULL); - self = type->tp_alloc(type, 0); - if (self == NULL) + PyObject *self = type->tp_alloc(type, 0); + if (self == NULL) { return NULL; - d = (PyDictObject *)self; - - /* The object has been implicitly tracked by tp_alloc */ - if (type == &PyDict_Type) { - _PyObject_GC_UNTRACK(d); } + PyDictObject *d = (PyDictObject *)self; d->ma_used = 0; d->ma_version_tag = DICT_NEXT_VERSION(); @@ -3344,6 +3341,17 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) d->ma_keys = Py_EMPTY_KEYS; d->ma_values = empty_values; ASSERT_CONSISTENT(d); + + if (type != &PyDict_Type) { + // Don't track if a subclass tp_alloc is PyType_GenericAlloc() + if (!_PyObject_GC_IS_TRACKED(d)) { + _PyObject_GC_TRACK(d); + } + } + else { + // _PyType_AllocNoTrack() does not track the created object + assert(!_PyObject_GC_IS_TRACKED(d)); + } return self; } @@ -3441,7 +3449,7 @@ PyTypeObject PyDict_Type = { 0, /* tp_descr_set */ 0, /* tp_dictoffset */ dict_init, /* tp_init */ - PyType_GenericAlloc, /* tp_alloc */ + _PyType_AllocNoTrack, /* tp_alloc */ dict_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ .tp_vectorcall = dict_vectorcall, diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 6b1ab740121..b7fd421196d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -25,13 +25,6 @@ get_tuple_state(void) #endif -static inline void -tuple_gc_track(PyTupleObject *op) -{ - _PyObject_GC_TRACK(op); -} - - /* Print summary info about the state of the optimized allocator */ void _PyTuple_DebugMallocStats(FILE *out) @@ -48,10 +41,12 @@ _PyTuple_DebugMallocStats(FILE *out) #endif } -/* Allocate an uninitialized tuple object. Before making it public following +/* Allocate an uninitialized tuple object. Before making it public, following steps must be done: - - initialize its items - - call tuple_gc_track() on it + + - Initialize its items. + - Call _PyObject_GC_TRACK() on it. + Because the empty tuple is always reused and it's already tracked by GC, this function must not be called with size == 0 (unless from PyTuple_New() which wraps this function). @@ -161,7 +156,7 @@ PyTuple_New(Py_ssize_t size) for (Py_ssize_t i = 0; i < size; i++) { op->ob_item[i] = NULL; } - tuple_gc_track(op); + _PyObject_GC_TRACK(op); return (PyObject *) op; } @@ -257,7 +252,7 @@ PyTuple_Pack(Py_ssize_t n, ...) items[i] = o; } va_end(vargs); - tuple_gc_track(result); + _PyObject_GC_TRACK(result); return (PyObject *)result; } @@ -473,7 +468,7 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) Py_INCREF(item); dst[i] = item; } - tuple_gc_track(tuple); + _PyObject_GC_TRACK(tuple); return (PyObject *)tuple; } @@ -551,7 +546,7 @@ tupleconcat(PyTupleObject *a, PyObject *bb) Py_INCREF(v); dest[i] = v; } - tuple_gc_track(np); + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -588,7 +583,7 @@ tuplerepeat(PyTupleObject *a, Py_ssize_t n) p++; } } - tuple_gc_track(np); + _PyObject_GC_TRACK(np); return (PyObject *) np; } @@ -783,6 +778,9 @@ tuple_subtype_new(PyTypeObject *type, PyObject *iterable) Py_ssize_t i, n; assert(PyType_IsSubtype(type, &PyTuple_Type)); + // tuple subclasses must implement the GC protocol + assert(_PyType_IS_GC(type)); + tmp = tuple_new_impl(&PyTuple_Type, iterable); if (tmp == NULL) return NULL; @@ -798,6 +796,11 @@ tuple_subtype_new(PyTypeObject *type, PyObject *iterable) PyTuple_SET_ITEM(newobj, i, item); } Py_DECREF(tmp); + + // Don't track if a subclass tp_alloc is PyType_GenericAlloc() + if (!_PyObject_GC_IS_TRACKED(newobj)) { + _PyObject_GC_TRACK(newobj); + } return newobj; } @@ -857,7 +860,7 @@ tuplesubscript(PyTupleObject* self, PyObject* item) dest[i] = it; } - tuple_gc_track(result); + _PyObject_GC_TRACK(result); return (PyObject *)result; } } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8ee4e813ee5..116ac14cbc2 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1164,7 +1164,7 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) } PyObject * -PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems) +_PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems) { PyObject *obj; const size_t size = _PyObject_VAR_SIZE(type, nitems+1); @@ -1189,6 +1189,16 @@ PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems) else { _PyObject_InitVar((PyVarObject *)obj, type, nitems); } + return obj; +} + +PyObject * +PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems) +{ + PyObject *obj = _PyType_AllocNoTrack(type, nitems); + if (obj == NULL) { + return NULL; + } if (_PyType_IS_GC(type)) { _PyObject_GC_TRACK(obj);