From eb0d23d71d6a2266de912d2bfac3fbec1ba6127b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:13:21 +0900 Subject: [PATCH 01/86] add document --- Doc/c-api/type.rst | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 0cae5c09505ebe..dd57d3b5339529 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -273,6 +273,55 @@ Type Objects .. versionadded:: 3.12 +.. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) + + Find a class whose token is valid and equal to the given one, + from the type and superclasses. + + * If found, set *\*result* to a new :term:`strong reference` + to the first type and return ``1``. + * If not found, set *\*result* to ``NULL`` and return ``0``. + * On error, set *\*result* to ``NULL`` and return ``-1`` with an exception. + * The ``result`` argument accepts ``NULL`` if you need only the return value. + + The token is a memory layout ID to identify the class. + You can store the preferred one in a heap type through + :c:func:`PyType_FromMetaclass()`, if you know that: + + * The pointer outlives the class, so it's not reused for something else + while the class exists. + * It "belongs" to the extension module where the class lives, so it will not + clash with other extensions. + + For the entry, enable the ``Py_tp_token`` slot:: + + PyType_Slot foo_slots[] = { + ... + {Py_tp_token, &pointee_in_the_module}, + } + + The slot accepts ``NULL`` **via** the ``Py_TP_USE_SPEC`` identifier, + with which a heap type holds the ``spec`` pointer passed to + :c:func:`PyType_FromMetaclass()`:: + + // Be careful when the spec is dynamically created + {Py_tp_token, Py_TP_USE_SPEC}, + + To disable the feature, remove the slot. + + .. versionadded:: 3.14 + +.. c:function:: int PyType_GetToken(PyTypeObject *type, void **result) + + Retrieve the token stored in the type. See :c:func:`PyType_GetBaseByToken()` + for the entry. + + * On error, set *\*result* to ``NULL``, set an exception, return ``-1``. + * If there's no token: set *\*result* to ``NULL``, return ``0``. + * Otherwise: set *\*result* to the non-NULL token, return ``1``. + + .. versionadded:: 3.14 + Creating Heap-Allocated Types ............................. From 273bac1a7e5a001305d8ec1728204dca18d53140 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:16:19 +0900 Subject: [PATCH 02/86] introduce Py_tp_token and ht_token --- Include/cpython/object.h | 1 + Include/typeslots.h | 5 +++++ Lib/test/test_sys.py | 2 +- Objects/typeobject.c | 7 +++++++ Objects/typeslots.inc | 1 + Objects/typeslots.py | 4 +++- 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 90cd7b54b34161..c3697399fb709b 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -268,6 +268,7 @@ typedef struct _heaptypeobject { PyObject *ht_name, *ht_slots, *ht_qualname; struct _dictkeysobject *ht_cached_keys; PyObject *ht_module; + void *ht_token; char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec struct _specialization_cache _spec_cache; // For use by the specializer. /* here are optional user slots, followed by the members. */ diff --git a/Include/typeslots.h b/Include/typeslots.h index 506b05580de146..e6701100e198ac 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -86,3 +86,8 @@ /* New in 3.10 */ #define Py_am_send 81 #endif +#if !defined(Py_LIMITED_API) +/* New in 3.14 */ +#define Py_tp_token 82 +#define Py_TP_USE_SPEC NULL +#endif diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 69dccf9a9322f6..7613724f2e7298 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1716,7 +1716,7 @@ def delx(self): del self.__x '3P' # PyMappingMethods '10P' # PySequenceMethods '2P' # PyBufferProcs - '6P' + '7P' '1PIP' # Specializer cache ) class newstyleclass(object): pass diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 51a7adf5c40503..a7d92094237d37 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3909,6 +3909,7 @@ type_new_alloc(type_new_ctx *ctx) et->ht_name = Py_NewRef(ctx->name); et->ht_module = NULL; et->_ht_tpname = NULL; + et->ht_token = NULL; _PyObject_SetDeferredRefcount((PyObject *)et); @@ -4931,6 +4932,11 @@ _PyType_FromMetaclass_impl( } } break; + case Py_tp_token: + { + res->ht_token = slot->pfunc ? slot->pfunc : spec; + } + break; default: { /* Copy other slots directly */ @@ -5927,6 +5933,7 @@ type_dealloc(PyObject *self) } Py_XDECREF(et->ht_module); PyMem_Free(et->_ht_tpname); + et->ht_token = NULL; Py_TYPE(type)->tp_free((PyObject *)type); } diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc index 896daa7d8066b7..f8f80380470433 100644 --- a/Objects/typeslots.inc +++ b/Objects/typeslots.inc @@ -80,3 +80,4 @@ {offsetof(PyAsyncMethods, am_anext), offsetof(PyTypeObject, tp_as_async)}, {-1, offsetof(PyTypeObject, tp_finalize)}, {offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)}, +{-1, offsetof(PyHeapTypeObject, ht_token)}, diff --git a/Objects/typeslots.py b/Objects/typeslots.py index 8ab05f91be12b0..a21af71cbf4ecd 100755 --- a/Objects/typeslots.py +++ b/Objects/typeslots.py @@ -13,7 +13,9 @@ def generate_typeslots(out=sys.stdout): continue member = m.group(1) - if member.startswith("tp_"): + if member == "tp_token": + member = '{-1, offsetof(PyHeapTypeObject, ht_token)}' + elif member.startswith("tp_"): member = f'{{-1, offsetof(PyTypeObject, {member})}}' elif member.startswith("am_"): member = (f'{{offsetof(PyAsyncMethods, {member}),'+ From d0411670c5dd0f17301612eef4391dc86a553792 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:17:31 +0900 Subject: [PATCH 03/86] fix PyType_GetSlot() --- Modules/_testcapimodule.c | 5 +++++ Objects/typeobject.c | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 5ebcfef6143e02..14d1ef97e46bc9 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -582,6 +582,11 @@ test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) return NULL; } + if (PyType_GetSlot(&PyLong_Type, Py_tp_token) != NULL) { + PyErr_SetString(PyExc_AssertionError, "slot offset >= sizeof(PyTypeObject)"); + return NULL; + } + Py_RETURN_NONE; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a7d92094237d37..dd5c02c79114ee 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5099,8 +5099,16 @@ PyType_GetSlot(PyTypeObject *type, int slot) PyErr_BadInternalCall(); return NULL; } + int slot_offset = pyslot_offsets[slot].slot_offset; - parent_slot = *(void**)((char*)type + pyslot_offsets[slot].slot_offset); + if (slot_offset >= sizeof(PyTypeObject)) { + assert(slot == Py_tp_token); // REMOVE ME LATER + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + return NULL; + } + } + + parent_slot = *(void**)((char*)type + slot_offset); if (parent_slot == NULL) { return NULL; } From d41ceade10f52209b30430068aa1d2790dea2aa8 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:18:39 +0900 Subject: [PATCH 04/86] add PyType_GetBaseByToken() PyType_GetToken() --- Include/cpython/object.h | 2 + Objects/typeobject.c | 88 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index c3697399fb709b..58a4cb4ca18c77 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -278,6 +278,8 @@ PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *); PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyType_LookupRef(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *); +PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); +PyAPI_FUNC(int) PyType_GetToken(PyTypeObject *, void **); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(void) _Py_BreakPoint(void); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index dd5c02c79114ee..2e1fe2db08b574 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5237,6 +5237,94 @@ _PyType_GetModuleByDef2(PyTypeObject *left, PyTypeObject *right, return module; } + +static PyTypeObject * +get_base_by_token_recursive(PyTypeObject *type, void *token, int initial) +{ + if (initial) { + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + return NULL; + } + if (((PyHeapTypeObject*)type)->ht_token == token) { + return type; + } + } + PyObject *bases = type->tp_bases; + Py_ssize_t n = PyTuple_GET_SIZE(bases); + for (Py_ssize_t i = 0; i < n; i++) { + PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i); + if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { + continue; + } + if (((PyHeapTypeObject*)base)->ht_token == token) { + return base; + } + base = get_base_by_token_recursive(base, token, 0); + if (base) { + return base; + } + } + return NULL; +} + +int +PyType_GetBaseByToken(PyTypeObject *type, void *token, + PyTypeObject **result) +{ + if (result != NULL) { + *result = NULL; + } + if (token == NULL) { + // We scan only heaptypes that contains a token + return 0; + } + assert(PyType_Check(type)); + PyObject *mro = lookup_tp_mro(type); + if (mro == NULL) { + PyTypeObject *base = get_base_by_token_recursive(type, token, 1); + if (base == NULL) { + return 0; + } + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); + } + return 1; + } + assert(PyTuple_Check(mro)); + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 0; i < n; i++) { + PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); + if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { + if (i) { + continue; + } + // Static type MRO contains no heap type, + // which type_ready_mro() ensures. + assert(base == type); + return 0; + } + if (((PyHeapTypeObject*)base)->ht_token == token) { + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); + } + return 1; + } + } + return 0; +} + +int +PyType_GetToken(PyTypeObject *type, void **result) +{ + assert(PyType_Check(type)); + if(_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + *result = ((PyHeapTypeObject*)type)->ht_token; + return *result ? 1 : 0; + } + *result = NULL; + return 0; +} + void * PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls) { From 1456baaef30bea6c6955e159b7888269e8c4d79f Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:19:21 +0900 Subject: [PATCH 05/86] add test --- Lib/test/test_capi/test_misc.py | 51 +++++++++++++++++++ Modules/_testcapi/heaptype.c | 87 +++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 9de97c0c2c776a..0d32c1642174d1 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1151,6 +1151,57 @@ class MyType: MyType.__module__ = 123 self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + def test_get_base_by_token(self): + def getbase(src, key): + found_in_mro = find_first_type(src, key, True) + found_in_bases = find_first_type(src, key, False) + return found_in_mro, found_in_bases + + create_type = _testcapi.create_type_with_token + get_token = _testcapi.pytype_gettoken + find_first_type = _testcapi.pytype_getbasebytoken + Py_TP_USE_SPEC = 0 + + A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC) + self.assertTrue(get_token(A1) != Py_TP_USE_SPEC) + + B1 = create_type('_testcapi.B1', id(self)) + self.assertTrue(get_token(B1) == id(self)) + + tokenA1 = get_token(A1) + # match exactly + found1, found2 = getbase(A1, tokenA1) + self.assertIs(found1, A1) + self.assertIs(found1, found2) + + # no token in static types + STATIC = type(1) + self.assertEqual(get_token(STATIC), 0) + + # no token in pure subtypes + class A2(A1): pass + self.assertEqual(get_token(A2), 0) + + # find A1 + class Z(STATIC, B1, A2): pass + found1, found2 = getbase(Z, tokenA1) + self.assertIs(found1, A1) + self.assertIs(found1, found2) + + # NULL finds nothing + found1, found2 = getbase(Z, 0) + self.assertIs(found1, None) + self.assertIs(found1, found2) + + # share the token with A1 + B1 = create_type('_testcapi.B1', tokenA1) + self.assertTrue(get_token(B1) == tokenA1) + + # find first B1 by shared token + class Z(B1, A1): pass + found1, found2 = getbase(Z, tokenA1) + self.assertIs(found1, B1) + self.assertIs(found1, found2) def test_gen_get_code(self): def genf(): yield diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 4526583a8059d9..7c1232e92ddcc0 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -410,6 +410,90 @@ pyobject_getitemdata(PyObject *self, PyObject *o) } +static PyObject * +create_type_with_token(PyObject *self, PyObject *args) +{ + assert(Py_TP_USE_SPEC == NULL); + const char *name; + PyObject *py_token; + if (!PyArg_ParseTuple(args, "sO", &name, &py_token)) { + return NULL; + } + + void *token = PyLong_AsVoidPtr(py_token); + PyType_Slot slots[] = { + {Py_tp_token, token}, + {0}, + }; + PyType_Spec spec = { + .name = name, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .slots = slots, + }; + PyTypeObject *type = (PyTypeObject *)PyType_FromSpec(&spec); + if (!type) { + return NULL; + } + if (token == Py_TP_USE_SPEC && PyType_GetSlot(type, Py_tp_token) != &spec) { + PyErr_SetString(PyExc_AssertionError, + "failed to convert token from Py_TP_USE_SPEC"); + Py_DECREF(type); + return NULL; + } + return (PyObject *)type; +} + +static PyObject * +pytype_gettoken(PyObject *self, PyObject *type) +{ + void *token; + if (PyType_GetToken((PyTypeObject *)type, &token) < 0) { + return NULL; + } + if (token != PyType_GetSlot((PyTypeObject *)type, Py_tp_token)) { + PyErr_SetString(PyExc_AssertionError, + "PyType_GetSlot returned different token from GetToken"); + return NULL; + } + return PyLong_FromVoidPtr(token); +} + +static PyObject * +pytype_getbasebytoken(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *py_token, *use_mro; + if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &use_mro)) { + return NULL; + } + assert(PyType_Check(type)); + + PyObject *mro_save = NULL; + if (use_mro != Py_True) { + mro_save = type->tp_mro; + type->tp_mro = NULL; + } + + void *token = PyLong_AsVoidPtr(py_token); + PyTypeObject *result; + int ret = PyType_GetBaseByToken(type, token, &result); + + if (mro_save != NULL) { + type->tp_mro = mro_save; + } + + if (ret < 0) { + return NULL; + } + if (result == NULL) { + assert(ret == 0); + Py_RETURN_NONE; + } + assert(ret == 1); + return (PyObject *)result; +} + + static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, {"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS}, @@ -423,6 +507,9 @@ static PyMethodDef TestMethods[] = { {"make_immutable_type_with_base", make_immutable_type_with_base, METH_O}, {"make_type_with_base", make_type_with_base, METH_O}, {"pyobject_getitemdata", pyobject_getitemdata, METH_O}, + {"create_type_with_token", create_type_with_token, METH_VARARGS}, + {"pytype_gettoken", pytype_gettoken, METH_O}, + {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, {NULL}, }; From 14b41036573a26279056fc4c9705bef5516015c6 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:19:52 +0900 Subject: [PATCH 06/86] add ctypes example --- Modules/_ctypes/_ctypes.c | 9 +++++---- Modules/_ctypes/ctypes.h | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 222700b4b7cc69..19d5ba1d8a6246 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -454,7 +454,7 @@ class _ctypes.CType_Type "PyObject *" "clinic_state()->CType_Type" static int CType_Type_traverse(PyObject *self, visitproc visit, void *arg) { - StgInfo *info = _PyStgInfo_FromType_NoState(self); + StgInfo *info = _PyStgInfo_FromType_NoState2(self); if (!info) { PyErr_WriteUnraisable(self); } @@ -485,7 +485,7 @@ ctype_clear_stginfo(StgInfo *info) static int CType_Type_clear(PyObject *self) { - StgInfo *info = _PyStgInfo_FromType_NoState(self); + StgInfo *info = _PyStgInfo_FromType_NoState2(self); if (!info) { PyErr_WriteUnraisable(self); } @@ -498,7 +498,7 @@ CType_Type_clear(PyObject *self) static void CType_Type_dealloc(PyObject *self) { - StgInfo *info = _PyStgInfo_FromType_NoState(self); + StgInfo *info = _PyStgInfo_FromType_NoState2(self); if (!info) { PyErr_WriteUnraisable(self); } @@ -564,12 +564,13 @@ static PyType_Slot ctype_type_slots[] = { {Py_tp_clear, CType_Type_clear}, {Py_tp_dealloc, CType_Type_dealloc}, {Py_tp_methods, ctype_methods}, + {Py_tp_token, Py_TP_USE_SPEC}, // Sequence protocol. {Py_sq_repeat, CType_Type_repeat}, {0, NULL}, }; -static PyType_Spec pyctype_type_spec = { +PyType_Spec pyctype_type_spec = { .name = "_ctypes.CType_Type", .basicsize = -(Py_ssize_t)sizeof(StgInfo), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 2d711dabab6c77..f686d5e334eb2c 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -102,6 +102,7 @@ get_module_state_by_def(PyTypeObject *cls) } +extern PyType_Spec pyctype_type_spec; extern PyType_Spec carg_spec; extern PyType_Spec cfield_spec; extern PyType_Spec cthunk_spec; @@ -507,6 +508,23 @@ _PyStgInfo_FromType_NoState(PyObject *type) return (StgInfo *)((char *)type + type_basicsize); } +static inline StgInfo * +_PyStgInfo_FromType_NoState2(PyObject *type) +{ + PyTypeObject *PyCType_Type; + if (PyType_GetBaseByToken(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0) { + return NULL; + } + if (PyCType_Type == NULL) { + PyErr_SetString(PyExc_TypeError, "PyCType_Type expected"); + return NULL; + } + StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); + Py_DECREF(PyCType_Type); + assert(info == _PyStgInfo_FromType_NoState(type)); + return info; +} + // Initialize StgInfo on a newly created type static inline StgInfo * PyStgInfo_Init(ctypes_state *state, PyTypeObject *type) From 7af3378020051c449fa76094e5005805b2fd6c46 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:22:08 +0900 Subject: [PATCH 07/86] use tp_mro directly (like PyType_IsSubtype) for free-threading err --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 2e1fe2db08b574..37394e0fc51539 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5279,7 +5279,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, return 0; } assert(PyType_Check(type)); - PyObject *mro = lookup_tp_mro(type); + PyObject *mro = type->tp_mro; if (mro == NULL) { PyTypeObject *base = get_base_by_token_recursive(type, token, 1); if (base == NULL) { From 0b8bd0765c8f726bed8e66132d9f2c0a750c0f4d Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:35:39 +0900 Subject: [PATCH 08/86] silence warning --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 37394e0fc51539..4b59ff5ea38fd9 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5101,7 +5101,7 @@ PyType_GetSlot(PyTypeObject *type, int slot) } int slot_offset = pyslot_offsets[slot].slot_offset; - if (slot_offset >= sizeof(PyTypeObject)) { + if (slot_offset >= (int)sizeof(PyTypeObject)) { assert(slot == Py_tp_token); // REMOVE ME LATER if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { return NULL; From f917ad93b502522963473e6038b8b5b406b9bea0 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:33:09 +0900 Subject: [PATCH 09/86] edit testcase --- Lib/test/test_capi/test_misc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 0d32c1642174d1..47f74ac9512da9 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1177,10 +1177,16 @@ def getbase(src, key): # no token in static types STATIC = type(1) self.assertEqual(get_token(STATIC), 0) + found1, found2 = getbase(STATIC, tokenA1) + self.assertIs(found1, None) + self.assertIs(found1, found2) # no token in pure subtypes class A2(A1): pass self.assertEqual(get_token(A2), 0) + found1, found2 = getbase(A2, tokenA1) + self.assertIs(found1, A1) + self.assertIs(found1, found2) # find A1 class Z(STATIC, B1, A2): pass From e08d58f4cfc03378ac03c310c9bc4328471e7d3d Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:34:42 +0900 Subject: [PATCH 10/86] remove an assertion --- Objects/typeobject.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4b59ff5ea38fd9..29ecabf3a6611a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5101,8 +5101,7 @@ PyType_GetSlot(PyTypeObject *type, int slot) } int slot_offset = pyslot_offsets[slot].slot_offset; - if (slot_offset >= (int)sizeof(PyTypeObject)) { - assert(slot == Py_tp_token); // REMOVE ME LATER + if (slot_offset >= sizeof(PyTypeObject)) { if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { return NULL; } From 22c20d97ded5cdc7d398dd161eaab8635c824487 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:42:30 +0900 Subject: [PATCH 11/86] add missing cast --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 29ecabf3a6611a..3a8e7b83c7edd3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5101,7 +5101,7 @@ PyType_GetSlot(PyTypeObject *type, int slot) } int slot_offset = pyslot_offsets[slot].slot_offset; - if (slot_offset >= sizeof(PyTypeObject)) { + if ((Py_ssize_t)slot_offset >= sizeof(PyTypeObject)) { if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { return NULL; } From 7fa0d81b4604b38c90d418eae8f10a8070e3f223 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:48:56 +0900 Subject: [PATCH 12/86] cast again --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3a8e7b83c7edd3..e3e031b57cebcb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5101,7 +5101,7 @@ PyType_GetSlot(PyTypeObject *type, int slot) } int slot_offset = pyslot_offsets[slot].slot_offset; - if ((Py_ssize_t)slot_offset >= sizeof(PyTypeObject)) { + if (slot_offset >= (int)sizeof(PyTypeObject)) { if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { return NULL; } From 5621704b448aa57f61f90e6fcb5e634e211633b6 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:30:24 +0900 Subject: [PATCH 13/86] edit testcase --- Lib/test/test_capi/test_misc.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 47f74ac9512da9..8ca87b98f046b3 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1152,9 +1152,11 @@ class MyType: self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') def test_get_base_by_token(self): - def getbase(src, key): + def getbase(src, key, comparable=True): found_in_mro = find_first_type(src, key, True) found_in_bases = find_first_type(src, key, False) + if comparable: + self.assertIs(found_in_mro, found_in_bases) return found_in_mro, found_in_bases create_type = _testcapi.create_type_with_token @@ -1172,32 +1174,27 @@ def getbase(src, key): # match exactly found1, found2 = getbase(A1, tokenA1) self.assertIs(found1, A1) - self.assertIs(found1, found2) # no token in static types STATIC = type(1) self.assertEqual(get_token(STATIC), 0) found1, found2 = getbase(STATIC, tokenA1) self.assertIs(found1, None) - self.assertIs(found1, found2) # no token in pure subtypes class A2(A1): pass self.assertEqual(get_token(A2), 0) found1, found2 = getbase(A2, tokenA1) self.assertIs(found1, A1) - self.assertIs(found1, found2) # find A1 class Z(STATIC, B1, A2): pass found1, found2 = getbase(Z, tokenA1) self.assertIs(found1, A1) - self.assertIs(found1, found2) # NULL finds nothing found1, found2 = getbase(Z, 0) self.assertIs(found1, None) - self.assertIs(found1, found2) # share the token with A1 B1 = create_type('_testcapi.B1', tokenA1) @@ -1207,7 +1204,6 @@ class Z(STATIC, B1, A2): pass class Z(B1, A1): pass found1, found2 = getbase(Z, tokenA1) self.assertIs(found1, B1) - self.assertIs(found1, found2) def test_gen_get_code(self): def genf(): yield From 492a7d50b8cf83315799c60ce901a5a40696701e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:44:40 +0900 Subject: [PATCH 14/86] remove redundant testcase --- Lib/test/test_capi/test_misc.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 8ca87b98f046b3..65c1b90042efcb 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1184,9 +1184,6 @@ def getbase(src, key, comparable=True): # no token in pure subtypes class A2(A1): pass self.assertEqual(get_token(A2), 0) - found1, found2 = getbase(A2, tokenA1) - self.assertIs(found1, A1) - # find A1 class Z(STATIC, B1, A2): pass found1, found2 = getbase(Z, tokenA1) From f71fa781ac871135ce1d6e6d9bdc32be4f251310 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:06:58 +0900 Subject: [PATCH 15/86] edit testcase --- Lib/test/test_capi/test_misc.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 65c1b90042efcb..7d82ff5670f349 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1157,6 +1157,7 @@ def getbase(src, key, comparable=True): found_in_bases = find_first_type(src, key, False) if comparable: self.assertIs(found_in_mro, found_in_bases) + return found_in_mro return found_in_mro, found_in_bases create_type = _testcapi.create_type_with_token @@ -1172,26 +1173,26 @@ def getbase(src, key, comparable=True): tokenA1 = get_token(A1) # match exactly - found1, found2 = getbase(A1, tokenA1) - self.assertIs(found1, A1) + found = getbase(A1, tokenA1) + self.assertIs(found, A1) # no token in static types STATIC = type(1) self.assertEqual(get_token(STATIC), 0) - found1, found2 = getbase(STATIC, tokenA1) - self.assertIs(found1, None) + found = getbase(STATIC, tokenA1) + self.assertIs(found, None) # no token in pure subtypes class A2(A1): pass self.assertEqual(get_token(A2), 0) # find A1 class Z(STATIC, B1, A2): pass - found1, found2 = getbase(Z, tokenA1) - self.assertIs(found1, A1) + found = getbase(Z, tokenA1) + self.assertIs(found, A1) # NULL finds nothing - found1, found2 = getbase(Z, 0) - self.assertIs(found1, None) + found = getbase(Z, 0) + self.assertIs(found, None) # share the token with A1 B1 = create_type('_testcapi.B1', tokenA1) @@ -1199,8 +1200,8 @@ class Z(STATIC, B1, A2): pass # find first B1 by shared token class Z(B1, A1): pass - found1, found2 = getbase(Z, tokenA1) - self.assertIs(found1, B1) + found = getbase(Z, tokenA1) + self.assertIs(found, B1) def test_gen_get_code(self): def genf(): yield From 83760d78e7440bc2373343bb477e2af46828a59e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:55:57 +0900 Subject: [PATCH 16/86] add test for no-result mode 1/2 --- Lib/test/test_capi/test_misc.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 7d82ff5670f349..01328bd178dd04 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1152,9 +1152,14 @@ class MyType: self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') def test_get_base_by_token(self): - def getbase(src, key, comparable=True): - found_in_mro = find_first_type(src, key, True) - found_in_bases = find_first_type(src, key, False) + def get_base_by_token(src, key, comparable=True): + def run(use_mro): + find_first = _testcapi.pytype_getbasebytoken + result = find_first(src, key, use_mro) + return result + + found_in_mro = run(True) + found_in_bases = run(False) if comparable: self.assertIs(found_in_mro, found_in_bases) return found_in_mro @@ -1162,7 +1167,6 @@ def getbase(src, key, comparable=True): create_type = _testcapi.create_type_with_token get_token = _testcapi.pytype_gettoken - find_first_type = _testcapi.pytype_getbasebytoken Py_TP_USE_SPEC = 0 A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC) @@ -1173,13 +1177,13 @@ def getbase(src, key, comparable=True): tokenA1 = get_token(A1) # match exactly - found = getbase(A1, tokenA1) + found = get_base_by_token(A1, tokenA1) self.assertIs(found, A1) # no token in static types STATIC = type(1) self.assertEqual(get_token(STATIC), 0) - found = getbase(STATIC, tokenA1) + found = get_base_by_token(STATIC, tokenA1) self.assertIs(found, None) # no token in pure subtypes @@ -1187,11 +1191,11 @@ class A2(A1): pass self.assertEqual(get_token(A2), 0) # find A1 class Z(STATIC, B1, A2): pass - found = getbase(Z, tokenA1) + found = get_base_by_token(Z, tokenA1) self.assertIs(found, A1) # NULL finds nothing - found = getbase(Z, 0) + found = get_base_by_token(Z, 0) self.assertIs(found, None) # share the token with A1 @@ -1200,7 +1204,7 @@ class Z(STATIC, B1, A2): pass # find first B1 by shared token class Z(B1, A1): pass - found = getbase(Z, tokenA1) + found = get_base_by_token(Z, tokenA1) self.assertIs(found, B1) def test_gen_get_code(self): From 245d9d4544b29d2d2491085ab17c74b191df5d14 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:02:08 +0900 Subject: [PATCH 17/86] add test for no-result mode 2/2 --- Lib/test/test_capi/test_misc.py | 6 +++++- Modules/_testcapi/heaptype.c | 30 ++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 01328bd178dd04..de537feaf16ebd 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1155,7 +1155,11 @@ def test_get_base_by_token(self): def get_base_by_token(src, key, comparable=True): def run(use_mro): find_first = _testcapi.pytype_getbasebytoken - result = find_first(src, key, use_mro) + ret1, result = find_first(src, key, use_mro, True) + ret2, no_result = find_first(src, key, use_mro, False) + self.assertEqual(ret1, result is not None) + self.assertEqual(ret1, ret2) + self.assertIsNone(no_result) return result found_in_mro = run(True) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 7c1232e92ddcc0..d5c46b47e4931f 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -462,8 +462,9 @@ static PyObject * pytype_getbasebytoken(PyObject *self, PyObject *args) { PyTypeObject *type; - PyObject *py_token, *use_mro; - if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &use_mro)) { + PyObject *py_token, *use_mro, *need_result; + if (!PyArg_ParseTuple(args, "OOOO", + &type, &py_token, &use_mro, &need_result)) { return NULL; } assert(PyType_Check(type)); @@ -475,22 +476,31 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) } void *token = PyLong_AsVoidPtr(py_token); - PyTypeObject *result; - int ret = PyType_GetBaseByToken(type, token, &result); + PyObject *result; + int ret; + if (need_result == Py_True) { + ret = PyType_GetBaseByToken(type, token, (PyTypeObject **)&result); + } + else { + result = NULL; + ret = PyType_GetBaseByToken(type, token, NULL); + } if (mro_save != NULL) { type->tp_mro = mro_save; } - if (ret < 0) { + if (ret < 0 || ret > 1) { return NULL; } - if (result == NULL) { - assert(ret == 0); - Py_RETURN_NONE; + PyObject *tuple = PyTuple_New(2); + if (tuple == NULL) { + Py_XDECREF(result); + return NULL; } - assert(ret == 1); - return (PyObject *)result; + PyTuple_SET_ITEM(tuple, 0, ret ? Py_True : Py_False); + PyTuple_SET_ITEM(tuple, 1, result ? result : Py_None); + return tuple; } From a0858a71900bbdfe4be11637c9c59ba58112d535 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 29 Jun 2024 07:13:29 +0900 Subject: [PATCH 18/86] edit test --- Lib/test/test_capi/test_misc.py | 14 +++++++++----- Modules/_testcapi/heaptype.c | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index de537feaf16ebd..5a2204dfb9ac18 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1157,6 +1157,7 @@ def run(use_mro): find_first = _testcapi.pytype_getbasebytoken ret1, result = find_first(src, key, use_mro, True) ret2, no_result = find_first(src, key, use_mro, False) + self.assertIn(ret1, (0, 1)) self.assertEqual(ret1, result is not None) self.assertEqual(ret1, ret2) self.assertIsNone(no_result) @@ -1203,13 +1204,16 @@ class Z(STATIC, B1, A2): pass self.assertIs(found, None) # share the token with A1 - B1 = create_type('_testcapi.B1', tokenA1) - self.assertTrue(get_token(B1) == tokenA1) + C1 = create_type('_testcapi.C1', tokenA1) + self.assertTrue(get_token(C1) == tokenA1) - # find first B1 by shared token - class Z(B1, A1): pass + # find first C1 by shared token + class Z(C1, A2): pass found = get_base_by_token(Z, tokenA1) - self.assertIs(found, B1) + self.assertIs(found, C1) + # B1 not found + found = get_base_by_token(Z, get_token(B1)) + self.assertIs(found, None) def test_gen_get_code(self): def genf(): yield diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index d5c46b47e4931f..d3ab04fb74a903 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -490,7 +490,7 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) type->tp_mro = mro_save; } - if (ret < 0 || ret > 1) { + if (ret < 0) { return NULL; } PyObject *tuple = PyTuple_New(2); @@ -498,7 +498,7 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) Py_XDECREF(result); return NULL; } - PyTuple_SET_ITEM(tuple, 0, ret ? Py_True : Py_False); + PyTuple_SET_ITEM(tuple, 0, PyLong_FromLong(ret)); PyTuple_SET_ITEM(tuple, 1, result ? result : Py_None); return tuple; } From 4fb9cf80a9e71539e0c01b579c4218c9149f78f1 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 29 Jun 2024 07:19:28 +0900 Subject: [PATCH 19/86] add functions to check perf --- Modules/_datetimemodule.c | 47 +++++++++++++++++++++++++++++++++++++ Modules/_decimal/_decimal.c | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 85595dce0bad5c..5734e94bc32bf9 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7159,7 +7159,54 @@ init_static_types(PyInterpreterState *interp, int reloading) * Module methods and initialization. */ +static void * +_getslot_recursive(PyTypeObject *type, int depth, int n) +{ + void *token = PyType_GetSlot(type, Py_tp_token); + if (n < depth && token != _getslot_recursive(type, depth, n+1)) { + token = NULL; + } + return token; +} + +static void * +_gettoken_recursive(PyTypeObject *type, int depth, int n) +{ + void *token; + PyType_GetToken(type, &token); + if (n < depth && token != _gettoken_recursive(type, depth, n+1)) { + token = NULL; + } + return token; +} + +static PyObject * +test_perf_getslot(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *depth; + if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { + return NULL; + } + void *token = _getslot_recursive(type, PyLong_AsLong(depth), 1); + return PyLong_FromVoidPtr(token); +} + +static PyObject * +test_perf_gettoken(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *depth; + if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { + return NULL; + } + void *token = _gettoken_recursive(type, PyLong_AsLong(depth), 1); + return PyLong_FromVoidPtr(token); +} + static PyMethodDef module_methods[] = { + {"test_perf_getslot", test_perf_getslot, METH_VARARGS}, + {"test_perf_gettoken", test_perf_gettoken, METH_VARARGS}, {NULL, NULL} }; diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 94a2cc2c8e5f8a..7f4ef312e6a722 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5746,8 +5746,55 @@ static PyType_Spec context_spec = { }; +static void * +_getslot_recursive(PyTypeObject *type, int depth, int n) +{ + void *token = PyType_GetSlot(type, Py_tp_token); + if (n < depth && token != _getslot_recursive(type, depth, n+1)) { + token = NULL; + } + return token; +} + +static void * +_gettoken_recursive(PyTypeObject *type, int depth, int n) +{ + void *token; + PyType_GetToken(type, &token); + if (n < depth && token != _gettoken_recursive(type, depth, n+1)) { + token = NULL; + } + return token; +} + +static PyObject * +test_perf_getslot(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *depth; + if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { + return NULL; + } + void *token = _getslot_recursive(type, PyLong_AsLong(depth), 1); + return PyLong_FromVoidPtr(token); +} + +static PyObject * +test_perf_gettoken(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *depth; + if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { + return NULL; + } + void *token = _gettoken_recursive(type, PyLong_AsLong(depth), 1); + return PyLong_FromVoidPtr(token); +} + static PyMethodDef _decimal_methods [] = { + { "test_perf_getslot", test_perf_getslot, METH_VARARGS}, + { "test_perf_gettoken", test_perf_gettoken, METH_VARARGS}, { "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, { "setcontext", (PyCFunction)PyDec_SetCurrentContext, METH_O, doc_setcontext}, { "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext}, From eb29cf8d10bf59f091454a5a063ed9fd710008ed Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 29 Jun 2024 08:42:37 +0900 Subject: [PATCH 20/86] remove previous experiment commit --- Modules/_datetimemodule.c | 47 ------------------------------------- Modules/_decimal/_decimal.c | 47 ------------------------------------- 2 files changed, 94 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 5734e94bc32bf9..85595dce0bad5c 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7159,54 +7159,7 @@ init_static_types(PyInterpreterState *interp, int reloading) * Module methods and initialization. */ -static void * -_getslot_recursive(PyTypeObject *type, int depth, int n) -{ - void *token = PyType_GetSlot(type, Py_tp_token); - if (n < depth && token != _getslot_recursive(type, depth, n+1)) { - token = NULL; - } - return token; -} - -static void * -_gettoken_recursive(PyTypeObject *type, int depth, int n) -{ - void *token; - PyType_GetToken(type, &token); - if (n < depth && token != _gettoken_recursive(type, depth, n+1)) { - token = NULL; - } - return token; -} - -static PyObject * -test_perf_getslot(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *depth; - if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { - return NULL; - } - void *token = _getslot_recursive(type, PyLong_AsLong(depth), 1); - return PyLong_FromVoidPtr(token); -} - -static PyObject * -test_perf_gettoken(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *depth; - if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { - return NULL; - } - void *token = _gettoken_recursive(type, PyLong_AsLong(depth), 1); - return PyLong_FromVoidPtr(token); -} - static PyMethodDef module_methods[] = { - {"test_perf_getslot", test_perf_getslot, METH_VARARGS}, - {"test_perf_gettoken", test_perf_gettoken, METH_VARARGS}, {NULL, NULL} }; diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 7f4ef312e6a722..94a2cc2c8e5f8a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5746,55 +5746,8 @@ static PyType_Spec context_spec = { }; -static void * -_getslot_recursive(PyTypeObject *type, int depth, int n) -{ - void *token = PyType_GetSlot(type, Py_tp_token); - if (n < depth && token != _getslot_recursive(type, depth, n+1)) { - token = NULL; - } - return token; -} - -static void * -_gettoken_recursive(PyTypeObject *type, int depth, int n) -{ - void *token; - PyType_GetToken(type, &token); - if (n < depth && token != _gettoken_recursive(type, depth, n+1)) { - token = NULL; - } - return token; -} - -static PyObject * -test_perf_getslot(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *depth; - if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { - return NULL; - } - void *token = _getslot_recursive(type, PyLong_AsLong(depth), 1); - return PyLong_FromVoidPtr(token); -} - -static PyObject * -test_perf_gettoken(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *depth; - if (!PyArg_ParseTuple(args, "OO", &type, &depth)) { - return NULL; - } - void *token = _gettoken_recursive(type, PyLong_AsLong(depth), 1); - return PyLong_FromVoidPtr(token); -} - static PyMethodDef _decimal_methods [] = { - { "test_perf_getslot", test_perf_getslot, METH_VARARGS}, - { "test_perf_gettoken", test_perf_gettoken, METH_VARARGS}, { "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, { "setcontext", (PyCFunction)PyDec_SetCurrentContext, METH_O, doc_setcontext}, { "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext}, From ccd5ede588ebe9b87b2ddbfefa654df22ae3fa5c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:34:04 +0900 Subject: [PATCH 21/86] abandon the proposal of PyType_GetToken() --- Doc/c-api/type.rst | 11 ----------- Include/cpython/object.h | 1 - Lib/test/test_capi/test_misc.py | 2 +- Modules/_testcapi/heaptype.c | 13 ++++--------- Objects/typeobject.c | 11 ----------- 5 files changed, 5 insertions(+), 33 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index dd57d3b5339529..8e21ca13b910b3 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -311,17 +311,6 @@ Type Objects .. versionadded:: 3.14 -.. c:function:: int PyType_GetToken(PyTypeObject *type, void **result) - - Retrieve the token stored in the type. See :c:func:`PyType_GetBaseByToken()` - for the entry. - - * On error, set *\*result* to ``NULL``, set an exception, return ``-1``. - * If there's no token: set *\*result* to ``NULL``, return ``0``. - * Otherwise: set *\*result* to the non-NULL token, return ``1``. - - .. versionadded:: 3.14 - Creating Heap-Allocated Types ............................. diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 58a4cb4ca18c77..1483ed82a6e324 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -279,7 +279,6 @@ PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyType_LookupRef(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *); PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); -PyAPI_FUNC(int) PyType_GetToken(PyTypeObject *, void **); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(void) _Py_BreakPoint(void); diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 5a2204dfb9ac18..4c477f22a8e61f 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1171,7 +1171,7 @@ def run(use_mro): return found_in_mro, found_in_bases create_type = _testcapi.create_type_with_token - get_token = _testcapi.pytype_gettoken + get_token = _testcapi.get_tp_token Py_TP_USE_SPEC = 0 A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index d3ab04fb74a903..a69ba5cc8d2b34 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -444,15 +444,10 @@ create_type_with_token(PyObject *self, PyObject *args) } static PyObject * -pytype_gettoken(PyObject *self, PyObject *type) +get_tp_token(PyObject *self, PyObject *type) { - void *token; - if (PyType_GetToken((PyTypeObject *)type, &token) < 0) { - return NULL; - } - if (token != PyType_GetSlot((PyTypeObject *)type, Py_tp_token)) { - PyErr_SetString(PyExc_AssertionError, - "PyType_GetSlot returned different token from GetToken"); + void *token = PyType_GetSlot((PyTypeObject *)type, Py_tp_token); + if (PyErr_Occurred()) { return NULL; } return PyLong_FromVoidPtr(token); @@ -518,7 +513,7 @@ static PyMethodDef TestMethods[] = { {"make_type_with_base", make_type_with_base, METH_O}, {"pyobject_getitemdata", pyobject_getitemdata, METH_O}, {"create_type_with_token", create_type_with_token, METH_VARARGS}, - {"pytype_gettoken", pytype_gettoken, METH_O}, + {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, {NULL}, }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e3e031b57cebcb..eedb0a184e9938 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5312,17 +5312,6 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, return 0; } -int -PyType_GetToken(PyTypeObject *type, void **result) -{ - assert(PyType_Check(type)); - if(_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - *result = ((PyHeapTypeObject*)type)->ht_token; - return *result ? 1 : 0; - } - *result = NULL; - return 0; -} void * PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls) From 7d3f8b693d9676005134a48a8c1b529d90773b64 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:42:17 +0900 Subject: [PATCH 22/86] optimize GetBaseByToken like GetModuleByDef --- Modules/_testcapi/heaptype.c | 22 +++++++------ Objects/typeobject.c | 63 ++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index a69ba5cc8d2b34..9317ebf88904b0 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -464,9 +464,8 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) } assert(PyType_Check(type)); - PyObject *mro_save = NULL; + PyObject *mro_save = type->tp_mro; if (use_mro != Py_True) { - mro_save = type->tp_mro; type->tp_mro = NULL; } @@ -481,21 +480,26 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) ret = PyType_GetBaseByToken(type, token, NULL); } - if (mro_save != NULL) { - type->tp_mro = mro_save; - } - + type->tp_mro = mro_save; if (ret < 0) { return NULL; } + PyObject *py_ret = PyLong_FromLong(ret); + if (py_ret == NULL) { + goto error; + } PyObject *tuple = PyTuple_New(2); if (tuple == NULL) { - Py_XDECREF(result); - return NULL; + goto error; } - PyTuple_SET_ITEM(tuple, 0, PyLong_FromLong(ret)); + PyTuple_SET_ITEM(tuple, 0, py_ret); PyTuple_SET_ITEM(tuple, 1, result ? result : Py_None); return tuple; +error: + Py_XDECREF(py_ret); + Py_XDECREF(result); + return NULL; +} } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index eedb0a184e9938..3a7d7fd40b958e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5238,17 +5238,10 @@ _PyType_GetModuleByDef2(PyTypeObject *left, PyTypeObject *right, static PyTypeObject * -get_base_by_token_recursive(PyTypeObject *type, void *token, int initial) +get_base_by_token_recursive(PyTypeObject *type, void *token) { - if (initial) { - if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - return NULL; - } - if (((PyHeapTypeObject*)type)->ht_token == token) { - return type; - } - } PyObject *bases = type->tp_bases; + assert(bases != NULL); Py_ssize_t n = PyTuple_GET_SIZE(bases); for (Py_ssize_t i = 0; i < n; i++) { PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i); @@ -5258,7 +5251,7 @@ get_base_by_token_recursive(PyTypeObject *type, void *token, int initial) if (((PyHeapTypeObject*)base)->ht_token == token) { return base; } - base = get_base_by_token_recursive(base, token, 0); + base = get_base_by_token_recursive(base, token); if (base) { return base; } @@ -5266,9 +5259,17 @@ get_base_by_token_recursive(PyTypeObject *type, void *token, int initial) return NULL; } +static inline int +_token_found(PyTypeObject *type, PyTypeObject **result) +{ + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(type); + } + return 1; +} + int -PyType_GetBaseByToken(PyTypeObject *type, void *token, - PyTypeObject **result) +PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (result != NULL) { *result = NULL; @@ -5278,35 +5279,33 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, return 0; } assert(PyType_Check(type)); + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // Static type MRO contains no heap type, + // which type_ready_mro() ensures. + return 0; + } + if (((PyHeapTypeObject*)type)->ht_token == token) { + return _token_found(type, result); + } PyObject *mro = type->tp_mro; if (mro == NULL) { - PyTypeObject *base = get_base_by_token_recursive(type, token, 1); - if (base == NULL) { - return 0; - } - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; + PyTypeObject *base = get_base_by_token_recursive(type, token, result); + return base ? _token_found(base, result) : 0; } assert(PyTuple_Check(mro)); + // mro_invoke() ensures that the type MRO cannot be empty. + assert(PyTuple_GET_SIZE(mro) >= 1); + // Also, the first item in the MRO is the type itself, which + // we already checked above. We skip it in the loop. + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); Py_ssize_t n = PyTuple_GET_SIZE(mro); - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 1; i < n; i++) { PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { - if (i) { - continue; - } - // Static type MRO contains no heap type, - // which type_ready_mro() ensures. - assert(base == type); - return 0; + continue; } if (((PyHeapTypeObject*)base)->ht_token == token) { - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; + return _token_found(base, result); } } return 0; From 100972db793a2cb545600289aa91fb856d862fb8 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:56:01 +0900 Subject: [PATCH 23/86] add a repeat test (temporary use) --- Modules/_testcapi/heaptype.c | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 9317ebf88904b0..2e079a3e15e7a6 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -500,6 +500,42 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) Py_XDECREF(result); return NULL; } + +static PyObject * +repeat_getbasebytoken(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *py_token, *repeat, *use_mro; + if (!PyArg_ParseTuple(args, "OOOO", &type, &py_token, &repeat, &use_mro)) { + return NULL; + } + assert(PyType_Check(type)); + + PyObject *mro_save = type->tp_mro; + if (use_mro != Py_True) { + type->tp_mro = NULL; + } + + void *token = PyLong_AsVoidPtr(py_token); + int n = PyLong_AsLong(repeat); + int found = 0; + PyObject *result; + for (int i = 0; i < n; i++) { + if (PyType_GetBaseByToken(type, token, (PyTypeObject **)&result) < 0) { + found = -1; + break; + } + if (result) { + Py_DECREF(result); + found = 1; + } + } + + type->tp_mro = mro_save; + if (!found) { + PyErr_SetString(PyExc_ValueError, "token not found"); + } + return found == 1 ? Py_None : NULL; } @@ -519,6 +555,7 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, + {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, {NULL}, }; From 7e89a982930dbe118d230bf61651ba745536c039 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:08:18 +0900 Subject: [PATCH 24/86] fix a build error --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f44494f5e3fd17..fb56470fed21d8 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5280,7 +5280,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) } PyObject *mro = type->tp_mro; if (mro == NULL) { - PyTypeObject *base = get_base_by_token_recursive(type, token, result); + PyTypeObject *base = get_base_by_token_recursive(type, token); return base ? _token_found(base, result) : 0; } assert(PyTuple_Check(mro)); From 1de4cbae88596c96505bf48ac44de8dad3d06f75 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:41:55 +0900 Subject: [PATCH 25/86] remove the repeat test --- Modules/_testcapi/heaptype.c | 38 ------------------------------------ 1 file changed, 38 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 2e079a3e15e7a6..664d557688b828 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -501,43 +501,6 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) return NULL; } -static PyObject * -repeat_getbasebytoken(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *py_token, *repeat, *use_mro; - if (!PyArg_ParseTuple(args, "OOOO", &type, &py_token, &repeat, &use_mro)) { - return NULL; - } - assert(PyType_Check(type)); - - PyObject *mro_save = type->tp_mro; - if (use_mro != Py_True) { - type->tp_mro = NULL; - } - - void *token = PyLong_AsVoidPtr(py_token); - int n = PyLong_AsLong(repeat); - int found = 0; - PyObject *result; - for (int i = 0; i < n; i++) { - if (PyType_GetBaseByToken(type, token, (PyTypeObject **)&result) < 0) { - found = -1; - break; - } - if (result) { - Py_DECREF(result); - found = 1; - } - } - - type->tp_mro = mro_save; - if (!found) { - PyErr_SetString(PyExc_ValueError, "token not found"); - } - return found == 1 ? Py_None : NULL; -} - static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, @@ -555,7 +518,6 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, - {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, {NULL}, }; From c0d4210331098b5ec55b29ae3ab3715645525235 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:48:48 +0900 Subject: [PATCH 26/86] add Py_TP_USE_SPEC to the test module --- Lib/test/test_capi/test_misc.py | 4 +++- Modules/_testcapi/heaptype.c | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 4c477f22a8e61f..deb22383ddc10a 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1172,7 +1172,9 @@ def run(use_mro): create_type = _testcapi.create_type_with_token get_token = _testcapi.get_tp_token - Py_TP_USE_SPEC = 0 + + Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC + self.assertEqual(Py_TP_USE_SPEC, 0) A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC) self.assertTrue(get_token(A1) != Py_TP_USE_SPEC) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 664d557688b828..02ed53db9d47a6 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -413,7 +413,6 @@ pyobject_getitemdata(PyObject *self, PyObject *o) static PyObject * create_type_with_token(PyObject *self, PyObject *args) { - assert(Py_TP_USE_SPEC == NULL); const char *name; PyObject *py_token; if (!PyArg_ParseTuple(args, "sO", &name, &py_token)) { @@ -1296,6 +1295,8 @@ _PyTestCapi_Init_Heaptype(PyObject *m) { &PyType_Type, m, &HeapCTypeMetaclassNullNew_spec, (PyObject *) &PyType_Type); ADD("HeapCTypeMetaclassNullNew", HeapCTypeMetaclassNullNew); + ADD("Py_TP_USE_SPEC", PyLong_FromVoidPtr(Py_TP_USE_SPEC)); + PyObject *HeapCCollection = PyType_FromMetaclass( NULL, m, &HeapCCollection_spec, NULL); if (HeapCCollection == NULL) { From 68c4b8a96854c14a04c5df2627fcda2735de79b0 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:46:25 +0900 Subject: [PATCH 27/86] remove a duplicate GetSlot test --- Modules/_testcapimodule.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 14d1ef97e46bc9..5ebcfef6143e02 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -582,11 +582,6 @@ test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) return NULL; } - if (PyType_GetSlot(&PyLong_Type, Py_tp_token) != NULL) { - PyErr_SetString(PyExc_AssertionError, "slot offset >= sizeof(PyTypeObject)"); - return NULL; - } - Py_RETURN_NONE; } From b0cb58a01782cb155564647d9664e720830719ec Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:58:58 +0900 Subject: [PATCH 28/86] tweak GetBaseBytoken() --- Modules/_testcapi/heaptype.c | 1 + Objects/typeobject.c | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 02ed53db9d47a6..73395fff9e8551 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -481,6 +481,7 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) type->tp_mro = mro_save; if (ret < 0) { + assert(result == NULL); return NULL; } PyObject *py_ret = PyLong_FromLong(ret); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb56470fed21d8..32faa854900392 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5243,7 +5243,7 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) return base; } base = get_base_by_token_recursive(base, token); - if (base) { + if (base != NULL) { return base; } } @@ -5262,18 +5262,15 @@ _token_found(PyTypeObject *type, PyTypeObject **result) int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { - if (result != NULL) { - *result = NULL; - } if (token == NULL) { // We scan only heaptypes that contains a token - return 0; + goto exit; } assert(PyType_Check(type)); if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // Static type MRO contains no heap type, // which type_ready_mro() ensures. - return 0; + goto exit; } if (((PyHeapTypeObject*)type)->ht_token == token) { return _token_found(type, result); @@ -5281,7 +5278,10 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) PyObject *mro = type->tp_mro; if (mro == NULL) { PyTypeObject *base = get_base_by_token_recursive(type, token); - return base ? _token_found(base, result) : 0; + if (base != NULL) { + return _token_found(base, result); + } + goto exit; } assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. @@ -5299,6 +5299,10 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) return _token_found(base, result); } } +exit: + if (result != NULL) { + *result = NULL; + } return 0; } From 2e59344a90a65c65ec6bba43a79ed5138d0da427 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:01:50 +0900 Subject: [PATCH 29/86] add repeat tests --- Modules/_testcapi/heaptype.c | 93 +++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 73395fff9e8551..5d7e1345c4aa11 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -411,7 +411,7 @@ pyobject_getitemdata(PyObject *self, PyObject *o) static PyObject * -create_type_with_token(PyObject *self, PyObject *args) +create_type_with_token(PyObject *module, PyObject *args) { const char *name; PyObject *py_token; @@ -429,7 +429,8 @@ create_type_with_token(PyObject *self, PyObject *args) .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .slots = slots, }; - PyTypeObject *type = (PyTypeObject *)PyType_FromSpec(&spec); + PyTypeObject *type; + type = (PyTypeObject *)PyType_FromMetaclass(NULL, module, &spec, NULL); if (!type) { return NULL; } @@ -501,6 +502,90 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) return NULL; } +static PyObject * +repeat_getbasebytoken(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *py_token, *repeat; + if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &repeat)) { + return NULL; + } + assert(PyType_Check(type)); + void *token = PyLong_AsVoidPtr(py_token); + int n = PyLong_AsLong(repeat); + for (int i = 0; i < n; i++) { + if (PyType_GetBaseByToken(type, token, NULL) != 1) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getslot(PyObject *self, PyObject *args) +{ + PyTypeObject *type; + PyObject *py_token, *repeat; + if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &repeat)) { + return NULL; + } + assert(PyType_Check(type)); + void *token = PyLong_AsVoidPtr(py_token); + int n = PyLong_AsLong(repeat); + for (int i = 0; i < n; i++) { + if (PyType_GetSlot(type, Py_tp_token) != token) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getmodonce(PyObject *self, PyObject *args) +{ + PyTypeObject *type, *super; + PyObject *repeat; + if (!PyArg_ParseTuple(args, "OOO", &type, &super, &repeat)) { + return NULL; + } + // assume the case where a module state is passed around among functions + PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); + if (module != self || !PyModule_GetState(module)) { + return NULL; + } + assert(PyType_Check(type)); + int n = PyLong_AsLong(repeat); + for (int i = 0; i < n; i++) { + if (type != super && !PyType_IsSubtype(type, super)) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getmodeach(PyObject *self, PyObject *args) +{ + PyTypeObject *type, *super; + PyObject *repeat; + if (!PyArg_ParseTuple(args, "OOO", &type, &super, &repeat)) { + return NULL; + } + assert(PyType_Check(type)); + int n = PyLong_AsLong(repeat); + for (int i = 0; i < n; i++) { + // assume the case where a module state is not passed around + PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); + if (module != self || !PyModule_GetState(module)) { + return NULL; + } + if (type != super && !PyType_IsSubtype(type, super)) { + return NULL; + } + } + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, @@ -518,6 +603,10 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, + {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, + {"repeat_getslot", repeat_getslot, METH_VARARGS}, + {"repeat_getmodonce", repeat_getmodonce, METH_VARARGS}, + {"repeat_getmodeach", repeat_getmodeach, METH_VARARGS}, {NULL}, }; From 7fcf53eaa147ddd1b5c8a4a112bc6f832e1a9203 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:48:20 +0900 Subject: [PATCH 30/86] Use Py_TP_USE_SPEC in PyType_FromMetaclass() --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 32faa854900392..5fcce01a671010 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4925,7 +4925,7 @@ _PyType_FromMetaclass_impl( break; case Py_tp_token: { - res->ht_token = slot->pfunc ? slot->pfunc : spec; + res->ht_token = slot->pfunc == Py_TP_USE_SPEC ? spec : slot->pfunc; } break; default: From 2312cac72d660c049a30994b2ca085559ae3638e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:23:11 +0900 Subject: [PATCH 31/86] reword --- Doc/c-api/type.rst | 4 ++-- Objects/typeobject.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 8e21ca13b910b3..6ba1e9020c6bda 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -284,7 +284,7 @@ Type Objects * On error, set *\*result* to ``NULL`` and return ``-1`` with an exception. * The ``result`` argument accepts ``NULL`` if you need only the return value. - The token is a memory layout ID to identify the class. + The token is a memory layout ID for the class. You can store the preferred one in a heap type through :c:func:`PyType_FromMetaclass()`, if you know that: @@ -301,7 +301,7 @@ Type Objects } The slot accepts ``NULL`` **via** the ``Py_TP_USE_SPEC`` identifier, - with which a heap type holds the ``spec`` pointer passed to + with which a heap type takes in the ``spec`` pointer passed to :c:func:`PyType_FromMetaclass()`:: // Be careful when the spec is dynamically created diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5fcce01a671010..5db5b0e8121aae 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5263,7 +5263,7 @@ int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (token == NULL) { - // We scan only heaptypes that contains a token + // We scan only heaptypes that has a token goto exit; } assert(PyType_Check(type)); From 958d84504ecb7628d6c04717b3cfa5d79da217e9 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 9 Jul 2024 03:44:21 +0900 Subject: [PATCH 32/86] typo --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5db5b0e8121aae..98f503c08a8779 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5263,7 +5263,7 @@ int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (token == NULL) { - // We scan only heaptypes that has a token + // We scan only the heaptypes that have a token goto exit; } assert(PyType_Check(type)); From a1736b0ab84852b4774df986ca547a46d6af63f1 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:32:18 +0900 Subject: [PATCH 33/86] Doc: GetSlot() returns &spec with Py_TP_USE_SPEC --- Doc/c-api/type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 6ba1e9020c6bda..e9e7aef14207d4 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -301,7 +301,7 @@ Type Objects } The slot accepts ``NULL`` **via** the ``Py_TP_USE_SPEC`` identifier, - with which a heap type takes in the ``spec`` pointer passed to + which is actually switched to the ``spec`` pointer passed to :c:func:`PyType_FromMetaclass()`:: // Be careful when the spec is dynamically created From 295f4980c9d0b896f6a462aca745decb3e2dc2c0 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 12 Jul 2024 06:12:24 +0900 Subject: [PATCH 34/86] fix comment in test --- Lib/test/test_capi/test_misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index deb22383ddc10a..685eacebdaf12a 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1183,7 +1183,7 @@ def run(use_mro): self.assertTrue(get_token(B1) == id(self)) tokenA1 = get_token(A1) - # match exactly + # find A1 from A1 found = get_base_by_token(A1, tokenA1) self.assertIs(found, A1) @@ -1209,7 +1209,7 @@ class Z(STATIC, B1, A2): pass C1 = create_type('_testcapi.C1', tokenA1) self.assertTrue(get_token(C1) == tokenA1) - # find first C1 by shared token + # find C1 first by shared token class Z(C1, A2): pass found = get_base_by_token(Z, tokenA1) self.assertIs(found, C1) From f45e4d070b3478dc30e60d83afed415be296e807 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 12 Jul 2024 06:25:27 +0900 Subject: [PATCH 35/86] remove repeat tests --- Modules/_testcapi/heaptype.c | 88 ------------------------------------ 1 file changed, 88 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 5d7e1345c4aa11..ffaabef360fe5f 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -502,90 +502,6 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) return NULL; } -static PyObject * -repeat_getbasebytoken(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *py_token, *repeat; - if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &repeat)) { - return NULL; - } - assert(PyType_Check(type)); - void *token = PyLong_AsVoidPtr(py_token); - int n = PyLong_AsLong(repeat); - for (int i = 0; i < n; i++) { - if (PyType_GetBaseByToken(type, token, NULL) != 1) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getslot(PyObject *self, PyObject *args) -{ - PyTypeObject *type; - PyObject *py_token, *repeat; - if (!PyArg_ParseTuple(args, "OOO", &type, &py_token, &repeat)) { - return NULL; - } - assert(PyType_Check(type)); - void *token = PyLong_AsVoidPtr(py_token); - int n = PyLong_AsLong(repeat); - for (int i = 0; i < n; i++) { - if (PyType_GetSlot(type, Py_tp_token) != token) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getmodonce(PyObject *self, PyObject *args) -{ - PyTypeObject *type, *super; - PyObject *repeat; - if (!PyArg_ParseTuple(args, "OOO", &type, &super, &repeat)) { - return NULL; - } - // assume the case where a module state is passed around among functions - PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); - if (module != self || !PyModule_GetState(module)) { - return NULL; - } - assert(PyType_Check(type)); - int n = PyLong_AsLong(repeat); - for (int i = 0; i < n; i++) { - if (type != super && !PyType_IsSubtype(type, super)) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getmodeach(PyObject *self, PyObject *args) -{ - PyTypeObject *type, *super; - PyObject *repeat; - if (!PyArg_ParseTuple(args, "OOO", &type, &super, &repeat)) { - return NULL; - } - assert(PyType_Check(type)); - int n = PyLong_AsLong(repeat); - for (int i = 0; i < n; i++) { - // assume the case where a module state is not passed around - PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); - if (module != self || !PyModule_GetState(module)) { - return NULL; - } - if (type != super && !PyType_IsSubtype(type, super)) { - return NULL; - } - } - Py_RETURN_NONE; -} - static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, @@ -603,10 +519,6 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, - {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, - {"repeat_getslot", repeat_getslot, METH_VARARGS}, - {"repeat_getmodonce", repeat_getmodonce, METH_VARARGS}, - {"repeat_getmodeach", repeat_getmodeach, METH_VARARGS}, {NULL}, }; From f5c082bb8446f12e70f469afc0e1348ff6742e2d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 14:18:52 +0200 Subject: [PATCH 36/86] Reword/rearrange documentation --- Doc/c-api/type.rst | 88 +++++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index e9e7aef14207d4..02602ced823581 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -275,39 +275,19 @@ Type Objects .. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) - Find a class whose token is valid and equal to the given one, - from the type and superclasses. + Find the first superclass in *type*'s :term:`method resolution order` whose + :c:macro:`Py_tp_token` token is equal to the given one. * If found, set *\*result* to a new :term:`strong reference` - to the first type and return ``1``. + to the found superclass and return ``1``. * If not found, set *\*result* to ``NULL`` and return ``0``. - * On error, set *\*result* to ``NULL`` and return ``-1`` with an exception. - * The ``result`` argument accepts ``NULL`` if you need only the return value. + * On error, set *\*result* to ``NULL`` and return ``-1`` with an + exception set. - The token is a memory layout ID for the class. - You can store the preferred one in a heap type through - :c:func:`PyType_FromMetaclass()`, if you know that: + The *result* argument may be ``NULL``, in which case *\*result* is not set. + Use this if you need only the return value. - * The pointer outlives the class, so it's not reused for something else - while the class exists. - * It "belongs" to the extension module where the class lives, so it will not - clash with other extensions. - - For the entry, enable the ``Py_tp_token`` slot:: - - PyType_Slot foo_slots[] = { - ... - {Py_tp_token, &pointee_in_the_module}, - } - - The slot accepts ``NULL`` **via** the ``Py_TP_USE_SPEC`` identifier, - which is actually switched to the ``spec`` pointer passed to - :c:func:`PyType_FromMetaclass()`:: - - // Be careful when the spec is dynamically created - {Py_tp_token, Py_TP_USE_SPEC}, - - To disable the feature, remove the slot. + The *token* argument may not be ``NULL``. .. versionadded:: 3.14 @@ -514,6 +494,11 @@ The following functions and structs are used to create * ``Py_nb_add`` to set :c:member:`PyNumberMethods.nb_add` * ``Py_sq_length`` to set :c:member:`PySequenceMethods.sq_length` + An additional slot is supported that does not correspond to a + :c:type:`!PyTypeObject` struct field: + + * :c:data:`Py_tp_token` + The following “offset” fields cannot be set using :c:type:`PyType_Slot`: * :c:member:`~PyTypeObject.tp_weaklistoffset` @@ -562,4 +547,49 @@ The following functions and structs are used to create The desired value of the slot. In most cases, this is a pointer to a function. - Slots other than ``Py_tp_doc`` may not be ``NULL``. + *pfunc* values may not be ``NULL``, except for the following slots: + + * ``Py_tp_doc`` + * :c:data:`Py_tp_token` (for clarity, prefer :c:data:`Py_TP_USE_SPEC` + rather than ``NULL``) + +.. c:macro:: Py_tp_token + + A :c:member:`~PyType_Slot.slot` that records a static memory layout ID + for a class. + + If the :c:type:`PyType_Spec` from which the class is statically + allocated, the token can be set to the spec using the special value + :c:data:`Py_TP_USE_SPEC`: + + .. code-block:: c + + PyType_Slot foo_slots[] = { + ... + {Py_tp_token, Py_TP_USE_SPEC}, + }; + + It can also be set to an arbitrary pointer, but you must ensure that: + + * The pointer outlives the class, so it's not reused for something else + while the class exists. + * It "belongs" to the extension module where the class lives, so it will not + clash with other extensions. + + Use :c:func:`PyType_GetBaseByToken` to check if a class's superclass has + a given token -- that is, check whether the memory layout is compatible. + + To get the token for a given class (without considering superclasses), + use :c:func:`PyType_GetSlot` with ``Py_tp_token``. + + .. versionadded:: 3.14 + + .. c:namespace:: NULL + + .. c:macro:: Py_TP_USE_SPEC + + Used as a value with :c:data:`Py_tp_token` to set the token to the + class's :c:type:`PyType_Spec`. + Expands to ``NULL``. + + .. versionadded:: 3.14 From 1f5e28984e7ad4b5fa059802547a82709d5c0b08 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 14:19:55 +0200 Subject: [PATCH 37/86] Add to stable ABI --- Doc/data/stable_abi.dat | 1 + Include/cpython/object.h | 1 - Include/object.h | 4 ++++ Include/typeslots.h | 3 +-- Lib/test/test_stable_abi_ctypes.py | 1 + Misc/stable_abi.toml | 6 ++++++ PC/python3dll.c | 1 + 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 1f7af436a4150b..edf96604b1c7d7 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -681,6 +681,7 @@ function,PyType_FromSpec,3.2,, function,PyType_FromSpecWithBases,3.3,, function,PyType_GenericAlloc,3.2,, function,PyType_GenericNew,3.2,, +function,PyType_GetBaseByToken,3.14,, function,PyType_GetFlags,3.2,, function,PyType_GetFullyQualifiedName,3.13,, function,PyType_GetModule,3.10,, diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 1483ed82a6e324..c3697399fb709b 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -278,7 +278,6 @@ PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *); PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyType_LookupRef(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *); -PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(void) _Py_BreakPoint(void); diff --git a/Include/object.h b/Include/object.h index abfdb6ce24df21..7124f58f6bdb37 100644 --- a/Include/object.h +++ b/Include/object.h @@ -391,6 +391,10 @@ PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spe PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls); PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 +PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); +#define Py_TP_USE_SPEC NULL +#endif /* Generic type check */ PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *); diff --git a/Include/typeslots.h b/Include/typeslots.h index e6701100e198ac..a7562aab934543 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -86,8 +86,7 @@ /* New in 3.10 */ #define Py_am_send 81 #endif -#if !defined(Py_LIMITED_API) +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 /* New in 3.14 */ #define Py_tp_token 82 -#define Py_TP_USE_SPEC NULL #endif diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index d1d8a967dbe62f..f6177b648a24bb 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -710,6 +710,7 @@ def test_windows_feature_macros(self): "PyType_FromSpecWithBases", "PyType_GenericAlloc", "PyType_GenericNew", + "PyType_GetBaseByToken", "PyType_GetFlags", "PyType_GetFullyQualifiedName", "PyType_GetModule", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 73012193d61485..67c1e06f9f65a8 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2508,3 +2508,9 @@ [function.Py_TYPE] added = '3.14' +[function.PyType_GetBaseByToken] + added = '3.14' +[const.Py_tp_token] + added = '3.14' +[const.Py_TP_USE_SPEC] + added = '3.14' diff --git a/PC/python3dll.c b/PC/python3dll.c index aa3c3965908ff4..167bbf1b92ec95 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -642,6 +642,7 @@ EXPORT_FUNC(PyType_FromSpec) EXPORT_FUNC(PyType_FromSpecWithBases) EXPORT_FUNC(PyType_GenericAlloc) EXPORT_FUNC(PyType_GenericNew) +EXPORT_FUNC(PyType_GetBaseByToken) EXPORT_FUNC(PyType_GetFlags) EXPORT_FUNC(PyType_GetFullyQualifiedName) EXPORT_FUNC(PyType_GetModule) From 0e14a593e6f479689605414a4c6336a14ae7a36b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 14:20:16 +0200 Subject: [PATCH 38/86] Nitpick: Add new field at the end --- Include/cpython/object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index c3697399fb709b..7eecf78bf26593 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -268,8 +268,8 @@ typedef struct _heaptypeobject { PyObject *ht_name, *ht_slots, *ht_qualname; struct _dictkeysobject *ht_cached_keys; PyObject *ht_module; - void *ht_token; char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec + void *ht_token; // Storage for the "Py_tp_token" slot struct _specialization_cache _spec_cache; // For use by the specializer. /* here are optional user slots, followed by the members. */ } PyHeapTypeObject; From ab9dc416ac8226bc9f4d9826e2f530c3662b7486 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 14:21:07 +0200 Subject: [PATCH 39/86] PyType_GetBaseByToken: Raise exceptions for user errors --- Lib/test/test_capi/test_misc.py | 10 +++++++--- Modules/_testcapi/heaptype.c | 12 +++++++++--- Objects/typeobject.c | 22 ++++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 685eacebdaf12a..2f1f250b45dacb 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1201,9 +1201,9 @@ class Z(STATIC, B1, A2): pass found = get_base_by_token(Z, tokenA1) self.assertIs(found, A1) - # NULL finds nothing - found = get_base_by_token(Z, 0) - self.assertIs(found, None) + # searching for NULL token is an error + with self.assertRaises(SystemError): + get_base_by_token(Z, 0) # share the token with A1 C1 = create_type('_testcapi.C1', tokenA1) @@ -1217,6 +1217,10 @@ class Z(C1, A2): pass found = get_base_by_token(Z, get_token(B1)) self.assertIs(found, None) + with self.assertRaises(TypeError): + _testcapi.pytype_getbasebytoken( + 'not a type', id(self), True, False) + def test_gen_get_code(self): def genf(): yield gen = genf() diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index ffaabef360fe5f..d0e3f765ac390a 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -462,10 +462,14 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) &type, &py_token, &use_mro, &need_result)) { return NULL; } - assert(PyType_Check(type)); - PyObject *mro_save = type->tp_mro; + PyObject *mro_save = NULL; if (use_mro != Py_True) { + // Test internal detail: PyType_GetBaseByToken works even with + // types that are only partially initialized (or torn down): + // if tp_mro=NULL we fall back to tp_bases. + assert(PyType_Check(type)); + mro_save = type->tp_mro; type->tp_mro = NULL; } @@ -480,7 +484,9 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) ret = PyType_GetBaseByToken(type, token, NULL); } - type->tp_mro = mro_save; + if (use_mro != Py_True) { + type->tp_mro = mro_save; + } if (ret < 0) { assert(result == NULL); return NULL; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1fae6e17542342..f8f293544fdedc 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5273,14 +5273,19 @@ int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (token == NULL) { - // We scan only the heaptypes that have a token - goto exit; + PyErr_Format(PyExc_SystemError, + "PyType_GetBaseByToken called with token=NULL"); + goto error; + } + if (!PyType_Check(type)) { + PyErr_Format(PyExc_TypeError, + "expected a type, got a '%T' object", type); + goto error; } - assert(PyType_Check(type)); if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // Static type MRO contains no heap type, // which type_ready_mro() ensures. - goto exit; + goto not_found; } if (((PyHeapTypeObject*)type)->ht_token == token) { return _token_found(type, result); @@ -5291,7 +5296,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) if (base != NULL) { return _token_found(base, result); } - goto exit; + goto not_found; } assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. @@ -5309,11 +5314,16 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) return _token_found(base, result); } } -exit: +not_found: if (result != NULL) { *result = NULL; } return 0; +error: + if (result != NULL) { + *result = NULL; + } + return -1; } From 00c0b7b39cbcc9970f3b189cd287a99a4c9e9599 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 14:21:47 +0200 Subject: [PATCH 40/86] Add internal comments --- Modules/_testcapi/heaptype.c | 9 +++++++++ Objects/typeslots.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index d0e3f765ac390a..3f8e7f97381120 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -419,6 +419,15 @@ create_type_with_token(PyObject *module, PyObject *args) return NULL; } + /* Calling this with Py_TP_USE_SPEC breaks the invariant that spec must be + * statically allocated! (Or at least: the pointer must outlive the class.) + * + * Strictly for the purposes of this test, that's OK. But type-checking + * this class using `PyType_GetBaseByToken` would be unreliable! + * (Technically, we rely on the fact that nothing *else* uses + * Py_TP_USE_SPEC with a stack-allocated spec.) + */ + void *token = PyLong_AsVoidPtr(py_token); PyType_Slot slots[] = { {Py_tp_token, token}, diff --git a/Objects/typeslots.py b/Objects/typeslots.py index a21af71cbf4ecd..c7f8a33bb1e74e 100755 --- a/Objects/typeslots.py +++ b/Objects/typeslots.py @@ -14,6 +14,8 @@ def generate_typeslots(out=sys.stdout): member = m.group(1) if member == "tp_token": + # The heap type structure (ht_*) is an implementation detail; + # the public slot for it has a familiar `tp_` prefix member = '{-1, offsetof(PyHeapTypeObject, ht_token)}' elif member.startswith("tp_"): member = f'{{-1, offsetof(PyTypeObject, {member})}}' From c75e355f4128e0ce0bdf00df679aa6bf5bd2e9f8 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:23:24 +0900 Subject: [PATCH 41/86] consider the suggestion in create_type_with_token() --- Modules/_testcapi/heaptype.c | 44 +++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 3f8e7f97381120..9265b60a54caab 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -418,17 +418,32 @@ create_type_with_token(PyObject *module, PyObject *args) if (!PyArg_ParseTuple(args, "sO", &name, &py_token)) { return NULL; } - - /* Calling this with Py_TP_USE_SPEC breaks the invariant that spec must be - * statically allocated! (Or at least: the pointer must outlive the class.) - * - * Strictly for the purposes of this test, that's OK. But type-checking - * this class using `PyType_GetBaseByToken` would be unreliable! - * (Technically, we rely on the fact that nothing *else* uses - * Py_TP_USE_SPEC with a stack-allocated spec.) - */ - void *token = PyLong_AsVoidPtr(py_token); + if (token == Py_TP_USE_SPEC) { + // Py_TP_USE_SPEC requires the spec that at least outlives the class + static PyType_Slot slots[] = { + {Py_tp_token, Py_TP_USE_SPEC}, + {0}, + }; + static PyType_Spec spec = { + .name = "_testcapi.DefaultTokenTest", + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .slots = slots, + }; + PyObject *type = PyType_FromMetaclass(NULL, NULL, &spec, NULL); + if (!type) { + return NULL; + } + token = PyType_GetSlot((PyTypeObject *)type, Py_tp_token); + assert(!PyErr_Occurred()); + Py_DECREF(type); + if (token != &spec) { + PyErr_SetString(PyExc_AssertionError, + "failed to convert token from Py_TP_USE_SPEC"); + return NULL; + } + } + // Test non-NULL token that must also outlive the class PyType_Slot slots[] = { {Py_tp_token, token}, {0}, @@ -438,17 +453,10 @@ create_type_with_token(PyObject *module, PyObject *args) .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .slots = slots, }; - PyTypeObject *type; - type = (PyTypeObject *)PyType_FromMetaclass(NULL, module, &spec, NULL); + PyObject *type = PyType_FromMetaclass(NULL, module, &spec, NULL); if (!type) { return NULL; } - if (token == Py_TP_USE_SPEC && PyType_GetSlot(type, Py_tp_token) != &spec) { - PyErr_SetString(PyExc_AssertionError, - "failed to convert token from Py_TP_USE_SPEC"); - Py_DECREF(type); - return NULL; - } return (PyObject *)type; } From fbdec08a0fadd9ca71eed1e201a23def5da953c3 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:58:15 +0900 Subject: [PATCH 42/86] remove a cast --- Modules/_testcapi/heaptype.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 9265b60a54caab..6a090fcb61790d 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -457,7 +457,7 @@ create_type_with_token(PyObject *module, PyObject *args) if (!type) { return NULL; } - return (PyObject *)type; + return type; } static PyObject * From 5341b1d1245b30c3e3ba6d1ad64102f466acc056 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:01:41 +0900 Subject: [PATCH 43/86] move a doc right under PyType_GetModuleByDef() --- Doc/c-api/type.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 02602ced823581..591794c608fdb9 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -264,15 +264,6 @@ Type Objects .. versionadded:: 3.11 -.. c:function:: int PyUnstable_Type_AssignVersionTag(PyTypeObject *type) - - Attempt to assign a version tag to the given type. - - Returns 1 if the type already had a valid version tag or a new one was - assigned, or 0 if a new tag could not be assigned. - - .. versionadded:: 3.12 - .. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) Find the first superclass in *type*'s :term:`method resolution order` whose @@ -291,6 +282,15 @@ Type Objects .. versionadded:: 3.14 +.. c:function:: int PyUnstable_Type_AssignVersionTag(PyTypeObject *type) + + Attempt to assign a version tag to the given type. + + Returns 1 if the type already had a valid version tag or a new one was + assigned, or 0 if a new tag could not be assigned. + + .. versionadded:: 3.12 + Creating Heap-Allocated Types ............................. From 79ef8259dcb737aa202a5478abe1eb115d73765c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 23 Jul 2024 22:01:58 +0900 Subject: [PATCH 44/86] cleanup create_type_with_token() --- Modules/_testcapi/heaptype.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 6a090fcb61790d..15bed645186d3a 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -453,11 +453,7 @@ create_type_with_token(PyObject *module, PyObject *args) .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .slots = slots, }; - PyObject *type = PyType_FromMetaclass(NULL, module, &spec, NULL); - if (!type) { - return NULL; - } - return type; + return PyType_FromMetaclass(NULL, module, &spec, NULL); } static PyObject * From 1bb40b8b6da6eb29834165eb5b05e11dd085ba0d Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:46:20 +0900 Subject: [PATCH 45/86] fix a typo in the doc --- Doc/c-api/type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 591794c608fdb9..c7a0f7fd156fed 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -558,7 +558,7 @@ The following functions and structs are used to create A :c:member:`~PyType_Slot.slot` that records a static memory layout ID for a class. - If the :c:type:`PyType_Spec` from which the class is statically + If the :c:type:`PyType_Spec` of the class is statically allocated, the token can be set to the spec using the special value :c:data:`Py_TP_USE_SPEC`: From 249c8fafe811600a1580d7f0240fc8c5d3967f22 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:17:02 +0900 Subject: [PATCH 46/86] clarify an example code in the doc --- Doc/c-api/type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index c7a0f7fd156fed..b91e04598671c1 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -564,7 +564,7 @@ The following functions and structs are used to create .. code-block:: c - PyType_Slot foo_slots[] = { + static PyType_Slot foo_slots[] = { ... {Py_tp_token, Py_TP_USE_SPEC}, }; From 5967fd070e90af5115206beabb0c2dae4dd07847 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:08:11 +0900 Subject: [PATCH 47/86] apply proposal to ctypes completely --- Modules/_ctypes/_ctypes.c | 6 +++--- Modules/_ctypes/ctypes.h | 13 +------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 0724ff9c659eba..b14dfff2a59b97 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -454,7 +454,7 @@ class _ctypes.CType_Type "PyObject *" "clinic_state()->CType_Type" static int CType_Type_traverse(PyObject *self, visitproc visit, void *arg) { - StgInfo *info = _PyStgInfo_FromType_NoState2(self); + StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { PyErr_WriteUnraisable(self); } @@ -485,7 +485,7 @@ ctype_clear_stginfo(StgInfo *info) static int CType_Type_clear(PyObject *self) { - StgInfo *info = _PyStgInfo_FromType_NoState2(self); + StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { PyErr_WriteUnraisable(self); } @@ -498,7 +498,7 @@ CType_Type_clear(PyObject *self) static void CType_Type_dealloc(PyObject *self) { - StgInfo *info = _PyStgInfo_FromType_NoState2(self); + StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { PyErr_WriteUnraisable(self); } diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 5a7fccb81ae4aa..2caf118ec18c6f 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -507,20 +507,10 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result) /* A variant of PyStgInfo_FromType that doesn't need the state, * so it can be called from finalization functions when the module - * state is torn down. Does no checks; cannot fail. - * This inlines the current implementation PyObject_GetTypeData, - * so it might break in the future. + * state is torn down. */ static inline StgInfo * _PyStgInfo_FromType_NoState(PyObject *type) -{ - size_t type_basicsize =_Py_SIZE_ROUND_UP(PyType_Type.tp_basicsize, - ALIGNOF_MAX_ALIGN_T); - return (StgInfo *)((char *)type + type_basicsize); -} - -static inline StgInfo * -_PyStgInfo_FromType_NoState2(PyObject *type) { PyTypeObject *PyCType_Type; if (PyType_GetBaseByToken(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0) { @@ -532,7 +522,6 @@ _PyStgInfo_FromType_NoState2(PyObject *type) } StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); Py_DECREF(PyCType_Type); - assert(info == _PyStgInfo_FromType_NoState(type)); return info; } From 82f511e6dc838d3fbbb97a45c22d233e3ba972de Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:08:08 +0900 Subject: [PATCH 48/86] What's New --- Doc/whatsnew/3.14.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index aecc7cabd0d1f5..2295d1960c2f1b 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -405,6 +405,11 @@ New Features (Contributed by Victor Stinner in :gh:`119182`.) +* Add :c:func:`PyType_GetBaseByToken` function for easier superclass + identification, which attempts to resolve the `type checking issue + `__ mentioned in :pep:`630`. + + Porting to Python 3.14 ---------------------- From 4507079282b76fd0c4873c3d0d0a4c5ffceb5901 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 2 Aug 2024 01:28:59 +0900 Subject: [PATCH 49/86] doc: fix the slot example usage --- Doc/c-api/type.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index b91e04598671c1..66d304e0ca06fd 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -567,6 +567,7 @@ The following functions and structs are used to create static PyType_Slot foo_slots[] = { ... {Py_tp_token, Py_TP_USE_SPEC}, + ... }; It can also be set to an arbitrary pointer, but you must ensure that: From 92b08eb09679d3795ea7c382ebf3c73fd4ae6af2 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:18:56 +0900 Subject: [PATCH 50/86] ditto --- Doc/c-api/type.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 66d304e0ca06fd..4b550e71612748 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -565,10 +565,7 @@ The following functions and structs are used to create .. code-block:: c static PyType_Slot foo_slots[] = { - ... {Py_tp_token, Py_TP_USE_SPEC}, - ... - }; It can also be set to an arbitrary pointer, but you must ensure that: From eb4c2f8ef704a0ad149f0327f21fccbda849d54c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:09:57 +0900 Subject: [PATCH 51/86] doc: use a pronoun --- Doc/c-api/type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 4b550e71612748..2404b666b79bc8 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -270,7 +270,7 @@ Type Objects :c:macro:`Py_tp_token` token is equal to the given one. * If found, set *\*result* to a new :term:`strong reference` - to the found superclass and return ``1``. + to it and return ``1``. * If not found, set *\*result* to ``NULL`` and return ``0``. * On error, set *\*result* to ``NULL`` and return ``-1`` with an exception set. From a1444bd898951931fc02567d47d1a18237f42fda Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:33:49 +0900 Subject: [PATCH 52/86] ctypes: fix an error handling --- Modules/_ctypes/_ctypes.c | 2 +- Modules/_ctypes/ctypes.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index b14dfff2a59b97..cd9f35a7b5cf3a 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -500,7 +500,7 @@ CType_Type_dealloc(PyObject *self) { StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { - PyErr_WriteUnraisable(self); + PyErr_WriteUnraisable(NULL); // do not print the repr here } if (info) { PyMem_Free(info->ffi_type_pointer.elements); diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 2caf118ec18c6f..f6fa595b8f8a96 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -517,7 +517,7 @@ _PyStgInfo_FromType_NoState(PyObject *type) return NULL; } if (PyCType_Type == NULL) { - PyErr_SetString(PyExc_TypeError, "PyCType_Type expected"); + PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%T'", type); return NULL; } StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); From d9fce2596d06b88e8ea6dca926ec6400ff642f78 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 9 Aug 2024 23:13:27 +0900 Subject: [PATCH 53/86] ctypes: do not print metaclass on error --- Modules/_ctypes/ctypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index f6fa595b8f8a96..70cccdb639353d 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -517,7 +517,7 @@ _PyStgInfo_FromType_NoState(PyObject *type) return NULL; } if (PyCType_Type == NULL) { - PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%T'", type); + PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type); return NULL; } StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); From 3faf73a187692074f6f0bc8e1fa9fda0864b6505 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:35:25 +0900 Subject: [PATCH 54/86] ctypes: reword a comment --- Modules/_ctypes/_ctypes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index cd9f35a7b5cf3a..1a50f74652a5d0 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -500,7 +500,7 @@ CType_Type_dealloc(PyObject *self) { StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { - PyErr_WriteUnraisable(NULL); // do not print the repr here + PyErr_WriteUnraisable(NULL); // use NULL here to avoid segfault } if (info) { PyMem_Free(info->ffi_type_pointer.elements); From fc39abc2a869ffe91ce7923752591d9df696b81b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:17:55 +0900 Subject: [PATCH 55/86] static type check first --- Objects/typeobject.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6f65e9f5c71781..bff9bda1a8f7bb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5304,11 +5304,6 @@ _token_found(PyTypeObject *type, PyTypeObject **result) int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { - if (token == NULL) { - PyErr_Format(PyExc_SystemError, - "PyType_GetBaseByToken called with token=NULL"); - goto error; - } if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); @@ -5319,6 +5314,11 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) // which type_ready_mro() ensures. goto not_found; } + if (token == NULL) { + PyErr_Format(PyExc_SystemError, + "PyType_GetBaseByToken called with token=NULL"); + goto error; + } if (((PyHeapTypeObject*)type)->ht_token == token) { return _token_found(type, result); } From 90edfefc6bff1653741a017ab72d29dde103a15f Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:01:06 +0900 Subject: [PATCH 56/86] revert for correctness --- Objects/typeobject.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index bff9bda1a8f7bb..6f65e9f5c71781 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5304,6 +5304,11 @@ _token_found(PyTypeObject *type, PyTypeObject **result) int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { + if (token == NULL) { + PyErr_Format(PyExc_SystemError, + "PyType_GetBaseByToken called with token=NULL"); + goto error; + } if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); @@ -5314,11 +5319,6 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) // which type_ready_mro() ensures. goto not_found; } - if (token == NULL) { - PyErr_Format(PyExc_SystemError, - "PyType_GetBaseByToken called with token=NULL"); - goto error; - } if (((PyHeapTypeObject*)type)->ht_token == token) { return _token_found(type, result); } From f21b166079068d5c4299347a7c22fa9f19cfc7dc Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:43:41 +0900 Subject: [PATCH 57/86] update test --- Lib/test/test_capi/test_misc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 4096a1aa475ce0..f3cfd88d614013 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1174,6 +1174,8 @@ class Z(STATIC, B1, A2): pass # searching for NULL token is an error with self.assertRaises(SystemError): get_base_by_token(Z, 0) + with self.assertRaises(SystemError): + get_base_by_token(STATIC, 0) # share the token with A1 C1 = create_type('_testcapi.C1', tokenA1) From 6f6ae41fae7f68797637924d0966a252e5ddb455 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:37:45 +0900 Subject: [PATCH 58/86] apply proposal to defdict_or() in _collectionsmodule.c test_union() in test_defaultdict.py is a test for this. --- Modules/_collectionsmodule.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index fbfed59995c21e..ac44ac13d58c09 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2179,6 +2179,8 @@ typedef struct { PyObject *default_factory; } defdictobject; +static PyType_Spec defdict_spec; + PyDoc_STRVAR(defdict_missing_doc, "__missing__(key) # Called by __getitem__ for missing key; pseudo-code:\n\ if self.default_factory is None: raise KeyError((key,))\n\ @@ -2358,23 +2360,16 @@ defdict_or(PyObject* left, PyObject* right) { PyObject *self, *other; - // Find module state - PyTypeObject *tp = Py_TYPE(left); - PyObject *mod = PyType_GetModuleByDef(tp, &_collectionsmodule); - if (mod == NULL) { - PyErr_Clear(); - tp = Py_TYPE(right); - mod = PyType_GetModuleByDef(tp, &_collectionsmodule); + int ret = PyType_GetBaseByToken(Py_TYPE(left), &defdict_spec, NULL); + if (ret < 0) { + return NULL; } - assert(mod != NULL); - collections_state *state = get_module_state(mod); - - if (PyObject_TypeCheck(left, state->defdict_type)) { + if (ret) { self = left; other = right; } else { - assert(PyObject_TypeCheck(right, state->defdict_type)); + assert(PyType_GetBaseByToken(Py_TYPE(right), &defdict_spec, NULL) == 1); self = right; other = left; } @@ -2466,6 +2461,7 @@ static PyType_Slot defdict_slots[] = { {Py_tp_init, defdict_init}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, + {Py_tp_token, Py_TP_USE_SPEC}, {0, NULL}, }; From a73f9562d53ed3843c2bbbc9dba5ec6d3d947af7 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 23 Aug 2024 07:06:24 +0900 Subject: [PATCH 59/86] apply proposal to _decimal.c (replace _PyType_GetModuleByDef2) --- Modules/_decimal/_decimal.c | 52 ++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 94a2cc2c8e5f8a..57100a64b43e9c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -112,6 +112,8 @@ typedef struct { PyCFunction _py_float_as_integer_ratio; } decimal_state; +static PyType_Spec dec_spec; + static inline decimal_state * get_module_state(PyObject *mod) { @@ -130,16 +132,6 @@ get_module_state_by_def(PyTypeObject *tp) return get_module_state(mod); } -static inline decimal_state * -find_state_left_or_right(PyObject *left, PyObject *right) -{ - PyObject *mod = _PyType_GetModuleByDef2(Py_TYPE(left), Py_TYPE(right), - &_decimal_module); - assert(mod != NULL); - return get_module_state(mod); -} - - #if !defined(MPD_VERSION_HEX) || MPD_VERSION_HEX < 0x02050000 #error "libmpdec version >= 2.5.0 required" #endif @@ -174,6 +166,7 @@ typedef struct { Py_hash_t hash; mpd_t dec; mpd_uint_t data[_Py_DEC_MINALLOC]; + decimal_state *mstate; } PyDecObject; typedef struct { @@ -208,6 +201,39 @@ typedef struct { #define CTX(v) (&((PyDecContextObject *)v)->ctx) #define CtxCaps(v) (((PyDecContextObject *)v)->capitals) +static inline decimal_state * +find_state_left_or_right(PyObject *left, PyObject *right) +{ + decimal_state *state; + if (PyType_GetBaseByToken(Py_TYPE(left), &dec_spec, NULL) == 1) { + state = ((PyDecObject *)left)->mstate; + } + else { + assert(!PyErr_Occurred()); + assert(PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, NULL) == 1); + state = ((PyDecObject *)right)->mstate; + } + assert(state != NULL); + return state; +} + +/* Alternative ver. + +static inline decimal_state * +find_state_left_or_right(PyObject *left, PyObject *right) +{ + // No slower than _PyType_GetModuleByDef2() on PGO, but unstable. + // Prefer the `right` argument to be a static type. + PyTypeObject *base; + if (PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base) == 1) { + void *state = _PyType_GetModuleState(base); + Py_DECREF(base); + return (decimal_state *)state; + } + assert(!PyErr_Occurred()); + return get_module_state_by_def(Py_TYPE(left)); +} + */ Py_LOCAL_INLINE(PyObject *) incr_true(void) @@ -749,7 +775,7 @@ signaldict_richcompare(PyObject *v, PyObject *w, int op) { PyObject *res = Py_NotImplemented; - decimal_state *state = find_state_left_or_right(v, w); + decimal_state *state = get_module_state_by_def(Py_TYPE(v)); assert(PyDecSignalDict_Check(state, v)); if ((SdFlagAddr(v) == NULL) || (SdFlagAddr(w) == NULL)) { @@ -2030,6 +2056,7 @@ PyDecType_New(PyTypeObject *type) } dec->hash = -1; + dec->mstate = state; MPD(dec)->flags = MPD_STATIC|MPD_STATIC_DATA; MPD(dec)->exp = 0; @@ -4648,7 +4675,7 @@ dec_richcompare(PyObject *v, PyObject *w, int op) uint32_t status = 0; int a_issnan, b_issnan; int r; - decimal_state *state = find_state_left_or_right(v, w); + decimal_state *state = get_module_state_by_def(Py_TYPE(v)); #ifdef Py_DEBUG assert(PyDec_Check(state, v)); @@ -5036,6 +5063,7 @@ static PyMethodDef dec_methods [] = }; static PyType_Slot dec_slots[] = { + {Py_tp_token, Py_TP_USE_SPEC}, {Py_tp_dealloc, dec_dealloc}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_traverse, dec_traverse}, From 1d17bec9d22bec6c6c7ff11e9eb3a4ca09935741 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:20:57 +0900 Subject: [PATCH 60/86] _decimal: make the code PGO-friendly with MSVC --- Modules/_decimal/_decimal.c | 62 +++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 265d647cf20b40..bcec5917e6f65c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -112,8 +112,6 @@ typedef struct { PyCFunction _py_float_as_integer_ratio; } decimal_state; -static PyType_Spec dec_spec; - static inline decimal_state * get_module_state(PyObject *mod) { @@ -132,6 +130,30 @@ get_module_state_by_def(PyTypeObject *tp) return get_module_state(mod); } +static inline PyObject * +_left_or_right(PyObject *left, PyObject *right, void *token) +{ + // Prefer the `right` argument to be a static type + if (PyType_GetBaseByToken(Py_TYPE(right), token, NULL) == 1) { + return right; + } + assert(!PyErr_Occurred()); + assert(PyType_GetBaseByToken(Py_TYPE(left), token, NULL) == 1); + return left; +} + +static PyType_Spec dec_spec; +static inline decimal_state *get_module_state_from_dec(PyObject *); + +// No conditional branch here so that MSVC can inline this on PGO builds +static inline decimal_state * +find_state_left_or_right(PyObject *left, PyObject *right) +{ + PyObject *dec = _left_or_right(left, right, &dec_spec); + return get_module_state_from_dec(dec); +} + + #if !defined(MPD_VERSION_HEX) || MPD_VERSION_HEX < 0x02050000 #error "libmpdec version >= 2.5.0 required" #endif @@ -166,7 +188,7 @@ typedef struct { Py_hash_t hash; mpd_t dec; mpd_uint_t data[_Py_DEC_MINALLOC]; - decimal_state *mstate; + decimal_state *modstate; } PyDecObject; typedef struct { @@ -202,38 +224,12 @@ typedef struct { #define CtxCaps(v) (((PyDecContextObject *)v)->capitals) static inline decimal_state * -find_state_left_or_right(PyObject *left, PyObject *right) -{ - decimal_state *state; - if (PyType_GetBaseByToken(Py_TYPE(left), &dec_spec, NULL) == 1) { - state = ((PyDecObject *)left)->mstate; - } - else { - assert(!PyErr_Occurred()); - assert(PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, NULL) == 1); - state = ((PyDecObject *)right)->mstate; - } +get_module_state_from_dec(PyObject *v) { + void *state = ((PyDecObject *)v)->modstate; assert(state != NULL); - return state; + return (decimal_state *)state; } -/* Alternative ver. - -static inline decimal_state * -find_state_left_or_right(PyObject *left, PyObject *right) -{ - // No slower than _PyType_GetModuleByDef2() on PGO, but unstable. - // Prefer the `right` argument to be a static type. - PyTypeObject *base; - if (PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base) == 1) { - void *state = _PyType_GetModuleState(base); - Py_DECREF(base); - return (decimal_state *)state; - } - assert(!PyErr_Occurred()); - return get_module_state_by_def(Py_TYPE(left)); -} - */ Py_LOCAL_INLINE(PyObject *) incr_true(void) @@ -2060,7 +2056,7 @@ PyDecType_New(PyTypeObject *type) } dec->hash = -1; - dec->mstate = state; + dec->modstate = state; MPD(dec)->flags = MPD_STATIC|MPD_STATIC_DATA; MPD(dec)->exp = 0; From 7324e2b3d9e725867d13a80344938c45846ff05c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:15:12 +0900 Subject: [PATCH 61/86] move an assert and a comment --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index bcec5917e6f65c..c374e6b7eb548c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -130,6 +130,7 @@ get_module_state_by_def(PyTypeObject *tp) return get_module_state(mod); } +// MSVC inlines a branch like this on PGO builds unless the caller branches static inline PyObject * _left_or_right(PyObject *left, PyObject *right, void *token) { @@ -138,14 +139,12 @@ _left_or_right(PyObject *left, PyObject *right, void *token) return right; } assert(!PyErr_Occurred()); - assert(PyType_GetBaseByToken(Py_TYPE(left), token, NULL) == 1); return left; } static PyType_Spec dec_spec; static inline decimal_state *get_module_state_from_dec(PyObject *); -// No conditional branch here so that MSVC can inline this on PGO builds static inline decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { @@ -225,6 +224,7 @@ typedef struct { static inline decimal_state * get_module_state_from_dec(PyObject *v) { + assert(PyType_GetBaseByToken(Py_TYPE(v), &dec_spec, NULL) == 1); void *state = ((PyDecObject *)v)->modstate; assert(state != NULL); return (decimal_state *)state; From a2fd0ff44284848eda973f409351502b31b1ced6 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 27 Aug 2024 00:16:59 +0900 Subject: [PATCH 62/86] nits * move Py_tp_token slot the upper * reword a comment in ctypes --- Modules/_collectionsmodule.c | 2 +- Modules/_ctypes/_ctypes.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index ac44ac13d58c09..aef04248c7e73c 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2449,6 +2449,7 @@ passed to the dict constructor, including keyword arguments.\n\ #define DEFERRED_ADDRESS(ADDR) 0 static PyType_Slot defdict_slots[] = { + {Py_tp_token, Py_TP_USE_SPEC}, {Py_tp_dealloc, defdict_dealloc}, {Py_tp_repr, defdict_repr}, {Py_nb_or, defdict_or}, @@ -2461,7 +2462,6 @@ static PyType_Slot defdict_slots[] = { {Py_tp_init, defdict_init}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, - {Py_tp_token, Py_TP_USE_SPEC}, {0, NULL}, }; diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 1a50f74652a5d0..b44da704ada767 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -500,7 +500,7 @@ CType_Type_dealloc(PyObject *self) { StgInfo *info = _PyStgInfo_FromType_NoState(self); if (!info) { - PyErr_WriteUnraisable(NULL); // use NULL here to avoid segfault + PyErr_WriteUnraisable(NULL); // NULL avoids segfault here } if (info) { PyMem_Free(info->ffi_type_pointer.elements); @@ -560,11 +560,11 @@ static PyMethodDef ctype_methods[] = { }; static PyType_Slot ctype_type_slots[] = { + {Py_tp_token, Py_TP_USE_SPEC}, {Py_tp_traverse, CType_Type_traverse}, {Py_tp_clear, CType_Type_clear}, {Py_tp_dealloc, CType_Type_dealloc}, {Py_tp_methods, ctype_methods}, - {Py_tp_token, Py_TP_USE_SPEC}, // Sequence protocol. {Py_sq_repeat, CType_Type_repeat}, {0, NULL}, From ec77d167bf5607dbcb38c2bcabc12319a6f84edd Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:20:44 +0900 Subject: [PATCH 63/86] edit whats new --- Doc/whatsnew/3.14.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 0936f436999f52..cd4d8599d3e1b8 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -445,8 +445,8 @@ New Features an interned string and deallocate it during module shutdown. (Contribued by Eddie Elizondo in :gh:`113601`.) -* Add :c:func:`PyType_GetBaseByToken` function for easier superclass - identification, which attempts to resolve the `type checking issue +* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier + superclass identification, which attempts to resolve the `type checking issue `__ mentioned in :pep:`630`. From 5c5daa9042663e459b52abe87b7b0fddad72b174 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:24:35 +0900 Subject: [PATCH 64/86] use _PyType_CAST in recursive function --- Objects/typeobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 2680723679ed82..b66fec82e5e13e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5273,11 +5273,11 @@ _PyType_GetModuleByDef2(PyTypeObject *left, PyTypeObject *right, static PyTypeObject * get_base_by_token_recursive(PyTypeObject *type, void *token) { - PyObject *bases = type->tp_bases; + PyObject *bases = lookup_tp_bases(type); assert(bases != NULL); Py_ssize_t n = PyTuple_GET_SIZE(bases); for (Py_ssize_t i = 0; i < n; i++) { - PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i); + PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i)); if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { continue; } From 7735ef1fe2fa5f94215dbb6a56ccd1a4be5b0f9d Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:31:12 +0900 Subject: [PATCH 65/86] setup fixed repeat tests --- Modules/_testcapi/heaptype.c | 87 ++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index aed4eae98d065e..3f331228728cd2 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -521,6 +521,88 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) return NULL; } +#define REPEAT_TEST_SETUP \ + PyTypeObject *type, *super; \ + PyObject *py_token, *repeat; \ + if (!PyArg_ParseTuple(args, "OOOO", \ + &type, &py_token, &super, &repeat)) { \ + return NULL; \ + } \ + void *token = PyLong_AsVoidPtr(py_token); \ + if (!token && !super) { \ + return NULL; \ + } \ + int n = PyLong_AsLong(repeat); + +static PyObject * +repeat_getbasebytoken(PyObject *self, PyObject *args) +{ + REPEAT_TEST_SETUP; + for (int i = 0; i < n; i++) { + if (PyType_GetBaseByToken(type, token, NULL) != 1) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getslot(PyObject *self, PyObject *args) +{ + REPEAT_TEST_SETUP; + for (int i = 0; i < n; i++) { + if (PyType_GetSlot(type, Py_tp_token) != token) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getmodonce(PyObject *self, PyObject *args) +{ + REPEAT_TEST_SETUP; + // assume the case where a module state is passed around among functions + PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); + if (module != self || !PyModule_GetState(module)) { + return NULL; + } + for (int i = 0; i < n; i++) { + if (type != super && !PyType_IsSubtype(type, super)) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_getmodeach(PyObject *self, PyObject *args) +{ + REPEAT_TEST_SETUP; + for (int i = 0; i < n; i++) { + // assume the case where a module state is not passed around + PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); + if (module != self || !PyModule_GetState(module)) { + return NULL; + } + if (type != super && !PyType_IsSubtype(type, super)) { + return NULL; + } + } + Py_RETURN_NONE; +} + +static PyObject * +repeat_issubtype(PyObject *self, PyObject *args) +{ + REPEAT_TEST_SETUP; + for (int i = 0; i < n; i++) { + if (!PyType_IsSubtype(type, super)) { + return NULL; + } + } + Py_RETURN_NONE; +} static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, @@ -538,6 +620,11 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, + {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, + {"repeat_getslot", repeat_getslot, METH_VARARGS}, + {"repeat_getmodonce", repeat_getmodonce, METH_VARARGS}, + {"repeat_getmodeach", repeat_getmodeach, METH_VARARGS}, + {"repeat_issubtype", repeat_issubtype, METH_VARARGS}, {NULL}, }; From eaa30075e04150ad17e845f296f89a037088ab7b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:35:41 +0900 Subject: [PATCH 66/86] revert repeat tests --- Modules/_testcapi/heaptype.c | 87 ------------------------------------ 1 file changed, 87 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 3f331228728cd2..aed4eae98d065e 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -521,88 +521,6 @@ pytype_getbasebytoken(PyObject *self, PyObject *args) return NULL; } -#define REPEAT_TEST_SETUP \ - PyTypeObject *type, *super; \ - PyObject *py_token, *repeat; \ - if (!PyArg_ParseTuple(args, "OOOO", \ - &type, &py_token, &super, &repeat)) { \ - return NULL; \ - } \ - void *token = PyLong_AsVoidPtr(py_token); \ - if (!token && !super) { \ - return NULL; \ - } \ - int n = PyLong_AsLong(repeat); - -static PyObject * -repeat_getbasebytoken(PyObject *self, PyObject *args) -{ - REPEAT_TEST_SETUP; - for (int i = 0; i < n; i++) { - if (PyType_GetBaseByToken(type, token, NULL) != 1) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getslot(PyObject *self, PyObject *args) -{ - REPEAT_TEST_SETUP; - for (int i = 0; i < n; i++) { - if (PyType_GetSlot(type, Py_tp_token) != token) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getmodonce(PyObject *self, PyObject *args) -{ - REPEAT_TEST_SETUP; - // assume the case where a module state is passed around among functions - PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); - if (module != self || !PyModule_GetState(module)) { - return NULL; - } - for (int i = 0; i < n; i++) { - if (type != super && !PyType_IsSubtype(type, super)) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_getmodeach(PyObject *self, PyObject *args) -{ - REPEAT_TEST_SETUP; - for (int i = 0; i < n; i++) { - // assume the case where a module state is not passed around - PyObject *module = PyType_GetModuleByDef(type, _testcapimodule); - if (module != self || !PyModule_GetState(module)) { - return NULL; - } - if (type != super && !PyType_IsSubtype(type, super)) { - return NULL; - } - } - Py_RETURN_NONE; -} - -static PyObject * -repeat_issubtype(PyObject *self, PyObject *args) -{ - REPEAT_TEST_SETUP; - for (int i = 0; i < n; i++) { - if (!PyType_IsSubtype(type, super)) { - return NULL; - } - } - Py_RETURN_NONE; -} static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, @@ -620,11 +538,6 @@ static PyMethodDef TestMethods[] = { {"create_type_with_token", create_type_with_token, METH_VARARGS}, {"get_tp_token", get_tp_token, METH_O}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, - {"repeat_getbasebytoken", repeat_getbasebytoken, METH_VARARGS}, - {"repeat_getslot", repeat_getslot, METH_VARARGS}, - {"repeat_getmodonce", repeat_getmodonce, METH_VARARGS}, - {"repeat_getmodeach", repeat_getmodeach, METH_VARARGS}, - {"repeat_issubtype", repeat_issubtype, METH_VARARGS}, {NULL}, }; From 15e05822978cff9bacfc6168884afd38c5028856 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 28 Aug 2024 04:16:58 +0900 Subject: [PATCH 67/86] _decimal: simplify dec_richcompare() --- Modules/_decimal/_decimal.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c374e6b7eb548c..0349c53e7abfbb 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -4679,11 +4679,8 @@ dec_richcompare(PyObject *v, PyObject *w, int op) uint32_t status = 0; int a_issnan, b_issnan; int r; - decimal_state *state = get_module_state_by_def(Py_TYPE(v)); + decimal_state *state = get_module_state_from_dec(v); -#ifdef Py_DEBUG - assert(PyDec_Check(state, v)); -#endif CURRENT_CONTEXT(state, context); CONVERT_BINOP_CMP(&a, &b, v, w, op, context); From 52616f15ada3c80aaaf4d372fae26b807dc6c06c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:21:22 +0900 Subject: [PATCH 68/86] _decimal: restore PyType_GetModuleByDef() in bin-ops This will keep the slowdown by up to 2% on the `telco` benchmark (PGO). Unlike the `PyDecContextObject`, extending the `PyDecObject` struct seems to affect only binary ops and seems to be a waste of memory. --- Modules/_decimal/_decimal.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 0349c53e7abfbb..a3ad4ca0982338 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -134,22 +134,21 @@ get_module_state_by_def(PyTypeObject *tp) static inline PyObject * _left_or_right(PyObject *left, PyObject *right, void *token) { - // Prefer the `right` argument to be a static type - if (PyType_GetBaseByToken(Py_TYPE(right), token, NULL) == 1) { - return right; + if (PyType_GetBaseByToken(Py_TYPE(left), token, NULL) == 1) { + return left; } assert(!PyErr_Occurred()); - return left; + assert(PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, NULL) == 1); + return right; } static PyType_Spec dec_spec; -static inline decimal_state *get_module_state_from_dec(PyObject *); static inline decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { PyObject *dec = _left_or_right(left, right, &dec_spec); - return get_module_state_from_dec(dec); + return get_module_state_by_def(Py_TYPE(dec)); } @@ -187,7 +186,6 @@ typedef struct { Py_hash_t hash; mpd_t dec; mpd_uint_t data[_Py_DEC_MINALLOC]; - decimal_state *modstate; } PyDecObject; typedef struct { @@ -222,14 +220,6 @@ typedef struct { #define CTX(v) (&((PyDecContextObject *)v)->ctx) #define CtxCaps(v) (((PyDecContextObject *)v)->capitals) -static inline decimal_state * -get_module_state_from_dec(PyObject *v) { - assert(PyType_GetBaseByToken(Py_TYPE(v), &dec_spec, NULL) == 1); - void *state = ((PyDecObject *)v)->modstate; - assert(state != NULL); - return (decimal_state *)state; -} - Py_LOCAL_INLINE(PyObject *) incr_true(void) @@ -2056,7 +2046,6 @@ PyDecType_New(PyTypeObject *type) } dec->hash = -1; - dec->modstate = state; MPD(dec)->flags = MPD_STATIC|MPD_STATIC_DATA; MPD(dec)->exp = 0; @@ -4679,8 +4668,11 @@ dec_richcompare(PyObject *v, PyObject *w, int op) uint32_t status = 0; int a_issnan, b_issnan; int r; - decimal_state *state = get_module_state_from_dec(v); + decimal_state *state = get_module_state_by_def(Py_TYPE(v)); +#ifdef Py_DEBUG + assert(PyDec_Check(state, v)); +#endif CURRENT_CONTEXT(state, context); CONVERT_BINOP_CMP(&a, &b, v, w, op, context); From ac03429a04c9f50a83b4bfaa7737512d7d912f2b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 30 Aug 2024 01:07:42 +0900 Subject: [PATCH 69/86] fix build error --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index a3ad4ca0982338..aee499a5e24a57 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -138,7 +138,7 @@ _left_or_right(PyObject *left, PyObject *right, void *token) return left; } assert(!PyErr_Occurred()); - assert(PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, NULL) == 1); + assert(PyType_GetBaseByToken(Py_TYPE(right), token, NULL) == 1); return right; } From ac82d36208a89d1c9909c7564a1d7ec2108249fa Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sun, 1 Sep 2024 02:43:45 +0900 Subject: [PATCH 70/86] optimize PyType_GetBaseByToken() --- Objects/typeobject.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8887acda0a4e9c..022b5933c04c72 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5293,7 +5293,7 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) } static inline int -_token_found(PyTypeObject *type, PyTypeObject **result) +_token_found(PyTypeObject **result, PyTypeObject *type) { if (result != NULL) { *result = (PyTypeObject *)Py_NewRef(type); @@ -5301,34 +5301,45 @@ _token_found(PyTypeObject *type, PyTypeObject **result) return 1; } +// Prefer this to gotos for optimization +static inline int +_token_not_found(PyTypeObject **result, int ret) +{ + assert(-1 <= ret && ret <= 0); + if (result != NULL) { + *result = NULL; + } + return ret; +} + int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (token == NULL) { PyErr_Format(PyExc_SystemError, "PyType_GetBaseByToken called with token=NULL"); - goto error; + return _token_not_found(result, -1); } if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); - goto error; + return _token_not_found(result, -1); } if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // Static type MRO contains no heap type, // which type_ready_mro() ensures. - goto not_found; + return _token_not_found(result, 0); } if (((PyHeapTypeObject*)type)->ht_token == token) { - return _token_found(type, result); + return _token_found(result, type); } PyObject *mro = type->tp_mro; if (mro == NULL) { PyTypeObject *base = get_base_by_token_recursive(type, token); if (base != NULL) { - return _token_found(base, result); + return _token_found(result, base); } - goto not_found; + return _token_not_found(result, 0); } assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. @@ -5343,19 +5354,10 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) continue; } if (((PyHeapTypeObject*)base)->ht_token == token) { - return _token_found(base, result); + return _token_found(result, base); } } -not_found: - if (result != NULL) { - *result = NULL; - } - return 0; -error: - if (result != NULL) { - *result = NULL; - } - return -1; + return _token_not_found(result, 0); } From 2a2493452b507442df5fee65f2b5373cd6635249 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sun, 1 Sep 2024 03:05:19 +0900 Subject: [PATCH 71/86] _decimal: improve performance Faster than the upstream by up to 2% on the `telco` benchmarks (PGO/non-PGO). Based on the GetBaseByToken() optimization by ac82d36208a89d1c9909c7564a1d7ec2108249fa. --- Modules/_decimal/_decimal.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index aee499a5e24a57..bc8b8f958bb91b 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -31,6 +31,7 @@ #include #include "pycore_long.h" // _PyLong_IsZero() +#include "pycore_object.h" // _Py_DECREF_NO_DEALLOC() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_typeobject.h" #include "complexobject.h" @@ -131,15 +132,17 @@ get_module_state_by_def(PyTypeObject *tp) } // MSVC inlines a branch like this on PGO builds unless the caller branches -static inline PyObject * +static inline PyTypeObject * _left_or_right(PyObject *left, PyObject *right, void *token) { - if (PyType_GetBaseByToken(Py_TYPE(left), token, NULL) == 1) { - return left; + PyTypeObject *super; + if (PyType_GetBaseByToken(Py_TYPE(left), token, &super) != 1) { + assert(!PyErr_Occurred()); + PyType_GetBaseByToken(Py_TYPE(right), token, &super); } - assert(!PyErr_Occurred()); - assert(PyType_GetBaseByToken(Py_TYPE(right), token, NULL) == 1); - return right; + assert(super != NULL); + _Py_DECREF_NO_DEALLOC((PyObject *)super); + return super; } static PyType_Spec dec_spec; @@ -147,8 +150,8 @@ static PyType_Spec dec_spec; static inline decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { - PyObject *dec = _left_or_right(left, right, &dec_spec); - return get_module_state_by_def(Py_TYPE(dec)); + PyTypeObject *super = _left_or_right(left, right, &dec_spec); + return (decimal_state *)_PyType_GetModuleState(super); } From fa936d7bf23e142e87ffdb11ec7f1b8136794133 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sun, 1 Sep 2024 07:23:51 +0900 Subject: [PATCH 72/86] rename --- Modules/_decimal/_decimal.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index bc8b8f958bb91b..155e4335b42d8c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -133,16 +133,16 @@ get_module_state_by_def(PyTypeObject *tp) // MSVC inlines a branch like this on PGO builds unless the caller branches static inline PyTypeObject * -_left_or_right(PyObject *left, PyObject *right, void *token) +base_from_left_or_right(PyObject *left, PyObject *right, void *token) { - PyTypeObject *super; - if (PyType_GetBaseByToken(Py_TYPE(left), token, &super) != 1) { + PyTypeObject *base; + if (PyType_GetBaseByToken(Py_TYPE(left), token, &base) != 1) { assert(!PyErr_Occurred()); - PyType_GetBaseByToken(Py_TYPE(right), token, &super); + PyType_GetBaseByToken(Py_TYPE(right), token, &base); } - assert(super != NULL); - _Py_DECREF_NO_DEALLOC((PyObject *)super); - return super; + assert(base != NULL); + _Py_DECREF_NO_DEALLOC((PyObject *)base); + return base; } static PyType_Spec dec_spec; @@ -150,8 +150,8 @@ static PyType_Spec dec_spec; static inline decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { - PyTypeObject *super = _left_or_right(left, right, &dec_spec); - return (decimal_state *)_PyType_GetModuleState(super); + PyTypeObject *base = base_from_left_or_right(left, right, &dec_spec); + return (decimal_state *)_PyType_GetModuleState(base); } From a9121fcf981cefcb6927d98791984729fe6359e2 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:50:58 +0900 Subject: [PATCH 73/86] clarify the optimizations --- Modules/_decimal/_decimal.c | 27 ++++++++++++--------------- Objects/typeobject.c | 4 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 155e4335b42d8c..29c76e5cf81fc4 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -131,27 +131,24 @@ get_module_state_by_def(PyTypeObject *tp) return get_module_state(mod); } -// MSVC inlines a branch like this on PGO builds unless the caller branches -static inline PyTypeObject * -base_from_left_or_right(PyObject *left, PyObject *right, void *token) +static PyType_Spec dec_spec; + +static inline Py_ALWAYS_INLINE decimal_state * +find_state_left_or_right(PyObject *left, PyObject *right) { PyTypeObject *base; - if (PyType_GetBaseByToken(Py_TYPE(left), token, &base) != 1) { + if (PyType_GetBaseByToken(Py_TYPE(left), &dec_spec, &base) != 1) { assert(!PyErr_Occurred()); - PyType_GetBaseByToken(Py_TYPE(right), token, &base); + PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base); } assert(base != NULL); + // Py_DECREF'ing the superclass immediately after Py_GetBaseByToken() + // finishes will be well optimized, which is safe here since the given + // subclass keeps it in tp_bases. _Py_DECREF_NO_DEALLOC((PyObject *)base); - return base; -} - -static PyType_Spec dec_spec; - -static inline decimal_state * -find_state_left_or_right(PyObject *left, PyObject *right) -{ - PyTypeObject *base = base_from_left_or_right(left, right, &dec_spec); - return (decimal_state *)_PyType_GetModuleState(base); + void *state = _PyType_GetModuleState(base); + assert(state != NULL); + return (decimal_state *)state; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c04c891954ccc3..c1baba95831dbb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5301,11 +5301,11 @@ _token_found(PyTypeObject **result, PyTypeObject *type) return 1; } -// Prefer this to gotos for optimization +// Prefer this to gotos for better PGO by MSVC static inline int _token_not_found(PyTypeObject **result, int ret) { - assert(-1 <= ret && ret <= 0); + assert(ret == 0 || ret == -1); if (result != NULL) { *result = NULL; } From 7f506336ad16ad4d81e9df0e091a36cb0c0032fa Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:40:48 +0900 Subject: [PATCH 74/86] typo --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 29c76e5cf81fc4..374624f0d9b609 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -142,9 +142,9 @@ find_state_left_or_right(PyObject *left, PyObject *right) PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base); } assert(base != NULL); - // Py_DECREF'ing the superclass immediately after Py_GetBaseByToken() + // Py_DECREF'ing the `base` immediately after PyType_GetBaseByToken() // finishes will be well optimized, which is safe here since the given - // subclass keeps it in tp_bases. + // subclass keeps the superclass in tp_bases. _Py_DECREF_NO_DEALLOC((PyObject *)base); void *state = _PyType_GetModuleState(base); assert(state != NULL); From ea6e9d374539611b4c99c57c37b9f9346164a21e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:51:32 +0900 Subject: [PATCH 75/86] Add a private function and use it Keeps the performance unchanged even if the private function is not inlined (i.e. not trained well on PGO). --- Include/internal/pycore_typeobject.h | 1 + Modules/_decimal/_decimal.c | 15 ++---- Objects/typeobject.c | 75 +++++++++++++++------------- 3 files changed, 45 insertions(+), 46 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 8ba635c5016380..87df1da5d6c968 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -211,6 +211,7 @@ extern PyObject * _PyType_GetMRO(PyTypeObject *type); extern PyObject* _PyType_GetSubclasses(PyTypeObject *); extern int _PyType_HasSubclasses(PyTypeObject *); PyAPI_FUNC(PyObject *) _PyType_GetModuleByDef2(PyTypeObject *, PyTypeObject *, PyModuleDef *); +PyAPI_FUNC(PyTypeObject *) _PyType_GetBaseByTokenNoNewRef(PyTypeObject *, void *); // Export for _testinternalcapi extension. PyAPI_FUNC(PyObject *) _PyType_GetSlotWrapperNames(void); diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 374624f0d9b609..a3e4467aee5ba1 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -31,7 +31,6 @@ #include #include "pycore_long.h" // _PyLong_IsZero() -#include "pycore_object.h" // _Py_DECREF_NO_DEALLOC() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_typeobject.h" #include "complexobject.h" @@ -137,15 +136,11 @@ static inline Py_ALWAYS_INLINE decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { PyTypeObject *base; - if (PyType_GetBaseByToken(Py_TYPE(left), &dec_spec, &base) != 1) { - assert(!PyErr_Occurred()); - PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base); - } - assert(base != NULL); - // Py_DECREF'ing the `base` immediately after PyType_GetBaseByToken() - // finishes will be well optimized, which is safe here since the given - // subclass keeps the superclass in tp_bases. - _Py_DECREF_NO_DEALLOC((PyObject *)base); + base = _PyType_GetBaseByTokenNoNewRef(Py_TYPE(left), &dec_spec); + if (base == NULL) { + base = _PyType_GetBaseByTokenNoNewRef(Py_TYPE(right), &dec_spec); + assert(base != NULL); + } void *state = _PyType_GetModuleState(base); assert(state != NULL); return (decimal_state *)state; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c1baba95831dbb..2193bb2a6a0772 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5292,16 +5292,44 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) return NULL; } -static inline int -_token_found(PyTypeObject **result, PyTypeObject *type) +PyTypeObject * +_PyType_GetBaseByTokenNoNewRef(PyTypeObject *type, void *token) { - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(type); + assert(token != NULL); + assert(PyType_Check(type)); + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // Static type MRO contains no heap type, + // which type_ready_mro() ensures. + return NULL; } - return 1; + if (((PyHeapTypeObject*)type)->ht_token == token) { + return type; + } + PyObject *mro = type->tp_mro; + if (mro == NULL) { + return get_base_by_token_recursive(type, token); + } + assert(PyTuple_Check(mro)); + // mro_invoke() ensures that the type MRO cannot be empty. + assert(PyTuple_GET_SIZE(mro) >= 1); + // Also, the first item in the MRO is the type itself, which + // we already checked above. We skip it in the loop. + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 1; i < n; i++) { + PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); + if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { + continue; + } + if (((PyHeapTypeObject*)base)->ht_token == token) { + return base; + } + } + return NULL; } -// Prefer this to gotos for better PGO by MSVC +// MSVC seems to prefer this to goto jumps and duplicated branches on PGO, +// which can affect the performance of setting a result. static inline int _token_not_found(PyTypeObject **result, int ret) { @@ -5325,39 +5353,14 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) "expected a type, got a '%T' object", type); return _token_not_found(result, -1); } - if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - // Static type MRO contains no heap type, - // which type_ready_mro() ensures. - return _token_not_found(result, 0); - } - if (((PyHeapTypeObject*)type)->ht_token == token) { - return _token_found(result, type); - } - PyObject *mro = type->tp_mro; - if (mro == NULL) { - PyTypeObject *base = get_base_by_token_recursive(type, token); - if (base != NULL) { - return _token_found(result, base); - } + PyTypeObject *base = _PyType_GetBaseByTokenNoNewRef(type, token); + if (base == NULL) { return _token_not_found(result, 0); } - assert(PyTuple_Check(mro)); - // mro_invoke() ensures that the type MRO cannot be empty. - assert(PyTuple_GET_SIZE(mro) >= 1); - // Also, the first item in the MRO is the type itself, which - // we already checked above. We skip it in the loop. - assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); - Py_ssize_t n = PyTuple_GET_SIZE(mro); - for (Py_ssize_t i = 1; i < n; i++) { - PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); - if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { - continue; - } - if (((PyHeapTypeObject*)base)->ht_token == token) { - return _token_found(result, base); - } + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); } - return _token_not_found(result, 0); + return 1; } From 588d5ee2b5b0ee499d8ffba845770c1c8fe548ec Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 9 Sep 2024 04:21:10 +0900 Subject: [PATCH 76/86] revert private function (borrowed ref ver.) PyType_GetBaseByToken() fails to inline the wrapped ptivate function, whose overhead appears to be not ignorable. --- Modules/_decimal/_decimal.c | 14 ++++---- Objects/typeobject.c | 64 +++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index a3e4467aee5ba1..44212301fe8341 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -31,6 +31,7 @@ #include #include "pycore_long.h" // _PyLong_IsZero() +#include "pycore_object.h" // _Py_DECREF_NO_DEALLOC() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_typeobject.h" #include "complexobject.h" @@ -121,6 +122,7 @@ get_module_state(PyObject *mod) } static struct PyModuleDef _decimal_module; +static PyType_Spec dec_spec; static inline decimal_state * get_module_state_by_def(PyTypeObject *tp) @@ -130,17 +132,17 @@ get_module_state_by_def(PyTypeObject *tp) return get_module_state(mod); } -static PyType_Spec dec_spec; - static inline Py_ALWAYS_INLINE decimal_state * find_state_left_or_right(PyObject *left, PyObject *right) { PyTypeObject *base; - base = _PyType_GetBaseByTokenNoNewRef(Py_TYPE(left), &dec_spec); - if (base == NULL) { - base = _PyType_GetBaseByTokenNoNewRef(Py_TYPE(right), &dec_spec); - assert(base != NULL); + if (PyType_GetBaseByToken(Py_TYPE(left), &dec_spec, &base) != 1) { + PyType_GetBaseByToken(Py_TYPE(right), &dec_spec, &base); } + assert(base != NULL); + // Immediate decref for optimization, which is safe + // while `base` lives in the subclass's tp_bases. + _Py_DECREF_NO_DEALLOC((PyObject *)base); void *state = _PyType_GetModuleState(base); assert(state != NULL); return (decimal_state *)state; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f986041638054f..0e4c846f2074ef 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5292,28 +5292,15 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) return NULL; } -PyTypeObject * -_PyType_GetBaseByTokenNoNewRef(PyTypeObject *type, void *token) +static inline PyTypeObject * +get_base_by_token_from_mro(PyTypeObject *type, void *token) { - assert(token != NULL); - assert(PyType_Check(type)); - if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - // Static type MRO contains no heap type, - // which type_ready_mro() ensures. - return NULL; - } - if (((PyHeapTypeObject*)type)->ht_token == token) { - return type; - } PyObject *mro = type->tp_mro; - if (mro == NULL) { - return get_base_by_token_recursive(type, token); - } assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. assert(PyTuple_GET_SIZE(mro) >= 1); - // Also, the first item in the MRO is the type itself, which - // we already checked above. We skip it in the loop. + // Also, the first item in the MRO is the type itself, which is supposed + // to be already checked by the caller. We skip it in the loop. assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 1; i < n; i++) { @@ -5328,39 +5315,54 @@ _PyType_GetBaseByTokenNoNewRef(PyTypeObject *type, void *token) return NULL; } -// MSVC seems to prefer this to goto jumps and duplicated branches on PGO, -// which can affect the performance of setting a result. static inline int -_token_not_found(PyTypeObject **result, int ret) +_token_found(PyTypeObject **result, PyTypeObject *base) { - assert(ret == 0 || ret == -1); if (result != NULL) { - *result = NULL; + *result = (PyTypeObject *)Py_NewRef(base); } - return ret; + return 1; } int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { + if (result != NULL) { + *result = NULL; + } if (token == NULL) { + // varargs avoids unnecessarily being inlined PyErr_Format(PyExc_SystemError, "PyType_GetBaseByToken called with token=NULL"); - return _token_not_found(result, -1); + return -1; } if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); - return _token_not_found(result, -1); + return -1; } - PyTypeObject *base = _PyType_GetBaseByTokenNoNewRef(type, token); - if (base == NULL) { - return _token_not_found(result, 0); + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // Static type MRO contains no heap type, + // which type_ready_mro() ensures. + return 0; } - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); + if (((PyHeapTypeObject*)type)->ht_token == token) { + return _token_found(result, type); } - return 1; + + PyTypeObject *base; + if (type->tp_mro != NULL) { + base = get_base_by_token_from_mro(type, token); + } + else { + base = get_base_by_token_recursive(type, token); + } + if (base != NULL) { + return _token_found(result, base); + } + // This section will be placed in a cold path due to the low score of PGO + // exercise, so `*result` is redundantly cleared first as a common case. + return 0; } From b730abbaae516a3ec5391404e5460a8bcfb22070 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 9 Sep 2024 04:44:10 +0900 Subject: [PATCH 77/86] edit header file --- Include/internal/pycore_typeobject.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 87df1da5d6c968..8ba635c5016380 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -211,7 +211,6 @@ extern PyObject * _PyType_GetMRO(PyTypeObject *type); extern PyObject* _PyType_GetSubclasses(PyTypeObject *); extern int _PyType_HasSubclasses(PyTypeObject *); PyAPI_FUNC(PyObject *) _PyType_GetModuleByDef2(PyTypeObject *, PyTypeObject *, PyModuleDef *); -PyAPI_FUNC(PyTypeObject *) _PyType_GetBaseByTokenNoNewRef(PyTypeObject *, void *); // Export for _testinternalcapi extension. PyAPI_FUNC(PyObject *) _PyType_GetSlotWrapperNames(void); From 77f143aad7a8ed8df2b6a00a89a0e0eaafa8cfe5 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:06:35 +0900 Subject: [PATCH 78/86] bad PyType_GetBaseByToken() example This cleanup can cause a slowdown by 10% on the `telco` benchmark for some reason. --- Objects/typeobject.c | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4b98acd17004e9..d2cafc868403d3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5341,6 +5341,7 @@ static inline PyTypeObject * get_base_by_token_from_mro(PyTypeObject *type, void *token) { PyObject *mro = type->tp_mro; + assert(mro != NULL); assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. assert(PyTuple_GET_SIZE(mro) >= 1); @@ -5360,23 +5361,16 @@ get_base_by_token_from_mro(PyTypeObject *type, void *token) return NULL; } -static inline int -_token_found(PyTypeObject **result, PyTypeObject *base) -{ - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; -} - +// Goto jumps here can disturb PGO by MSVC int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { if (result != NULL) { + // The clear should not be under cold branches (e.g. not found) *result = NULL; } if (token == NULL) { - // varargs avoids unnecessarily being inlined + // This avoids being inlined thanks to varargs PyErr_Format(PyExc_SystemError, "PyType_GetBaseByToken called with token=NULL"); return -1; @@ -5387,27 +5381,29 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) return -1; } if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - // Static type MRO contains no heap type, - // which type_ready_mro() ensures. + // No static type has a heaptype superclass, + // which is ensured by type_ready_mro(). return 0; } - if (((PyHeapTypeObject*)type)->ht_token == token) { - return _token_found(result, type); - } - PyTypeObject *base; - if (type->tp_mro != NULL) { - base = get_base_by_token_from_mro(type, token); + if (((PyHeapTypeObject*)type)->ht_token == token) { + base = type; } else { - base = get_base_by_token_recursive(type, token); + if (type->tp_mro != NULL) { + base = get_base_by_token_from_mro(type, token); + } + else { + base = get_base_by_token_recursive(type, token); + } + if (base == NULL) { + return 0; + } } - if (base != NULL) { - return _token_found(result, base); + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); } - // This section will be placed in a cold path due to the low score of PGO - // exercise, so `*result` is redundantly cleared first as a common case. - return 0; + return 1; } From b114bc8190ecd3c162c204c0f652992c04f8f94e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 13 Sep 2024 20:31:23 +0900 Subject: [PATCH 79/86] recover performance when a reference is needed --- Modules/_ctypes/ctypes.h | 24 ++++++++++++++++++++++++ Objects/typeobject.c | 35 +++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index ad98f0fa4c00ff..5e48886903005c 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -489,6 +489,28 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result) return _stginfo_from_type(state, Py_TYPE(obj), result); } +static inline void +exercise_get_base_by_token(PyTypeObject *PyCType_Type) +{ + // CType_Type_traverse() almost alone trains PyType_GetBaseByToken() when + // running PGO tests (130,000 calls, whereas PyType_GetModuleByDef() has + // been trained with 2,700,000 calls), which is invoked by gc.collect(). + // Its way of finding a superclass is currently biased, so the following + // should be complemented. Expects little perf impact on this module. + PyTypeObject *result; + + // Find the type itself by token + if (PyType_GetBaseByToken(PyCType_Type, &pyctype_type_spec, &result) == 1) { + Py_DECREF(result); + } + assert(result == PyCType_Type); + + // Reject a static type + if (PyType_GetBaseByToken(&PyBaseObject_Type, &PyBaseObject_Type, &result)) { + assert(0); + } +} + /* A variant of PyStgInfo_FromType that doesn't need the state, * so it can be called from finalization functions when the module * state is torn down. @@ -504,6 +526,8 @@ _PyStgInfo_FromType_NoState(PyObject *type) PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type); return NULL; } + exercise_get_base_by_token(PyCType_Type); + StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); Py_DECREF(PyCType_Type); return info; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d2cafc868403d3..458448598f1ecb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5338,9 +5338,8 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) } static inline PyTypeObject * -get_base_by_token_from_mro(PyTypeObject *type, void *token) +get_base_by_token_from_mro(PyObject *mro, void *token, PyTypeObject *type) { - PyObject *mro = type->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); // mro_invoke() ensures that the type MRO cannot be empty. @@ -5361,10 +5360,11 @@ get_base_by_token_from_mro(PyTypeObject *type, void *token) return NULL; } -// Goto jumps here can disturb PGO by MSVC int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { + // The rich usage of this API needs a well-balanced exercise. Also, MSVC + // prefers if-else statements and tiny functions on PGO here over gotos. if (result != NULL) { // The clear should not be under cold branches (e.g. not found) *result = NULL; @@ -5383,27 +5383,30 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // No static type has a heaptype superclass, // which is ensured by type_ready_mro(). - return 0; } - PyTypeObject *base; - if (((PyHeapTypeObject*)type)->ht_token == token) { - base = type; + else if (((PyHeapTypeObject*)type)->ht_token == token) { + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(type); + } + return 1; } else { - if (type->tp_mro != NULL) { - base = get_base_by_token_from_mro(type, token); + PyTypeObject *base; + PyObject *mro = type->tp_mro; // Bypass lookup_tp_mro() for now + if (mro != NULL) { + base = get_base_by_token_from_mro(mro, token, type); } else { - base = get_base_by_token_recursive(type, token); + base = get_base_by_token_recursive(type, token); } - if (base == NULL) { - return 0; + if (base != NULL) { + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); + } + return 1; } } - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; + return 0; } From f08da25a7c3328a0b07b07e5e11368bb9efca38b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:08:16 +0900 Subject: [PATCH 80/86] edit PyType_GetBaseByToken() --- Objects/typeobject.c | 48 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 18941688d90a6c..32392f8fd46f69 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5311,17 +5311,14 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) } static inline PyTypeObject * -get_base_by_token_from_mro(PyObject *mro, void *token, PyTypeObject *type) +get_base_by_token_from_mro(PyObject *mro, void *token, int start) { - assert(mro != NULL); - assert(PyTuple_Check(mro)); - // mro_invoke() ensures that the type MRO cannot be empty. - assert(PyTuple_GET_SIZE(mro) >= 1); - // Also, the first item in the MRO is the type itself, which is supposed - // to be already checked by the caller. We skip it in the loop. - assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); + // NOTE: Not inlining this function might be better for the caller's size + // and response before entering MRO. The overhead to come here would be + // trivial for pure python subclasses? + assert(start == 0 || start == 1); Py_ssize_t n = PyTuple_GET_SIZE(mro); - for (Py_ssize_t i = 1; i < n; i++) { + for (Py_ssize_t i = start; i < n; i++) { PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { continue; @@ -5333,6 +5330,15 @@ get_base_by_token_from_mro(PyObject *mro, void *token, PyTypeObject *type) return NULL; } +static inline int +_token_found(PyTypeObject **result, PyTypeObject *base) +{ + if (result != NULL) { + *result = (PyTypeObject *)Py_NewRef(base); + } + return 1; +} + int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { @@ -5354,29 +5360,29 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) return -1; } if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - // No static type has a heaptype superclass, - // which is ensured by type_ready_mro(). + // No static type has a heaptype superclass, which is ensured + // by type_ready_mro(). } else if (((PyHeapTypeObject*)type)->ht_token == token) { - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(type); - } - return 1; + return _token_found(result, type); } else { PyTypeObject *base; PyObject *mro = type->tp_mro; // Bypass lookup_tp_mro() for now if (mro != NULL) { - base = get_base_by_token_from_mro(mro, token, type); + assert(PyTuple_Check(mro)); + // mro_invoke() ensures that the type MRO cannot be empty. + assert(PyTuple_GET_SIZE(mro) >= 1); + // Also, the first item in the MRO is the type itself, which + // we already checked above. We skip it in the loop. + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); + base = get_base_by_token_from_mro(mro, token, 1); } else { - base = get_base_by_token_recursive(type, token); + base = get_base_by_token_recursive(type, token); } if (base != NULL) { - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; + return _token_found(result, base); } } return 0; From e3c11826b556613dcd5759803c399e09c9bb6b26 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:45:57 +0900 Subject: [PATCH 81/86] recover performance take2 This version sets the *result to NULL at the end to reduce the overhead of double memory acces when returning true. Under verification. --- Objects/typeobject.c | 98 ++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 32392f8fd46f69..8f26e9648b2884 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5310,15 +5310,43 @@ get_base_by_token_recursive(PyTypeObject *type, void *token) return NULL; } +Py_NO_INLINE static PyTypeObject * +get_base_by_token_from_bases(PyTypeObject *type, void *token) +{ + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // No static type has a heaptype superclass, + // which is ensured by type_ready_mro(). + return NULL; + } + if (((PyHeapTypeObject*)type)->ht_token == token) { + return type; + } + return get_base_by_token_recursive(type, token); +} + static inline PyTypeObject * -get_base_by_token_from_mro(PyObject *mro, void *token, int start) +get_base_by_token_from_mro(PyTypeObject *type, void *token) { - // NOTE: Not inlining this function might be better for the caller's size - // and response before entering MRO. The overhead to come here would be - // trivial for pure python subclasses? - assert(start == 0 || start == 1); + if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // No static type has a heaptype superclass, + // which is ensured by type_ready_mro(). + return NULL; + } + if (((PyHeapTypeObject*)type)->ht_token == token) { + return type; + } + // Bypass lookup_tp_mro() as PyType_IsSubtype() does + PyObject *mro = type->tp_mro; + assert(mro != NULL); + assert(PyTuple_Check(mro)); + // mro_invoke() ensures that the type MRO cannot be empty. + assert(PyTuple_GET_SIZE(mro) >= 1); + // Also, the first item in the MRO is the type itself, which + // we already checked above. We skip it in the loop. + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); + Py_ssize_t n = PyTuple_GET_SIZE(mro); - for (Py_ssize_t i = start; i < n; i++) { + for (Py_ssize_t i = 1; i < n; i++) { PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) { continue; @@ -5330,62 +5358,36 @@ get_base_by_token_from_mro(PyObject *mro, void *token, int start) return NULL; } -static inline int -_token_found(PyTypeObject **result, PyTypeObject *base) -{ - if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(base); - } - return 1; -} - int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { - // The rich usage of this API needs a well-balanced exercise. Also, MSVC - // prefers if-else statements and tiny functions on PGO here over gotos. - if (result != NULL) { - // The clear should not be under cold branches (e.g. not found) - *result = NULL; - } + // MSVC prefers no gotos here on PGO. This function can also be less + // optimized if the given type is copied to a local variable directly. + int ret = 0; + PyTypeObject *base = NULL; if (token == NULL) { // This avoids being inlined thanks to varargs PyErr_Format(PyExc_SystemError, "PyType_GetBaseByToken called with token=NULL"); - return -1; + ret = -1; } - if (!PyType_Check(type)) { + else if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); - return -1; + ret = -1; } - if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - // No static type has a heaptype superclass, which is ensured - // by type_ready_mro(). - } - else if (((PyHeapTypeObject*)type)->ht_token == token) { - return _token_found(result, type); + else if (type->tp_mro != NULL) { + base = get_base_by_token_from_mro(type, token); } else { - PyTypeObject *base; - PyObject *mro = type->tp_mro; // Bypass lookup_tp_mro() for now - if (mro != NULL) { - assert(PyTuple_Check(mro)); - // mro_invoke() ensures that the type MRO cannot be empty. - assert(PyTuple_GET_SIZE(mro) >= 1); - // Also, the first item in the MRO is the type itself, which - // we already checked above. We skip it in the loop. - assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); - base = get_base_by_token_from_mro(mro, token, 1); - } - else { - base = get_base_by_token_recursive(type, token); - } - if (base != NULL) { - return _token_found(result, base); - } + base = get_base_by_token_from_bases(type, token); } - return 0; + // Finish in one place when profiling. Returning false is rare on the + // PGO tests, but it should not be run in a cold path in practice. + if (result != NULL) { + *result = (PyTypeObject *)Py_XNewRef(base); + } + return base != NULL ? 1 : ret; } From 974fce3f7151fbd20acaa9502e552f0d4abc8b6c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:45:47 +0900 Subject: [PATCH 82/86] do not exercise in ctypes --- Modules/_ctypes/ctypes.h | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 5e48886903005c..738dcd1aaf8a01 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -489,28 +489,6 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result) return _stginfo_from_type(state, Py_TYPE(obj), result); } -static inline void -exercise_get_base_by_token(PyTypeObject *PyCType_Type) -{ - // CType_Type_traverse() almost alone trains PyType_GetBaseByToken() when - // running PGO tests (130,000 calls, whereas PyType_GetModuleByDef() has - // been trained with 2,700,000 calls), which is invoked by gc.collect(). - // Its way of finding a superclass is currently biased, so the following - // should be complemented. Expects little perf impact on this module. - PyTypeObject *result; - - // Find the type itself by token - if (PyType_GetBaseByToken(PyCType_Type, &pyctype_type_spec, &result) == 1) { - Py_DECREF(result); - } - assert(result == PyCType_Type); - - // Reject a static type - if (PyType_GetBaseByToken(&PyBaseObject_Type, &PyBaseObject_Type, &result)) { - assert(0); - } -} - /* A variant of PyStgInfo_FromType that doesn't need the state, * so it can be called from finalization functions when the module * state is torn down. @@ -526,7 +504,6 @@ _PyStgInfo_FromType_NoState(PyObject *type) PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type); return NULL; } - exercise_get_base_by_token(PyCType_Type); StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); Py_DECREF(PyCType_Type); From 61bb346e40500b8bcbc9a34194d875624b78e08b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:16:11 +0900 Subject: [PATCH 83/86] edit this version of PyType_GetBaseByToken() --- Objects/typeobject.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8f26e9648b2884..3360c05dc394e4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5361,22 +5361,31 @@ get_base_by_token_from_mro(PyTypeObject *type, void *token) int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) { +// Use a macro for lazy evaluation +#define RETURN_RESULT(BASE, RET) \ + do { \ + if (result != NULL) { \ + *result = (PyTypeObject *)(BASE); \ + } \ + return (RET); \ + } while (0) + // MSVC prefers no gotos here on PGO. This function can also be less // optimized if the given type is copied to a local variable directly. - int ret = 0; PyTypeObject *base = NULL; if (token == NULL) { // This avoids being inlined thanks to varargs PyErr_Format(PyExc_SystemError, "PyType_GetBaseByToken called with token=NULL"); - ret = -1; + RETURN_RESULT(NULL, -1); } - else if (!PyType_Check(type)) { + if (!PyType_Check(type)) { PyErr_Format(PyExc_TypeError, "expected a type, got a '%T' object", type); - ret = -1; + RETURN_RESULT(NULL, -1); } - else if (type->tp_mro != NULL) { + + if (type->tp_mro != NULL) { base = get_base_by_token_from_mro(type, token); } else { @@ -5384,10 +5393,8 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) } // Finish in one place when profiling. Returning false is rare on the // PGO tests, but it should not be run in a cold path in practice. - if (result != NULL) { - *result = (PyTypeObject *)Py_XNewRef(base); - } - return base != NULL ? 1 : ret; + RETURN_RESULT(Py_XNewRef(base), base ? 1 : 0); +#undef RETURN_RESULT } From cd750ca8e07070c2cf74957a6f6dd739369d6174 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:31:31 +0900 Subject: [PATCH 84/86] ditto --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3360c05dc394e4..8c79db58206d6f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5372,7 +5372,6 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) // MSVC prefers no gotos here on PGO. This function can also be less // optimized if the given type is copied to a local variable directly. - PyTypeObject *base = NULL; if (token == NULL) { // This avoids being inlined thanks to varargs PyErr_Format(PyExc_SystemError, @@ -5385,6 +5384,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) RETURN_RESULT(NULL, -1); } + PyTypeObject *base; if (type->tp_mro != NULL) { base = get_base_by_token_from_mro(type, token); } From ca3043f12c33ba8e9888c29f7567f0149d5b23dc Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 05:23:37 +0000 Subject: [PATCH 85/86] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst diff --git a/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst b/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst new file mode 100644 index 00000000000000..2795f01989e777 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier +type checking, related to :pep:`489` and :pep:`630`. From 5ccf0b81b761d7adf36ef2c6f19b05b5838570dd Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:30:37 +0900 Subject: [PATCH 86/86] typo --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8c79db58206d6f..698f5dbf399ad6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5365,7 +5365,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) #define RETURN_RESULT(BASE, RET) \ do { \ if (result != NULL) { \ - *result = (PyTypeObject *)(BASE); \ + *result = (PyTypeObject *)(BASE); \ } \ return (RET); \ } while (0)