summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorneonene <53406459+neonene@users.noreply.github.com>2024-09-18 07:18:19 (GMT)
committerGitHub <noreply@github.com>2024-09-18 07:18:19 (GMT)
commit646f16bdeed6ebe1069e1d64886fbaa26edac75c (patch)
tree44baf199481af36108124d0a5015a28816041b9d
parent79a74102362996bbd4ff5d410a0d57d43c236da4 (diff)
downloadcpython-646f16bdeed6ebe1069e1d64886fbaa26edac75c.zip
cpython-646f16bdeed6ebe1069e1d64886fbaa26edac75c.tar.gz
cpython-646f16bdeed6ebe1069e1d64886fbaa26edac75c.tar.bz2
gh-124153: Implement `PyType_GetBaseByToken()` and `Py_tp_token` slot (GH-124163)
-rw-r--r--Doc/c-api/type.rst68
-rw-r--r--Doc/data/stable_abi.dat1
-rw-r--r--Doc/whatsnew/3.14.rst5
-rw-r--r--Include/cpython/object.h1
-rw-r--r--Include/object.h4
-rw-r--r--Include/typeslots.h4
-rw-r--r--Lib/test/test_capi/test_misc.py71
-rw-r--r--Lib/test/test_stable_abi_ctypes.py1
-rw-r--r--Lib/test/test_sys.py2
-rw-r--r--Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst2
-rw-r--r--Misc/stable_abi.toml8
-rw-r--r--Modules/_ctypes/_ctypes.c5
-rw-r--r--Modules/_ctypes/ctypes.h20
-rw-r--r--Modules/_testcapi/heaptype.c117
-rw-r--r--Objects/typeobject.c139
-rw-r--r--Objects/typeslots.inc1
-rwxr-xr-xObjects/typeslots.py6
-rwxr-xr-xPC/python3dll.c1
18 files changed, 443 insertions, 13 deletions
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index b25a407..b56da69 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -264,6 +264,24 @@ Type Objects
.. versionadded:: 3.11
+.. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result)
+
+ 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 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.
+
+ The *result* argument may be ``NULL``, in which case *\*result* is not set.
+ Use this if you need only the return value.
+
+ The *token* argument may not be ``NULL``.
+
+ .. versionadded:: 3.14
+
.. c:function:: int PyUnstable_Type_AssignVersionTag(PyTypeObject *type)
Attempt to assign a version tag to the given type.
@@ -488,6 +506,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`
@@ -538,4 +561,47 @@ 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` of 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
+
+ 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:
+
+ * 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
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 7eeee27..7c4b56d 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -690,6 +690,7 @@ func,PyType_FromSpec,3.2,,
func,PyType_FromSpecWithBases,3.3,,
func,PyType_GenericAlloc,3.2,,
func,PyType_GenericNew,3.2,,
+func,PyType_GetBaseByToken,3.14,,
func,PyType_GetFlags,3.2,,
func,PyType_GetFullyQualifiedName,3.13,,
func,PyType_GetModule,3.10,,
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 5e76233..53399aa 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -554,6 +554,11 @@ New Features
(Contributed by Victor Stinner in :gh:`107954`.)
+* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
+ superclass identification, which attempts to resolve the `type checking issue
+ <https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`
+ (:gh:`124153`).
+
Porting to Python 3.14
----------------------
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index e1024dd..9d09274 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -269,6 +269,7 @@ typedef struct _heaptypeobject {
struct _dictkeysobject *ht_cached_keys;
PyObject *ht_module;
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.
#ifdef Py_GIL_DISABLED
Py_ssize_t unique_id; // ID used for thread-local refcounting
diff --git a/Include/object.h b/Include/object.h
index abfdb6c..7124f58 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 e91caa1..a7f3017 100644
--- a/Include/typeslots.h
+++ b/Include/typeslots.h
@@ -90,3 +90,7 @@
/* New in 3.14 */
#define Py_tp_vectorcall 82
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
+/* New in 3.14 */
+#define Py_tp_token 83
+#endif
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index ebc0a8a..5c6faa1 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1144,6 +1144,77 @@ class CAPITest(unittest.TestCase):
MyType.__module__ = 123
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+ 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
+ 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)
+ 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
+ return found_in_mro, found_in_bases
+
+ create_type = _testcapi.create_type_with_token
+ get_token = _testcapi.get_tp_token
+
+ 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)
+
+ B1 = create_type('_testcapi.B1', id(self))
+ self.assertTrue(get_token(B1) == id(self))
+
+ tokenA1 = get_token(A1)
+ # find A1 from A1
+ 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 = get_base_by_token(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
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, A1)
+
+ # 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)
+ self.assertTrue(get_token(C1) == tokenA1)
+
+ # find C1 first by shared token
+ class Z(C1, A2): pass
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, C1)
+ # B1 not found
+ 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
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 4bca33b..4e509e3 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -719,6 +719,7 @@ SYMBOL_NAMES = (
"PyType_FromSpecWithBases",
"PyType_GenericAlloc",
"PyType_GenericNew",
+ "PyType_GetBaseByToken",
"PyType_GetFlags",
"PyType_GetFullyQualifiedName",
"PyType_GetModule",
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 42b5a7c..7700964 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1718,7 +1718,7 @@ class SizeofTest(unittest.TestCase):
'3P' # PyMappingMethods
'10P' # PySequenceMethods
'2P' # PyBufferProcs
- '6P'
+ '7P'
'1PIP' # Specializer cache
+ typeid # heap type id (free-threaded only)
)
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 0000000..b8c0b46
--- /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`.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 6036fc9..d8a9d1f 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2527,4 +2527,10 @@
[function.PyLong_AsUInt64]
added = '3.14'
[const.Py_tp_vectorcall]
- added = '3.14' \ No newline at end of file
+ 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/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 2b23be7..1d9e169 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); // NULL avoids segfault here
}
if (info) {
PyMem_Free(info->ffi_type_pointer.elements);
@@ -560,6 +560,7 @@ 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},
@@ -569,7 +570,7 @@ static PyType_Slot ctype_type_slots[] = {
{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 2eb1b6c..738dcd1 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -108,6 +108,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;
@@ -490,16 +491,23 @@ 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);
+ PyTypeObject *PyCType_Type;
+ if (PyType_GetBaseByToken(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0) {
+ return NULL;
+ }
+ if (PyCType_Type == NULL) {
+ PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type);
+ return NULL;
+ }
+
+ StgInfo *info = PyObject_GetTypeData(type, PyCType_Type);
+ Py_DECREF(PyCType_Type);
+ return info;
}
// Initialize StgInfo on a newly created type
diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c
index b3fb9ec..cc88147 100644
--- a/Modules/_testcapi/heaptype.c
+++ b/Modules/_testcapi/heaptype.c
@@ -410,6 +410,118 @@ pyobject_getitemdata(PyObject *self, PyObject *o)
}
+static PyObject *
+create_type_with_token(PyObject *module, PyObject *args)
+{
+ const char *name;
+ PyObject *py_token;
+ if (!PyArg_ParseTuple(args, "sO", &name, &py_token)) {
+ return NULL;
+ }
+ 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},
+ };
+ PyType_Spec spec = {
+ .name = name,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = slots,
+ };
+ return PyType_FromMetaclass(NULL, module, &spec, NULL);
+}
+
+static PyObject *
+get_tp_token(PyObject *self, PyObject *type)
+{
+ void *token = PyType_GetSlot((PyTypeObject *)type, Py_tp_token);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ return PyLong_FromVoidPtr(token);
+}
+
+static PyObject *
+pytype_getbasebytoken(PyObject *self, PyObject *args)
+{
+ PyTypeObject *type;
+ PyObject *py_token, *use_mro, *need_result;
+ if (!PyArg_ParseTuple(args, "OOOO",
+ &type, &py_token, &use_mro, &need_result)) {
+ return NULL;
+ }
+
+ 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;
+ }
+
+ void *token = PyLong_AsVoidPtr(py_token);
+ 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 (use_mro != Py_True) {
+ type->tp_mro = mro_save;
+ }
+ if (ret < 0) {
+ assert(result == NULL);
+ return NULL;
+ }
+ PyObject *py_ret = PyLong_FromLong(ret);
+ if (py_ret == NULL) {
+ goto error;
+ }
+ PyObject *tuple = PyTuple_New(2);
+ if (tuple == NULL) {
+ goto error;
+ }
+ 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;
+}
+
+
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 +535,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},
+ {"get_tp_token", get_tp_token, METH_O},
+ {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS},
{NULL},
};
@@ -1287,6 +1402,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) {
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 28edd80..68e481f 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3926,6 +3926,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;
#ifdef Py_GIL_DISABLED
_PyType_AssignId(et);
@@ -4984,6 +4985,11 @@ PyType_FromMetaclass(
}
}
break;
+ case Py_tp_token:
+ {
+ res->ht_token = slot->pfunc == Py_TP_USE_SPEC ? spec : slot->pfunc;
+ }
+ break;
default:
{
/* Copy other slots directly */
@@ -5144,8 +5150,15 @@ 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 >= (int)sizeof(PyTypeObject)) {
+ if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ return NULL;
+ }
+ }
+
+ parent_slot = *(void**)((char*)type + slot_offset);
if (parent_slot == NULL) {
return NULL;
}
@@ -5274,6 +5287,129 @@ _PyType_GetModuleByDef2(PyTypeObject *left, PyTypeObject *right,
return module;
}
+
+static PyTypeObject *
+get_base_by_token_recursive(PyTypeObject *type, void *token)
+{
+ assert(PyType_GetSlot(type, Py_tp_token) != token);
+ 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 = _PyType_CAST(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);
+ if (base != NULL) {
+ return base;
+ }
+ }
+ return NULL;
+}
+
+static inline PyTypeObject *
+get_base_by_token_from_mro(PyTypeObject *type, void *token)
+{
+ // 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 is supposed
+ // to be already checked by the caller. We skip it in the loop.
+ assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type);
+ assert(PyType_GetSlot(type, Py_tp_token) != token);
+
+ 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;
+}
+
+static int
+check_base_by_token(PyTypeObject *type, void *token) {
+ // Chain the branches, which will be optimized exclusive here
+ if (token == NULL) {
+ PyErr_Format(PyExc_SystemError,
+ "PyType_GetBaseByToken called with token=NULL");
+ return -1;
+ }
+ else if (!PyType_Check(type)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected a type, got a '%T' object", type);
+ return -1;
+ }
+ else if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ return 0;
+ }
+ else if (((PyHeapTypeObject*)type)->ht_token == token) {
+ return 1;
+ }
+ else if (type->tp_mro != NULL) {
+ // This will not be inlined
+ return get_base_by_token_from_mro(type, token) ? 1 : 0;
+ }
+ else {
+ return get_base_by_token_recursive(type, token) ? 1 : 0;
+ }
+}
+
+int
+PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result)
+{
+ if (result == NULL) {
+ // If the `result` is checked only once here, the subsequent
+ // branches will become trivial to optimize.
+ return check_base_by_token(type, token);
+ }
+ if (token == NULL || !PyType_Check(type)) {
+ *result = NULL;
+ return check_base_by_token(type, token);
+ }
+
+ // Chain the branches, which will be optimized exclusive here
+ PyTypeObject *base;
+ if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ // No static type has a heaptype superclass,
+ // which is ensured by type_ready_mro().
+ *result = NULL;
+ return 0;
+ }
+ else if (((PyHeapTypeObject*)type)->ht_token == token) {
+ *result = (PyTypeObject *)Py_NewRef(type);
+ return 1;
+ }
+ else if (type->tp_mro != NULL) {
+ // Expect this to be inlined
+ base = get_base_by_token_from_mro(type, token);
+ }
+ else {
+ base = get_base_by_token_recursive(type, token);
+ }
+
+ if (base != NULL) {
+ *result = (PyTypeObject *)Py_NewRef(base);
+ return 1;
+ }
+ else {
+ *result = NULL;
+ return 0;
+ }
+}
+
+
void *
PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls)
{
@@ -5966,6 +6102,7 @@ type_dealloc(PyObject *self)
#ifdef Py_GIL_DISABLED
_PyType_ReleaseId(et);
#endif
+ et->ht_token = NULL;
Py_TYPE(type)->tp_free((PyObject *)type);
}
diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc
index ffb85ff..642160f 100644
--- a/Objects/typeslots.inc
+++ b/Objects/typeslots.inc
@@ -81,3 +81,4 @@
{-1, offsetof(PyTypeObject, tp_finalize)},
{offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)},
{-1, offsetof(PyTypeObject, tp_vectorcall)},
+{-1, offsetof(PyHeapTypeObject, ht_token)},
diff --git a/Objects/typeslots.py b/Objects/typeslots.py
index 8ab05f9..c7f8a33 100755
--- a/Objects/typeslots.py
+++ b/Objects/typeslots.py
@@ -13,7 +13,11 @@ def generate_typeslots(out=sys.stdout):
continue
member = m.group(1)
- if member.startswith("tp_"):
+ 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})}}'
elif member.startswith("am_"):
member = (f'{{offsetof(PyAsyncMethods, {member}),'+
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 1845334..7bd04cb 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -651,6 +651,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)