diff --git a/.gitattributes b/.gitattributes index f4d65dfd1dfbd3..4d52f900b93179 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,6 +84,7 @@ Include/internal/pycore_uop_ids.h generated Include/internal/pycore_uop_metadata.h generated Include/opcode.h generated Include/opcode_ids.h generated +Include/slots_generated.h generated Include/token.h generated Lib/_opcode_metadata.py generated Lib/idlelib/help.html generated @@ -99,7 +100,6 @@ Lib/token.py generated Misc/sbom.spdx.json generated Modules/_testinternalcapi/test_cases.c.h generated Modules/_testinternalcapi/test_targets.h generated -Objects/typeslots.inc generated PC/python3dll.c generated Parser/parser.c generated Parser/token.c generated @@ -110,6 +110,7 @@ Python/generated_cases.c.h generated Python/optimizer_cases.c.h generated Python/opcode_targets.h generated Python/record_functions.c.h generated +Python/slots_generated.c generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated aclocal.m4 generated diff --git a/Doc/c-api/extension-modules.rst b/Doc/c-api/extension-modules.rst index 7bc04970b19503..34ee86c7876ae7 100644 --- a/Doc/c-api/extension-modules.rst +++ b/Doc/c-api/extension-modules.rst @@ -38,7 +38,7 @@ Extension export hook The export hook must be an exported function with the following signature: -.. c:function:: PyModuleDef_Slot *PyModExport_modulename(void) +.. c:function:: PySlot *PyModExport_modulename(void) For modules with ASCII-only names, the :ref:`export hook ` must be named :samp:`PyModExport_{}`, @@ -57,7 +57,7 @@ Python's *punycode* encoding with hyphens replaced by underscores. In Python: suffix = b'U_' + name.encode('punycode').replace(b'-', b'_') return b'PyModExport' + suffix -The export hook returns an array of :c:type:`PyModuleDef_Slot` entries, +The export hook returns an array of :c:type:`PySlot` entries, terminated by an entry with a slot ID of ``0``. These slots describe how the module should be created and initialized. @@ -75,7 +75,7 @@ It is recommended to define the export hook function using a helper macro: Declare an extension module export hook. This macro: - * specifies the :c:expr:`PyModuleDef_Slot*` return type, + * specifies the :c:expr:`PySlot*` return type, * adds any special linkage declarations required by the platform, and * for C++, declares the function as ``extern "C"``. @@ -83,12 +83,12 @@ For example, a module called ``spam`` would be defined like this:: PyABIInfo_VAR(abi_info); - static PyModuleDef_Slot spam_slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "spam"}, - {Py_mod_init, spam_init_function}, + static PySlot spam_slots[] = { + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_name, "spam"), + PySlot_FUNC(Py_mod_init, spam_init_function), ... - {0, NULL}, + PySlot_END }; PyMODEXPORT_FUNC diff --git a/Doc/c-api/index.rst b/Doc/c-api/index.rst index eabe00f4004001..051f6fd765e850 100644 --- a/Doc/c-api/index.rst +++ b/Doc/c-api/index.rst @@ -18,6 +18,7 @@ document the API functions in detail. refcounting.rst exceptions.rst extension-modules.rst + slots.rst utilities.rst abstract.rst concrete.rst diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index b67ca671a2a118..7229ea6c69ee46 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -133,14 +133,16 @@ Module Objects unencodable filenames, use :c:func:`PyModule_GetFilenameObject` instead. +.. _c_module_slots: .. _pymoduledef_slot: Module definition ----------------- Modules created using the C API are typically defined using an -array of :dfn:`slots`. -The slots provide a "description" of how a module should be created. +array of :c:type:`PySlot` structs, which provides a "description" of how a +module should be created. +See :ref:`capi-slots` for more information on slots in general. .. versionchanged:: 3.15 @@ -158,30 +160,12 @@ Unless specified otherwise, the same slot ID may not be repeated in an array of slots. -.. c:type:: PyModuleDef_Slot - - .. c:member:: int slot - - A slot ID, chosen from the available ``Py_mod_*`` values explained below. - - An ID of 0 marks the end of a :c:type:`!PyModuleDef_Slot` array. - - .. c:member:: void* value - - Value of the slot, whose meaning depends on the slot ID. - - The value may not be NULL. - To leave a slot out, omit the :c:type:`PyModuleDef_Slot` entry entirely. - - .. versionadded:: 3.5 - - Metadata slots .............. .. c:macro:: Py_mod_name - :c:type:`Slot ID ` for the name of the new module, + :c:member:`Slot ID ` for the name of the new module, as a NUL-terminated UTF8-encoded ``const char *``. Note that modules are typically created using a @@ -196,7 +180,7 @@ Metadata slots .. c:macro:: Py_mod_doc - :c:type:`Slot ID ` for the docstring of the new + :c:type:`Slot ID ` for the docstring of the new module, as a NUL-terminated UTF8-encoded ``const char *``. Usually it is set to a variable created with :c:macro:`PyDoc_STRVAR`. @@ -211,7 +195,7 @@ Feature slots .. c:macro:: Py_mod_abi - :c:type:`Slot ID ` whose value points to + :c:member:`Slot ID ` whose value points to a :c:struct:`PyABIInfo` structure describing the ABI that the extension is using. @@ -222,8 +206,8 @@ Feature slots PyABIInfo_VAR(abi_info); - static PyModuleDef_Slot mymodule_slots[] = { - {Py_mod_abi, &abi_info}, + static PySlot mymodule_slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), ... }; @@ -237,7 +221,7 @@ Feature slots .. c:macro:: Py_mod_multiple_interpreters - :c:type:`Slot ID ` whose value is one of: + :c:member:`Slot ID ` whose value is one of: .. c:namespace:: NULL @@ -267,7 +251,7 @@ Feature slots .. c:macro:: Py_mod_gil - :c:type:`Slot ID ` whose value is one of: + :c:member:`Slot ID ` whose value is one of: .. c:namespace:: NULL @@ -296,7 +280,7 @@ Creation and initialization slots .. c:macro:: Py_mod_create - :c:type:`Slot ID ` for a function that creates + :c:member:`Slot ID ` for a function that creates the module object itself. The function must have the signature: @@ -346,7 +330,7 @@ Creation and initialization slots .. c:macro:: Py_mod_exec - :c:type:`Slot ID ` for a function that will + :c:member:`Slot ID ` for a function that will :dfn:`execute`, or initialize, the module. This function does the equivalent to executing the code of a Python module: typically, it adds classes and constants to the module. @@ -375,7 +359,7 @@ Creation and initialization slots .. c:macro:: Py_mod_methods - :c:type:`Slot ID ` for a table of module-level + :c:member:`Slot ID ` for a table of module-level functions, as an array of :c:type:`PyMethodDef` values suitable as the *functions* argument to :c:func:`PyModule_AddFunctions`. @@ -446,12 +430,12 @@ To retrieve the state from a given module, use the following functions: Slots for defining module state ............................... -The following :c:member:`PyModuleDef_Slot.slot` IDs are available for +The following :c:member:`slot IDs ` are available for defining the module state. .. c:macro:: Py_mod_state_size - :c:type:`Slot ID ` for the size of the module state, + :c:member:`Slot ID ` for the size of the module state, in bytes. Setting the value to a non-negative value means that the module can be @@ -468,7 +452,7 @@ defining the module state. .. c:macro:: Py_mod_state_traverse - :c:type:`Slot ID ` for a traversal function to call + :c:member:`Slot ID ` for a traversal function to call during GC traversal of the module object. The signature of the function, and meanings of the arguments, @@ -491,7 +475,7 @@ defining the module state. .. c:macro:: Py_mod_state_clear - :c:type:`Slot ID ` for a clear function to call + :c:member:`Slot ID ` for a clear function to call during GC clearing of the module object. The signature of the function is: @@ -519,7 +503,7 @@ defining the module state. .. c:macro:: Py_mod_state_free - :c:type:`Slot ID ` for a function to call during + :c:member:`Slot ID ` for a function to call during deallocation of the module object. The signature of the function is: @@ -578,7 +562,7 @@ A module's token -- and the *your_token* value to use in the above code -- is: .. c:macro:: Py_mod_token - :c:type:`Slot ID ` for the module token. + :c:member:`Slot ID ` for the module token. If you use this slot to set the module token (rather than rely on the default), you must ensure that: @@ -617,14 +601,14 @@ Creating extension modules dynamically The following functions may be used to create an extension module dynamically, rather than from an extension's :ref:`export hook `. -.. c:function:: PyObject *PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec) +.. c:function:: PyObject *PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec) Create a new module object, given an array of :ref:`slots ` and the :py:class:`~importlib.machinery.ModuleSpec` *spec*. - The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot` + The *slots* argument must point to an array of :c:type:`PySlot` structures, terminated by an entry with slot ID of 0 - (typically written as ``{0}`` or ``{0, NULL}`` in C). + (typically written as :c:macro:`PySlot_END`). The array must include a :c:data:`Py_mod_abi` entry. The *spec* argument may be any ``ModuleSpec``-like object, as described @@ -640,10 +624,6 @@ rather than from an extension's :ref:`export hook `. must be called to fully initialize a module. (See also :ref:`multi-phase-initialization`.) - The *slots* array only needs to be valid for the duration of the - :c:func:`!PyModule_FromSlotsAndSpec` call. - In particular, it may be heap-allocated. - .. versionadded:: 3.15 .. c:function:: int PyModule_Exec(PyObject *module) @@ -738,6 +718,8 @@ remove it. .. c:member:: PyModuleDef_Slot* m_slots An array of additional slots, terminated by a ``{0, NULL}`` entry. + Note that the entries use the older :c:type:`PyModuleDef_Slot` structure, + rather than :c:type:`PySlot`. If the array contains slots corresponding to :c:type:`PyModuleDef` members, the values must match. @@ -752,6 +734,39 @@ remove it. .. c:member:: inquiry m_reload + .. c:namespace:: NULL + + .. c:type:: PyModuleDef_Slot + + Older structure defining additional slots of a module. + + Note that a :c:type:`!PyModuleDef_Slot` array may be included in a + :c:type:`!PySlot` array using :c:macro:`Py_mod_slots`, + and vice versa using :c:macro:`Py_slot_subslots`. + + Each :c:type:`!PyModuleDef_Slot` structure ``modslot`` is interpreted + as the following :c:type:`PySlot` structure:: + + (PySlot){ + .sl_id=modslot.slot, + .sl_flags=PySlot_INTPTR | sub_static, + .sl_ptr=modslot.value + } + + where ``sub_static`` is ``PySlot_STATIC`` if the slot requires + the flag (such as for :c:macro:`Py_mod_methods`), or if this flag + is present on the "parent" :c:macro:`!Py_mod_slots` slot (if any). + + .. c:member:: int slot + + Corresponds to :c:member:`PySlot.sl_id`. + + .. c:member:: void* value + + Corresponds to :c:member:`PySlot.sl_ptr`. + + .. versionadded:: 3.5 + .. c:member:: traverseproc m_traverse inquiry m_clear freefunc m_free @@ -774,6 +789,14 @@ remove it. The type of ``PyModuleDef`` objects. +.. c:macro:: Py_mod_slots + + :c:member:`Slot ID ` that works like + :c:macro:`Py_slot_subslots`, except it specifies an array of + :c:type:`PyModuleDef_Slot` structures. + + .. versionadded:: next + .. _moduledef-dynamic: The following API can be used to create modules from a :c:type:`!PyModuleDef` @@ -813,6 +836,10 @@ struct: .. versionadded:: 3.5 + .. soft-deprecated:: next + + Prefer :c:func:`PyModule_FromSlotsAndSpec` in new code. + .. c:function:: PyObject * PyModule_FromDefAndSpec2(PyModuleDef *def, PyObject *spec, int module_api_version) Create a new module object, given the definition in *def* and the @@ -833,12 +860,22 @@ struct: .. versionadded:: 3.5 + .. soft-deprecated:: next + + Prefer :c:func:`PyModule_FromSlotsAndSpec` in new code. + .. c:function:: int PyModule_ExecDef(PyObject *module, PyModuleDef *def) Process any execution slots (:c:data:`Py_mod_exec`) given in *def*. .. versionadded:: 3.5 + .. soft-deprecated:: next + + To run a module's own execution slots, prefer :c:func:`PyModule_Exec`, + which works on modules that were not created from a + :c:type:`PyModuleDef` structure. + .. c:macro:: PYTHON_API_VERSION PYTHON_API_STRING diff --git a/Doc/c-api/slots.rst b/Doc/c-api/slots.rst new file mode 100644 index 00000000000000..6461274b8e092e --- /dev/null +++ b/Doc/c-api/slots.rst @@ -0,0 +1,240 @@ +.. highlight:: c + +.. _capi-slots: + +Definition slots +================ + +To define :ref:`module objects ` and +:ref:`classes ` using the C API, you may use +an array of *slots* -- essentally, key-value pairs that describe features +of the object to create. +This decouples the data from the structures used at runtime, allowing CPython +-- and other Python C API implementations -- to update the stuctures without +breaking backwards compatibility. + +This section documents slots in general. +For object-specific behavior and slot values, see documentation for functions +that apply slots: + +- :c:func:`PyType_FromSlots` for types; +- :c:func:`PyModule_FromSlotsAndSpec` and :ref:`extension-export-hook` + for modules. + +When slots are passed to a function that applies them, the function will +not modify the slot array, nor any data it points to (recursively). +After the function is done, the caller is allowed to modify or deallocate +the array and any data it points to (recursively), except data +explicitly marked with :c:macro:`PySlot_STATIC`. + +Except when documented otherwise, multiple slots with the same ID +(:c:member:`~PySlot.sl_id`) may not occur in a single slots array. + +.. versionadded:: next + + Slot arrays generalize an earlier way of defining objects: + using :c:type:`PyType_Spec` with :c:type:`PyType_Slot` for types, and + :c:type:`PyModuleDef` with :c:type:`PyModuleDef_Slot` for modules. + The earlier API is :term:`soft deprecated`; there are no plans to remove it. + +Entries of the slots array use the following structure: + +.. c:type:: PySlot + + An entry in a slots array. Defined as: + + .. code-block:: c + + typedef struct { + uint16_t sl_id; + uint16_t sl_flags; + uint32_t _reserved; // must be 0 + union { + void *sl_ptr; + void (*sl_func)(void); + Py_ssize_t sl_size; + int64_t sl_int64; + uint64_t sl_uint64; + }; + } PySlot; + + .. c:member:: uint16_t sl_id + + A slot ID, chosen from: + + - ``Py_slot_*`` values documented in :ref:`pyslot-common-ids` below; + - ``Py_mod_*`` values for modules, as documented in :ref:`c_module_slots`; + - Values for types, as documented in :ref:`pyslot_type_slot_ids`. + + A :c:member:`!sl_id` of zero (:c:macro:`Py_slot_end`) marks the end of a + slots array. + + .. c:member:: void *sl_ptr + void (*sl_func)(void) + Py_ssize_t sl_size + int64_t sl_int64 + uint64_t sl_uint64 + + The data for the slot. + These members are part of an anonymous union; + the member to use depends on which data type is required by the slot ID: + data pointer, function pointer, size, signed or unsigned + integer, respectively. + + Except when documented otherwise for a specific slot ID, pointers + (that is :c:member:`!sl_ptr` and :c:member:`!sl_func`) may not be NULL. + + .. c:member:: uint16_t sl_flags + + Zero or more of the following flags, OR-ed together: + + .. c:namespace:: NULL + + .. c:macro:: PySlot_STATIC + + All data the slot points to is statically allocated and constant. + Thus, the interpreter does not need to copy the information. + + This flag is implied for function pointers. + + The flag applies even to data the slot points to “indirectly”, + except for slots nested via :c:macro:`Py_slot_subslots` which may + have their own :c:macro:`!PySlot_STATIC`` flags. + For example, if applied to a :c:macro:`Py_tp_members` slot that + points to an array of :c:type:`PyMemberDef` structures, + then the entire array, as well as the name and doc strings + in its elements, must be static and constant. + + .. c:macro:: PySlot_INTPTR + + The data is stored in ``sl_ptr``; CPython will cast it to + the appropriate type. + + This flag can simplify porting from the older :c:type:`PyType_Slot` + and :c:type:`PyModuleDef_Slot` structures. + + .. c:macro:: PySlot_OPTIONAL + + If the slot ID is unknown, the interpreter should ignore the + slot, rather than fail. + + For example, if Python 3.16 adds a new feature with a new slot ID,attr + the corresponding slot may be marked :c:macro:`!PySlot_OPTIONAL` + so that Python 3.15 ignores it. + + Note that the "optionality" only applies to unknown slot IDs. + This flag does not make Python skip invalid values of known slots. + + .. versionadded:: next + + +Convenience macros +------------------ + +.. c:macro:: PySlot_DATA(name, value) + PySlot_FUNC(name, value) + PySlot_SIZE(name, value) + PySlot_INT64(name, value) + PySlot_UINT64(name, value) + PySlot_STATIC_DATA(name, value) + + Convenience macros to define :c:type:`!PySlot` structures with + :c:member:`~PySlot.sl_id` and a particular union member set. + + :c:macro:`!PySlot_STATIC_DATA` sets the :c:macro:`PySlot_STATIC` flag; + others set no flags. + + Note that these macros use *designated initializers*, a C language feature + that C++ added in the 2000 version of the standard. + If your code needs to be compatible with C++11 or older, + use :c:macro:`PySlot_PTR` instead. + + Defined as:: + + #define PySlot_DATA(NAME, VALUE) \ + {.sl_id=NAME, .sl_ptr=(void*)(VALUE)} + + #define PySlot_FUNC(NAME, VALUE) \ + {.sl_id=NAME, .sl_func=(VALUE)} + + #define PySlot_SIZE(NAME, VALUE) \ + {.sl_id=NAME, .sl_size=(VALUE)} + + #define PySlot_INT64(NAME, VALUE) \ + {.sl_id=NAME, .sl_int64=(VALUE)} + + #define PySlot_UINT64(NAME, VALUE) \ + {.sl_id=NAME, .sl_uint64=(VALUE)} + + #define PySlot_STATIC_DATA(NAME, VALUE) \ + {.sl_id=NAME, .sl_flags=PySlot_STATIC, .sl_ptr=(VALUE)} + + .. versionadded:: next + +.. c:macro:: PySlot_END + + Convenience macros to mark the end of a :c:type:`!PySlot` array. + + Defined as:: + + #define PySlot_END {0} + + .. versionadded:: next + +.. c:macro:: PySlot_PTR(name, value) + PySlot_PTR_STATIC(name, value) + + Convenience macros for use in C++11-compatible code. + This version of C++ does not allow setting arbitrary union members in + literals, these macros set the :c:macro:`PySlot_INTPTR` flag and cast + the value to ``(void*)``. + + Defined as:: + + #define PySlot_PTR(NAME, VALUE) \ + {NAME, PySlot_INTPTR, {0}, {(void*)(VALUE)}} + + #define PySlot_PTR_STATIC(NAME, VALUE) \ + {NAME, PySlot_INTPTR|Py_SLOT_STATIC, {0}, {(void*)(VALUE)}} + + .. versionadded:: next + +.. _pyslot-common-ids: + +Common slot IDs +--------------- + +The following slot IDs may be used in both type and module definitions. + +.. c:macro:: Py_slot_end + + Marks the end of a slots array. + Defined as zero. + + .. versionadded:: next + +.. c:macro:: Py_slot_subslots + + Nested slots array. + + The value (:c:member:`~PySlot.sl_ptr`) should point to an array of + :c:type:`PySlot` structures. + The slots in the array (up to but not including the zero-ID + terminator) will be treated as if they were inserted if the current + slot array, at the point :c:macro:`!Py_slot_subslots` appears. + + Slot nesting depth is limited to 5 levels. + This restriction may be lifted in the future. + + .. versionadded:: next + +.. c:macro:: Py_slot_invalid + + Reserved; will always be treated as an unknown slot ID. + Defined as ``UINT16_MAX`` (``0xFFFF``). + + When used with the :c:macro:`PySlot_OPTIONAL` flag, defines a slot with + no effect. + Without the flag, processing a slot with this ID will fail. + + .. versionadded:: next diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index c9bb5c3f09ac18..c1e4fc27d57269 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -3,7 +3,7 @@ .. _typeobjects: Type Objects ------------- +============ .. index:: pair: object; type @@ -384,36 +384,19 @@ Type Objects * :py:mod:`weakref` -Creating Heap-Allocated Types -............................. - -The following functions and structs are used to create -:ref:`heap types `. +.. _creating-heap-types: -.. c:function:: PyObject* PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases) +Creating Heap-Allocated Types +----------------------------- - Create and return a :ref:`heap type ` from the *spec* - (see :c:macro:`Py_TPFLAGS_HEAPTYPE`). +The following function is used to create :ref:`heap types `: - The metaclass *metaclass* is used to construct the resulting type object. - When *metaclass* is ``NULL``, the metaclass is derived from *bases* - (or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below). +.. c:function:: PyObject *PyType_FromSlots(const PySlot *slots) - Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not - supported, except if ``tp_new`` is ``NULL``. - - The *bases* argument can be used to specify base classes; it can either - be only one class or a tuple of classes. - If *bases* is ``NULL``, the :c:data:`Py_tp_bases` slot is used instead. - If that also is ``NULL``, the :c:data:`Py_tp_base` slot is used instead. - If that also is ``NULL``, the new type derives from :class:`object`. - - The *module* argument can be used to record the module in which the new - class is defined. It must be a module object or ``NULL``. - If not ``NULL``, the module is associated with the new type and can later be - retrieved with :c:func:`PyType_GetModule`. - The associated module is not inherited by subclasses; it must be specified - for each class individually. + Create and return a :ref:`heap type ` from a :c:type:`!PySlot` + array. + See :ref:`capi-slots` for general information on slots, + and :ref:`pyslot_type_slot_ids` for slots specific to type creation. This function calls :c:func:`PyType_Ready` on the new type. @@ -430,8 +413,371 @@ The following functions and structs are used to create * :py:meth:`~object.__init_subclass__` is not called on any bases. * :py:meth:`~object.__set_name__` is not called on new descriptors. + Slots are typically defined as a global static constant arrays. + However, sometimes slot values are not statically known at compile time. + For example, slots like :c:data:`Py_tp_bases`, :c:data:`Py_tp_metaclass` + and :c:data:`Py_tp_module` require live Python objects. + In this case, it is recommended to put such slots on the stack, + and use :c:macro:`Py_slot_subslots` to refer to an array of static slots. + For example:: + + static const PySlot my_slots[] = { + PySlot_STATIC_DATA(Py_tp_name, "MyClass"), + PySlot_FUNC(Py_tp_repr, my_repr_func), + ... + PySlot_END + }; + + PyObject *make_my_class(PyObject *module) { + PySlot all_slots[] = { + PySlot_STATIC_DATA(Py_slot_subslots, my_slots), + PySlot_DATA(Py_tp_module, module), + PySlot_END + }; + return PyType_FromSlots(all_slots); + } + +Heap types created without the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag may be +modified, for example by setting attributes on them, as with classes defined +in Python code. +Sometimes, such modifications are necessary to fully initialize a type, +but you may wish to prevent users from changing the type after +the initialization is done: + +.. c:function:: int PyType_Freeze(PyTypeObject *type) + + Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag. + + All base classes of *type* must be immutable. + + On success, return ``0``. + On error, set an exception and return ``-1``. + + The type must not be used before it's made immutable. For example, type + instances must not be created before the type is made immutable. + + .. versionadded:: 3.14 + + +.. _pyslot_type_slot_ids: + +Type slot IDs +............. + +Most type slot IDs are named like the field names of the structures +:c:type:`PyTypeObject`, :c:type:`PyNumberMethods`, +:c:type:`PySequenceMethods`, :c:type:`PyMappingMethods` and +:c:type:`PyAsyncMethods` with an added ``Py_`` prefix. +For example, use: + +* :c:data:`Py_tp_dealloc` to set :c:member:`PyTypeObject.tp_dealloc` +* :c:data:`Py_nb_add` to set :c:member:`PyNumberMethods.nb_add` +* :c:data:`Py_sq_length` to set :c:member:`PySequenceMethods.sq_length` + +The following slots need additional considerations when specified as slots: + +* :c:data:`Py_tp_name` +* :c:data:`Py_tp_basicsize` and :c:data:`Py_tp_extra_basicsize` +* :c:data:`Py_tp_itemsize` +* :c:data:`Py_tp_flags` + +Additional slots do not directly correspond to a :c:type:`!PyTypeObject` +struct field: + +* :c:data:`Py_tp_token` +* :c:data:`Py_tp_metaclass` +* :c:data:`Py_tp_module` + +The following “offset” fields cannot be set using :c:type:`PyType_Slot`: + +* :c:member:`~PyTypeObject.tp_weaklistoffset` + (use :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` instead if possible) +* :c:member:`~PyTypeObject.tp_dictoffset` + (use :c:macro:`Py_TPFLAGS_MANAGED_DICT` instead if possible) +* :c:member:`~PyTypeObject.tp_vectorcall_offset` + (use ``"__vectorcalloffset__"`` in :ref:`PyMemberDef `) + +If it is not possible to switch to a ``MANAGED`` flag (for example, +for vectorcall or to support Python older than 3.12), specify the +offset in :c:data:`Py_tp_members`. +See :ref:`PyMemberDef documentation ` +for details. + +The following internal fields cannot be set at all when creating a heap +type: + +* :c:member:`~PyTypeObject.tp_dict`, + :c:member:`~PyTypeObject.tp_mro`, + :c:member:`~PyTypeObject.tp_cache`, + :c:member:`~PyTypeObject.tp_subclasses`, and + :c:member:`~PyTypeObject.tp_weaklist`. + +The :c:data:`Py_tp_base` slot is equivalent to :c:data:`Py_tp_bases`; +both may be set either to a type or a tuple of types. +If both are specified, the value of :c:data:`Py_tp_bases` +is used. + +Slot values may not be ``NULL``, except for the following: + +* :c:data:`Py_tp_doc` +* :c:data:`Py_tp_token` (for clarity, prefer :c:data:`Py_TP_USE_SPEC` + rather than ``NULL``) + +.. versionchanged:: 3.9 + Slots in :c:type:`PyBufferProcs` may be set in the unlimited API. + +.. versionchanged:: 3.11 + :c:member:`~PyBufferProcs.bf_getbuffer` and + :c:member:`~PyBufferProcs.bf_releasebuffer` are now available + under the :ref:`limited API `. + +.. versionchanged:: 3.14 + The field :c:member:`~PyTypeObject.tp_vectorcall` can now be set + using :c:data:`Py_tp_vectorcall`. See the field's documentation + for details. + +The following slots correspond to fields in the underlying type structure, +but need extra remarks for use as slots: + +.. c:macro:: Py_tp_name + + :c:member:`Slot ID ` for the name of the type, + used to set :c:member:`PyTypeObject.tp_name`. + + This slot (or :c:func:`PyType_Spec.name`) is required to create a type. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use :c:func:`PyType_Spec.name` instead. + + .. impl-detail:: + + CPython processes slots in order. + It is recommended to put ``Py_tp_name`` at the beginning of the slots + array, so that if processing of a later slots fails, error messages + can include the name. + + .. versionadded:: next + +.. c:macro:: Py_tp_basicsize + + :c:member:`Slot ID ` for the size of the instance in bytes. + It is used to set :c:member:`PyTypeObject.tp_basicsize`. + + The value must be positive. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use :c:func:`PyType_Spec.basicsize` instead. + + This slot may not be used with :c:func:`PyType_GetSlot`. + Use :c:member:`PyTypeObject.tp_basicsize` instead if needed, but be aware + that a type's size is often considered an implementation detail. + + .. versionadded:: next + +.. c:macro:: Py_tp_extra_basicsize + + :c:member:`Slot ID ` for type data size in bytes, that is, + how much space instances of the class need *in addition* + to space needed for superclasses. + + The value is used, together with the size of superclasses, to set + :c:member:`PyTypeObject.tp_basicsize`. + Python will insert padding as needed to meet + :c:member:`!tp_basicsize`'s alignment requirements. + + Use :c:func:`PyObject_GetTypeData` to get a pointer to subclass-specific + memory reserved this way. + + The value must be positive. + To specify that instances need no additional size (that is, size should be + inherited), omit the :c:macro:`!Py_tp_extra_basicsize` slot rather than + set it to zero. + + Specifying both :c:macro:`Py_tp_basicsize` and + :c:macro:`!Py_tp_extra_basicsize` is an error. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use negative :c:func:`PyType_Spec.basicsize` instead. + + This slot may not be used with :c:func:`PyType_GetSlot`. + + .. versionadded:: next + +.. c:macro:: Py_tp_itemsize + + :c:member:`Slot ID ` for the size of one element of a + variable-size type, in bytes. + Used to set :c:member:`PyTypeObject.tp_itemsize`. + See :c:member:`!tp_itemsize` documentation for caveats. + + The value must be positive. + + If this slot is missing, :c:member:`~PyTypeObject.tp_itemsize` is inherited. + Extending arbitrary variable-sized classes is dangerous, + since some types use a fixed offset for variable-sized memory, + which can then overlap fixed-sized memory used by a subclass. + To help prevent mistakes, inheriting ``itemsize`` is only possible + in the following situations: + + - The base is not variable-sized (its + :c:member:`~PyTypeObject.tp_itemsize`). + - The requested :c:member:`PyType_Spec.basicsize` is positive, + suggesting that the memory layout of the base class is known. + - The requested :c:member:`PyType_Spec.basicsize` is zero, + suggesting that the subclass does not access the instance's memory + directly. + - With the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use :c:func:`PyType_Spec.itemsize` instead. + + This slot may not be used with :c:func:`PyType_GetSlot`. + + .. versionadded:: next + +.. c:macro:: Py_tp_flags + + :c:member:`Slot ID ` for type flags, used to set + :c:member:`PyTypeObject.tp_flags`. + + The ``Py_TPFLAGS_HEAPTYPE`` flag is not set, + :c:func:`PyType_FromSpecWithBases` sets it automatically. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use negative :c:func:`PyType_Spec.basicsize` instead. + + This slot may not be used with :c:func:`PyType_GetSlot`. + Use :c:func:`PyType_GetFlags` instead. + + .. versionadded:: next + +The following slots do not correspond to public fields in the +underlying structures: + +.. c:macro:: Py_tp_metaclass + + :c:member:`Slot ID ` for the metaclass used to construct + the resulting type object. + When omitted the metaclass is derived from bases + (:c:macro:`Py_tp_bases` or the *bases* argument of + :c:func:`PyType_FromMetaclass`). + + Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not + supported, except if ``tp_new`` is ``NULL``. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use :c:func:`PyType_FromMetaclass` to specify a metaclass with + :c:type:`!PyType_Spec`. + + This slot may not be used with :c:func:`PyType_GetSlot`. + Use :c:func:`Py_TYPE` on the type object instead. + + .. versionadded:: next + +.. c:macro:: Py_tp_module + + :c:member:`Slot ID ` for recording the module in which + the new class is defined. + + The value must be a module object. + The module is associated with the new type and can later be + retrieved with :c:func:`PyType_GetModule`. + The associated module is not inherited by subclasses; it must be specified + for each class individually. + + This may not be used in :c:member:`PyType_Spec.slots`. + Use :c:func:`PyType_FromMetaclass` to specify a module with + :c:type:`!PyType_Spec`. + + This slot may not be used with :c:func:`PyType_GetSlot`. + Use :c:func:`PyType_GetModule` instead. + + .. versionadded:: next + +.. c:macro:: Py_tp_token + + :c:member:`Slot ID ` for recording a static memory layout ID + for a class. + + If the class is defined using a :c:type:`PyType_Spec`, and that spec 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`. + May only be used for classes defined using :c:type:`!PyType_Spec`. + + Expands to ``NULL``. + + .. versionadded:: 3.14 + +.. c:macro:: Py_tp_slots + + :c:member:`Slot ID ` that works like + :c:macro:`Py_slot_subslots`, except it specifies an array of + :c:type:`PyType_Slot` structures. + + .. versionadded:: next + + +Soft-deprecated API +------------------- + +The following functions are :term:`soft deprecated`. +They will continue to work, but new features will be added as slots for +:c:func:`PyType_FromSlots`, not as arguments to new ``PyType_From*`` functions. + +.. c:function:: PyObject* PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases) + + Create and return a :ref:`heap type ` from the *spec* + (see :c:macro:`Py_TPFLAGS_HEAPTYPE`). + + A non-``NULL`` *metaclass* argument corresponds to the + :c:macro:`Py_tp_metaclass` slot. + + A non-``NULL`` *bases* argument corresponds to the :c:data:`Py_tp_bases` + slot, and takes precedence over :c:data:`Py_tp_bases` and + :c:data:`Py_tp_bases` slots. + + A non-``NULL`` *module* argument corresponds to the + :c:macro:`Py_tp_module` slot. + + This function calls :c:func:`PyType_Ready` on the new type. + + Note that this function does *not* fully match the behavior of + calling :py:class:`type() ` or using the :keyword:`class` statement. + See the note in :c:func:`PyType_FromSlots` documentation for details. + .. versionadded:: 3.12 + .. soft-deprecated:: next + + Prefer :c:func:`PyType_FromSlots` in new code. + .. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases) @@ -459,6 +805,10 @@ The following functions and structs are used to create Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new` is no longer allowed. + .. soft-deprecated:: next + + Prefer :c:func:`PyType_FromSlots` in new code. + .. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) @@ -481,6 +831,10 @@ The following functions and structs are used to create Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new` is no longer allowed. + .. soft-deprecated:: next + + Prefer :c:func:`PyType_FromSlots` in new code. + .. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec) @@ -502,20 +856,9 @@ The following functions and structs are used to create Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new` is no longer allowed. + .. soft-deprecated:: next -.. c:function:: int PyType_Freeze(PyTypeObject *type) - - Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag. - - All base classes of *type* must be immutable. - - On success, return ``0``. - On error, set an exception and return ``-1``. - - The type must not be used before it's made immutable. For example, type - instances must not be created before the type is made immutable. - - .. versionadded:: 3.14 + Prefer :c:func:`PyType_FromSlots` in new code. .. raw:: html @@ -528,27 +871,23 @@ The following functions and structs are used to create .. c:type:: PyType_Spec - Structure defining a type's behavior. + Structure defining a type's behavior, used for soft-deprecated functions + like :c:func:`PyType_FromMetaclass`. + + This structure contains several members that can instead be specified + as :ref:`slots ` for :c:func:`PyType_FromSlots`, + and an array of slot entries with a simpler structure. .. c:member:: const char* name - Name of the type, used to set :c:member:`PyTypeObject.tp_name`. + Corresponds to :c:macro:`Py_tp_name`. .. c:member:: int basicsize - If positive, specifies the size of the instance in bytes. - It is used to set :c:member:`PyTypeObject.tp_basicsize`. - - If zero, specifies that :c:member:`~PyTypeObject.tp_basicsize` - should be inherited. + If positive, corresponds to :c:macro:`Py_tp_basicsize`. - If negative, the absolute value specifies how much space instances of the - class need *in addition* to the superclass. - Use :c:func:`PyObject_GetTypeData` to get a pointer to subclass-specific - memory reserved this way. - For negative :c:member:`!basicsize`, Python will insert padding when - needed to meet :c:member:`~PyTypeObject.tp_basicsize`'s alignment - requirements. + If negative, corresponds to :c:macro:`Py_tp_extra_basicsize` set to + the absolute value. .. versionchanged:: 3.12 @@ -556,160 +895,53 @@ The following functions and structs are used to create .. c:member:: int itemsize - Size of one element of a variable-size type, in bytes. - Used to set :c:member:`PyTypeObject.tp_itemsize`. - See ``tp_itemsize`` documentation for caveats. - - If zero, :c:member:`~PyTypeObject.tp_itemsize` is inherited. - Extending arbitrary variable-sized classes is dangerous, - since some types use a fixed offset for variable-sized memory, - which can then overlap fixed-sized memory used by a subclass. - To help prevent mistakes, inheriting ``itemsize`` is only possible - in the following situations: - - - The base is not variable-sized (its - :c:member:`~PyTypeObject.tp_itemsize`). - - The requested :c:member:`PyType_Spec.basicsize` is positive, - suggesting that the memory layout of the base class is known. - - The requested :c:member:`PyType_Spec.basicsize` is zero, - suggesting that the subclass does not access the instance's memory - directly. - - With the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag. + Corresponds to :c:macro:`Py_tp_itemsize`. .. c:member:: unsigned int flags - Type flags, used to set :c:member:`PyTypeObject.tp_flags`. - - If the ``Py_TPFLAGS_HEAPTYPE`` flag is not set, - :c:func:`PyType_FromSpecWithBases` sets it automatically. + Corresponds to :c:macro:`Py_tp_flags`. .. c:member:: PyType_Slot *slots - Array of :c:type:`PyType_Slot` structures. - Terminated by the special slot value ``{0, NULL}``. + Array of :c:type:`PyType_Slot` (not :c:type:`PySlot`) structures. + Terminated by the special slot value ``{0, NULL}``. Each slot ID should be specified at most once. -.. raw:: html - - - - - -.. c:type:: PyType_Slot - - Structure defining optional functionality of a type, containing a slot ID - and a value pointer. - - .. c:member:: int slot + .. c:namespace:: NULL - A slot ID. + .. raw:: html - Slot IDs are named like the field names of the structures - :c:type:`PyTypeObject`, :c:type:`PyNumberMethods`, - :c:type:`PySequenceMethods`, :c:type:`PyMappingMethods` and - :c:type:`PyAsyncMethods` with an added ``Py_`` prefix. - For example, use: + + + - * :c:data:`Py_tp_dealloc` to set :c:member:`PyTypeObject.tp_dealloc` - * :c:data:`Py_nb_add` to set :c:member:`PyNumberMethods.nb_add` - * :c:data:`Py_sq_length` to set :c:member:`PySequenceMethods.sq_length` + .. c:type:: PyType_Slot - An additional slot is supported that does not correspond to a - :c:type:`!PyTypeObject` struct field: + Structure defining optional functionality of a type, used for + soft-deprecated functions like :c:func:`PyType_FromMetaclass`. - * :c:data:`Py_tp_token` + Note that a :c:type:`!PyType_Slot` array may be included in a + :c:type:`!PySlot` array using :c:macro:`Py_tp_slots`, + and vice versa using :c:macro:`Py_slot_subslots`. - The following “offset” fields cannot be set using :c:type:`PyType_Slot`: + Each :c:type:`!PyType_Slot` structure ``tpslot`` is interpreted + as the following :c:type:`PySlot` structure:: - * :c:member:`~PyTypeObject.tp_weaklistoffset` - (use :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` instead if possible) - * :c:member:`~PyTypeObject.tp_dictoffset` - (use :c:macro:`Py_TPFLAGS_MANAGED_DICT` instead if possible) - * :c:member:`~PyTypeObject.tp_vectorcall_offset` - (use ``"__vectorcalloffset__"`` in - :ref:`PyMemberDef `) + (PySlot){ + .sl_id=tpslot.slot, + .sl_flags=PySlot_INTPTR | sub_static, + .sl_ptr=tpslot.func + } - If it is not possible to switch to a ``MANAGED`` flag (for example, - for vectorcall or to support Python older than 3.12), specify the - offset in :c:data:`Py_tp_members`. - See :ref:`PyMemberDef documentation ` - for details. + where ``sub_static`` is ``PySlot_STATIC`` if the slot requires + the flag (such as for :c:macro:`Py_tp_methods`), or if this flag + is present on the "parent" :c:macro:`!Py_tp_slots` slot (if any). - The following internal fields cannot be set at all when creating a heap - type: + .. c:member:: int slot - * :c:member:`~PyTypeObject.tp_dict`, - :c:member:`~PyTypeObject.tp_mro`, - :c:member:`~PyTypeObject.tp_cache`, - :c:member:`~PyTypeObject.tp_subclasses`, and - :c:member:`~PyTypeObject.tp_weaklist`. + Corresponds to :c:member:`PySlot.sl_id`. - Setting :c:data:`Py_tp_bases` or :c:data:`Py_tp_base` may be - problematic on some platforms. - To avoid issues, use the *bases* argument of - :c:func:`PyType_FromSpecWithBases` instead. + .. c:member:: void *pfunc - .. versionchanged:: 3.9 - Slots in :c:type:`PyBufferProcs` may be set in the unlimited API. - - .. versionchanged:: 3.11 - :c:member:`~PyBufferProcs.bf_getbuffer` and - :c:member:`~PyBufferProcs.bf_releasebuffer` are now available - under the :ref:`limited API `. - - .. versionchanged:: 3.14 - The field :c:member:`~PyTypeObject.tp_vectorcall` can now be set - using :c:data:`Py_tp_vectorcall`. See the field's documentation - for details. - - .. c:member:: void *pfunc - - The desired value of the slot. In most cases, this is a pointer - to a function. - - *pfunc* values may not be ``NULL``, except for the following slots: - - * :c:data:`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 + Corresponds to :c:member:`PySlot.sl_ptr`. diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index d3d8239365f9bf..38db69e5c6db96 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -555,6 +555,9 @@ and :c:data:`PyType_Type` effectively act as defaults.) .. c:member:: const char* PyTypeObject.tp_name + See :c:macro:`Py_tp_name` for the corresponding + :c:member:`Slot ID `. + Pointer to a NUL-terminated string containing the name of the type. For types that are accessible as module globals, the string should be the full module name, followed by a dot, followed by the type name; for built-in types, it @@ -594,6 +597,10 @@ and :c:data:`PyType_Type` effectively act as defaults.) These fields allow calculating the size in bytes of instances of the type. + See :c:macro:`Py_tp_basicsize`, :c:macro:`Py_tp_extra_basicsize` and + :c:macro:`Py_tp_itemsize` for the corresponding + :c:member:`Slot IDs `. + There are two kinds of types: types with fixed-length instances have a zero :c:member:`!tp_itemsize` field, types with variable-length instances have a non-zero :c:member:`!tp_itemsize` field. For a type with fixed-length instances, all @@ -1133,6 +1140,9 @@ and :c:data:`PyType_Type` effectively act as defaults.) .. c:member:: unsigned long PyTypeObject.tp_flags + See :c:macro:`Py_tp_flags` for the corresponding + :c:member:`Slot ID `. + This field is a bit mask of various flags. Some flags indicate variant semantics for certain situations; others are used to indicate that certain fields in the type object (or in the extension structures referenced via diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 4ae5e999f0bf21..dbfb86e5060f53 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -669,6 +669,19 @@ func,PySlice_GetIndicesEx,3.2,, func,PySlice_New,3.2,, data,PySlice_Type,3.2,, func,PySlice_Unpack,3.7,, +type,PySlot,3.15,,full-abi +macro,PySlot_DATA,3.15,, +macro,PySlot_END,3.15,, +macro,PySlot_FUNC,3.15,, +macro,PySlot_INT64,3.15,, +macro,PySlot_INTPTR,3.15,, +macro,PySlot_OPTIONAL,3.15,, +macro,PySlot_PTR,3.15,, +macro,PySlot_PTR_STATIC,3.15,, +macro,PySlot_SIZE,3.15,, +macro,PySlot_STATIC,3.15,, +macro,PySlot_STATIC_DATA,3.15,, +macro,PySlot_UINT64,3.15,, func,PyState_AddModule,3.3,, func,PyState_FindModule,3.2,, func,PyState_RemoveModule,3.3,, @@ -748,6 +761,7 @@ func,PyType_ClearCache,3.2,, func,PyType_Freeze,3.14,, func,PyType_FromMetaclass,3.12,, func,PyType_FromModuleAndSpec,3.10,, +func,PyType_FromSlots,3.15,, func,PyType_FromSpec,3.2,, func,PyType_FromSpecWithBases,3.3,, func,PyType_GenericAlloc,3.2,, @@ -1007,6 +1021,7 @@ macro,Py_mod_gil,3.13,, macro,Py_mod_methods,3.15,, macro,Py_mod_multiple_interpreters,3.12,, macro,Py_mod_name,3.15,, +macro,Py_mod_slots,3.15,, macro,Py_mod_state_clear,3.15,, macro,Py_mod_state_free,3.15,, macro,Py_mod_state_size,3.15,, @@ -1050,6 +1065,9 @@ macro,Py_nb_rshift,3.2,, macro,Py_nb_subtract,3.2,, macro,Py_nb_true_divide,3.2,, macro,Py_nb_xor,3.2,, +macro,Py_slot_end,3.15,, +macro,Py_slot_invalid,3.15,, +macro,Py_slot_subslots,3.15,, macro,Py_sq_ass_item,3.2,, macro,Py_sq_concat,3.2,, macro,Py_sq_contains,3.2,, @@ -1062,6 +1080,7 @@ type,Py_ssize_t,3.2,, macro,Py_tp_alloc,3.2,, macro,Py_tp_base,3.2,, macro,Py_tp_bases,3.2,, +macro,Py_tp_basicsize,3.15,, macro,Py_tp_call,3.2,, macro,Py_tp_clear,3.2,, macro,Py_tp_dealloc,3.2,, @@ -1069,7 +1088,9 @@ macro,Py_tp_del,3.2,, macro,Py_tp_descr_get,3.2,, macro,Py_tp_descr_set,3.2,, macro,Py_tp_doc,3.2,, +macro,Py_tp_extra_basicsize,3.15,, macro,Py_tp_finalize,3.5,, +macro,Py_tp_flags,3.15,, macro,Py_tp_free,3.2,, macro,Py_tp_getattr,3.2,, macro,Py_tp_getattro,3.2,, @@ -1077,15 +1098,20 @@ macro,Py_tp_getset,3.2,, macro,Py_tp_hash,3.2,, macro,Py_tp_init,3.2,, macro,Py_tp_is_gc,3.2,, +macro,Py_tp_itemsize,3.15,, macro,Py_tp_iter,3.2,, macro,Py_tp_iternext,3.2,, macro,Py_tp_members,3.2,, +macro,Py_tp_metaclass,3.15,, macro,Py_tp_methods,3.2,, +macro,Py_tp_module,3.15,, +macro,Py_tp_name,3.15,, macro,Py_tp_new,3.2,, macro,Py_tp_repr,3.2,, macro,Py_tp_richcompare,3.2,, macro,Py_tp_setattr,3.2,, macro,Py_tp_setattro,3.2,, +macro,Py_tp_slots,3.15,, macro,Py_tp_str,3.2,, macro,Py_tp_token,3.14,, macro,Py_tp_traverse,3.2,, diff --git a/Doc/extending/first-extension-module.rst b/Doc/extending/first-extension-module.rst index cd755a98f7f5f4..894f5bdbb8f09c 100644 --- a/Doc/extending/first-extension-module.rst +++ b/Doc/extending/first-extension-module.rst @@ -259,21 +259,25 @@ Rather than ``NULL``, the export hook should return the information needed to create a module. Let's start with the basics: the name and docstring. -The information should be defined in a ``static`` array of -:c:type:`PyModuleDef_Slot` entries, which are essentially key-value pairs. +The information should be defined in an array of +:c:type:`PySlot` entries, which are essentially key-value pairs. Define this array just before your export hook: .. code-block:: c PyABIInfo_VAR(abi_info); - static PyModuleDef_Slot spam_slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "spam"}, - {Py_mod_doc, "A wonderful module with an example function"}, - {0, NULL} + static PySlot spam_slots[] = { + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_name, "spam"), + PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"), + PySlot_END }; +The :c:macro:`PySlot_STATIC_DATA` macro is used when the slot value +(here: ``&abi_info``, ``"spam"``, and the docstring) is a pointer to constant, +statically allocated data. + The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot are a bit of boilerplate that helps prevent extensions compiled for a different version of Python from crashing the interpreter. @@ -281,7 +285,8 @@ a different version of Python from crashing the interpreter. For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C strings -- that is, NUL-terminated, UTF-8 encoded byte arrays. -Note the zero-filled sentinel entry at the end. +Note ``PySlot_END`` sentinel entry at the end. +This marks the end of the array. If you forget it, you'll trigger undefined behavior. The array is defined as ``static`` -- that is, not visible outside this ``.c`` file. diff --git a/Doc/includes/capi-extension/spammodule-01.c b/Doc/includes/capi-extension/spammodule-01.c index 0bc34ef57445cb..8ddb416e7d6cd0 100644 --- a/Doc/includes/capi-extension/spammodule-01.c +++ b/Doc/includes/capi-extension/spammodule-01.c @@ -37,12 +37,12 @@ static PyMethodDef spam_methods[] = { PyABIInfo_VAR(abi_info); -static PyModuleDef_Slot spam_slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "spam"}, - {Py_mod_doc, "A wonderful module with an example function"}, - {Py_mod_methods, spam_methods}, - {0, NULL} +static PySlot spam_slots[] = { + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_name, "spam"), + PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"), + PySlot_STATIC_DATA(Py_mod_methods, spam_methods), + PySlot_END }; /// Export hook prototype diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index dbdd5de01700a3..dda3bac8a61d23 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -84,6 +84,7 @@ Summary -- Release highlights * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object ` * :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds ` +* :pep:`820`: :ref:`PySlot: Unified slot system for the C API ` * :ref:`The JIT compiler has been significantly upgraded ` * :ref:`Improved error messages ` * :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter @@ -1911,6 +1912,43 @@ New features It should only be used for debugging. (Contributed by Victor Stinner in :gh:`141070`.) +.. _whatsnew315-pyslot: + +* Implement :pep:`820`: ``PySlot`` -- Unified slot system for the C API. + See :ref:`capi-slots` for documentation. + + This adds: + + * The :c:type:`PySlot` struct; + * the :c:func:`PyType_FromSlots` function; + * new slot IDs: :c:macro:`Py_slot_end`, :c:macro:`Py_slot_invalid`; + :c:macro:`Py_slot_subslots`, :c:macro:`Py_tp_slots` + :c:macro:`Py_mod_slots`; + :c:macro:`Py_tp_name`, :c:macro:`Py_tp_basicsize`, + :c:macro:`Py_tp_extra_basicsize`, :c:macro:`Py_tp_itemsize`, + :c:macro:`Py_tp_flags`, :c:macro:`Py_tp_metaclass`, + :c:macro:`Py_tp_module`, :c:macro:`Py_tp_flags`; + * convenience macros: :c:macro:`PySlot_DATA`, :c:macro:`PySlot_FUNC`, + :c:macro:`PySlot_SIZE` :c:macro:`PySlot_INT64`, :c:macro:`PySlot_UINT64`, + :c:macro:`PySlot_STATIC_DATA`, :c:macro:`PySlot_END`, + :c:macro:`PySlot_PTR`, :c:macro:`PySlot_PTR_STATIC`. + + The :c:func:`PyModule_FromSlotsAndSpec` function and + ``PyModExport`` :ref:`module export hook ` also + use the new :c:type:`!PySlot` struct. + + These following functions are :term:`soft deprecated`: + + * :c:func:`PyType_FromSpec` + * :c:func:`PyType_FromSpecWithBases` + * :c:func:`PyType_FromModuleAndSpec` + * :c:func:`PyType_FromMetaclass` + * :c:func:`PyModule_FromDefAndSpec` + * :c:func:`PyModule_FromDefAndSpec2` + * :c:func:`PyModule_ExecDef` + + (Contributed by Petr Viktorin in :gh:`149044`.) + * Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and :c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the stack protection base address and stack protection size of a Python diff --git a/Include/Python.h b/Include/Python.h index 8b76195b320998..b10fcdac9c1de0 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -79,7 +79,8 @@ __pragma(warning(disable: 4201)) #include "object.h" #include "refcount.h" #include "objimpl.h" -#include "typeslots.h" +#include "slots.h" +#include "slots_generated.h" #include "pyhash.h" #include "cpython/pydebug.h" #include "bytearrayobject.h" diff --git a/Include/exports.h b/Include/exports.h index a863ecb33078ab..18692283005e59 100644 --- a/Include/exports.h +++ b/Include/exports.h @@ -103,7 +103,7 @@ #define PyMODINIT_FUNC _PyINIT_FUNC_DECLSPEC PyObject* #endif #ifndef PyMODEXPORT_FUNC - #define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PyModuleDef_Slot* + #define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PySlot* #endif #endif /* Py_EXPORTS_H */ diff --git a/Include/internal/pycore_importdl.h b/Include/internal/pycore_importdl.h index f60c5510d20075..9ed87a544234c5 100644 --- a/Include/internal/pycore_importdl.h +++ b/Include/internal/pycore_importdl.h @@ -124,7 +124,7 @@ extern void _Py_ext_module_loader_result_apply_error( /* The module init function. */ typedef PyObject *(*PyModInitFunction)(void); -typedef PyModuleDef_Slot *(*PyModExportFunction)(void); +typedef PySlot *(*PyModExportFunction)(void); #ifdef HAVE_DYNAMIC_LOADING extern int _PyImport_GetModuleExportHooks( struct _Py_ext_module_loader_info *info, diff --git a/Include/internal/pycore_slots.h b/Include/internal/pycore_slots.h new file mode 100644 index 00000000000000..bc115a85fae25a --- /dev/null +++ b/Include/internal/pycore_slots.h @@ -0,0 +1,138 @@ +#ifndef _Py_PYCORE_SLOTS_H +#define _Py_PYCORE_SLOTS_H + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#include + +/* Slot data type */ +typedef enum _PySlot_DTYPE { + _PySlot_DTYPE_VOID, + _PySlot_DTYPE_FUNC, + _PySlot_DTYPE_PTR, + _PySlot_DTYPE_SIZE, + _PySlot_DTYPE_INT64, + _PySlot_DTYPE_UINT64, +}_PySlot_DTYPE; + +/* Slot kind, used to identify: + * - the thing the slot initializes (type/module/special) + * - the struct type (PySlot/PyType_Slot/PyModuleDef_Slot) + */ +typedef enum _PySlot_KIND { + _PySlot_KIND_TYPE, + _PySlot_KIND_MOD, + _PySlot_KIND_COMPAT, + _PySlot_KIND_SLOT, +} _PySlot_KIND; + +typedef enum _PySlot_PROBLEM_HANDLING { + _PySlot_PROBLEM_ALLOW = 0, + _PySlot_PROBLEM_DEPRECATED, + _PySlot_PROBLEM_REJECT, +} _PySlot_PROBLEM_HANDLING; + +PyAPI_DATA(const char *) _PySlot_names[]; + +#define _PySlot_MAX_NESTING 5 + +/* State for one nesting level of a slots iterator */ +typedef struct _PySlotIterator_state { + union { + // tagged by slot_struct_kind: + const PySlot *slot; // with _PySlot_KIND_SLOT + const PyType_Slot *tp_slot; // with _PySlot_KIND_TYPE + const PyModuleDef_Slot *mod_slot; // with _PySlot_KIND_MOD + const void *any_slot; + }; + _PySlot_KIND slot_struct_kind; +} _PySlotIterator_state; + +#define SEEN_ENTRY_BITS (8 * sizeof(unsigned int)) + +/* State for a slots iterator */ +typedef struct { + _PySlotIterator_state *state; + _PySlotIterator_state states[_PySlot_MAX_NESTING]; + unsigned int seen[_Py_slot_COUNT / SEEN_ENTRY_BITS + 1]; + _PySlot_KIND kind; + uint8_t recursion_level; + bool is_at_end :1; + bool is_first_run :1; + + // Name of the object (type/module) being defined, NULL if unknown. + // Must be set by the callers as soon as it's known. + const char *name; + + /* Output information: */ + + // The slot. Always a copy; may be modified by caller of the iterator. + PySlot current; + +} _PySlotIterator; + +/* Initialize an iterator using a Py_Slot array */ +PyAPI_FUNC(void) +_PySlotIterator_Init(_PySlotIterator *it, const PySlot *slots, + _PySlot_KIND result_kind); + +/* Initialize an iterator using a legacy slot array */ +PyAPI_FUNC(void) +_PySlotIterator_InitLegacy(_PySlotIterator *it, const void *slots, + _PySlot_KIND kind); + +/* Reset a *successfully exhausted* iterator to the beginning. + * The *slots* must be the same as for the previous + * `_PySlotIterator_InitWithKind` call. + * (Unlike creating a new iterator, we can skip some validation after Rewind.) + */ +PyAPI_FUNC(void) _PySlotIterator_Rewind(_PySlotIterator *it, const void *slots); + +/* Iteration function. + * + * Return false at the end (when successfully exhausted). + * Otherwise (even on error), fill output information in `it` and return true. + * + * On error, set an exception and set `it->current.sl_id` to `Py_slot_invalid`. + */ +PyAPI_FUNC(bool) _PySlotIterator_Next(_PySlotIterator *it); + +/* Return 1 if given slot was "seen" by an earlier _PySlotIterator_Next call. + * (This state is not reset by rewinding.) + */ +PyAPI_FUNC(bool) _PySlotIterator_SawSlot(_PySlotIterator *, int); + +static inline const char * +_PySlot_GetName(uint16_t id) +{ + if (id >= _Py_slot_COUNT) { + return ""; + } + if (id == Py_slot_invalid) { + return "Py_slot_invalid"; + } + return _PySlot_names[id]; +} + +static inline void +_PySlot_err_bad_slot(char *kind, uint16_t id) +{ + if (id < _Py_slot_COUNT) { + PyErr_Format(PyExc_SystemError, "invalid %s slot %d (%s)", + kind, (unsigned int)id, _PySlot_names[id]); + } + else if (id == Py_slot_invalid) { + PyErr_Format(PyExc_SystemError, "invalid slot (Py_slot_invalid, %u)", + (unsigned int)id); + } + else { + PyErr_Format(PyExc_SystemError, "unknown %s slot ID %u", + kind, (unsigned int)id); + } +} + +#include "internal/pycore_slots_generated.h" + +#endif // _Py_PYCORE_SLOTS_H diff --git a/Include/internal/pycore_slots_generated.h b/Include/internal/pycore_slots_generated.h new file mode 100644 index 00000000000000..73a77070038cef --- /dev/null +++ b/Include/internal/pycore_slots_generated.h @@ -0,0 +1,958 @@ +/* Generated by Tools/build/generate_slots.py */ + +#ifndef _PY_HAVE_INTERNAL_SLOTS_GENERATED_H +#define _PY_HAVE_INTERNAL_SLOTS_GENERATED_H + +static inline uint16_t +_PySlot_resolve_type_slot(uint16_t slot_id) +{ + switch (slot_id) { + case 1: + return Py_bf_getbuffer; + case 2: + return Py_bf_releasebuffer; + case 3: + return Py_mp_ass_subscript; + case 4: + return Py_mp_length; + case Py_slot_end: + case Py_mp_subscript: + case Py_nb_absolute: + case Py_nb_add: + case Py_nb_and: + case Py_nb_bool: + case Py_nb_divmod: + case Py_nb_float: + case Py_nb_floor_divide: + case Py_nb_index: + case Py_nb_inplace_add: + case Py_nb_inplace_and: + case Py_nb_inplace_floor_divide: + case Py_nb_inplace_lshift: + case Py_nb_inplace_multiply: + case Py_nb_inplace_or: + case Py_nb_inplace_power: + case Py_nb_inplace_remainder: + case Py_nb_inplace_rshift: + case Py_nb_inplace_subtract: + case Py_nb_inplace_true_divide: + case Py_nb_inplace_xor: + case Py_nb_int: + case Py_nb_invert: + case Py_nb_lshift: + case Py_nb_multiply: + case Py_nb_negative: + case Py_nb_or: + case Py_nb_positive: + case Py_nb_power: + case Py_nb_remainder: + case Py_nb_rshift: + case Py_nb_subtract: + case Py_nb_true_divide: + case Py_nb_xor: + case Py_sq_ass_item: + case Py_sq_concat: + case Py_sq_contains: + case Py_sq_inplace_concat: + case Py_sq_inplace_repeat: + case Py_sq_item: + case Py_sq_length: + case Py_sq_repeat: + case Py_tp_alloc: + case Py_tp_base: + case Py_tp_bases: + case Py_tp_call: + case Py_tp_clear: + case Py_tp_dealloc: + case Py_tp_del: + case Py_tp_descr_get: + case Py_tp_descr_set: + case Py_tp_doc: + case Py_tp_getattr: + case Py_tp_getattro: + case Py_tp_hash: + case Py_tp_init: + case Py_tp_is_gc: + case Py_tp_iter: + case Py_tp_iternext: + case Py_tp_methods: + case Py_tp_new: + case Py_tp_repr: + case Py_tp_richcompare: + case Py_tp_setattr: + case Py_tp_setattro: + case Py_tp_str: + case Py_tp_traverse: + case Py_tp_members: + case Py_tp_getset: + case Py_tp_free: + case Py_nb_matrix_multiply: + case Py_nb_inplace_matrix_multiply: + case Py_am_await: + case Py_am_aiter: + case Py_am_anext: + case Py_tp_finalize: + case Py_am_send: + case Py_tp_vectorcall: + case Py_tp_token: + case Py_bf_getbuffer: + case Py_bf_releasebuffer: + case Py_mp_ass_subscript: + case Py_mp_length: + case Py_slot_subslots: + case Py_tp_slots: + case Py_tp_name: + case Py_tp_basicsize: + case Py_tp_extra_basicsize: + case Py_tp_itemsize: + case Py_tp_flags: + case Py_tp_metaclass: + case Py_tp_module: + return slot_id; + default: + return Py_slot_invalid; + } +} + +static inline uint16_t +_PySlot_resolve_mod_slot(uint16_t slot_id) +{ + switch (slot_id) { + case 1: + return Py_mod_create; + case 2: + return Py_mod_exec; + case 3: + return Py_mod_multiple_interpreters; + case 4: + return Py_mod_gil; + case Py_slot_end: + case Py_mod_create: + case Py_mod_exec: + case Py_mod_multiple_interpreters: + case Py_mod_gil: + case Py_slot_subslots: + case Py_mod_slots: + case Py_mod_name: + case Py_mod_doc: + case Py_mod_state_size: + case Py_mod_methods: + case Py_mod_state_traverse: + case Py_mod_state_clear: + case Py_mod_state_free: + case Py_mod_abi: + case Py_mod_token: + return slot_id; + default: + return Py_slot_invalid; + } +} + +static inline void* +_PySlot_type_getslot(PyTypeObject *tp, uint16_t slot_id) +{ + switch (slot_id) { + case Py_mp_subscript: + if (!(tp->tp_as_mapping)) return NULL; + return (void*)tp->tp_as_mapping->mp_subscript; + case Py_nb_absolute: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_absolute; + case Py_nb_add: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_add; + case Py_nb_and: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_and; + case Py_nb_bool: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_bool; + case Py_nb_divmod: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_divmod; + case Py_nb_float: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_float; + case Py_nb_floor_divide: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_floor_divide; + case Py_nb_index: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_index; + case Py_nb_inplace_add: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_add; + case Py_nb_inplace_and: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_and; + case Py_nb_inplace_floor_divide: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_floor_divide; + case Py_nb_inplace_lshift: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_lshift; + case Py_nb_inplace_multiply: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_multiply; + case Py_nb_inplace_or: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_or; + case Py_nb_inplace_power: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_power; + case Py_nb_inplace_remainder: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_remainder; + case Py_nb_inplace_rshift: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_rshift; + case Py_nb_inplace_subtract: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_subtract; + case Py_nb_inplace_true_divide: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_true_divide; + case Py_nb_inplace_xor: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_xor; + case Py_nb_int: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_int; + case Py_nb_invert: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_invert; + case Py_nb_lshift: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_lshift; + case Py_nb_multiply: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_multiply; + case Py_nb_negative: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_negative; + case Py_nb_or: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_or; + case Py_nb_positive: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_positive; + case Py_nb_power: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_power; + case Py_nb_remainder: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_remainder; + case Py_nb_rshift: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_rshift; + case Py_nb_subtract: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_subtract; + case Py_nb_true_divide: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_true_divide; + case Py_nb_xor: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_xor; + case Py_sq_ass_item: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_ass_item; + case Py_sq_concat: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_concat; + case Py_sq_contains: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_contains; + case Py_sq_inplace_concat: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_inplace_concat; + case Py_sq_inplace_repeat: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_inplace_repeat; + case Py_sq_item: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_item; + case Py_sq_length: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_length; + case Py_sq_repeat: + if (!(tp->tp_as_sequence)) return NULL; + return (void*)tp->tp_as_sequence->sq_repeat; + case Py_tp_alloc: + return (void*)tp->tp_alloc; + case Py_tp_base: + return (void*)tp->tp_base; + case Py_tp_bases: + return (void*)tp->tp_bases; + case Py_tp_call: + return (void*)tp->tp_call; + case Py_tp_clear: + return (void*)tp->tp_clear; + case Py_tp_dealloc: + return (void*)tp->tp_dealloc; + case Py_tp_del: + return (void*)tp->tp_del; + case Py_tp_descr_get: + return (void*)tp->tp_descr_get; + case Py_tp_descr_set: + return (void*)tp->tp_descr_set; + case Py_tp_doc: + return (void*)tp->tp_doc; + case Py_tp_getattr: + return (void*)tp->tp_getattr; + case Py_tp_getattro: + return (void*)tp->tp_getattro; + case Py_tp_hash: + return (void*)tp->tp_hash; + case Py_tp_init: + return (void*)tp->tp_init; + case Py_tp_is_gc: + return (void*)tp->tp_is_gc; + case Py_tp_iter: + return (void*)tp->tp_iter; + case Py_tp_iternext: + return (void*)tp->tp_iternext; + case Py_tp_methods: + return (void*)tp->tp_methods; + case Py_tp_new: + return (void*)tp->tp_new; + case Py_tp_repr: + return (void*)tp->tp_repr; + case Py_tp_richcompare: + return (void*)tp->tp_richcompare; + case Py_tp_setattr: + return (void*)tp->tp_setattr; + case Py_tp_setattro: + return (void*)tp->tp_setattro; + case Py_tp_str: + return (void*)tp->tp_str; + case Py_tp_traverse: + return (void*)tp->tp_traverse; + case Py_tp_members: + return (void*)tp->tp_members; + case Py_tp_getset: + return (void*)tp->tp_getset; + case Py_tp_free: + return (void*)tp->tp_free; + case Py_nb_matrix_multiply: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_matrix_multiply; + case Py_nb_inplace_matrix_multiply: + if (!(tp->tp_as_number)) return NULL; + return (void*)tp->tp_as_number->nb_inplace_matrix_multiply; + case Py_am_await: + if (!(tp->tp_as_async)) return NULL; + return (void*)tp->tp_as_async->am_await; + case Py_am_aiter: + if (!(tp->tp_as_async)) return NULL; + return (void*)tp->tp_as_async->am_aiter; + case Py_am_anext: + if (!(tp->tp_as_async)) return NULL; + return (void*)tp->tp_as_async->am_anext; + case Py_tp_finalize: + return (void*)tp->tp_finalize; + case Py_am_send: + if (!(tp->tp_as_async)) return NULL; + return (void*)tp->tp_as_async->am_send; + case Py_tp_vectorcall: + return (void*)tp->tp_vectorcall; + case Py_tp_token: + if (!(tp->tp_flags & Py_TPFLAGS_HEAPTYPE)) return NULL; + return (void*)((PyHeapTypeObject*)tp)->ht_token; + case Py_bf_getbuffer: + if (!(tp->tp_as_buffer)) return NULL; + return (void*)tp->tp_as_buffer->bf_getbuffer; + case Py_bf_releasebuffer: + if (!(tp->tp_as_buffer)) return NULL; + return (void*)tp->tp_as_buffer->bf_releasebuffer; + case Py_mp_ass_subscript: + if (!(tp->tp_as_mapping)) return NULL; + return (void*)tp->tp_as_mapping->mp_ass_subscript; + case Py_mp_length: + if (!(tp->tp_as_mapping)) return NULL; + return (void*)tp->tp_as_mapping->mp_length; + } + _PySlot_err_bad_slot("PyType_GetSlot", slot_id); + return NULL; +} + +static inline void +_PySlot_heaptype_apply_field_slot(PyHeapTypeObject *ht, PySlot slot) +{ + switch (slot.sl_id) { + case Py_mp_subscript: + ht->as_mapping.mp_subscript = (binaryfunc)slot.sl_func; + break; + case Py_nb_absolute: + ht->as_number.nb_absolute = (unaryfunc)slot.sl_func; + break; + case Py_nb_add: + ht->as_number.nb_add = (binaryfunc)slot.sl_func; + break; + case Py_nb_and: + ht->as_number.nb_and = (binaryfunc)slot.sl_func; + break; + case Py_nb_bool: + ht->as_number.nb_bool = (inquiry)slot.sl_func; + break; + case Py_nb_divmod: + ht->as_number.nb_divmod = (binaryfunc)slot.sl_func; + break; + case Py_nb_float: + ht->as_number.nb_float = (unaryfunc)slot.sl_func; + break; + case Py_nb_floor_divide: + ht->as_number.nb_floor_divide = (binaryfunc)slot.sl_func; + break; + case Py_nb_index: + ht->as_number.nb_index = (unaryfunc)slot.sl_func; + break; + case Py_nb_inplace_add: + ht->as_number.nb_inplace_add = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_and: + ht->as_number.nb_inplace_and = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_floor_divide: + ht->as_number.nb_inplace_floor_divide = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_lshift: + ht->as_number.nb_inplace_lshift = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_multiply: + ht->as_number.nb_inplace_multiply = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_or: + ht->as_number.nb_inplace_or = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_power: + ht->as_number.nb_inplace_power = (ternaryfunc)slot.sl_func; + break; + case Py_nb_inplace_remainder: + ht->as_number.nb_inplace_remainder = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_rshift: + ht->as_number.nb_inplace_rshift = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_subtract: + ht->as_number.nb_inplace_subtract = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_true_divide: + ht->as_number.nb_inplace_true_divide = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_xor: + ht->as_number.nb_inplace_xor = (binaryfunc)slot.sl_func; + break; + case Py_nb_int: + ht->as_number.nb_int = (unaryfunc)slot.sl_func; + break; + case Py_nb_invert: + ht->as_number.nb_invert = (unaryfunc)slot.sl_func; + break; + case Py_nb_lshift: + ht->as_number.nb_lshift = (binaryfunc)slot.sl_func; + break; + case Py_nb_multiply: + ht->as_number.nb_multiply = (binaryfunc)slot.sl_func; + break; + case Py_nb_negative: + ht->as_number.nb_negative = (unaryfunc)slot.sl_func; + break; + case Py_nb_or: + ht->as_number.nb_or = (binaryfunc)slot.sl_func; + break; + case Py_nb_positive: + ht->as_number.nb_positive = (unaryfunc)slot.sl_func; + break; + case Py_nb_power: + ht->as_number.nb_power = (ternaryfunc)slot.sl_func; + break; + case Py_nb_remainder: + ht->as_number.nb_remainder = (binaryfunc)slot.sl_func; + break; + case Py_nb_rshift: + ht->as_number.nb_rshift = (binaryfunc)slot.sl_func; + break; + case Py_nb_subtract: + ht->as_number.nb_subtract = (binaryfunc)slot.sl_func; + break; + case Py_nb_true_divide: + ht->as_number.nb_true_divide = (binaryfunc)slot.sl_func; + break; + case Py_nb_xor: + ht->as_number.nb_xor = (binaryfunc)slot.sl_func; + break; + case Py_sq_ass_item: + ht->as_sequence.sq_ass_item = (ssizeobjargproc)slot.sl_func; + break; + case Py_sq_concat: + ht->as_sequence.sq_concat = (binaryfunc)slot.sl_func; + break; + case Py_sq_contains: + ht->as_sequence.sq_contains = (objobjproc)slot.sl_func; + break; + case Py_sq_inplace_concat: + ht->as_sequence.sq_inplace_concat = (binaryfunc)slot.sl_func; + break; + case Py_sq_inplace_repeat: + ht->as_sequence.sq_inplace_repeat = (ssizeargfunc)slot.sl_func; + break; + case Py_sq_item: + ht->as_sequence.sq_item = (ssizeargfunc)slot.sl_func; + break; + case Py_sq_length: + ht->as_sequence.sq_length = (lenfunc)slot.sl_func; + break; + case Py_sq_repeat: + ht->as_sequence.sq_repeat = (ssizeargfunc)slot.sl_func; + break; + case Py_tp_alloc: + ht->ht_type.tp_alloc = (allocfunc)slot.sl_func; + break; + case Py_tp_base: + ht->ht_type.tp_base = slot.sl_ptr; + break; + case Py_tp_bases: + ht->ht_type.tp_bases = slot.sl_ptr; + break; + case Py_tp_call: + ht->ht_type.tp_call = (ternaryfunc)slot.sl_func; + break; + case Py_tp_clear: + ht->ht_type.tp_clear = (inquiry)slot.sl_func; + break; + case Py_tp_dealloc: + ht->ht_type.tp_dealloc = (destructor)slot.sl_func; + break; + case Py_tp_del: + ht->ht_type.tp_del = (destructor)slot.sl_func; + break; + case Py_tp_descr_get: + ht->ht_type.tp_descr_get = (descrgetfunc)slot.sl_func; + break; + case Py_tp_descr_set: + ht->ht_type.tp_descr_set = (descrsetfunc)slot.sl_func; + break; + case Py_tp_doc: + ht->ht_type.tp_doc = slot.sl_ptr; + break; + case Py_tp_getattr: + ht->ht_type.tp_getattr = (getattrfunc)slot.sl_func; + break; + case Py_tp_getattro: + ht->ht_type.tp_getattro = (getattrofunc)slot.sl_func; + break; + case Py_tp_hash: + ht->ht_type.tp_hash = (hashfunc)slot.sl_func; + break; + case Py_tp_init: + ht->ht_type.tp_init = (initproc)slot.sl_func; + break; + case Py_tp_is_gc: + ht->ht_type.tp_is_gc = (inquiry)slot.sl_func; + break; + case Py_tp_iter: + ht->ht_type.tp_iter = (getiterfunc)slot.sl_func; + break; + case Py_tp_iternext: + ht->ht_type.tp_iternext = (iternextfunc)slot.sl_func; + break; + case Py_tp_methods: + ht->ht_type.tp_methods = slot.sl_ptr; + break; + case Py_tp_new: + ht->ht_type.tp_new = (newfunc)slot.sl_func; + break; + case Py_tp_repr: + ht->ht_type.tp_repr = (reprfunc)slot.sl_func; + break; + case Py_tp_richcompare: + ht->ht_type.tp_richcompare = (richcmpfunc)slot.sl_func; + break; + case Py_tp_setattr: + ht->ht_type.tp_setattr = (setattrfunc)slot.sl_func; + break; + case Py_tp_setattro: + ht->ht_type.tp_setattro = (setattrofunc)slot.sl_func; + break; + case Py_tp_str: + ht->ht_type.tp_str = (reprfunc)slot.sl_func; + break; + case Py_tp_traverse: + ht->ht_type.tp_traverse = (traverseproc)slot.sl_func; + break; + case Py_tp_members: + ht->ht_type.tp_members = slot.sl_ptr; + break; + case Py_tp_getset: + ht->ht_type.tp_getset = slot.sl_ptr; + break; + case Py_tp_free: + ht->ht_type.tp_free = (freefunc)slot.sl_func; + break; + case Py_nb_matrix_multiply: + ht->as_number.nb_matrix_multiply = (binaryfunc)slot.sl_func; + break; + case Py_nb_inplace_matrix_multiply: + ht->as_number.nb_inplace_matrix_multiply = (binaryfunc)slot.sl_func; + break; + case Py_am_await: + ht->as_async.am_await = (unaryfunc)slot.sl_func; + break; + case Py_am_aiter: + ht->as_async.am_aiter = (unaryfunc)slot.sl_func; + break; + case Py_am_anext: + ht->as_async.am_anext = (unaryfunc)slot.sl_func; + break; + case Py_tp_finalize: + ht->ht_type.tp_finalize = (destructor)slot.sl_func; + break; + case Py_am_send: + ht->as_async.am_send = (sendfunc)slot.sl_func; + break; + case Py_tp_vectorcall: + ht->ht_type.tp_vectorcall = (vectorcallfunc)slot.sl_func; + break; + case Py_bf_getbuffer: + ht->as_buffer.bf_getbuffer = (getbufferproc)slot.sl_func; + break; + case Py_bf_releasebuffer: + ht->as_buffer.bf_releasebuffer = (releasebufferproc)slot.sl_func; + break; + case Py_mp_ass_subscript: + ht->as_mapping.mp_ass_subscript = (objobjargproc)slot.sl_func; + break; + case Py_mp_length: + ht->as_mapping.mp_length = (lenfunc)slot.sl_func; + break; + } +} + +static inline _PySlot_DTYPE +_PySlot_get_dtype(uint16_t slot_id) +{ + switch (slot_id) { + case Py_slot_end: return _PySlot_DTYPE_VOID; + case Py_mp_subscript: return _PySlot_DTYPE_FUNC; + case Py_nb_absolute: return _PySlot_DTYPE_FUNC; + case Py_nb_add: return _PySlot_DTYPE_FUNC; + case Py_nb_and: return _PySlot_DTYPE_FUNC; + case Py_nb_bool: return _PySlot_DTYPE_FUNC; + case Py_nb_divmod: return _PySlot_DTYPE_FUNC; + case Py_nb_float: return _PySlot_DTYPE_FUNC; + case Py_nb_floor_divide: return _PySlot_DTYPE_FUNC; + case Py_nb_index: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_add: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_and: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_floor_divide: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_lshift: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_multiply: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_or: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_power: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_remainder: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_rshift: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_subtract: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_true_divide: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_xor: return _PySlot_DTYPE_FUNC; + case Py_nb_int: return _PySlot_DTYPE_FUNC; + case Py_nb_invert: return _PySlot_DTYPE_FUNC; + case Py_nb_lshift: return _PySlot_DTYPE_FUNC; + case Py_nb_multiply: return _PySlot_DTYPE_FUNC; + case Py_nb_negative: return _PySlot_DTYPE_FUNC; + case Py_nb_or: return _PySlot_DTYPE_FUNC; + case Py_nb_positive: return _PySlot_DTYPE_FUNC; + case Py_nb_power: return _PySlot_DTYPE_FUNC; + case Py_nb_remainder: return _PySlot_DTYPE_FUNC; + case Py_nb_rshift: return _PySlot_DTYPE_FUNC; + case Py_nb_subtract: return _PySlot_DTYPE_FUNC; + case Py_nb_true_divide: return _PySlot_DTYPE_FUNC; + case Py_nb_xor: return _PySlot_DTYPE_FUNC; + case Py_sq_ass_item: return _PySlot_DTYPE_FUNC; + case Py_sq_concat: return _PySlot_DTYPE_FUNC; + case Py_sq_contains: return _PySlot_DTYPE_FUNC; + case Py_sq_inplace_concat: return _PySlot_DTYPE_FUNC; + case Py_sq_inplace_repeat: return _PySlot_DTYPE_FUNC; + case Py_sq_item: return _PySlot_DTYPE_FUNC; + case Py_sq_length: return _PySlot_DTYPE_FUNC; + case Py_sq_repeat: return _PySlot_DTYPE_FUNC; + case Py_tp_alloc: return _PySlot_DTYPE_FUNC; + case Py_tp_base: return _PySlot_DTYPE_PTR; + case Py_tp_bases: return _PySlot_DTYPE_PTR; + case Py_tp_call: return _PySlot_DTYPE_FUNC; + case Py_tp_clear: return _PySlot_DTYPE_FUNC; + case Py_tp_dealloc: return _PySlot_DTYPE_FUNC; + case Py_tp_del: return _PySlot_DTYPE_FUNC; + case Py_tp_descr_get: return _PySlot_DTYPE_FUNC; + case Py_tp_descr_set: return _PySlot_DTYPE_FUNC; + case Py_tp_doc: return _PySlot_DTYPE_PTR; + case Py_tp_getattr: return _PySlot_DTYPE_FUNC; + case Py_tp_getattro: return _PySlot_DTYPE_FUNC; + case Py_tp_hash: return _PySlot_DTYPE_FUNC; + case Py_tp_init: return _PySlot_DTYPE_FUNC; + case Py_tp_is_gc: return _PySlot_DTYPE_FUNC; + case Py_tp_iter: return _PySlot_DTYPE_FUNC; + case Py_tp_iternext: return _PySlot_DTYPE_FUNC; + case Py_tp_methods: return _PySlot_DTYPE_PTR; + case Py_tp_new: return _PySlot_DTYPE_FUNC; + case Py_tp_repr: return _PySlot_DTYPE_FUNC; + case Py_tp_richcompare: return _PySlot_DTYPE_FUNC; + case Py_tp_setattr: return _PySlot_DTYPE_FUNC; + case Py_tp_setattro: return _PySlot_DTYPE_FUNC; + case Py_tp_str: return _PySlot_DTYPE_FUNC; + case Py_tp_traverse: return _PySlot_DTYPE_FUNC; + case Py_tp_members: return _PySlot_DTYPE_PTR; + case Py_tp_getset: return _PySlot_DTYPE_PTR; + case Py_tp_free: return _PySlot_DTYPE_FUNC; + case Py_nb_matrix_multiply: return _PySlot_DTYPE_FUNC; + case Py_nb_inplace_matrix_multiply: return _PySlot_DTYPE_FUNC; + case Py_am_await: return _PySlot_DTYPE_FUNC; + case Py_am_aiter: return _PySlot_DTYPE_FUNC; + case Py_am_anext: return _PySlot_DTYPE_FUNC; + case Py_tp_finalize: return _PySlot_DTYPE_FUNC; + case Py_am_send: return _PySlot_DTYPE_FUNC; + case Py_tp_vectorcall: return _PySlot_DTYPE_FUNC; + case Py_tp_token: return _PySlot_DTYPE_PTR; + case Py_mod_create: return _PySlot_DTYPE_FUNC; + case Py_mod_exec: return _PySlot_DTYPE_FUNC; + case Py_mod_multiple_interpreters: return _PySlot_DTYPE_UINT64; + case Py_mod_gil: return _PySlot_DTYPE_UINT64; + case Py_bf_getbuffer: return _PySlot_DTYPE_FUNC; + case Py_bf_releasebuffer: return _PySlot_DTYPE_FUNC; + case Py_mp_ass_subscript: return _PySlot_DTYPE_FUNC; + case Py_mp_length: return _PySlot_DTYPE_FUNC; + case Py_slot_subslots: return _PySlot_DTYPE_PTR; + case Py_tp_slots: return _PySlot_DTYPE_PTR; + case Py_mod_slots: return _PySlot_DTYPE_PTR; + case Py_tp_name: return _PySlot_DTYPE_PTR; + case Py_tp_basicsize: return _PySlot_DTYPE_SIZE; + case Py_tp_extra_basicsize: return _PySlot_DTYPE_SIZE; + case Py_tp_itemsize: return _PySlot_DTYPE_SIZE; + case Py_tp_flags: return _PySlot_DTYPE_UINT64; + case Py_mod_name: return _PySlot_DTYPE_PTR; + case Py_mod_doc: return _PySlot_DTYPE_PTR; + case Py_mod_state_size: return _PySlot_DTYPE_SIZE; + case Py_mod_methods: return _PySlot_DTYPE_PTR; + case Py_mod_state_traverse: return _PySlot_DTYPE_FUNC; + case Py_mod_state_clear: return _PySlot_DTYPE_FUNC; + case Py_mod_state_free: return _PySlot_DTYPE_FUNC; + case Py_tp_metaclass: return _PySlot_DTYPE_PTR; + case Py_tp_module: return _PySlot_DTYPE_PTR; + case Py_mod_abi: return _PySlot_DTYPE_PTR; + case Py_mod_token: return _PySlot_DTYPE_PTR; + default: return _PySlot_DTYPE_VOID; + } +} + +static inline _PySlot_PROBLEM_HANDLING +_PySlot_get_duplicate_handling(uint16_t slot_id) +{ + switch (slot_id) { + case Py_mp_subscript: + case Py_nb_absolute: + case Py_nb_add: + case Py_nb_and: + case Py_nb_bool: + case Py_nb_divmod: + case Py_nb_float: + case Py_nb_floor_divide: + case Py_nb_index: + case Py_nb_inplace_add: + case Py_nb_inplace_and: + case Py_nb_inplace_floor_divide: + case Py_nb_inplace_lshift: + case Py_nb_inplace_multiply: + case Py_nb_inplace_or: + case Py_nb_inplace_power: + case Py_nb_inplace_remainder: + case Py_nb_inplace_rshift: + case Py_nb_inplace_subtract: + case Py_nb_inplace_true_divide: + case Py_nb_inplace_xor: + case Py_nb_int: + case Py_nb_invert: + case Py_nb_lshift: + case Py_nb_multiply: + case Py_nb_negative: + case Py_nb_or: + case Py_nb_positive: + case Py_nb_power: + case Py_nb_remainder: + case Py_nb_rshift: + case Py_nb_subtract: + case Py_nb_true_divide: + case Py_nb_xor: + case Py_sq_ass_item: + case Py_sq_concat: + case Py_sq_contains: + case Py_sq_inplace_concat: + case Py_sq_inplace_repeat: + case Py_sq_item: + case Py_sq_length: + case Py_sq_repeat: + case Py_tp_alloc: + case Py_tp_base: + case Py_tp_bases: + case Py_tp_call: + case Py_tp_clear: + case Py_tp_dealloc: + case Py_tp_del: + case Py_tp_descr_get: + case Py_tp_descr_set: + case Py_tp_getattr: + case Py_tp_getattro: + case Py_tp_hash: + case Py_tp_init: + case Py_tp_is_gc: + case Py_tp_iter: + case Py_tp_iternext: + case Py_tp_methods: + case Py_tp_new: + case Py_tp_repr: + case Py_tp_richcompare: + case Py_tp_setattr: + case Py_tp_setattro: + case Py_tp_str: + case Py_tp_traverse: + case Py_tp_getset: + case Py_tp_free: + case Py_nb_matrix_multiply: + case Py_nb_inplace_matrix_multiply: + case Py_am_await: + case Py_am_aiter: + case Py_am_anext: + case Py_tp_finalize: + case Py_am_send: + case Py_tp_vectorcall: + case Py_tp_token: + case Py_bf_getbuffer: + case Py_bf_releasebuffer: + case Py_mp_ass_subscript: + case Py_mp_length: + return _PySlot_PROBLEM_DEPRECATED; + case Py_mod_exec: + case Py_mod_abi: + return _PySlot_PROBLEM_ALLOW; + default: + return _PySlot_PROBLEM_REJECT; + } +} + +static inline _PySlot_PROBLEM_HANDLING +_PySlot_get_null_handling(uint16_t slot_id) +{ + switch (slot_id) { + case Py_slot_end: + case Py_tp_doc: + case Py_tp_token: + case Py_mod_multiple_interpreters: + case Py_mod_gil: + case Py_slot_subslots: + case Py_tp_slots: + case Py_mod_slots: + case Py_tp_basicsize: + case Py_tp_extra_basicsize: + case Py_tp_itemsize: + case Py_tp_flags: + case Py_mod_state_size: + return _PySlot_PROBLEM_ALLOW; + case Py_mp_subscript: + case Py_nb_absolute: + case Py_nb_add: + case Py_nb_and: + case Py_nb_bool: + case Py_nb_divmod: + case Py_nb_float: + case Py_nb_floor_divide: + case Py_nb_index: + case Py_nb_inplace_add: + case Py_nb_inplace_and: + case Py_nb_inplace_floor_divide: + case Py_nb_inplace_lshift: + case Py_nb_inplace_multiply: + case Py_nb_inplace_or: + case Py_nb_inplace_power: + case Py_nb_inplace_remainder: + case Py_nb_inplace_rshift: + case Py_nb_inplace_subtract: + case Py_nb_inplace_true_divide: + case Py_nb_inplace_xor: + case Py_nb_int: + case Py_nb_invert: + case Py_nb_lshift: + case Py_nb_multiply: + case Py_nb_negative: + case Py_nb_or: + case Py_nb_positive: + case Py_nb_power: + case Py_nb_remainder: + case Py_nb_rshift: + case Py_nb_subtract: + case Py_nb_true_divide: + case Py_nb_xor: + case Py_sq_ass_item: + case Py_sq_concat: + case Py_sq_contains: + case Py_sq_inplace_concat: + case Py_sq_inplace_repeat: + case Py_sq_item: + case Py_sq_length: + case Py_sq_repeat: + case Py_tp_alloc: + case Py_tp_base: + case Py_tp_bases: + case Py_tp_call: + case Py_tp_clear: + case Py_tp_dealloc: + case Py_tp_del: + case Py_tp_descr_get: + case Py_tp_descr_set: + case Py_tp_getattr: + case Py_tp_getattro: + case Py_tp_hash: + case Py_tp_init: + case Py_tp_is_gc: + case Py_tp_iter: + case Py_tp_iternext: + case Py_tp_methods: + case Py_tp_new: + case Py_tp_repr: + case Py_tp_richcompare: + case Py_tp_setattr: + case Py_tp_setattro: + case Py_tp_str: + case Py_tp_traverse: + case Py_tp_getset: + case Py_tp_free: + case Py_nb_matrix_multiply: + case Py_nb_inplace_matrix_multiply: + case Py_am_await: + case Py_am_aiter: + case Py_am_anext: + case Py_tp_finalize: + case Py_am_send: + case Py_tp_vectorcall: + case Py_mod_create: + case Py_bf_getbuffer: + case Py_bf_releasebuffer: + case Py_mp_ass_subscript: + case Py_mp_length: + return _PySlot_PROBLEM_DEPRECATED; + default: + return _PySlot_PROBLEM_REJECT; + } +} + +static inline bool +_PySlot_get_must_be_static(uint16_t slot_id) +{ + switch (slot_id) { + case Py_tp_methods: return true; + case Py_tp_members: return true; + case Py_tp_getset: return true; + case Py_mod_methods: return true; + } + return false; +} + +#endif /* _PY_HAVE_INTERNAL_SLOTS_GENERATED_H */ diff --git a/Include/moduleobject.h b/Include/moduleobject.h index c2fb1f85165f7d..88c66672ff164a 100644 --- a/Include/moduleobject.h +++ b/Include/moduleobject.h @@ -73,31 +73,6 @@ struct PyModuleDef_Slot { void *value; }; -#define Py_mod_create 1 -#define Py_mod_exec 2 -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000 -# define Py_mod_multiple_interpreters 3 -#endif -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 -# define Py_mod_gil 4 -#endif -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) -# define Py_mod_abi 5 -# define Py_mod_name 6 -# define Py_mod_doc 7 -# define Py_mod_state_size 8 -# define Py_mod_methods 9 -# define Py_mod_state_traverse 10 -# define Py_mod_state_clear 11 -# define Py_mod_state_free 12 -# define Py_mod_token 13 -#endif - - -#ifndef Py_LIMITED_API -#define _Py_mod_LAST_SLOT 13 -#endif - #endif /* New in 3.5 */ /* for Py_mod_multiple_interpreters: */ @@ -120,7 +95,7 @@ PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil); #endif #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) -PyAPI_FUNC(PyObject *) PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, +PyAPI_FUNC(PyObject *) PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec); PyAPI_FUNC(int) PyModule_Exec(PyObject *module); PyAPI_FUNC(int) PyModule_GetStateSize(PyObject *module, Py_ssize_t *result); diff --git a/Include/object.h b/Include/object.h index d51132be1a6656..32f5948932f004 100644 --- a/Include/object.h +++ b/Include/object.h @@ -364,6 +364,9 @@ PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls); PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); #define Py_TP_USE_SPEC NULL #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) +PyAPI_FUNC(PyObject *) PyType_FromSlots(struct PySlot *); +#endif /* Generic type check */ PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *); diff --git a/Include/pytypedefs.h b/Include/pytypedefs.h index e78ed56a3b67cd..1d0b4e3e85ef74 100644 --- a/Include/pytypedefs.h +++ b/Include/pytypedefs.h @@ -14,6 +14,7 @@ typedef struct PyModuleDef_Slot PyModuleDef_Slot; typedef struct PyMethodDef PyMethodDef; typedef struct PyGetSetDef PyGetSetDef; typedef struct PyMemberDef PyMemberDef; +typedef struct PySlot PySlot; typedef struct _object PyObject; typedef struct _longobject PyLongObject; diff --git a/Include/slots.h b/Include/slots.h new file mode 100644 index 00000000000000..65bfa068be6e6c --- /dev/null +++ b/Include/slots.h @@ -0,0 +1,54 @@ +#ifndef _Py_HAVE_SLOTS_H +#define _Py_HAVE_SLOTS_H + +typedef void (*_Py_funcptr_t)(void); + +struct PySlot { + uint16_t sl_id; + uint16_t sl_flags; + _Py_ANONYMOUS union { + uint32_t sl_reserved; + }; + _Py_ANONYMOUS union { + void *sl_ptr; + _Py_funcptr_t sl_func; + Py_ssize_t sl_size; + int64_t sl_int64; + uint64_t sl_uint64; + }; +}; + +#define PySlot_OPTIONAL 0x01 +#define PySlot_STATIC 0x02 +#define PySlot_INTPTR 0x04 + +#define Py_slot_invalid 0xffff + +#define PySlot_DATA(NAME, VALUE) \ + {.sl_id=(NAME), .sl_flags=PySlot_INTPTR, .sl_ptr=(void*)(VALUE)} + +#define PySlot_FUNC(NAME, VALUE) \ + {.sl_id=(NAME), .sl_func=(_Py_funcptr_t)(VALUE)} + +#define PySlot_SIZE(NAME, VALUE) \ + {.sl_id=(NAME), .sl_size=(Py_ssize_t)(VALUE)} + +#define PySlot_INT64(NAME, VALUE) \ + {.sl_id=(NAME), .sl_int64=(int64_t)(VALUE)} + +#define PySlot_UINT64(NAME, VALUE) \ + {.sl_id=(NAME), .sl_uint64=(uint64_t)(VALUE)} + +#define PySlot_STATIC_DATA(NAME, VALUE) \ + {.sl_id=(NAME), .sl_flags=PySlot_STATIC, .sl_ptr=(VALUE)} + +#define PySlot_END {0} + + +#define PySlot_PTR(NAME, VALUE) \ + {(NAME), PySlot_INTPTR, {0}, {(void*)(VALUE)}} + +#define PySlot_PTR_STATIC(NAME, VALUE) \ + {(NAME), PySlot_INTPTR | PySlot_STATIC, {0}, {(void*)(VALUE)}} + +#endif // _Py_HAVE_SLOTS_H diff --git a/Include/slots_generated.h b/Include/slots_generated.h new file mode 100644 index 00000000000000..42edd3ad4c69ff --- /dev/null +++ b/Include/slots_generated.h @@ -0,0 +1,121 @@ +/* Generated by Tools/build/generate_slots.py */ + +#ifndef _PY_HAVE_SLOTS_GENERATED_H +#define _PY_HAVE_SLOTS_GENERATED_H + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) +#define _Py_SLOT_COMPAT_VALUE(OLD, NEW) NEW +#else +#define _Py_SLOT_COMPAT_VALUE(OLD, NEW) OLD +#endif + +#define Py_slot_end 0 +#define Py_mp_subscript 5 +#define Py_nb_absolute 6 +#define Py_nb_add 7 +#define Py_nb_and 8 +#define Py_nb_bool 9 +#define Py_nb_divmod 10 +#define Py_nb_float 11 +#define Py_nb_floor_divide 12 +#define Py_nb_index 13 +#define Py_nb_inplace_add 14 +#define Py_nb_inplace_and 15 +#define Py_nb_inplace_floor_divide 16 +#define Py_nb_inplace_lshift 17 +#define Py_nb_inplace_multiply 18 +#define Py_nb_inplace_or 19 +#define Py_nb_inplace_power 20 +#define Py_nb_inplace_remainder 21 +#define Py_nb_inplace_rshift 22 +#define Py_nb_inplace_subtract 23 +#define Py_nb_inplace_true_divide 24 +#define Py_nb_inplace_xor 25 +#define Py_nb_int 26 +#define Py_nb_invert 27 +#define Py_nb_lshift 28 +#define Py_nb_multiply 29 +#define Py_nb_negative 30 +#define Py_nb_or 31 +#define Py_nb_positive 32 +#define Py_nb_power 33 +#define Py_nb_remainder 34 +#define Py_nb_rshift 35 +#define Py_nb_subtract 36 +#define Py_nb_true_divide 37 +#define Py_nb_xor 38 +#define Py_sq_ass_item 39 +#define Py_sq_concat 40 +#define Py_sq_contains 41 +#define Py_sq_inplace_concat 42 +#define Py_sq_inplace_repeat 43 +#define Py_sq_item 44 +#define Py_sq_length 45 +#define Py_sq_repeat 46 +#define Py_tp_alloc 47 +#define Py_tp_base 48 +#define Py_tp_bases 49 +#define Py_tp_call 50 +#define Py_tp_clear 51 +#define Py_tp_dealloc 52 +#define Py_tp_del 53 +#define Py_tp_descr_get 54 +#define Py_tp_descr_set 55 +#define Py_tp_doc 56 +#define Py_tp_getattr 57 +#define Py_tp_getattro 58 +#define Py_tp_hash 59 +#define Py_tp_init 60 +#define Py_tp_is_gc 61 +#define Py_tp_iter 62 +#define Py_tp_iternext 63 +#define Py_tp_methods 64 +#define Py_tp_new 65 +#define Py_tp_repr 66 +#define Py_tp_richcompare 67 +#define Py_tp_setattr 68 +#define Py_tp_setattro 69 +#define Py_tp_str 70 +#define Py_tp_traverse 71 +#define Py_tp_members 72 +#define Py_tp_getset 73 +#define Py_tp_free 74 +#define Py_nb_matrix_multiply 75 +#define Py_nb_inplace_matrix_multiply 76 +#define Py_am_await 77 +#define Py_am_aiter 78 +#define Py_am_anext 79 +#define Py_tp_finalize 80 +#define Py_am_send 81 +#define Py_tp_vectorcall 82 +#define Py_tp_token 83 +#define Py_mod_create _Py_SLOT_COMPAT_VALUE(1, 84) +#define Py_mod_exec _Py_SLOT_COMPAT_VALUE(2, 85) +#define Py_mod_multiple_interpreters _Py_SLOT_COMPAT_VALUE(3, 86) +#define Py_mod_gil _Py_SLOT_COMPAT_VALUE(4, 87) +#define Py_bf_getbuffer _Py_SLOT_COMPAT_VALUE(1, 88) +#define Py_bf_releasebuffer _Py_SLOT_COMPAT_VALUE(2, 89) +#define Py_mp_ass_subscript _Py_SLOT_COMPAT_VALUE(3, 90) +#define Py_mp_length _Py_SLOT_COMPAT_VALUE(4, 91) +#define Py_slot_subslots 92 +#define Py_tp_slots 93 +#define Py_mod_slots 94 +#define Py_tp_name 95 +#define Py_tp_basicsize 96 +#define Py_tp_extra_basicsize 97 +#define Py_tp_itemsize 98 +#define Py_tp_flags 99 +#define Py_mod_name 100 +#define Py_mod_doc 101 +#define Py_mod_state_size 102 +#define Py_mod_methods 103 +#define Py_mod_state_traverse 104 +#define Py_mod_state_clear 105 +#define Py_mod_state_free 106 +#define Py_tp_metaclass 107 +#define Py_tp_module 108 +#define Py_mod_abi 109 +#define Py_mod_token 110 + +#define _Py_slot_COUNT 111 +#endif /* _PY_HAVE_SLOTS_GENERATED_H */ diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 4c16bbd4cb0acf..3debc6369e89fb 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -923,8 +923,8 @@ def test_tp_bases_slot(self): def test_tp_bases_slot_none(self): self.assertRaisesRegex( - SystemError, - "Py_tp_bases is not a tuple", + TypeError, + "metaclass conflict", _testcapi.create_heapctype_with_none_bases_slot ) @@ -1055,13 +1055,13 @@ def test_heaptype_relative_members(self): def test_heaptype_relative_members_errors(self): with self.assertRaisesRegex( SystemError, - r"With Py_RELATIVE_OFFSET, basicsize must be negative"): + r"With Py_RELATIVE_OFFSET, basicsize must be extended"): _testlimitedcapi.make_heaptype_with_member(0, 1234, 0, True) with self.assertRaisesRegex( - SystemError, r"Member offset out of range \(0\.\.-basicsize\)"): + SystemError, r"Member offset out of range \(0\.\.extra_basicsize\)"): _testlimitedcapi.make_heaptype_with_member(0, -8, 1234, True) with self.assertRaisesRegex( - SystemError, r"Member offset out of range \(0\.\.-basicsize\)"): + SystemError, r"Member offset must not be negative"): _testlimitedcapi.make_heaptype_with_member(0, -8, -1, True) Sub = _testlimitedcapi.make_heaptype_with_member(0, -8, 0, True) @@ -1078,7 +1078,7 @@ def test_heaptype_relative_special_members_errors(self): with self.subTest(member_name=member_name): with self.assertRaisesRegex( SystemError, - r"With Py_RELATIVE_OFFSET, basicsize must be negative."): + r"With Py_RELATIVE_OFFSET, basicsize must be extended"): _testlimitedcapi.make_heaptype_with_member( basicsize=sys.getsizeof(object()) + 100, add_relative_flag=True, @@ -1089,7 +1089,7 @@ def test_heaptype_relative_special_members_errors(self): ) with self.assertRaisesRegex( SystemError, - r"Member offset out of range \(0\.\.-basicsize\)"): + r"Member offset must not be negative"): _testlimitedcapi.make_heaptype_with_member( basicsize=-8, add_relative_flag=True, @@ -1098,6 +1098,17 @@ def test_heaptype_relative_special_members_errors(self): member_type=_testlimitedcapi.Py_T_PYSSIZET, member_flags=_testlimitedcapi.Py_READONLY, ) + with self.assertRaisesRegex( + SystemError, + r"Member offset out of range \(0\.\.extra_basicsize\)"): + _testlimitedcapi.make_heaptype_with_member( + basicsize=-8, + add_relative_flag=True, + member_name=member_name, + member_offset=1234, + member_type=_testlimitedcapi.Py_T_PYSSIZET, + member_flags=_testlimitedcapi.Py_READONLY, + ) with self.assertRaisesRegex( SystemError, r"type of %s must be Py_T_PYSSIZET" % member_name): diff --git a/Lib/test/test_capi/test_module.py b/Lib/test/test_capi/test_module.py index c32ca1098edc56..29e1ce5b9af87f 100644 --- a/Lib/test/test_capi/test_module.py +++ b/Lib/test/test_capi/test_module.py @@ -150,11 +150,13 @@ def test_repeated_def_slot(self): with self.assertRaises(SystemError) as cm: _testcapi.module_from_slots_repeat_slot(spec) self.assertIn(name, str(cm.exception)) - self.assertIn("more than one", str(cm.exception)) + self.assertRegex( + str(cm.exception), + rf"^module( [_\w]+)? has multiple {name}( \(\d+\))? slots$") def test_null_def_slot(self): """Slots that replace PyModuleDef fields can't be NULL""" - for name in (*DEF_SLOTS, 'Py_mod_exec'): + for name in {*DEF_SLOTS, 'Py_mod_exec'} - {'Py_mod_state_size'}: with self.subTest(name): spec = FakeSpec() spec._test_slot_id = getattr(_testcapi, name) diff --git a/Lib/test/test_capi/test_slots.py b/Lib/test/test_capi/test_slots.py new file mode 100644 index 00000000000000..156d3f3eda5049 --- /dev/null +++ b/Lib/test/test_capi/test_slots.py @@ -0,0 +1,307 @@ +from test.support import import_helper, subTests +import importlib.machinery +import contextlib +import unittest +import types + +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +class FakeSpec: + name = 'module' + + +# See Modules/_testlimitedcapi/slots.c for the definitions. +# This module is full of "magic constants" which simply need to match +# between the C and Python part of the tests. + +class TypeSlotsTests(unittest.TestCase): + def test_basic_type_slots(self): + cls = _testlimitedcapi.type_from_slots("basic") + self.assertEqual(cls.__name__, "MyType") + + # Py_TPFLAGS_IMMUTABLETYPE is *not* set + cls.attr = 123 + + # Py_TPFLAGS_BASETYPE is *not* set + with self.assertRaises(TypeError): + class Sub(cls): + pass + + def test_mod_slot_in_type(self): + with self.assertRaisesRegex(SystemError, "invalid.* 100 .*Py_mod_name"): + _testlimitedcapi.type_from_slots("foreign_slot") + + def test_size_slots(self): + cls = _testlimitedcapi.type_from_slots("basicsize") + self.assertEqual(cls.__basicsize__, 256) + + cls = _testlimitedcapi.type_from_slots("extra_basicsize") + self.assertEqual(cls.__basicsize__, object.__basicsize__ + 256) + + cls = _testlimitedcapi.type_from_slots("itemsize") + self.assertEqual(cls.__itemsize__, 16) + + def test_flag_slots(self): + cls = _testlimitedcapi.type_from_slots("flags") + with self.assertRaises(TypeError): + # Py_TPFLAGS_IMMUTABLETYPE is set + cls.attr = 123 + class Sub(cls): + # Py_TPFLAGS_BASETYPE is set + pass + + def test_func_slots(self): + cls = _testlimitedcapi.type_from_slots("matmul_123") + self.assertEqual(cls() @ None, 123) + + def test_optional_end(self): + with self.assertRaisesRegex(SystemError, "invalid flags.*Py_slot_end"): + cls = _testlimitedcapi.type_from_slots("optional_end") + + def test_invalid(self): + with self.assertRaisesRegex(SystemError, "Py_slot_invalid"): + cls = _testlimitedcapi.type_from_slots("invalid") + with self.assertRaisesRegex(SystemError, f"slot ID {0xfbad}"): + cls = _testlimitedcapi.type_from_slots("invalid_fbad") + + cls = _testlimitedcapi.type_from_slots("optional_invalid") + self.assertEqual(cls.__basicsize__, object.__basicsize__ + 256) + + cls = _testlimitedcapi.type_from_slots("optional_invalid_fbad") + self.assertEqual(cls.__basicsize__, object.__basicsize__ + 256) + + @subTests("case_name", ["old_slot_numbers", "new_slot_numbers"]) + def test_compat_slot_numbers(self, case_name): + cls = _testlimitedcapi.type_from_slots(case_name) + obj = cls() + + # Py_bf_getbuffer (1), Py_bf_releasebuffer (2) + self.assertEqual(obj.buf_counter, 0) + mem = memoryview(obj) + self.assertEqual(bytes(mem), b"buf\0") + self.assertEqual(obj.buf_counter, 1) + mem.release() + self.assertEqual(obj.buf_counter, 0) + + # Py_mp_ass_subscript (3) + with self.assertRaises(KeyError): + obj["key"] = "value" + + # Py_mp_length (4) + self.assertEqual(len(obj), 456) + + def test_nonstatic_tp_members(self): + with self.assertRaisesRegex(SystemError, "Py_tp_members.*STATIC"): + _testlimitedcapi.type_from_slots("nonstatic_tp_members") + + def test_intptr_flags(self): + cls = _testlimitedcapi.type_from_slots("intptr_flags_macro") + with self.assertRaises(TypeError): + # Py_TPFLAGS_IMMUTABLETYPE is set + cls.attr = 123 + + cls = _testlimitedcapi.type_from_slots("intptr_flags_struct") + with self.assertRaises(TypeError): + # Py_TPFLAGS_IMMUTABLETYPE is set + cls.attr = 123 + + cls = _testlimitedcapi.type_from_slots("intptr_static") + cls.attribute = 123 + + def test_nested(self): + cls = _testlimitedcapi.type_from_slots("nested") + self.assertEqual(cls() + 1, 123) + self.assertEqual(cls() - 1, 234) + + cls = _testlimitedcapi.type_from_slots("nested_max") + self.assertEqual(cls() + 1, 123) + self.assertEqual(cls() - 1, 234) + self.assertEqual(cls() * 1, 345) + self.assertEqual(cls() / 1, 456) + self.assertEqual(cls() ** 1, 567) + + with self.assertRaisesRegex(SystemError, "too many levels"): + _testlimitedcapi.type_from_slots("nested_over_limit") + + cls = _testlimitedcapi.type_from_slots("nested_old") + self.assertEqual(cls() + 1, 123) + self.assertEqual(cls() - 1, 234) + + cls = _testlimitedcapi.type_from_slots("nested_old_max") + self.assertEqual(cls() + 1, 123) + self.assertEqual(cls() - 1, 234) + self.assertEqual(cls() * 1, 345) + self.assertEqual(cls() / 1, 456) + self.assertEqual(cls() ** 1, 567) + + with self.assertRaisesRegex(SystemError, "too many levels"): + _testlimitedcapi.type_from_slots("nested_old_over_limit") + + cls = _testlimitedcapi.type_from_slots("nested_pingpong") + self.assertEqual(cls() + 1, 123) + self.assertEqual(cls() - 1, 234) + self.assertEqual(cls() * 1, 345) + self.assertEqual(cls() / 1, 456) + self.assertEqual(cls() ** 1, 567) + + # Slot names aren't exposed to Python yet; see Include/slots_generated.h + # for the definitions. + + @subTests("slot_number", [ + *range(1, 83), # Original slots + *range(88, 92), # New compat slot values + *range(95, 99), # Slots for PyType_Spec fields + *range(107, 109), # Slots for PyType_FromMetaclass args + ]) + def test_null_slot_handling(self, slot_number): + if slot_number == 56: + # Py_tp_doc + return + elif slot_number == 72 or slot_number >= 95: + # Py_tp_members; all new slots + ctx = self.assertRaisesRegex( + SystemError, "NULL not allowed|must be positive") + ctx_old = ctx + else: + ctx = self.assertWarnsRegex(DeprecationWarning, "NULL") + ctx_old = contextlib.nullcontext() + with ctx: + _testlimitedcapi.type_from_null_slot(slot_number) + if slot_number < 95: + with ctx_old: + _testlimitedcapi.type_from_null_spec_slot(slot_number) + + def test_repeat_warning(self): + with self.assertWarnsRegex(DeprecationWarning, "multiple"): + cls = _testlimitedcapi.type_from_slots("repeat_add") + self.assertEqual(cls() + 1, 456) + + def test_repeat_error(self): + with self.assertRaisesRegex(SystemError, "multiple"): + cls = _testlimitedcapi.type_from_slots("repeat_module") + +class ModuleSlotsTests(unittest.TestCase): + def test_basic_module_slots(self): + mod = _testlimitedcapi.module_from_slots("basic", FakeSpec()) + self.assertIsInstance(mod, types.ModuleType) + + def test_type_slot_in_module(self): + with self.assertRaisesRegex(SystemError, "invalid.* 95 .*Py_tp_name"): + _testlimitedcapi.module_from_slots("foreign_slot", FakeSpec()) + + def test_size_slots(self): + mod = _testlimitedcapi.module_from_slots("state_size", FakeSpec()) + self.assertEqual(mod.state_size, 42) + + def test_flag_slots(self): + mod = _testlimitedcapi.module_from_slots("multi_interp", FakeSpec()) + mod = _testlimitedcapi.module_from_slots("gil", FakeSpec()) + + def test_exec_slot(self): + mod = _testlimitedcapi.module_from_slots("exec", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + + def test_optional_end(self): + with self.assertRaisesRegex(SystemError, "invalid flags.*Py_slot_end"): + _testlimitedcapi.module_from_slots("optional_end", FakeSpec()) + + def test_invalid(self): + with self.assertRaisesRegex(SystemError, "Py_slot_invalid"): + _testlimitedcapi.module_from_slots("invalid", FakeSpec()) + with self.assertRaisesRegex(SystemError, f"slot ID {0xfbad}"): + _testlimitedcapi.module_from_slots("invalid_fbad", FakeSpec()) + + mod = _testlimitedcapi.module_from_slots("optional_invalid", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + + mod = _testlimitedcapi.module_from_slots("optional_invalid_fbad", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + + @subTests("case_name", ["old_slot_numbers", "new_slot_numbers"]) + def test_compat_slot_numbers(self, case_name): + mod = _testlimitedcapi.module_from_slots(case_name, FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + + @subTests("case_name", ["old_slot_number_create", "new_slot_number_create"]) + def test_compat_slot_number_create(self, case_name): + spec = FakeSpec() + mod = _testlimitedcapi.module_from_slots(case_name, spec) + self.assertIs(mod, spec) + + def test_nonstatic_mod_methods(self): + with self.assertRaisesRegex(SystemError, "Py_mod_methods.*STATIC"): + _testlimitedcapi.module_from_slots("nonstatic_mod_methods", + FakeSpec()) + + def test_intptr_methods(self): + mod = _testlimitedcapi.module_from_slots("intptr_methods", + FakeSpec()) + self.assertEqual(mod.type_from_slots.__name__, "type_from_slots") + + def test_nested(self): + mod = _testlimitedcapi.module_from_slots("nested", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + self.assertEqual(mod.__doc__, "doc") + + mod = _testlimitedcapi.module_from_slots("nested_max", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + self.assertEqual(mod.state_size, 53) + self.assertEqual(mod.__doc__, "doc") + + with self.assertRaisesRegex(SystemError, "too many levels"): + _testlimitedcapi.module_from_slots("nested_over_limit", FakeSpec()) + + mod = _testlimitedcapi.module_from_slots("nested_old", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + self.assertEqual(mod.__doc__, "doc") + + mod = _testlimitedcapi.module_from_slots("nested_old_max", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + self.assertEqual(mod.state_size, 53) + self.assertEqual(mod.__doc__, "doc") + + with self.assertRaisesRegex(SystemError, "too many levels"): + _testlimitedcapi.module_from_slots("nested_old_over_limit", FakeSpec()) + + mod = _testlimitedcapi.module_from_slots("nested_pingpong", FakeSpec()) + self.assertEqual(mod.exec_done, "yes") + self.assertEqual(mod.state_size, 53) + self.assertEqual(mod.__doc__, "doc") + + def test_nested_nonstatic_from_def(self): + with self.assertRaisesRegex(SystemError, "must be static"): + _testlimitedcapi.module_from_def_nonstatic_nested(FakeSpec()) + + # Slot names aren't exposed to Python yet; see Include/slots_generated.h + # for the definitions. + + @subTests("slot_number", [ + *range(1, 5), # Old compat slot values + *range(84, 88), # New compat slot values + *range(100, 107), # Slots for PyModuleDef fields + *range(109, 111), # Slots new in 3.15 + ]) + def test_null_slot_handling(self, slot_number): + if slot_number in {3, 86, 4, 87, 102}: + # Py_mod_mult.interp., Py_mod_gil, Py_mod_state_size + return + elif slot_number in {2, 85} or slot_number > 85: + # Py_mod_exec, new slots + ctx = self.assertRaisesRegex(SystemError, "NULL not allowed") + ctx_old = ctx + else: + ctx = self.assertWarnsRegex(DeprecationWarning, "NULL") + ctx_old = contextlib.nullcontext() + with ctx: + _testlimitedcapi.module_from_null_slot(slot_number, FakeSpec()) + with ctx_old: + _testlimitedcapi.module_from_null_def_slot(slot_number, + FakeSpec()) + + def test_repeat_error(self): + with self.assertRaisesRegex(SystemError, "multiple"): + _testlimitedcapi.module_from_slots("repeat_create", FakeSpec()) + with self.assertRaisesRegex(SystemError, "multiple"): + _testlimitedcapi.module_from_slots("repeat_exec", FakeSpec()) + with self.assertRaisesRegex(SystemError, "multiple"): + _testlimitedcapi.module_from_slots("repeat_gil", FakeSpec()) diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index a880cb82811f78..89c8ec7efbffc9 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -121,13 +121,13 @@ _Py_COMP_DIAG_PUSH PyDoc_STRVAR(_testcext_doc, "C test extension."); PyABIInfo_VAR(abi_info); -static PyModuleDef_Slot _testcext_slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, STR(MODULE_NAME)}, - {Py_mod_doc, (void*)(char*)_testcext_doc}, - {Py_mod_exec, (void*)_testcext_exec}, - {Py_mod_methods, _testcext_methods}, - {0, NULL} +static PySlot _testcext_slots[] = { + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_name, STR(MODULE_NAME)), + PySlot_STATIC_DATA(Py_mod_doc, (void*)(char*)_testcext_doc), + PySlot_FUNC(Py_mod_exec, (void*)_testcext_exec), + PySlot_STATIC_DATA(Py_mod_methods, _testcext_methods), + PySlot_END, }; _Py_COMP_DIAG_POP diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index ed0868e0017fce..7907b240bc36f7 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -738,6 +738,7 @@ def test_windows_feature_macros(self): "PyType_Freeze", "PyType_FromMetaclass", "PyType_FromModuleAndSpec", + "PyType_FromSlots", "PyType_FromSpec", "PyType_FromSpecWithBases", "PyType_GenericAlloc", diff --git a/Makefile.pre.in b/Makefile.pre.in index 8b46db33a2ac18..3f4eff469b4b1e 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -497,6 +497,8 @@ PYTHON_OBJS= \ Python/qsbr.o \ Python/bootstrap_hash.o \ Python/specialize.o \ + Python/slots.o \ + Python/slots_generated.o \ Python/stackrefs.o \ Python/structmember.o \ Python/symtable.o \ @@ -1242,12 +1244,13 @@ PYTHON_HEADERS= \ $(srcdir)/Include/refcount.h \ $(srcdir)/Include/setobject.h \ $(srcdir)/Include/sliceobject.h \ + $(srcdir)/Include/slots.h \ + $(srcdir)/Include/slots_generated.h \ $(srcdir)/Include/structmember.h \ $(srcdir)/Include/structseq.h \ $(srcdir)/Include/sysmodule.h \ $(srcdir)/Include/traceback.h \ $(srcdir)/Include/tupleobject.h \ - $(srcdir)/Include/typeslots.h \ $(srcdir)/Include/unicodeobject.h \ $(srcdir)/Include/warnings.h \ $(srcdir)/Include/weakrefobject.h \ @@ -1429,6 +1432,8 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_setobject.h \ $(srcdir)/Include/internal/pycore_signal.h \ $(srcdir)/Include/internal/pycore_sliceobject.h \ + $(srcdir)/Include/internal/pycore_slots.h \ + $(srcdir)/Include/internal/pycore_slots_generated.h \ $(srcdir)/Include/internal/pycore_stats.h \ $(srcdir)/Include/internal/pycore_strhex.h \ $(srcdir)/Include/internal/pycore_stackref.h \ @@ -1938,7 +1943,7 @@ regen-unicodedata: # "clinic" is regenerated implicitly via "regen-global-objects". .PHONY: regen-all -regen-all: regen-cases regen-typeslots \ +regen-all: regen-cases regen-slots \ regen-token regen-ast regen-keyword regen-sre regen-frozen \ regen-pegen-metaparser regen-pegen regen-test-frozenmain \ regen-test-levenshtein regen-global-objects @@ -2311,16 +2316,17 @@ Python/import.o: $(srcdir)/Include/pydtrace.h Python/pydtrace.o: $(srcdir)/Include/pydtrace.d $(DTRACE_DEPS) CC="$(CC)" CFLAGS="$(CFLAGS)" $(DTRACE) $(DFLAGS) -o $@ -G -s $(srcdir)/Include/pydtrace.d $(DTRACE_DEPS) -Objects/typeobject.o: Objects/typeslots.inc - .PHONY: regen-typeslots regen-typeslots: - # Regenerate Objects/typeslots.inc from Include/typeslotsh - # using Objects/typeslots.py - $(PYTHON_FOR_REGEN) $(srcdir)/Objects/typeslots.py \ - < $(srcdir)/Include/typeslots.h \ - $(srcdir)/Objects/typeslots.inc.new - $(UPDATE_FILE) $(srcdir)/Objects/typeslots.inc $(srcdir)/Objects/typeslots.inc.new + echo 'NOTE: "regen-typeslots" was renamed to "regen-slots"' + $(MAKE) regen-slots + +.PHONY: regen-slots +regen-slots: Python/slots.toml + # Regenerate {Python,Include}/slots_generated.{c,h} + # from Python/slots.toml using Tools/build/generate_slots.py + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_slots.py \ + --generate-all $(LIBRARY_OBJS) $(MODOBJS) Programs/python.o: $(PYTHON_HEADERS) diff --git a/Misc/NEWS.d/next/C_API/2026-04-27-10-56-22.gh-issue-149044.TbOcUS.rst b/Misc/NEWS.d/next/C_API/2026-04-27-10-56-22.gh-issue-149044.TbOcUS.rst new file mode 100644 index 00000000000000..d7bb38f7cd7366 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-04-27-10-56-22.gh-issue-149044.TbOcUS.rst @@ -0,0 +1 @@ +Implement :pep:`820`: Unified slot system for the C API. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 101737a27829c9..bacaf106dd1b16 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -296,10 +296,12 @@ [const.Py_sq_inplace_repeat] added = '3.2' [const.Py_mp_length] + # Note: value changed in 3.15 (old value is still accepted) added = '3.2' [const.Py_mp_subscript] added = '3.2' [const.Py_mp_ass_subscript] + # Note: value changed in 3.15 (old value is still accepted) added = '3.2' [typedef.Py_uintptr_t] @@ -1914,8 +1916,10 @@ [data.PyModuleDef_Type] added = '3.5' [const.Py_mod_create] + # Note: value changed in 3.15 (old value is still accepted) added = '3.5' [const.Py_mod_exec] + # Note: value changed in 3.15 (old value is still accepted) added = '3.5' [struct.PyModuleDef_Slot] added = '3.5' @@ -2325,8 +2329,10 @@ [function.PyMemoryView_FromBuffer] added = '3.11' [const.Py_bf_getbuffer] + # Note: value changed in 3.15 (old value is still accepted) added = '3.11' [const.Py_bf_releasebuffer] + # Note: value changed in 3.15 (old value is still accepted) added = '3.11' # Constants for Py_buffer API added to this list in Python 3.11.1 (https://github.com/python/cpython/issues/98680) @@ -2463,6 +2469,7 @@ [const.Py_TPFLAGS_ITEMS_AT_END] added = '3.12' [const.Py_mod_multiple_interpreters] + # Note: value changed in 3.15 (old value is still accepted) added = '3.12' [function.PyImport_AddModuleRef] @@ -2544,6 +2551,7 @@ [function.PyEval_GetFrameLocals] added = '3.13' [const.Py_mod_gil] + # Note: value changed in 3.15 (old value is still accepted) added = '3.13' [function.Py_TYPE] @@ -2689,6 +2697,59 @@ added = '3.15' [function.PyType_GetModuleByToken_DuringGC] added = '3.15' +[struct.PySlot] + added = '3.15' + struct_abi_kind = 'full-abi' +[function.PyType_FromSlots] + added = '3.15' +[const.PySlot_OPTIONAL] + added = '3.15' +[const.PySlot_STATIC] + added = '3.15' +[const.PySlot_INTPTR] + added = '3.15' +[macro.PySlot_DATA] + added = '3.15' +[macro.PySlot_FUNC] + added = '3.15' +[macro.PySlot_SIZE] + added = '3.15' +[macro.PySlot_INT64] + added = '3.15' +[macro.PySlot_UINT64] + added = '3.15' +[macro.PySlot_STATIC_DATA] + added = '3.15' +[macro.PySlot_END] + added = '3.15' +[macro.PySlot_PTR] + added = '3.15' +[macro.PySlot_PTR_STATIC] + added = '3.15' +[const.Py_slot_end] + added = '3.15' +[const.Py_slot_invalid] + added = '3.15' +[const.Py_slot_subslots] + added = '3.15' +[const.Py_tp_slots] + added = '3.15' +[const.Py_mod_slots] + added = '3.15' +[const.Py_tp_name] + added = '3.15' +[const.Py_tp_basicsize] + added = '3.15' +[const.Py_tp_extra_basicsize] + added = '3.15' +[const.Py_tp_itemsize] + added = '3.15' +[const.Py_tp_flags] + added = '3.15' +[const.Py_tp_metaclass] + added = '3.15' +[const.Py_tp_module] + added = '3.15' # PEP 757 import/export API. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 0d520684c795d6..5cabd5f71f5bf1 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -176,7 +176,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/module.c b/Modules/_testcapi/module.c index 29eda7e008c6cb..b2ddaa10829755 100644 --- a/Modules/_testcapi/module.c +++ b/Modules/_testcapi/module.c @@ -13,8 +13,8 @@ PyABIInfo_VAR(abi_info); static PyObject * module_from_slots_empty(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {0}, + PySlot slots[] = { + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -22,9 +22,9 @@ module_from_slots_empty(PyObject *self, PyObject *spec) static PyObject * module_from_slots_minimal(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -38,12 +38,12 @@ module_from_slots_null(PyObject *self, PyObject *spec) static PyObject * module_from_slots_name(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "currently ignored..."}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "currently ignored..."), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -51,12 +51,12 @@ module_from_slots_name(PyObject *self, PyObject *spec) static PyObject * module_from_slots_doc(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_doc, "the docstring"}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_doc, "the docstring"), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -64,12 +64,12 @@ module_from_slots_doc(PyObject *self, PyObject *spec) static PyObject * module_from_slots_size(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_state_size, (void*)123}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_SIZE(Py_mod_state_size, (void*)123), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); if (!mod) { @@ -92,12 +92,12 @@ static PyMethodDef a_methoddef_array[] = { static PyObject * module_from_slots_methods(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_methods, a_methoddef_array}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_methods, a_methoddef_array), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -111,14 +111,14 @@ static void noop_free(void *self) { } static PyObject * module_from_slots_gc(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_state_traverse, noop_traverse}, - {Py_mod_state_clear, noop_clear}, - {Py_mod_state_free, noop_free}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_FUNC(Py_mod_state_traverse, noop_traverse), + PySlot_FUNC(Py_mod_state_clear, noop_clear), + PySlot_FUNC(Py_mod_state_free, noop_free), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); if (!mod) { @@ -144,12 +144,12 @@ static const char test_token; static PyObject * module_from_slots_token(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_token, (void*)&test_token}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_token, (void*)&test_token), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); if (!mod) { @@ -175,12 +175,12 @@ simple_exec(PyObject *module) static PyObject * module_from_slots_exec(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_exec, simple_exec}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_FUNC(Py_mod_exec, simple_exec), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); if (!mod) { @@ -209,12 +209,12 @@ create_attr_from_spec(PyObject *spec, PyModuleDef *def) static PyObject * module_from_slots_create(PyObject *self, PyObject *spec) { - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_create, create_attr_from_spec}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + const PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_FUNC(Py_mod_create, create_attr_from_spec), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -241,13 +241,13 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec) if (slot_id < 0) { return NULL; } - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {slot_id, "anything"}, - {slot_id, "anything else"}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + const PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_PTR_STATIC(slot_id, "anything"), + PySlot_PTR_STATIC(slot_id, "anything_else"), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -259,12 +259,12 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec) if (slot_id < 0) { return NULL; } - PyModuleDef_Slot slots[] = { - {slot_id, NULL}, - {Py_mod_abi, &abi_info}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + const PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_PTR_STATIC(slot_id, NULL), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -347,12 +347,12 @@ module_from_bad_abiinfo(PyObject *self, PyObject *spec) 1, 0, .abi_version=0x02080000, }; - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_abi, &bad_abi_info}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_abi, &bad_abi_info), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } @@ -365,14 +365,14 @@ module_from_multiple_abiinfo(PyObject *self, PyObject *spec) .flags=PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC, .abi_version=0x03040000, }; - PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_abi, &abi_info}, - {Py_mod_abi, &extra_abi_info}, - {Py_mod_abi, &extra_abi_info}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_abi, &extra_abi_info), + PySlot_DATA(Py_mod_abi, &extra_abi_info), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return PyModule_FromSlotsAndSpec(slots, spec); } diff --git a/Modules/_testcapi/type.c b/Modules/_testcapi/type.c index 9bef58d1f83668..f566efa0ca15ae 100644 --- a/Modules/_testcapi/type.c +++ b/Modules/_testcapi/type.c @@ -110,9 +110,9 @@ test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) return NULL; } - void *over_value = PyType_GetSlot(&PyLong_Type, Py_bf_releasebuffer + 1); + void *over_value = PyType_GetSlot(&PyLong_Type, Py_mod_name + 1); if (over_value != NULL) { - PyErr_SetString(PyExc_AssertionError, "mismatch: max+1 of long"); + PyErr_SetString(PyExc_AssertionError, "mismatch: mod_name of long"); return NULL; } diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index d3eb02d4727347..5f2be0dd43954e 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -74,6 +74,9 @@ PyInit__testlimitedcapi(void) if (_PyTestLimitedCAPI_Init_Set(mod) < 0) { return NULL; } + if (_PyTestLimitedCAPI_Init_Slots(mod) < 0) { + return NULL; + } if (_PyTestLimitedCAPI_Init_Sys(mod) < 0) { return NULL; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index 1cbb4f5659af0f..1eea4f74d14416 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -37,6 +37,7 @@ int _PyTestLimitedCAPI_Init_List(PyObject *module); int _PyTestLimitedCAPI_Init_Long(PyObject *module); int _PyTestLimitedCAPI_Init_PyOS(PyObject *module); int _PyTestLimitedCAPI_Init_Set(PyObject *module); +int _PyTestLimitedCAPI_Init_Slots(PyObject *module); int _PyTestLimitedCAPI_Init_Sys(PyObject *module); int _PyTestLimitedCAPI_Init_ThreadState(PyObject *module); int _PyTestLimitedCAPI_Init_Tuple(PyObject *module); diff --git a/Modules/_testlimitedcapi/slots.c b/Modules/_testlimitedcapi/slots.c new file mode 100644 index 00000000000000..4a04d59c5b6aac --- /dev/null +++ b/Modules/_testlimitedcapi/slots.c @@ -0,0 +1,651 @@ +#define Py_LIMITED_API 0x030f0000 + +#include "parts.h" + +PyABIInfo_VAR(abi_info); + +/* Define a bunch of (mostly nonsensical) functions to put in slots, so + * Lib/test/test_capi/test_slots.py can verify they've been assigned to + * the right slots. + + * This module is full of "magic constants" which simply need to match + * between the C and Python part of the tests. + */ + +// getbufferproc: export buffer; increment a counter +static int +demo_getbuffer(PyObject *exporter, Py_buffer *view, int flags) +{ + Py_INCREF(exporter); + // PyObject_GetTypeData & Py_TYPE: safe on non-subclassable type + int *data = PyObject_GetTypeData(exporter, Py_TYPE(exporter)); + if (!data) { + return -1; + } + (*data)++; + return PyBuffer_FillInfo(view, exporter, "buf", 4, 1, flags); +} + +// releasebufferproc: release buffer; decrement a counter +static void +demo_releasebuffer(PyObject *exporter, Py_buffer *view) +{ + Py_DECREF(exporter); + // PyObject_GetTypeData & Py_TYPE: safe on non-subclassable type + int *data = PyObject_GetTypeData(exporter, Py_TYPE(exporter)); + if (!data) { + PyErr_WriteUnraisable(exporter); + return; + } + (*data)--; + return; +} + +// objobjargproc: raise KeyError +static int +demo_ass_subscript(PyObject *o, PyObject *key, PyObject *v) +{ + PyErr_Format(PyExc_KeyError, "I don't like that key"); + return -1; +} + +// lenfunc: report 456 +static Py_ssize_t +demo_length(PyObject *o) +{ + return (Py_ssize_t)456; +} + +// binaryfunc; return constant value +static PyObject *binop_123(PyObject* a, PyObject *b) { return PyLong_FromLong(123); } +static PyObject *binop_234(PyObject* a, PyObject *b) { return PyLong_FromLong(234); } +static PyObject *binop_345(PyObject* a, PyObject *b) { return PyLong_FromLong(345); } +static PyObject *binop_456(PyObject* a, PyObject *b) { return PyLong_FromLong(456); } +static PyObject *binop_567(PyObject* a, PyObject *b) { return PyLong_FromLong(567); } +static PyObject *binop_678(PyObject* a, PyObject *b) { return PyLong_FromLong(678); } + +static PyObject * +type_from_slots(PyObject* module, PyObject *args) +{ + char *case_name; + if (!PyArg_ParseTuple(args, "s", &case_name)) { + return NULL; + } +#define CASE(NAME) \ + if (strcmp(case_name, NAME) == 0) { \ + return PyType_FromSlots((PySlot[]) { \ + PySlot_DATA(Py_tp_name, "_testlimitedcapi.MyType"), \ + PySlot_DATA(Py_tp_module, module), \ + ///////////////////////////////////////////////////////////////////////// +#define ENDCASE() \ + PySlot_END \ + }); \ + } \ + ///////////////////////////////////////////////////////////////////////// + + CASE("basic") + ENDCASE() + CASE("foreign_slot") + PySlot_DATA(Py_mod_name, "this is not a module"), + ENDCASE() + CASE("basicsize") + PySlot_SIZE(Py_tp_basicsize, 256), + ENDCASE() + CASE("extra_basicsize") + PySlot_SIZE(Py_tp_extra_basicsize, 256), + ENDCASE() + CASE("itemsize") + PySlot_SIZE(Py_tp_itemsize, 16), + ENDCASE() + CASE("flags") + PySlot_UINT64(Py_tp_flags, + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_BASETYPE), + ENDCASE() + CASE("matmul_123") + PySlot_FUNC(Py_nb_matrix_multiply, binop_123), + ENDCASE() + CASE("optional_end") + {.sl_flags=PySlot_OPTIONAL}, + ENDCASE() + CASE("invalid") + {.sl_id=Py_slot_invalid}, + ENDCASE() + CASE("invalid_fbad") + {.sl_id=0xfbad}, + ENDCASE() + CASE("optional_invalid") + {.sl_id=Py_slot_invalid, .sl_flags=PySlot_OPTIONAL}, + PySlot_SIZE(Py_tp_extra_basicsize, 256), + ENDCASE() + CASE("optional_invalid_fbad") + {.sl_id=0xfbad, .sl_flags=PySlot_OPTIONAL}, + PySlot_SIZE(Py_tp_extra_basicsize, 256), + ENDCASE() + CASE("old_slot_numbers") + PySlot_FUNC(1, demo_getbuffer), + PySlot_FUNC(2, demo_releasebuffer), + PySlot_FUNC(3, demo_ass_subscript), + PySlot_FUNC(4, demo_length), + PySlot_SIZE(Py_tp_extra_basicsize, sizeof(int)), + PySlot_STATIC_DATA(Py_tp_members, ((PyMemberDef[]) { + {"buf_counter", Py_T_INT, 0, Py_READONLY | Py_RELATIVE_OFFSET}, + {NULL}, + })), + ENDCASE() + CASE("new_slot_numbers") + PySlot_FUNC(88, demo_getbuffer), + PySlot_FUNC(89, demo_releasebuffer), + PySlot_FUNC(90, demo_ass_subscript), + PySlot_FUNC(91, demo_length), + PySlot_SIZE(Py_tp_extra_basicsize, sizeof(int)), + PySlot_STATIC_DATA(Py_tp_members, ((PyMemberDef[]) { + {"buf_counter", Py_T_INT, 0, Py_READONLY | Py_RELATIVE_OFFSET}, + {NULL}, + })), + ENDCASE() + CASE("nonstatic_tp_members") + PySlot_SIZE(Py_tp_extra_basicsize, sizeof(int)), + PySlot_DATA(Py_tp_members, ((PyMemberDef[]) { + {"buf_counter", Py_T_INT, 0, Py_READONLY | Py_RELATIVE_OFFSET}, + {NULL}, + })), + ENDCASE() + CASE("intptr_flags_macro") + PySlot_PTR(Py_tp_flags, (void*)(intptr_t)Py_TPFLAGS_IMMUTABLETYPE), + ENDCASE() + CASE("intptr_flags_struct") + {.sl_id=Py_tp_flags, + .sl_flags=PySlot_INTPTR, + .sl_ptr=(void*)(intptr_t)Py_TPFLAGS_IMMUTABLETYPE, + }, + ENDCASE() + CASE("intptr_static") + PySlot_SIZE(Py_tp_extra_basicsize, sizeof(int)), + PySlot_PTR_STATIC(Py_tp_members, ((PyMemberDef[]) { + {"attribute", Py_T_INT, 0, Py_READONLY | Py_RELATIVE_OFFSET}, + {NULL}, + })), + ENDCASE() + CASE("nested") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_subtract, binop_234), + PySlot_END, + })), + ENDCASE() + CASE("nested_max") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_subtract, binop_234), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_multiply, binop_345), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_true_divide, binop_456), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_power, binop_567), + PySlot_END + })), + PySlot_END, + })), + PySlot_END, + })), + PySlot_END, + })), + ENDCASE() + CASE("nested_over_limit") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_subtract, binop_234), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_multiply, binop_345), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_true_divide, binop_456), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_power, binop_567), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_xor, binop_678), + PySlot_END + })), + PySlot_END + })), + PySlot_END, + })), + PySlot_END, + })), + PySlot_END, + })), + ENDCASE() + CASE("nested_old") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_subtract, binop_234}, + {0}, + })), + ENDCASE() + CASE("nested_old_max") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_subtract, binop_234}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_multiply, binop_345}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_true_divide, binop_456}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_power, binop_567}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })), + ENDCASE() + CASE("nested_old_over_limit") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_subtract, binop_234}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_multiply, binop_345}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_true_divide, binop_456}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_power, binop_567}, + {Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_xor, binop_678}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })), + ENDCASE() + CASE("nested_pingpong") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_DATA(Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_subtract, binop_234}, + {Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_multiply, binop_345), + PySlot_DATA(Py_tp_slots, ((PyType_Slot[]) { + {Py_nb_true_divide, binop_456}, + {Py_slot_subslots, ((PySlot[]) { + PySlot_FUNC(Py_nb_power, binop_567), + PySlot_END + })}, + {0}, + })), + PySlot_END, + })}, + {0}, + })), + ENDCASE() + CASE("repeat_add") + PySlot_FUNC(Py_nb_add, binop_123), + PySlot_FUNC(Py_nb_add, binop_456), + ENDCASE() + CASE("repeat_module") + PySlot_DATA(Py_tp_module, Py_True), + PySlot_DATA(Py_tp_module, Py_False), + ENDCASE() + +#undef CASE +#undef ENDCASE + PyErr_Format(PyExc_AssertionError, "bad case: %s", case_name); + return NULL; +} + +static PyObject * +type_from_null_slot(PyObject* module, PyObject *args) +{ + long slot_number; + if (!PyArg_ParseTuple(args, "l", &slot_number)) { + return NULL; + } + return PyType_FromSlots((PySlot[]) { + PySlot_DATA(Py_tp_name, "_testlimitedcapi.MyType"), + PySlot_DATA(Py_tp_module, module), + PySlot_PTR_STATIC(slot_number, NULL), + PySlot_END + }); +} + +static PyObject * +type_from_null_spec_slot(PyObject* Py_UNUSED(module), PyObject *args) +{ + long slot_number; + if (!PyArg_ParseTuple(args, "l", &slot_number)) { + return NULL; + } + return PyType_FromSpec(&(PyType_Spec) { + .name = "_testlimitedcapi.MyType", + .slots = (PyType_Slot[]) { + {slot_number, NULL}, + {0}, + }, + }); +} + +static PyObject * +demo_create(PyObject *spec, PyModuleDef *def) +{ + assert(def == NULL); + return Py_NewRef(spec); +} + +static int +demo_exec(PyObject *mod) +{ + return PyModule_AddStringConstant(mod, "exec_done", "yes"); +} + +static PyMethodDef TestMethods[]; + +static PyObject * +module_from_slots(PyObject* Py_UNUSED(module), PyObject *args) +{ + PyObject *spec; + char *case_name; + if (!PyArg_ParseTuple(args, "sO", &case_name, &spec)) { + return NULL; + } + PyObject *mod = NULL; +#define CASE(NAME) \ + if (strcmp(case_name, NAME) == 0) { \ + mod = PyModule_FromSlotsAndSpec((PySlot[]) { \ + PySlot_DATA(Py_mod_abi, &abi_info), \ + ///////////////////////////////////////////////////////////////////////// +#define ENDCASE() \ + PySlot_END \ + }, spec); \ + } \ + ///////////////////////////////////////////////////////////////////////// + + CASE("basic") + ENDCASE() + CASE("foreign_slot") + PySlot_DATA(Py_tp_name, "this is not a type"), + ENDCASE() + CASE("state_size") + PySlot_SIZE(Py_mod_state_size, 42), + ENDCASE() + CASE("multi_interp") + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + ENDCASE() + CASE("gil") + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + ENDCASE() + CASE("exec") + PySlot_FUNC(Py_mod_exec, demo_exec), + ENDCASE() + CASE("optional_end") + {.sl_flags=PySlot_OPTIONAL}, + ENDCASE() + CASE("invalid") + {.sl_id=Py_slot_invalid}, + ENDCASE() + CASE("invalid_fbad") + {.sl_id=0xfbad}, + ENDCASE() + CASE("optional_invalid") + {.sl_id=Py_slot_invalid, .sl_flags=PySlot_OPTIONAL}, + PySlot_SIZE(Py_mod_exec, demo_exec), + ENDCASE() + CASE("optional_invalid_fbad") + {.sl_id=0xfbad, .sl_flags=PySlot_OPTIONAL}, + PySlot_SIZE(Py_mod_exec, demo_exec), + ENDCASE() + CASE("old_slot_numbers") + PySlot_FUNC(2, demo_exec), + PySlot_DATA(3, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_FUNC(4, Py_MOD_GIL_NOT_USED), + ENDCASE() + CASE("new_slot_numbers") + PySlot_FUNC(85, demo_exec), + PySlot_FUNC(86, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_FUNC(87, Py_MOD_GIL_NOT_USED), + ENDCASE() + CASE("old_slot_number_create") + PySlot_FUNC(1, demo_create), + PySlot_DATA(3, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_FUNC(4, Py_MOD_GIL_NOT_USED), + ENDCASE() + CASE("new_slot_number_create") + PySlot_FUNC(84, demo_create), + ENDCASE() + CASE("nonstatic_mod_methods") + PySlot_DATA(Py_mod_methods, TestMethods), + ENDCASE() + CASE("intptr_methods") + PySlot_PTR_STATIC(Py_mod_methods, TestMethods), + ENDCASE() + CASE("nested") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_mod_doc, "doc"), + PySlot_END, + })), + ENDCASE() + CASE("nested_max") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_SIZE(Py_mod_state_size, 53), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_mod_doc, "doc"), + PySlot_END + })), + PySlot_END, + })), + PySlot_END, + })), + PySlot_END, + })), + ENDCASE() + CASE("nested_over_limit") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_SIZE(Py_mod_state_size, 53), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_mod_doc, "doc"), + PySlot_DATA(Py_slot_subslots, ((PySlot[]) { + PySlot_END + })), + PySlot_END + })), + PySlot_END, + })), + PySlot_END, + })), + PySlot_END, + })), + ENDCASE() + CASE("nested_old") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_doc, "doc"}, + {0}, + })), + ENDCASE() + CASE("nested_old_max") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_state_size, (void*)(intptr_t)53}, + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_doc, "doc"}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })), + ENDCASE() + CASE("nested_old_over_limit") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_state_size, (void*)(intptr_t)53}, + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_doc, "doc"}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })}, + {0}, + })), + ENDCASE() + CASE("nested_pingpong") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_DATA(Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_mod_slots, ((PyModuleDef_Slot[]) { + {Py_mod_state_size, (void*)(intptr_t)53}, + {Py_slot_subslots, ((PySlot[]) { + PySlot_DATA(Py_mod_doc, "doc"), + PySlot_END + })}, + {0}, + })), + PySlot_END, + })}, + {0}, + })), + ENDCASE() + CASE("repeat_create") + PySlot_DATA(Py_mod_create, demo_create), + PySlot_DATA(Py_mod_create, demo_create), + PySlot_DATA(Py_mod_create, demo_create), + ENDCASE() + CASE("repeat_gil") + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + ENDCASE() + CASE("repeat_exec") + PySlot_FUNC(Py_mod_exec, demo_exec), + PySlot_FUNC(Py_mod_exec, demo_exec), + ENDCASE() + +#undef CASE +#undef ENDCASE + if (!mod) { + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_AssertionError, "bad case: %s", case_name); + return NULL; + } + return NULL; + } + if (PyModule_Check(mod)) { + Py_ssize_t size; + if (PyModule_GetStateSize(mod, &size) < 0) { + Py_DECREF(mod); + return NULL; + } + if (PyModule_AddIntConstant(mod, "state_size", size) < 0) { + Py_DECREF(mod); + return NULL; + } + if (PyModule_Exec(mod) < 0) { + return NULL; + } + } + return mod; +} + +static PyObject * +module_from_null_slot(PyObject* Py_UNUSED(module), PyObject *args) +{ + long slot_number; + PyObject *spec; + if (!PyArg_ParseTuple(args, "lO", &slot_number, &spec)) { + return NULL; + } + return PyModule_FromSlotsAndSpec((PySlot[]) { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "mymod"), + PySlot_PTR_STATIC(slot_number, NULL), + PySlot_END + }, spec); +} + +static PyObject * +module_from_null_def_slot(PyObject* Py_UNUSED(module), PyObject *args) +{ + long slot_number; + PyObject *spec; + if (!PyArg_ParseTuple(args, "lO", &slot_number, &spec)) { + return NULL; + } + static PyModuleDef_Slot slots[] = { + {0, NULL}, + {0}, + }; + static PyModuleDef def = { + .m_name = "mymod", + .m_slots = slots, + }; + // hack: def is supposed to be constant. + // Don't do this at home; use PyModule_FromSlotsAndSpec throwaway + // definitions! + slots[0].slot = slot_number; + return PyModule_FromDefAndSpec2(&def, spec, PYTHON_ABI_VERSION); +} + +static PyObject * +module_from_def_nonstatic_nested(PyObject* Py_UNUSED(module), PyObject *spec) +{ + static PyModuleDef_Slot subsubslots[] = { + {Py_mod_exec, demo_exec}, + {0}, + }; + static PySlot subslots[] = { + PySlot_DATA(Py_mod_slots, subsubslots), + PySlot_END, + }; + static PyModuleDef_Slot slots[] = { + {Py_slot_subslots, subslots}, + {0}, + }; + static PyModuleDef def = { + .m_name = "mymod", + .m_slots = slots, + }; + return PyModule_FromDefAndSpec2(&def, spec, PYTHON_ABI_VERSION); +} + +static PyMethodDef TestMethods[] = { + {"type_from_slots", type_from_slots, METH_VARARGS}, + {"type_from_null_slot", type_from_null_slot, METH_VARARGS}, + {"type_from_null_spec_slot", type_from_null_spec_slot, METH_VARARGS}, + {"module_from_slots", module_from_slots, METH_VARARGS}, + {"module_from_null_slot", module_from_null_slot, METH_VARARGS}, + {"module_from_null_def_slot", module_from_null_def_slot, METH_VARARGS}, + {"module_from_def_nonstatic_nested", module_from_def_nonstatic_nested, METH_O}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Slots(PyObject *m) +{ + if (PyModule_AddFunctions(m, TestMethods) < 0) { + return -1; + } + + return 0; +} diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index b0668e32eb57f4..43ee2e5a204bc2 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -603,7 +603,7 @@ PyInit__testmultiphase_null_slots(void) /**** Problematic modules ****/ static PyModuleDef_Slot slots_bad_large[] = { - {_Py_mod_LAST_SLOT + 1, NULL}, + {Py_slot_invalid, NULL}, {0, NULL}, }; @@ -1051,12 +1051,12 @@ PyABIInfo_VAR(abi_info); PyMODEXPORT_FUNC PyModExport__test_from_modexport(void) { - static PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "_test_from_modexport"}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "_test_from_modexport"), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_USED), + PySlot_END, }; return slots; } @@ -1064,12 +1064,12 @@ PyModExport__test_from_modexport(void) PyMODEXPORT_FUNC PyModExport__test_from_modexport_gil_used(void) { - static PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "_test_from_modexport_gil_used"}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_USED}, - {0}, + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "_test_from_modexport_gil_used"), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_USED), + PySlot_END, }; return slots; } @@ -1115,13 +1115,13 @@ modexport_create_string(PyObject *spec, PyModuleDef *def) PyMODEXPORT_FUNC PyModExport__test_from_modexport_create_nonmodule(void) { - static PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "_test_from_modexport_create_nonmodule"}, - {Py_mod_create, modexport_create_string}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "_test_from_modexport_create_nonmodule"), + PySlot_FUNC(Py_mod_create, modexport_create_string), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return slots; } @@ -1129,19 +1129,19 @@ PyModExport__test_from_modexport_create_nonmodule(void) PyMODEXPORT_FUNC PyModExport__test_from_modexport_create_nonmodule_gil_used(void) { - static PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "_test_from_modexport_create_nonmodule"}, - {Py_mod_create, modexport_create_string}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_USED}, - {0}, + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "_test_from_modexport_create_nonmodule"), + PySlot_FUNC(Py_mod_create, modexport_create_string), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_USED), + PySlot_END, }; return slots; } -static PyModuleDef_Slot modexport_empty_slots[] = { - {0}, +static PySlot modexport_empty_slots[] = { + PySlot_END, }; PyMODEXPORT_FUNC @@ -1151,9 +1151,9 @@ PyModExport__test_from_modexport_empty_slots(void) } -static PyModuleDef_Slot modexport_minimal_slots[] = { - {Py_mod_abi, &abi_info}, - {0}, +static PySlot modexport_minimal_slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_END, }; PyMODEXPORT_FUNC @@ -1234,18 +1234,18 @@ PyModExport__test_from_modexport_smoke(void) {"get_modexport_minimal_slots", modexport_get_minimal_slots, METH_NOARGS}, {0}, }; - static PyModuleDef_Slot slots[] = { - {Py_mod_abi, &abi_info}, - {Py_mod_name, "_test_from_modexport_smoke"}, - {Py_mod_doc, "the expected docstring"}, - {Py_mod_exec, modexport_smoke_exec}, - {Py_mod_state_size, (void*)sizeof(int)}, - {Py_mod_methods, methods}, - {Py_mod_state_free, modexport_smoke_free}, - {Py_mod_token, (void*)&modexport_smoke_test_token}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - {0}, + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_name, "_test_from_modexport_smoke"), + PySlot_DATA(Py_mod_doc, "the expected docstring"), + PySlot_FUNC(Py_mod_exec, modexport_smoke_exec), + PySlot_SIZE(Py_mod_state_size, (void*)sizeof(int)), + PySlot_STATIC_DATA(Py_mod_methods, methods), + PySlot_FUNC(Py_mod_state_free, modexport_smoke_free), + PySlot_DATA(Py_mod_token, (void*)&modexport_smoke_test_token), + PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, }; return slots; } diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 19e5134d5cf26b..72894cd3988fda 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -14,6 +14,7 @@ #include "pycore_object.h" // _PyType_AllocNoTrack #include "pycore_pyerrors.h" // _PyErr_FormatFromCause() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_slots.h" // _PySlotIterator_Init #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -396,22 +397,20 @@ _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version) return (PyObject*)m; } +typedef PyObject *(*createfunc_t)(PyObject *, PyModuleDef*); + static PyObject * -module_from_def_and_spec( - PyModuleDef* def_like, /* not necessarily a valid Python object */ +module_from_slots_and_spec( + const PySlot *slots, PyObject *spec, int module_api_version, PyModuleDef* original_def /* NULL if not defined by a def */) { - PyModuleDef_Slot* cur_slot; - PyObject *(*create)(PyObject *, PyModuleDef*) = NULL; + createfunc_t create = NULL; PyObject *nameobj; PyObject *m = NULL; - int has_multiple_interpreters_slot = 0; - void *multiple_interpreters = (void *)0; - int has_gil_slot = 0; + uint64_t multiple_interpreters = (uint64_t)Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED; bool requires_gil = true; - int has_execution_slots = 0; const char *name; int ret; void *token = NULL; @@ -431,142 +430,55 @@ module_from_def_and_spec( goto error; } - if (def_like->m_size < 0) { - PyErr_Format( - PyExc_SystemError, - "module %s: m_size may not be negative for multi-phase initialization", - name); - goto error; + _PySlotIterator it; + + PyModuleDef _dummy_def = {0}; + PyModuleDef* def_like; + if (slots) { + assert(!original_def); + def_like = &_dummy_def; + _PySlotIterator_Init(&it, slots, _PySlot_KIND_MOD); } + else { + assert(original_def); + def_like = original_def; + _PySlotIterator_InitLegacy(&it, def_like->m_slots, _PySlot_KIND_MOD); + } + it.name = name; - bool seen_m_name_slot = false; - bool seen_m_doc_slot = false; - bool seen_m_size_slot = false; - bool seen_m_methods_slot = false; - bool seen_m_traverse_slot = false; - bool seen_m_clear_slot = false; - bool seen_m_free_slot = false; - bool seen_m_abi_slot = false; - for (cur_slot = def_like->m_slots; cur_slot && cur_slot->slot; cur_slot++) { - - // Macro to copy a non-NULL, non-repeatable slot. -#define COPY_NONNULL_SLOT(SLOTNAME, TYPE, DEST) \ - do { \ - if (!(TYPE)(cur_slot->value)) { \ - PyErr_Format( \ - PyExc_SystemError, \ - "module %s: %s must not be NULL", \ - name, SLOTNAME); \ - goto error; \ - } \ - DEST = (TYPE)(cur_slot->value); \ - } while (0); \ - ///////////////////////////////////////////////////////////////// - - // Macro to copy a non-NULL, non-repeatable slot to def_like. -#define COPY_DEF_SLOT(SLOTNAME, TYPE, MEMBER) \ - do { \ - if (seen_ ## MEMBER ## _slot) { \ - PyErr_Format( \ - PyExc_SystemError, \ - "module %s has more than one %s slot", \ - name, SLOTNAME); \ - goto error; \ - } \ - seen_ ## MEMBER ## _slot = true; \ - if (original_def) { \ - TYPE orig_value = (TYPE)original_def->MEMBER; \ - TYPE new_value = (TYPE)cur_slot->value; \ - if (orig_value != new_value) { \ - PyErr_Format( \ - PyExc_SystemError, \ - "module %s: %s conflicts with " \ - "PyModuleDef." #MEMBER, \ - name, SLOTNAME); \ - goto error; \ - } \ - } \ - COPY_NONNULL_SLOT(SLOTNAME, TYPE, (def_like->MEMBER)) \ - } while (0); \ - ///////////////////////////////////////////////////////////////// - - // Macro to copy a non-NULL, non-repeatable slot without a - // corresponding PyModuleDef member. - // DEST must be initially NULL (so we don't need a seen_* flag). -#define COPY_NONDEF_SLOT(SLOTNAME, TYPE, DEST) \ - do { \ - if (DEST) { \ - PyErr_Format( \ - PyExc_SystemError, \ - "module %s has more than one %s slot", \ - name, SLOTNAME); \ - goto error; \ - } \ - COPY_NONNULL_SLOT(SLOTNAME, TYPE, DEST) \ - } while (0); \ - ///////////////////////////////////////////////////////////////// - - // Define the whole common case -#define DEF_SLOT_CASE(SLOT, TYPE, MEMBER) \ - case SLOT: \ - COPY_DEF_SLOT(#SLOT, TYPE, MEMBER); \ - break; \ - ///////////////////////////////////////////////////////////////// - switch (cur_slot->slot) { + while (_PySlotIterator_Next(&it)) { + switch (it.current.sl_id) { + case Py_slot_invalid: + goto error; case Py_mod_create: - if (create) { - PyErr_Format( - PyExc_SystemError, - "module %s has multiple create slots", - name); - goto error; - } - create = cur_slot->value; + create = (createfunc_t)it.current.sl_func; break; case Py_mod_exec: - has_execution_slots = 1; if (!original_def) { - COPY_NONDEF_SLOT("Py_mod_exec", _Py_modexecfunc, m_exec); + if (m_exec) { + PyErr_Format( + PyExc_SystemError, + "module %s has multiple Py_mod_exec slots", + name); + goto error; + } + m_exec = (_Py_modexecfunc)it.current.sl_func; } break; case Py_mod_multiple_interpreters: - if (has_multiple_interpreters_slot) { - PyErr_Format( - PyExc_SystemError, - "module %s has more than one 'multiple interpreters' " - "slots", - name); - goto error; - } - multiple_interpreters = cur_slot->value; - has_multiple_interpreters_slot = 1; + multiple_interpreters = it.current.sl_uint64; break; case Py_mod_gil: - if (has_gil_slot) { - PyErr_Format( - PyExc_SystemError, - "module %s has more than one 'gil' slot", - name); - goto error; - } - requires_gil = (cur_slot->value != Py_MOD_GIL_NOT_USED); - has_gil_slot = 1; + uint64_t val = it.current.sl_uint64; + requires_gil = (val != (uint64_t)Py_MOD_GIL_NOT_USED); break; case Py_mod_abi: - if (PyABIInfo_Check((PyABIInfo *)cur_slot->value, name) < 0) { + if (PyABIInfo_Check(it.current.sl_ptr, name) < 0) { goto error; } - seen_m_abi_slot = true; break; - DEF_SLOT_CASE(Py_mod_name, char*, m_name) - DEF_SLOT_CASE(Py_mod_doc, char*, m_doc) - DEF_SLOT_CASE(Py_mod_state_size, Py_ssize_t, m_size) - DEF_SLOT_CASE(Py_mod_methods, PyMethodDef*, m_methods) - DEF_SLOT_CASE(Py_mod_state_traverse, traverseproc, m_traverse) - DEF_SLOT_CASE(Py_mod_state_clear, inquiry, m_clear) - DEF_SLOT_CASE(Py_mod_state_free, freefunc, m_free) case Py_mod_token: - if (original_def && original_def != cur_slot->value) { + if (original_def && original_def != it.current.sl_ptr) { PyErr_Format( PyExc_SystemError, "module %s: arbitrary Py_mod_token not " @@ -574,22 +486,40 @@ module_from_def_and_spec( name); goto error; } - COPY_NONDEF_SLOT("Py_mod_token", void*, token); + token = it.current.sl_ptr; break; - default: - assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT); - PyErr_Format( - PyExc_SystemError, - "module %s uses unknown slot ID %i", - name, cur_slot->slot); - goto error; - } + // Common case: Copy a PEP 793 slot to def_like +#define DEF_SLOT_CASE(SLOT, TYPE, SL_MEMBER, MEMBER) \ + case SLOT: \ + do { \ + if (original_def) { \ + TYPE orig_value = (TYPE)original_def->MEMBER; \ + TYPE new_value = (TYPE)it.current.SL_MEMBER; \ + if (orig_value != new_value) { \ + PyErr_Format( \ + PyExc_SystemError, \ + "module %s: %s conflicts with " \ + "PyModuleDef." #MEMBER, \ + name, _PySlot_GetName(it.current.sl_id)); \ + goto error; \ + } \ + } \ + (def_like->MEMBER) = (TYPE)it.current.SL_MEMBER; \ + } while (0); \ + break; \ + ///////////////////////////////////////////////////////////////// + DEF_SLOT_CASE(Py_mod_name, char*, sl_ptr, m_name) + DEF_SLOT_CASE(Py_mod_doc, char*, sl_ptr, m_doc) + DEF_SLOT_CASE(Py_mod_state_size, Py_ssize_t, sl_size, m_size) + DEF_SLOT_CASE(Py_mod_methods, PyMethodDef*, sl_ptr, m_methods) + DEF_SLOT_CASE(Py_mod_state_traverse, + traverseproc, sl_func, m_traverse) + DEF_SLOT_CASE(Py_mod_state_clear, inquiry, sl_func, m_clear) + DEF_SLOT_CASE(Py_mod_state_free, freefunc, sl_func, m_free) #undef DEF_SLOT_CASE -#undef COPY_DEF_SLOT -#undef COPY_NONDEF_SLOT -#undef COPY_NONNULL_SLOT + } } - if (!original_def && !seen_m_abi_slot) { + if (!original_def && !_PySlotIterator_SawSlot(&it, Py_mod_abi)) { PyErr_Format( PyExc_SystemError, "module %s does not define Py_mod_abi," @@ -614,19 +544,24 @@ module_from_def_and_spec( } #endif + if (def_like->m_size < 0) { + PyErr_Format( + PyExc_SystemError, + "module %s: m_size may not be negative for multi-phase initialization", + name); + goto error; + } + /* By default, multi-phase init modules are expected to work under multiple interpreters. */ - if (!has_multiple_interpreters_slot) { - multiple_interpreters = Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED; - } - if (multiple_interpreters == Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED) { + if (multiple_interpreters == (int64_t)(intptr_t)Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED) { if (!_Py_IsMainInterpreter(interp) && _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0) { goto error; } } - else if (multiple_interpreters != Py_MOD_PER_INTERPRETER_GIL_SUPPORTED + else if (multiple_interpreters != (int64_t)(intptr_t)Py_MOD_PER_INTERPRETER_GIL_SUPPORTED && interp->ceval.own_gil && !_Py_IsMainInterpreter(interp) && _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0) @@ -688,7 +623,7 @@ module_from_def_and_spec( name); goto error; } - if (has_execution_slots) { + if (_PySlotIterator_SawSlot(&it, Py_mod_exec)) { PyErr_Format( PyExc_SystemError, "module %s specifies execution slots, but did not create " @@ -733,11 +668,11 @@ PyObject * PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_version) { PyModuleDef_Init(def); - return module_from_def_and_spec(def, spec, module_api_version, def); + return module_from_slots_and_spec(NULL, spec, module_api_version, def); } PyObject * -PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec) +PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec) { if (!slots) { PyErr_SetString( @@ -745,11 +680,9 @@ PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec) "PyModule_FromSlotsAndSpec called with NULL slots"); return NULL; } - // Fill in enough of a PyModuleDef to pass to common machinery - PyModuleDef def_like = {.m_slots = (PyModuleDef_Slot *)slots}; - return module_from_def_and_spec(&def_like, spec, PYTHON_API_VERSION, - NULL); + return module_from_slots_and_spec(slots, spec, PYTHON_API_VERSION, + NULL); } #ifdef Py_GIL_DISABLED @@ -835,8 +768,6 @@ PyModule_Exec(PyObject *module) int PyModule_ExecDef(PyObject *module, PyModuleDef *def) { - PyModuleDef_Slot *cur_slot; - if (alloc_state(module) < 0) { return -1; } @@ -847,13 +778,17 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def) return 0; } - for (cur_slot = def->m_slots; cur_slot && cur_slot->slot; cur_slot++) { - if (cur_slot->slot == Py_mod_exec) { - int (*func)(PyObject *) = cur_slot->value; - if (run_exec_func(module, func) < 0) { + _PySlotIterator it; + _PySlotIterator_InitLegacy(&it, def->m_slots, _PySlot_KIND_MOD); + while (_PySlotIterator_Next(&it)) { + switch (it.current.sl_id) { + case Py_slot_invalid: return -1; - } - continue; + case Py_mod_exec: + _Py_modexecfunc func = (_Py_modexecfunc)it.current.sl_func; + if (run_exec_func(module, func) < 0) { + return -1; + } } } return 0; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb3c7101410683..75d5325d4eb604 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -18,6 +18,7 @@ #include "pycore_pyatomic_ft_wrappers.h" #include "pycore_pyerrors.h" // _PyErr_Occurred() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_slots.h" // _PySlotIterator_Init #include "pycore_symtable.h" // _Py_Mangle() #include "pycore_tuple.h" // _PyTuple_FromPair #include "pycore_typeobject.h" // struct type_cache @@ -198,11 +199,6 @@ type_lock_allow_release(void) #define PyTypeObject_CAST(op) ((PyTypeObject *)(op)) -typedef struct PySlot_Offset { - short subslot_offset; - short slot_offset; -} PySlot_Offset; - static void slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer); @@ -5112,21 +5108,6 @@ type_vectorcall(PyObject *metatype, PyObject *const *args, return _PyObject_MakeTpCall(tstate, metatype, args, nargs, kwnames); } -/* An array of type slot offsets corresponding to Py_tp_* constants, - * for use in e.g. PyType_Spec and PyType_GetSlot. - * Each entry has two offsets: "slot_offset" and "subslot_offset". - * If is subslot_offset is -1, slot_offset is an offset within the - * PyTypeObject struct. - * Otherwise slot_offset is an offset to a pointer to a sub-slots struct - * (such as "tp_as_number"), and subslot_offset is the offset within - * that struct. - * The actual table is generated by a script. - */ -static const PySlot_Offset pyslot_offsets[] = { - {0, 0}, -#include "typeslots.inc" -}; - /* Align up to the nearest multiple of alignof(max_align_t) * (like _Py_ALIGN_UP, but for a size rather than pointer) */ @@ -5136,43 +5117,6 @@ _align_up(Py_ssize_t size) return (size + ALIGNOF_MAX_ALIGN_T - 1) & ~(ALIGNOF_MAX_ALIGN_T - 1); } -/* Given a PyType_FromMetaclass `bases` argument (NULL, type, or tuple of - * types), return a tuple of types. - */ -inline static PyObject * -get_bases_tuple(PyObject *bases_in, PyType_Spec *spec) -{ - if (!bases_in) { - /* Default: look in the spec, fall back to (type,). */ - PyTypeObject *base = &PyBaseObject_Type; // borrowed ref - PyObject *bases = NULL; // borrowed ref - const PyType_Slot *slot; - for (slot = spec->slots; slot->slot; slot++) { - switch (slot->slot) { - case Py_tp_base: - base = slot->pfunc; - break; - case Py_tp_bases: - bases = slot->pfunc; - break; - } - } - if (!bases) { - return PyTuple_Pack(1, base); - } - if (PyTuple_Check(bases)) { - return Py_NewRef(bases); - } - PyErr_SetString(PyExc_SystemError, "Py_tp_bases is not a tuple"); - return NULL; - } - if (PyTuple_Check(bases_in)) { - return Py_NewRef(bases_in); - } - // Not a tuple, should be a single type - return PyTuple_Pack(1, bases_in); -} - static inline int check_basicsize_includes_size_and_offsets(PyTypeObject* type) { @@ -5274,10 +5218,11 @@ special_offset_from_member( return -1; } -PyObject * -PyType_FromMetaclass( - PyTypeObject *metaclass, PyObject *module, - PyType_Spec *spec, PyObject *bases_in) + +static PyObject * +type_from_slots_or_spec( + PySlot *slots, PyType_Spec *spec, + PyTypeObject *metaclass, PyObject *module, PyObject *bases_in) { /* Invariant: A non-NULL value in one of these means this function holds * a strong reference or owns allocated memory. @@ -5292,47 +5237,130 @@ PyType_FromMetaclass( int r; - /* Prepare slots that need special handling. - * Keep in mind that a slot can be given multiple times: - * if that would cause trouble (leaks, UB, ...), raise an exception. - */ + /* First pass of slots */ - const PyType_Slot *slot; Py_ssize_t nmembers = 0; const PyMemberDef *weaklistoffset_member = NULL; const PyMemberDef *dictoffset_member = NULL; const PyMemberDef *vectorcalloffset_member = NULL; - char *res_start; + Py_ssize_t basicsize = 0; + Py_ssize_t extra_basicsize = 0; + Py_ssize_t itemsize = 0; + int flags = 0; + void *token = NULL; - for (slot = spec->slots; slot->slot; slot++) { - if (slot->slot < 0 - || (size_t)slot->slot >= Py_ARRAY_LENGTH(pyslot_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "invalid slot offset"); - goto finally; + bool have_relative_members = false; + Py_ssize_t max_relative_offset = 0; + + PyObject *bases_slot = NULL; /* borrowed from the slots */ + + _PySlotIterator it; + + if (spec) { + assert(!slots); + if (spec->basicsize > 0) { + basicsize = spec->basicsize; } - switch (slot->slot) { - case Py_tp_members: - if (nmembers != 0) { + if (spec->basicsize < 0) { + extra_basicsize = -spec->basicsize; + } + itemsize = spec->itemsize; + flags = spec->flags; + _PySlotIterator_InitLegacy(&it, spec->slots, _PySlot_KIND_TYPE); + it.name = spec->name; + } + else { + assert(!spec); + assert(!metaclass); + assert(!module); + assert(!bases_in); + _PySlotIterator_Init(&it, slots, _PySlot_KIND_TYPE); + } + + #define NO_SPEC \ + if (spec) { \ + PyErr_Format( \ + PyExc_SystemError, \ + "%s must not be used with PyType_Spec", \ + _PySlot_GetName(it.current.sl_id)); \ + goto finally; \ + } \ + ///////////////////////////////////////////////////// + + while (_PySlotIterator_Next(&it)) { + switch (it.current.sl_id) { + case Py_slot_invalid: + goto finally; + case Py_tp_name: + NO_SPEC; + it.name = it.current.sl_ptr; + break; + case Py_tp_metaclass: + NO_SPEC; + metaclass = it.current.sl_ptr; + break; + case Py_tp_module: + NO_SPEC; + module = it.current.sl_ptr; + break; + case Py_tp_bases: + bases_slot = it.current.sl_ptr; + break; + case Py_tp_base: + if (!_PySlotIterator_SawSlot(&it, Py_tp_bases)) { + bases_slot = it.current.sl_ptr; + } + break; + case Py_tp_basicsize: + NO_SPEC; + basicsize = it.current.sl_size; + if (basicsize <= 0) { PyErr_SetString( PyExc_SystemError, - "Multiple Py_tp_members slots are not supported."); + "Py_tp_basicsize must be positive"); goto finally; } - for (const PyMemberDef *memb = slot->pfunc; memb->name != NULL; memb++) { + break; + case Py_tp_extra_basicsize: + NO_SPEC; + extra_basicsize = it.current.sl_size; + if (extra_basicsize <= 0) { + PyErr_SetString( + PyExc_SystemError, + "Py_tp_extra_basicsize must be positive"); + goto finally; + } + break; + case Py_tp_itemsize: + NO_SPEC; + itemsize = it.current.sl_size; + if (itemsize <= 0) { + PyErr_SetString( + PyExc_SystemError, + "Py_tp_itemsize must be positive"); + goto finally; + } + break; + case Py_tp_flags: + NO_SPEC; + flags = it.current.sl_uint64; + break; + case Py_tp_members: + for (const PyMemberDef *memb = it.current.sl_ptr; + memb->name != NULL; + memb++) + { nmembers++; if (memb->flags & Py_RELATIVE_OFFSET) { - if (spec->basicsize > 0) { + if (memb->offset < 0) { PyErr_SetString( PyExc_SystemError, - "With Py_RELATIVE_OFFSET, basicsize must be negative."); - goto finally; - } - if (memb->offset < 0 || memb->offset >= -spec->basicsize) { - PyErr_SetString( - PyExc_SystemError, - "Member offset out of range (0..-basicsize)"); + "Member offset must not be negative"); goto finally; } + have_relative_members = true; + max_relative_offset = Py_MAX(max_relative_offset, + memb->offset); } if (strcmp(memb->name, "__weaklistoffset__") == 0) { weaklistoffset_member = memb; @@ -5345,43 +5373,86 @@ PyType_FromMetaclass( } } break; + case Py_tp_token: + token = it.current.sl_ptr; + if (token == Py_TP_USE_SPEC) { + if (!spec) { + PyErr_SetString( + PyExc_SystemError, + "Py_tp_token: Py_TP_USE_SPEC (NULL) can only be " + "used with PyType_Spec"); + goto finally; + } + token = spec; + } + break; case Py_tp_doc: /* For the docstring slot, which usually points to a static string literal, we need to make a copy */ - if (tp_doc != NULL) { - PyErr_SetString( - PyExc_SystemError, - "Multiple Py_tp_doc slots are not supported."); - goto finally; - } - if (slot->pfunc == NULL) { + if (it.current.sl_ptr == NULL) { PyMem_Free(tp_doc); tp_doc = NULL; } else { - size_t len = strlen(slot->pfunc)+1; + size_t len = strlen(it.current.sl_ptr)+1; tp_doc = PyMem_Malloc(len); if (tp_doc == NULL) { PyErr_NoMemory(); goto finally; } - memcpy(tp_doc, slot->pfunc, len); + memcpy(tp_doc, it.current.sl_ptr, len); } break; } } + #undef NO_SPEC - /* Prepare the type name and qualname */ + /* Required slots & bad combinations */ - if (spec->name == NULL) { - PyErr_SetString(PyExc_SystemError, - "Type spec does not define the name field."); + if (it.name == NULL) { + if (spec) { + PyErr_SetString(PyExc_SystemError, + "Type spec does not define the name field."); + } + else { + PyErr_SetString(PyExc_SystemError, + "Py_tp_name slot is required."); + } goto finally; } - const char *s = strrchr(spec->name, '.'); + if (_PySlotIterator_SawSlot(&it, Py_tp_basicsize) + && _PySlotIterator_SawSlot(&it, Py_tp_extra_basicsize)) + { + PyErr_Format( + PyExc_SystemError, + "type %s: Py_tp_basicsize and Py_tp_extra_basicsize are " + "mutually exclusive", + it.name); + goto finally; + } + + if (have_relative_members) { + if (!extra_basicsize) { + PyErr_SetString( + PyExc_SystemError, + "With Py_RELATIVE_OFFSET, basicsize must be extended"); + goto finally; + } + if (max_relative_offset >= extra_basicsize) { + PyErr_SetString( + PyExc_SystemError, + "Member offset out of range (0..extra_basicsize)"); + goto finally; + } + } + + /* Prepare the type name and qualname */ + + assert(it.name); + const char *s = strrchr(it.name, '.'); if (s == NULL) { - s = spec->name; + s = it.name; } else { s++; @@ -5392,7 +5463,7 @@ PyType_FromMetaclass( goto finally; } - /* Copy spec->name to a buffer we own. + /* Copy the name to a buffer we own. * * Unfortunately, we can't use tp_name directly (with some * flag saying that it should be deallocated with the type), @@ -5401,28 +5472,42 @@ PyType_FromMetaclass( * So, we use a separate buffer, _ht_tpname, that's always * deallocated with the type (if it's non-NULL). */ - Py_ssize_t name_buf_len = strlen(spec->name) + 1; + Py_ssize_t name_buf_len = strlen(it.name) + 1; _ht_tpname = PyMem_Malloc(name_buf_len); if (_ht_tpname == NULL) { goto finally; } - memcpy(_ht_tpname, spec->name, name_buf_len); + memcpy(_ht_tpname, it.name, name_buf_len); /* Get a tuple of bases. * bases is a strong reference (unlike bases_in). + * (This is convoluted for backwards compatibility -- preserving priority + * of the various ways to specify bases) */ - bases = get_bases_tuple(bases_in, spec); + if (!bases_in) { + bases_in = bases_slot; + } + if (bases_in) { + if (PyTuple_Check(bases_in)) { + bases = Py_NewRef(bases_in); + } + else { + bases = PyTuple_Pack(1, bases_in); + } + } + else { + bases = PyTuple_Pack(1, &PyBaseObject_Type); + } if (!bases) { goto finally; } - /* If this is an immutable type, check if all bases are also immutable, - * and (for now) fire a deprecation warning if not. + /* If this is an immutable type, check if all bases are also immutable. * (This isn't necessary for static types: those can't have heap bases, * and only heap types can be mutable.) */ - if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) { - if (check_immutable_bases(spec->name, bases, 0) < 0) { + if (flags & Py_TPFLAGS_IMMUTABLETYPE) { + if (check_immutable_bases(it.name, bases, 0) < 0) { goto finally; } } @@ -5460,20 +5545,16 @@ PyType_FromMetaclass( /* Calculate sizes */ - Py_ssize_t basicsize = spec->basicsize; - Py_ssize_t type_data_offset = spec->basicsize; - if (basicsize == 0) { - /* Inherit */ - basicsize = base->tp_basicsize; - } - else if (basicsize < 0) { + Py_ssize_t type_data_offset = basicsize; + if (extra_basicsize) { /* Extend */ + assert(basicsize == 0); type_data_offset = _align_up(base->tp_basicsize); - basicsize = type_data_offset + _align_up(-spec->basicsize); + basicsize = type_data_offset + _align_up(extra_basicsize); /* Inheriting variable-sized types is limited */ if (base->tp_itemsize - && !((base->tp_flags | spec->flags) & Py_TPFLAGS_ITEMS_AT_END)) + && !((base->tp_flags | flags) & Py_TPFLAGS_ITEMS_AT_END)) { PyErr_SetString( PyExc_SystemError, @@ -5481,8 +5562,10 @@ PyType_FromMetaclass( goto finally; } } - - Py_ssize_t itemsize = spec->itemsize; + if (basicsize == 0) { + /* Inherit */ + basicsize = base->tp_basicsize; + } /* Compute special offsets */ @@ -5514,11 +5597,10 @@ PyType_FromMetaclass( if (res == NULL) { goto finally; } - res_start = (char*)res; type = &res->ht_type; /* The flags must be initialized early, before the GC traverses us */ - type_set_flags(type, spec->flags | Py_TPFLAGS_HEAPTYPE); + type_set_flags(type, flags | Py_TPFLAGS_HEAPTYPE); res->ht_module = Py_XNewRef(module); @@ -5547,15 +5629,20 @@ PyType_FromMetaclass( res->_ht_tpname = _ht_tpname; _ht_tpname = NULL; // Give ownership to the type + res->ht_token = token; + /* Copy the sizes */ type->tp_basicsize = basicsize; type->tp_itemsize = itemsize; - /* Copy all the ordinary slots */ + /* Second pass of slots: copy most of them into the type */ - for (slot = spec->slots; slot->slot; slot++) { - switch (slot->slot) { + _PySlotIterator_Rewind(&it, spec ? (void*)spec->slots : (void*)slots); + while (_PySlotIterator_Next(&it)) { + switch (it.current.sl_id) { + case Py_slot_invalid: + goto finally; case Py_tp_base: case Py_tp_bases: case Py_tp_doc: @@ -5565,7 +5652,7 @@ PyType_FromMetaclass( { /* Move the slots to the heap type itself */ size_t len = Py_TYPE(type)->tp_itemsize * nmembers; - memcpy(_PyHeapType_GET_MEMBERS(res), slot->pfunc, len); + memcpy(_PyHeapType_GET_MEMBERS(res), it.current.sl_ptr, len); type->tp_members = _PyHeapType_GET_MEMBERS(res); PyMemberDef *memb; Py_ssize_t i; @@ -5579,26 +5666,8 @@ 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 */ - PySlot_Offset slotoffsets = pyslot_offsets[slot->slot]; - short slot_offset = slotoffsets.slot_offset; - if (slotoffsets.subslot_offset == -1) { - /* Set a slot in the main PyTypeObject */ - *(void**)((char*)res_start + slot_offset) = slot->pfunc; - } - else { - void *procs = *(void**)((char*)res_start + slot_offset); - short subslot_offset = slotoffsets.subslot_offset; - *(void**)((char*)procs + subslot_offset) = slot->pfunc; - } - } + _PySlot_heaptype_apply_field_slot(res, it.current); break; } } @@ -5664,10 +5733,10 @@ PyType_FromMetaclass( goto finally; } if (r == 0) { - s = strrchr(spec->name, '.'); + s = strrchr(it.name, '.'); if (s != NULL) { PyObject *modname = PyUnicode_FromStringAndSize( - spec->name, (Py_ssize_t)(s - spec->name)); + it.name, (Py_ssize_t)(s - it.name)); if (modname == NULL) { goto finally; } @@ -5680,7 +5749,7 @@ PyType_FromMetaclass( else { if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, "builtin type %.200s has no __module__ attribute", - spec->name)) + it.name)) goto finally; } } @@ -5702,22 +5771,36 @@ PyType_FromMetaclass( return (PyObject*)res; } +PyObject * +PyType_FromSlots(PySlot *slots) +{ + return type_from_slots_or_spec(slots, NULL, NULL, NULL, NULL); +} + +PyObject * +PyType_FromMetaclass( + PyTypeObject *metaclass, PyObject *module, + PyType_Spec *spec, PyObject *bases) +{ + return type_from_slots_or_spec(NULL, spec, metaclass, module, bases); +} + PyObject * PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases) { - return PyType_FromMetaclass(NULL, module, spec, bases); + return type_from_slots_or_spec(NULL, spec, NULL, module, bases); } PyObject * PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) { - return PyType_FromMetaclass(NULL, NULL, spec, bases); + return type_from_slots_or_spec(NULL, spec, NULL, NULL, bases); } PyObject * PyType_FromSpec(PyType_Spec *spec) { - return PyType_FromMetaclass(NULL, NULL, spec, NULL); + return type_from_slots_or_spec(NULL, spec, NULL, NULL, NULL); } PyObject * @@ -5739,32 +5822,10 @@ PyType_GetModuleName(PyTypeObject *type) } void * -PyType_GetSlot(PyTypeObject *type, int slot) +PyType_GetSlot(PyTypeObject *type, int slot_in) { - void *parent_slot; - int slots_len = Py_ARRAY_LENGTH(pyslot_offsets); - - if (slot <= 0 || slot >= slots_len) { - PyErr_BadInternalCall(); - return NULL; - } - int slot_offset = 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; - } - /* Return slot directly if we have no sub slot. */ - if (pyslot_offsets[slot].subslot_offset == -1) { - return parent_slot; - } - return *(void**)((char*)parent_slot + pyslot_offsets[slot].subslot_offset); + uint16_t slot = _PySlot_resolve_type_slot(slot_in); + return _PySlot_type_getslot(type, slot); } PyObject * diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc deleted file mode 100644 index 642160fe0bd8bc..00000000000000 --- a/Objects/typeslots.inc +++ /dev/null @@ -1,84 +0,0 @@ -/* Generated by typeslots.py */ -{offsetof(PyBufferProcs, bf_getbuffer), offsetof(PyTypeObject, tp_as_buffer)}, -{offsetof(PyBufferProcs, bf_releasebuffer), offsetof(PyTypeObject, tp_as_buffer)}, -{offsetof(PyMappingMethods, mp_ass_subscript), offsetof(PyTypeObject, tp_as_mapping)}, -{offsetof(PyMappingMethods, mp_length), offsetof(PyTypeObject, tp_as_mapping)}, -{offsetof(PyMappingMethods, mp_subscript), offsetof(PyTypeObject, tp_as_mapping)}, -{offsetof(PyNumberMethods, nb_absolute), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_add), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_and), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_bool), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_divmod), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_float), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_floor_divide), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_index), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_add), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_and), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_floor_divide), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_lshift), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_multiply), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_or), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_power), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_remainder), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_rshift), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_subtract), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_true_divide), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_xor), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_int), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_invert), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_lshift), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_multiply), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_negative), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_or), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_positive), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_power), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_remainder), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_rshift), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_subtract), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_true_divide), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_xor), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PySequenceMethods, sq_ass_item), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_concat), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_contains), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_inplace_concat), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_inplace_repeat), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_item), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_length), offsetof(PyTypeObject, tp_as_sequence)}, -{offsetof(PySequenceMethods, sq_repeat), offsetof(PyTypeObject, tp_as_sequence)}, -{-1, offsetof(PyTypeObject, tp_alloc)}, -{-1, offsetof(PyTypeObject, tp_base)}, -{-1, offsetof(PyTypeObject, tp_bases)}, -{-1, offsetof(PyTypeObject, tp_call)}, -{-1, offsetof(PyTypeObject, tp_clear)}, -{-1, offsetof(PyTypeObject, tp_dealloc)}, -{-1, offsetof(PyTypeObject, tp_del)}, -{-1, offsetof(PyTypeObject, tp_descr_get)}, -{-1, offsetof(PyTypeObject, tp_descr_set)}, -{-1, offsetof(PyTypeObject, tp_doc)}, -{-1, offsetof(PyTypeObject, tp_getattr)}, -{-1, offsetof(PyTypeObject, tp_getattro)}, -{-1, offsetof(PyTypeObject, tp_hash)}, -{-1, offsetof(PyTypeObject, tp_init)}, -{-1, offsetof(PyTypeObject, tp_is_gc)}, -{-1, offsetof(PyTypeObject, tp_iter)}, -{-1, offsetof(PyTypeObject, tp_iternext)}, -{-1, offsetof(PyTypeObject, tp_methods)}, -{-1, offsetof(PyTypeObject, tp_new)}, -{-1, offsetof(PyTypeObject, tp_repr)}, -{-1, offsetof(PyTypeObject, tp_richcompare)}, -{-1, offsetof(PyTypeObject, tp_setattr)}, -{-1, offsetof(PyTypeObject, tp_setattro)}, -{-1, offsetof(PyTypeObject, tp_str)}, -{-1, offsetof(PyTypeObject, tp_traverse)}, -{-1, offsetof(PyTypeObject, tp_members)}, -{-1, offsetof(PyTypeObject, tp_getset)}, -{-1, offsetof(PyTypeObject, tp_free)}, -{offsetof(PyNumberMethods, nb_matrix_multiply), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyNumberMethods, nb_inplace_matrix_multiply), offsetof(PyTypeObject, tp_as_number)}, -{offsetof(PyAsyncMethods, am_await), offsetof(PyTypeObject, tp_as_async)}, -{offsetof(PyAsyncMethods, am_aiter), offsetof(PyTypeObject, tp_as_async)}, -{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(PyTypeObject, tp_vectorcall)}, -{-1, offsetof(PyHeapTypeObject, ht_token)}, diff --git a/Objects/typeslots.py b/Objects/typeslots.py deleted file mode 100755 index c7f8a33bb1e74e..00000000000000 --- a/Objects/typeslots.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/python -# Usage: typeslots.py < Include/typeslots.h typeslots.inc - -import sys, re - - -def generate_typeslots(out=sys.stdout): - out.write("/* Generated by typeslots.py */\n") - res = {} - for line in sys.stdin: - m = re.match("#define Py_([a-z_]+) ([0-9]+)", line) - if not m: - continue - - 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})}}' - elif member.startswith("am_"): - member = (f'{{offsetof(PyAsyncMethods, {member}),'+ - ' offsetof(PyTypeObject, tp_as_async)}') - elif member.startswith("nb_"): - member = (f'{{offsetof(PyNumberMethods, {member}),'+ - ' offsetof(PyTypeObject, tp_as_number)}') - elif member.startswith("mp_"): - member = (f'{{offsetof(PyMappingMethods, {member}),'+ - ' offsetof(PyTypeObject, tp_as_mapping)}') - elif member.startswith("sq_"): - member = (f'{{offsetof(PySequenceMethods, {member}),'+ - ' offsetof(PyTypeObject, tp_as_sequence)}') - elif member.startswith("bf_"): - member = (f'{{offsetof(PyBufferProcs, {member}),'+ - ' offsetof(PyTypeObject, tp_as_buffer)}') - res[int(m.group(2))] = member - - M = max(res.keys())+1 - for i in range(1,M): - if i in res: - out.write("%s,\n" % res[i]) - else: - out.write("{0, 0},\n") - - -def main(): - if len(sys.argv) == 2: - with open(sys.argv[1], "w") as f: - generate_typeslots(f) - else: - generate_typeslots() - -if __name__ == "__main__": - main() diff --git a/PC/python3dll.c b/PC/python3dll.c index abbe35c342c13e..7f9194d96d701f 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -676,6 +676,7 @@ EXPORT_FUNC(PyType_ClearCache) EXPORT_FUNC(PyType_Freeze) EXPORT_FUNC(PyType_FromMetaclass) EXPORT_FUNC(PyType_FromModuleAndSpec) +EXPORT_FUNC(PyType_FromSlots) EXPORT_FUNC(PyType_FromSpec) EXPORT_FUNC(PyType_FromSpecWithBases) EXPORT_FUNC(PyType_GenericAlloc) diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 935467dfcb3283..113b7600ee8564 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -109,6 +109,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index 5e0a0f65cfcc3d..a29973786c9485 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -24,6 +24,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 07305add81d055..c1207665691479 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -315,6 +315,8 @@ + + @@ -386,6 +388,8 @@ + + @@ -680,6 +684,8 @@ + + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 629f063861de9a..a6dad119e129d0 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -228,6 +228,12 @@ Include + + Include + + + Include + Include @@ -843,6 +849,12 @@ Include\internal + + Include\internal + + + Include\internal + Include\internal @@ -1568,6 +1580,12 @@ Python + + Objects + + + Objects + Python diff --git a/Python/import.c b/Python/import.c index 7aa96196ec1e10..60a5ee6e770f59 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2059,7 +2059,7 @@ import_run_modexport(PyThreadState *tstate, PyModExportFunction ex0, /* This is like import_run_extension, but avoids interpreter switching * and code for for single-phase modules. */ - PyModuleDef_Slot *slots = ex0(); + PySlot *slots = ex0(); if (!slots) { if (!PyErr_Occurred()) { PyErr_Format( diff --git a/Python/slots.c b/Python/slots.c new file mode 100644 index 00000000000000..2fd45399dc1f78 --- /dev/null +++ b/Python/slots.c @@ -0,0 +1,404 @@ +/* Common handling of type/module slots + */ + +#include "Python.h" + +#include "pycore_slots.h" + +#include + +// Iterating through a recursive structure doesn't look great in a debugger. +// Flip the #if to 1 to get a trace on stderr. +// (The messages can also serve as code comments.) +#if 0 +#define MSG(...) { \ + fprintf(stderr, "slotiter: " __VA_ARGS__); fprintf(stderr, "\n");} +#else +#define MSG(...) +#endif + +static char* +kind_name(_PySlot_KIND kind) +{ + switch (kind) { + case _PySlot_KIND_TYPE: return "type"; + case _PySlot_KIND_MOD: return "module"; + case _PySlot_KIND_COMPAT: return "compat"; + case _PySlot_KIND_SLOT: return "generic slot"; + } + Py_UNREACHABLE(); +} + +static void +init_with_kind(_PySlotIterator *it, const void *slots, + _PySlot_KIND result_kind, + _PySlot_KIND slot_struct_kind) +{ + MSG(""); + MSG("init (%s slot iterator)", kind_name(result_kind)); + it->state = it->states; + it->state->any_slot = slots; + it->state->slot_struct_kind = slot_struct_kind; + it->kind = result_kind; + it->name = NULL; + it->recursion_level = 0; + it->is_at_end = false; + it->is_first_run = true; + it->current.sl_id = 0; + memset(it->seen, 0, sizeof(it->seen)); +} + +void +_PySlotIterator_Init(_PySlotIterator *it, const PySlot *slots, + _PySlot_KIND result_kind) +{ + init_with_kind(it, slots, result_kind, _PySlot_KIND_SLOT); +} + +void +_PySlotIterator_InitLegacy(_PySlotIterator *it, const void *slots, + _PySlot_KIND kind) +{ + init_with_kind(it, slots, kind, kind); +} + +void +_PySlotIterator_Rewind(_PySlotIterator *it, const void *slots) +{ + MSG(""); + MSG("rewind (%s slot iterator)", kind_name(it->kind)); + assert (it->is_at_end); + assert (it->recursion_level == 0); + assert (it->state == it->states); + it->is_at_end = false; + it->state->any_slot = slots; + it->is_first_run = false; +} + +static Py_ssize_t +seen_index(uint16_t id) +{ + return id / SEEN_ENTRY_BITS; +} + +static unsigned int +seen_mask(uint16_t id) +{ + return ((unsigned int)1) << (id % SEEN_ENTRY_BITS); +} + +bool +_PySlotIterator_SawSlot(_PySlotIterator *it, int id) +{ + assert (id > 0); + assert (id < _Py_slot_COUNT); + return it->seen[seen_index(id)] & seen_mask(id); +} + +// Advance `it` to the next entry. Currently cannot fail. +static void +advance(_PySlotIterator *it) +{ + MSG("advance (at level %d)", (int)it->recursion_level); + switch (it->state->slot_struct_kind) { + case _PySlot_KIND_SLOT: it->state->slot++; break; + case _PySlot_KIND_TYPE: it->state->tp_slot++; break; + case _PySlot_KIND_MOD: it->state->mod_slot++; break; + default: + Py_UNREACHABLE(); + } +} + +static int handle_first_run(_PySlotIterator *it); + +bool +_PySlotIterator_Next(_PySlotIterator *it) +{ + MSG("next"); + assert(it); + assert(!it->is_at_end); + assert(!PyErr_Occurred()); + + it->current.sl_id = -1; + + while (true) { + if (it->state->slot == NULL) { + if (it->recursion_level == 0) { + MSG("end (initial nesting level done)"); + it->is_at_end = true; + return 0; + } + MSG("pop nesting level %d", (int)it->recursion_level); + it->recursion_level--; + it->state = &it->states[it->recursion_level]; + advance(it); + continue; + } + + switch (it->state->slot_struct_kind) { + case _PySlot_KIND_SLOT: { + MSG("copying PySlot structure"); + it->current = *it->state->slot; + } break; + case _PySlot_KIND_TYPE: { + MSG("converting PyType_Slot structure"); + memset(&it->current, 0, sizeof(it->current)); + it->current.sl_id = (uint16_t)it->state->tp_slot->slot; + it->current.sl_flags = PySlot_INTPTR; + it->current.sl_ptr = (void*)it->state->tp_slot->pfunc; + } break; + case _PySlot_KIND_MOD: { + MSG("converting PyModuleDef_Slot structure"); + memset(&it->current, 0, sizeof(it->current)); + it->current.sl_id = (uint16_t)it->state->mod_slot->slot; + it->current.sl_flags = PySlot_INTPTR; + it->current.sl_ptr = (void*)it->state->mod_slot->value; + } break; + default: { + Py_UNREACHABLE(); + } break; + } + + /* shorter local names */ + PySlot *const result = &it->current; + uint16_t flags = result->sl_flags; + + MSG("slot %d, flags 0x%x, from %p", + (int)result->sl_id, (unsigned)flags, it->state->slot); + + uint16_t orig_id = result->sl_id; + switch (it->kind) { + case _PySlot_KIND_TYPE: + result->sl_id = _PySlot_resolve_type_slot(result->sl_id); + break; + case _PySlot_KIND_MOD: + result->sl_id = _PySlot_resolve_mod_slot(result->sl_id); + break; + default: + Py_UNREACHABLE(); + } + MSG("resolved to slot %d (%s)", + (int)result->sl_id, _PySlot_GetName(result->sl_id)); + + if (result->sl_id == Py_slot_invalid) { + MSG("error (unknown/invalid slot)"); + if (flags & PySlot_OPTIONAL) { + advance(it); + continue; + } + _PySlot_err_bad_slot(kind_name(it->kind), orig_id); + goto error; + } + if (result->sl_id == Py_slot_end) { + MSG("sentinel slot, flags %x", (unsigned)flags); + if (flags & PySlot_OPTIONAL) { + MSG("error (bad flags on sentinel)"); + PyErr_Format(PyExc_SystemError, + "invalid flags for Py_slot_end: 0x%x", + (unsigned int)flags); + goto error; + } + it->state->slot = NULL; + continue; + } + + if (result->sl_id == Py_slot_subslots + || result->sl_id == Py_tp_slots + || result->sl_id == Py_mod_slots + ) { + if (result->sl_ptr == NULL) { + MSG("NULL subslots; skipping"); + advance(it); + continue; + } + if ((it->states[0].slot_struct_kind == _PySlot_KIND_MOD) + && (it->state->slot_struct_kind == _PySlot_KIND_SLOT) + && !(result->sl_flags & PySlot_STATIC)) + { + PyErr_Format(PyExc_SystemError, + "slots included from PyModuleDef must be static"); + goto error; + } + it->recursion_level++; + MSG("recursing into level %d", it->recursion_level); + if (it->recursion_level >= _PySlot_MAX_NESTING) { + MSG("error (too much nesting)"); + PyErr_Format(PyExc_SystemError, + "%s (slot %d): too many levels of nested slots", + _PySlot_GetName(result->sl_id), orig_id); + goto error; + } + it->state = &it->states[it->recursion_level]; + memset(it->state, 0, sizeof(_PySlotIterator_state)); + it->state->slot = result->sl_ptr; + switch (result->sl_id) { + case Py_slot_subslots: + it->state->slot_struct_kind = _PySlot_KIND_SLOT; break; + case Py_tp_slots: + it->state->slot_struct_kind = _PySlot_KIND_TYPE; break; + case Py_mod_slots: + it->state->slot_struct_kind = _PySlot_KIND_MOD; break; + } + continue; + } + + if (flags & PySlot_INTPTR) { + MSG("casting from intptr"); + /* this should compile to nothing on common architectures */ + switch (_PySlot_get_dtype(result->sl_id)) { + case _PySlot_DTYPE_SIZE: { + result->sl_size = (Py_ssize_t)(intptr_t)result->sl_ptr; + } break; + case _PySlot_DTYPE_INT64: { + result->sl_int64 = (int64_t)(intptr_t)result->sl_ptr; + } break; + case _PySlot_DTYPE_UINT64: { + result->sl_uint64 = (uint64_t)(intptr_t)result->sl_ptr; + } break; + case _PySlot_DTYPE_PTR: + case _PySlot_DTYPE_FUNC: + case _PySlot_DTYPE_VOID: + break; + } + } + + advance(it); + switch (_PySlot_get_dtype(result->sl_id)) { + case _PySlot_DTYPE_VOID: + case _PySlot_DTYPE_PTR: + MSG("result: %d (%s): %p", + (int)result->sl_id, _PySlot_GetName(result->sl_id), + (void*)result->sl_ptr); + break; + case _PySlot_DTYPE_FUNC: + MSG("result: %d (%s): %p", + (int)result->sl_id, _PySlot_GetName(result->sl_id), + (void*)result->sl_func); + break; + case _PySlot_DTYPE_SIZE: + MSG("result: %d (%s): %zd", + (int)result->sl_id, _PySlot_GetName(result->sl_id), + (Py_ssize_t)result->sl_size); + break; + case _PySlot_DTYPE_INT64: + MSG("result: %d (%s): %ld", + (int)result->sl_id, _PySlot_GetName(result->sl_id), + (long)result->sl_int64); + break; + case _PySlot_DTYPE_UINT64: + MSG("result: %d (%s): %lu (0x%lx)", + (int)result->sl_id, _PySlot_GetName(result->sl_id), + (unsigned long)result->sl_int64, + (unsigned long)result->sl_int64); + break; + } + assert (result->sl_id > 0); + assert (result->sl_id <= _Py_slot_COUNT); + if (it->is_first_run && (handle_first_run(it) < 0)) { + goto error; + } + return result->sl_id != Py_slot_end; + } + Py_UNREACHABLE(); + +error: + it->current.sl_id = Py_slot_invalid; + return true; +} + +/* Validate current slot, and do bookkeeping */ +static int +handle_first_run(_PySlotIterator *it) +{ + int id = it->current.sl_id; + + if (_PySlot_get_must_be_static(id)) { + if (!(it->current.sl_flags & PySlot_STATIC) + && (it->state->slot_struct_kind == _PySlot_KIND_SLOT)) + { + PyErr_Format( + PyExc_SystemError, + "%s requires PySlot_STATIC", + _PySlot_GetName(id)); + return -1; + } + } + + _PySlot_PROBLEM_HANDLING null_handling = _PySlot_get_null_handling(id); + if (null_handling != _PySlot_PROBLEM_ALLOW) { + bool is_null = false; + switch (_PySlot_get_dtype(id)) { + case _PySlot_DTYPE_PTR: { + is_null = it->current.sl_ptr == NULL; + } break; + case _PySlot_DTYPE_FUNC: { + is_null = it->current.sl_func == NULL; + } break; + default: { + //Py_UNREACHABLE(); + } break; + } + if (is_null) { + MSG("slot is NULL but shouldn't"); + if (null_handling == _PySlot_PROBLEM_REJECT) { + MSG("error (NULL rejected)"); + PyErr_Format(PyExc_SystemError, + "NULL not allowed for slot %s", + _PySlot_GetName(id)); + return -1; + } + if (it->states[0].slot_struct_kind == _PySlot_KIND_SLOT) { + MSG("deprecated NULL"); + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, + 1, + "NULL value in slot %s is deprecated", + _PySlot_GetName(id)) < 0) + { + return -1; + } + } + else { + MSG("unwanted NULL in legacy struct"); + } + } + } + + _PySlot_PROBLEM_HANDLING duplicate_handling = _PySlot_get_duplicate_handling(id); + if (duplicate_handling != _PySlot_PROBLEM_ALLOW) { + if (_PySlotIterator_SawSlot(it, id)) { + MSG("slot was seen before but shouldn't be duplicated"); + if (duplicate_handling == _PySlot_PROBLEM_REJECT) { + MSG("error (duplicate rejected)"); + PyErr_Format( + PyExc_SystemError, + "%s%s%s has multiple %s (%d) slots", + kind_name(it->kind), + it->name ? " " : "", + it->name ? it->name : "", + _PySlot_GetName(id), + (int)it->current.sl_id); + return -1; + } + if (it->states[0].slot_struct_kind == _PySlot_KIND_SLOT) { + MSG("deprecated duplicate"); + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, + 0, + "%s%s%s has multiple %s (%d) slots. This is deprecated.", + kind_name(it->kind), + it->name ? " " : "", + it->name ? it->name : "", + _PySlot_GetName(id), + (int)it->current.sl_id) < 0) { + return -1; + } + } + else { + MSG("unwanted duplicate in legacy struct"); + } + } + } + it->seen[seen_index(id)] |= seen_mask(id); + return 0; +} diff --git a/Python/slots.toml b/Python/slots.toml new file mode 100644 index 00000000000000..626a44d2f2c80e --- /dev/null +++ b/Python/slots.toml @@ -0,0 +1,836 @@ +# This file lists all PySlot values +# This should only be used as input to Tools/build/generate_slots.py, +# its format can change at any time (e.g. we can switch to slots.csv) + +# Entries: +# name: name of the slot +# kind: +# - 'type', 'mod': slots to create a particular kind of object +# - 'slot': special slots applicable to any kind of object +# - 'compat': old IDs that need to be resolved +# dtype: data type (tag for the union of sl_ptr, sl_size, etc.) +# equivalents: for 'compat' slots; the slots to resolve to +# is_type_field: slot that corresponds to a field in the type object (or in +# an array like PyNumberMethods). +# functype: C function type, where needed +# duplicates, nulls: How to handle common "problems" -- duplicate slots with +# the same ID, and NULL pointers, respectively +# - 'allow': not a problem for this slot +# - 'deprecated': issue a deprecation warning. Don't use for new slots. +# (typically, the problem was disallowed in docs, but allowed in practice) +# - 'reject': raise error +# The default for duplicate slots is 'reject' +# The default for NULLs is 'reject' for pointer slots; 'allow' for +# non-pointer ones +# must_be_static: true if slot needs the PySlot_STATIC flag (in PySlot struct) + + +[0] +name = 'Py_slot_end' +kind = 'slot' +dtype = 'void' + +[1] +kind = 'compat' +equivalents = {type='Py_bf_getbuffer', mod='Py_mod_create'} + +[2] +kind = 'compat' +equivalents = {type='Py_bf_releasebuffer', mod='Py_mod_exec'} + +[3] +kind = 'compat' +equivalents = {type='Py_mp_ass_subscript', mod='Py_mod_multiple_interpreters'} + +[4] +kind = 'compat' +equivalents = {type='Py_mp_length', mod='Py_mod_gil'} + +[5] +name = 'Py_mp_subscript' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[6] +name = 'Py_nb_absolute' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[7] +name = 'Py_nb_add' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[8] +name = 'Py_nb_and' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[9] +name = 'Py_nb_bool' +kind = 'type' +is_type_field = true +functype = 'inquiry' +duplicates = 'deprecated' +nulls = 'deprecated' + +[10] +name = 'Py_nb_divmod' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[11] +name = 'Py_nb_float' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[12] +name = 'Py_nb_floor_divide' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[13] +name = 'Py_nb_index' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[14] +name = 'Py_nb_inplace_add' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[15] +name = 'Py_nb_inplace_and' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[16] +name = 'Py_nb_inplace_floor_divide' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[17] +name = 'Py_nb_inplace_lshift' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[18] +name = 'Py_nb_inplace_multiply' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[19] +name = 'Py_nb_inplace_or' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[20] +name = 'Py_nb_inplace_power' +kind = 'type' +is_type_field = true +functype = 'ternaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[21] +name = 'Py_nb_inplace_remainder' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[22] +name = 'Py_nb_inplace_rshift' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[23] +name = 'Py_nb_inplace_subtract' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[24] +name = 'Py_nb_inplace_true_divide' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[25] +name = 'Py_nb_inplace_xor' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[26] +name = 'Py_nb_int' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[27] +name = 'Py_nb_invert' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[28] +name = 'Py_nb_lshift' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[29] +name = 'Py_nb_multiply' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[30] +name = 'Py_nb_negative' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[31] +name = 'Py_nb_or' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[32] +name = 'Py_nb_positive' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[33] +name = 'Py_nb_power' +kind = 'type' +is_type_field = true +functype = 'ternaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[34] +name = 'Py_nb_remainder' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[35] +name = 'Py_nb_rshift' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[36] +name = 'Py_nb_subtract' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[37] +name = 'Py_nb_true_divide' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[38] +name = 'Py_nb_xor' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[39] +name = 'Py_sq_ass_item' +kind = 'type' +is_type_field = true +functype = 'ssizeobjargproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[40] +name = 'Py_sq_concat' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[41] +name = 'Py_sq_contains' +kind = 'type' +is_type_field = true +functype = 'objobjproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[42] +name = 'Py_sq_inplace_concat' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[43] +name = 'Py_sq_inplace_repeat' +kind = 'type' +is_type_field = true +functype = 'ssizeargfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[44] +name = 'Py_sq_item' +kind = 'type' +is_type_field = true +functype = 'ssizeargfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[45] +name = 'Py_sq_length' +kind = 'type' +is_type_field = true +functype = 'lenfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[46] +name = 'Py_sq_repeat' +kind = 'type' +is_type_field = true +functype = 'ssizeargfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[47] +name = 'Py_tp_alloc' +kind = 'type' +is_type_field = true +functype = 'allocfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[48] +name = 'Py_tp_base' +kind = 'type' +is_type_field = true +dtype = 'ptr' +duplicates = 'deprecated' +nulls = 'deprecated' + +[49] +name = 'Py_tp_bases' +kind = 'type' +is_type_field = true +dtype = 'ptr' +duplicates = 'deprecated' +nulls = 'deprecated' + +[50] +name = 'Py_tp_call' +kind = 'type' +is_type_field = true +functype = 'ternaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[51] +name = 'Py_tp_clear' +kind = 'type' +is_type_field = true +functype = 'inquiry' +duplicates = 'deprecated' +nulls = 'deprecated' + +[52] +name = 'Py_tp_dealloc' +kind = 'type' +is_type_field = true +functype = 'destructor' +duplicates = 'deprecated' +nulls = 'deprecated' + +[53] +name = 'Py_tp_del' +kind = 'type' +is_type_field = true +functype = 'destructor' +duplicates = 'deprecated' +nulls = 'deprecated' + +[54] +name = 'Py_tp_descr_get' +kind = 'type' +is_type_field = true +functype = 'descrgetfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[55] +name = 'Py_tp_descr_set' +kind = 'type' +is_type_field = true +functype = 'descrsetfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[56] +name = 'Py_tp_doc' +kind = 'type' +is_type_field = true +dtype = 'ptr' +nulls = 'allow' + +[57] +name = 'Py_tp_getattr' +kind = 'type' +is_type_field = true +functype = 'getattrfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[58] +name = 'Py_tp_getattro' +kind = 'type' +is_type_field = true +functype = 'getattrofunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[59] +name = 'Py_tp_hash' +kind = 'type' +is_type_field = true +functype = 'hashfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[60] +name = 'Py_tp_init' +kind = 'type' +is_type_field = true +functype = 'initproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[61] +name = 'Py_tp_is_gc' +kind = 'type' +is_type_field = true +functype = 'inquiry' +duplicates = 'deprecated' +nulls = 'deprecated' + +[62] +name = 'Py_tp_iter' +kind = 'type' +is_type_field = true +functype = 'getiterfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[63] +name = 'Py_tp_iternext' +kind = 'type' +is_type_field = true +functype = 'iternextfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[64] +name = 'Py_tp_methods' +kind = 'type' +is_type_field = true +dtype = 'ptr' +duplicates = 'deprecated' +nulls = 'deprecated' +must_be_static = true + +[65] +name = 'Py_tp_new' +kind = 'type' +is_type_field = true +functype = 'newfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[66] +name = 'Py_tp_repr' +kind = 'type' +is_type_field = true +functype = 'reprfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[67] +name = 'Py_tp_richcompare' +kind = 'type' +is_type_field = true +functype = 'richcmpfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[68] +name = 'Py_tp_setattr' +kind = 'type' +is_type_field = true +functype = 'setattrfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[69] +name = 'Py_tp_setattro' +kind = 'type' +is_type_field = true +functype = 'setattrofunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[70] +name = 'Py_tp_str' +kind = 'type' +is_type_field = true +functype = 'reprfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[71] +name = 'Py_tp_traverse' +kind = 'type' +is_type_field = true +functype = 'traverseproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[72] +name = 'Py_tp_members' +kind = 'type' +is_type_field = true +dtype = 'ptr' +nulls = 'reject' +must_be_static = true + +[73] +name = 'Py_tp_getset' +kind = 'type' +is_type_field = true +dtype = 'ptr' +duplicates = 'deprecated' +nulls = 'deprecated' +must_be_static = true + +[74] +name = 'Py_tp_free' +kind = 'type' +is_type_field = true +functype = 'freefunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[75] +name = 'Py_nb_matrix_multiply' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[76] +name = 'Py_nb_inplace_matrix_multiply' +kind = 'type' +is_type_field = true +functype = 'binaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[77] +name = 'Py_am_await' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[78] +name = 'Py_am_aiter' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[79] +name = 'Py_am_anext' +kind = 'type' +is_type_field = true +functype = 'unaryfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[80] +name = 'Py_tp_finalize' +kind = 'type' +is_type_field = true +functype = 'destructor' +duplicates = 'deprecated' +nulls = 'deprecated' + +[81] +name = 'Py_am_send' +kind = 'type' +is_type_field = true +functype = 'sendfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[82] +name = 'Py_tp_vectorcall' +kind = 'type' +is_type_field = true +functype = 'vectorcallfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[83] +name = 'Py_tp_token' +kind = 'type' +is_type_field = true +dtype = 'ptr' +field = 'ht_token' +duplicates = 'deprecated' +nulls = 'allow' + +[84] +name = 'Py_mod_create' +kind = 'mod' +dtype = 'func' +nulls = 'deprecated' + +[85] +name = 'Py_mod_exec' +kind = 'mod' +dtype = 'func' +duplicates = 'allow' # only alowed in PyModuleDef.m_slots +nulls = 'reject' + +[86] +name = 'Py_mod_multiple_interpreters' +kind = 'mod' +dtype = 'uint64' + +[87] +name = 'Py_mod_gil' +kind = 'mod' +dtype = 'uint64' + +[88] +name = 'Py_bf_getbuffer' +kind = 'type' +is_type_field = true +functype = 'getbufferproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[89] +name = 'Py_bf_releasebuffer' +kind = 'type' +is_type_field = true +functype = 'releasebufferproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[90] +name = 'Py_mp_ass_subscript' +kind = 'type' +is_type_field = true +functype = 'objobjargproc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[91] +name = 'Py_mp_length' +kind = 'type' +is_type_field = true +functype = 'lenfunc' +duplicates = 'deprecated' +nulls = 'deprecated' + +[92] +name = 'Py_slot_subslots' +kind = 'slot' +dtype = 'ptr' +nulls = 'allow' + +[93] +name = 'Py_tp_slots' +kind = 'type' +dtype = 'ptr' +nulls = 'allow' + +[94] +name = 'Py_mod_slots' +kind = 'mod' +dtype = 'ptr' +nulls = 'allow' + +[95] +name = 'Py_tp_name' +kind = 'type' +dtype = 'ptr' + +[96] +name = 'Py_tp_basicsize' +kind = 'type' +dtype = 'size' + +[97] +name = 'Py_tp_extra_basicsize' +kind = 'type' +dtype = 'size' + +[98] +name = 'Py_tp_itemsize' +kind = 'type' +dtype = 'size' + +[99] +name = 'Py_tp_flags' +kind = 'type' +dtype = 'uint64' + +[100] +name = 'Py_mod_name' +kind = 'mod' +dtype = 'ptr' + +[101] +name = 'Py_mod_doc' +kind = 'mod' +dtype = 'ptr' + +[102] +name = 'Py_mod_state_size' +kind = 'mod' +dtype = 'size' + +[103] +name = 'Py_mod_methods' +kind = 'mod' +dtype = 'ptr' +must_be_static = true + +[104] +name = 'Py_mod_state_traverse' +kind = 'mod' +dtype = 'func' + +[105] +name = 'Py_mod_state_clear' +kind = 'mod' +dtype = 'func' + +[106] +name = 'Py_mod_state_free' +kind = 'mod' +dtype = 'func' + +[107] +name = 'Py_tp_metaclass' +kind = 'type' +dtype = 'ptr' + +[108] +name = 'Py_tp_module' +kind = 'type' +dtype = 'ptr' + +[109] +name = 'Py_mod_abi' +kind = 'mod' +dtype = 'ptr' +duplicates = 'allow' + +[110] +name = 'Py_mod_token' +kind = 'mod' +dtype = 'ptr' diff --git a/Python/slots_generated.c b/Python/slots_generated.c new file mode 100644 index 00000000000000..0b946b55b3789f --- /dev/null +++ b/Python/slots_generated.c @@ -0,0 +1,119 @@ +/* Generated by Tools/build/generate_slots.py */ + +#include "Python.h" +#include "pycore_slots.h" // _PySlot_names + +const char *_PySlot_names[] = { + "Py_slot_end", + "Py_bf_getbuffer/Py_mod_create", + "Py_bf_releasebuffer/Py_mod_exec", + "Py_mp_ass_subscript/Py_mod_multiple_interpreters", + "Py_mp_length/Py_mod_gil", + "Py_mp_subscript", + "Py_nb_absolute", + "Py_nb_add", + "Py_nb_and", + "Py_nb_bool", + "Py_nb_divmod", + "Py_nb_float", + "Py_nb_floor_divide", + "Py_nb_index", + "Py_nb_inplace_add", + "Py_nb_inplace_and", + "Py_nb_inplace_floor_divide", + "Py_nb_inplace_lshift", + "Py_nb_inplace_multiply", + "Py_nb_inplace_or", + "Py_nb_inplace_power", + "Py_nb_inplace_remainder", + "Py_nb_inplace_rshift", + "Py_nb_inplace_subtract", + "Py_nb_inplace_true_divide", + "Py_nb_inplace_xor", + "Py_nb_int", + "Py_nb_invert", + "Py_nb_lshift", + "Py_nb_multiply", + "Py_nb_negative", + "Py_nb_or", + "Py_nb_positive", + "Py_nb_power", + "Py_nb_remainder", + "Py_nb_rshift", + "Py_nb_subtract", + "Py_nb_true_divide", + "Py_nb_xor", + "Py_sq_ass_item", + "Py_sq_concat", + "Py_sq_contains", + "Py_sq_inplace_concat", + "Py_sq_inplace_repeat", + "Py_sq_item", + "Py_sq_length", + "Py_sq_repeat", + "Py_tp_alloc", + "Py_tp_base", + "Py_tp_bases", + "Py_tp_call", + "Py_tp_clear", + "Py_tp_dealloc", + "Py_tp_del", + "Py_tp_descr_get", + "Py_tp_descr_set", + "Py_tp_doc", + "Py_tp_getattr", + "Py_tp_getattro", + "Py_tp_hash", + "Py_tp_init", + "Py_tp_is_gc", + "Py_tp_iter", + "Py_tp_iternext", + "Py_tp_methods", + "Py_tp_new", + "Py_tp_repr", + "Py_tp_richcompare", + "Py_tp_setattr", + "Py_tp_setattro", + "Py_tp_str", + "Py_tp_traverse", + "Py_tp_members", + "Py_tp_getset", + "Py_tp_free", + "Py_nb_matrix_multiply", + "Py_nb_inplace_matrix_multiply", + "Py_am_await", + "Py_am_aiter", + "Py_am_anext", + "Py_tp_finalize", + "Py_am_send", + "Py_tp_vectorcall", + "Py_tp_token", + "Py_mod_create", + "Py_mod_exec", + "Py_mod_multiple_interpreters", + "Py_mod_gil", + "Py_bf_getbuffer", + "Py_bf_releasebuffer", + "Py_mp_ass_subscript", + "Py_mp_length", + "Py_slot_subslots", + "Py_tp_slots", + "Py_mod_slots", + "Py_tp_name", + "Py_tp_basicsize", + "Py_tp_extra_basicsize", + "Py_tp_itemsize", + "Py_tp_flags", + "Py_mod_name", + "Py_mod_doc", + "Py_mod_state_size", + "Py_mod_methods", + "Py_mod_state_traverse", + "Py_mod_state_clear", + "Py_mod_state_free", + "Py_tp_metaclass", + "Py_tp_module", + "Py_mod_abi", + "Py_mod_token", + NULL +}; diff --git a/Tools/build/.ruff.toml b/Tools/build/.ruff.toml index 996f725fdcb9b5..4a3dd618f6559f 100644 --- a/Tools/build/.ruff.toml +++ b/Tools/build/.ruff.toml @@ -38,3 +38,7 @@ ignore = [ "generate_{re_casefix,sre_constants,token}.py" = [ "UP031", # Use format specifiers instead of percent format ] +"generate_slots.py" = [ + "I001", # Import block is un-sorted + "ISC003", # Explicitly concatenated string +] diff --git a/Tools/build/generate_slots.py b/Tools/build/generate_slots.py new file mode 100755 index 00000000000000..a5f87a19209ee4 --- /dev/null +++ b/Tools/build/generate_slots.py @@ -0,0 +1,399 @@ +#!/usr/bin/python +"""Generate type/module slot files +""" + +# See the input file (Python/slots.toml) for a description of its format. + +import io +import sys +import json +import tomllib +import argparse +import functools +import contextlib +import collections +from pathlib import Path + +GENERATED_BY = 'Generated by Tools/build/generate_slots.py' + +REPO_ROOT = Path(__file__).parent.parent.parent +DEFAULT_INPUT_PATH = REPO_ROOT / 'Python/slots.toml' +INCLUDE_PATH = REPO_ROOT / 'Include' +DEFAULT_PUBLIC_HEADER_PATH = INCLUDE_PATH / 'slots_generated.h' +DEFAULT_PRIVATE_HEADER_PATH = INCLUDE_PATH / 'internal/pycore_slots_generated.h' +DEFAULT_C_PATH = REPO_ROOT / 'Python/slots_generated.c' + +TABLES = { + 'tp': 'ht_type', + 'am': 'as_async', + 'nb': 'as_number', + 'mp': 'as_mapping', + 'sq': 'as_sequence', + 'bf': 'as_buffer', +} + + +class SlotInfo: + def __init__(self, id, data): + self.id = id + self.kind = data['kind'] + self._data = data + try: + self.name = data['name'] + except KeyError: + self.name = '/'.join(data["equivalents"].values()) + else: + assert self.name.isidentifier + + @functools.cached_property + def equivalents(self): + return self._data['equivalents'] + + @functools.cached_property + def dtype(self): + try: + return self._data['dtype'] + except KeyError: + if self.is_type_field: + return 'func' + raise + + @functools.cached_property + def functype(self): + return self._data['functype'] + + @functools.cached_property + def is_type_field(self): + return self._data.get('is_type_field') + + @functools.cached_property + def type_field(self): + assert self.is_type_field + return self._data.get('field', self.name.removeprefix('Py_')) + + @functools.cached_property + def type_table_ident(self): + assert self.is_type_field + return self._data.get('table', self.type_field[:2]) + + @functools.cached_property + def duplicate_handling(self): + return self._data.get('duplicates', 'reject') + + @functools.cached_property + def null_handling(self): + try: + return self._data['nulls'] + except KeyError: + if self.kind == 'compat': + return 'allow' + if self.dtype in {'ptr', 'func'}: + return 'reject' + return 'allow' + + @functools.cached_property + def must_be_static(self): + return self._data.get('must_be_static', False) + + +def parse_slots(file): + toml_contents = tomllib.load(file) + result = [None] * len(toml_contents) + for key, data in toml_contents.items(): + slot_id = int(key) + try: + if result[slot_id]: + raise ValueError(f'slot ID {slot_id} repeated') + result[slot_id] = SlotInfo(slot_id, data) + except Exception as e: + e.add_note(f'handling slot {slot_id}') + raise + return result + + +class CWriter: + """Simple helper for generating C code""" + + def __init__(self, file): + self.file = file + self.indent = '' + self(f'/* {GENERATED_BY} */') + self() + + def out(self, *args, **kwargs): + """print args to the file, with current indent at the start""" + print(self.indent, end='', file=self.file) + print(*args, file=self.file, **kwargs) + + __call__ = out + + @contextlib.contextmanager + def block(self, header=None, end=''): + """Context for a {}-enclosed block of C""" + if header is None: + self.out('{') + else: + self.out(header, '{') + old_indent = self.indent + self.indent += ' ' + yield + self.indent = old_indent + self.out('}' + end) + + +def write_public_header(f, slots): + out = CWriter(f) + out(f'#ifndef _PY_HAVE_SLOTS_GENERATED_H') + out(f'#define _PY_HAVE_SLOTS_GENERATED_H') + out() + out(f'#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)') + out(f'#define _Py_SLOT_COMPAT_VALUE(OLD, NEW) NEW') + out(f'#else') + out(f'#define _Py_SLOT_COMPAT_VALUE(OLD, NEW) OLD') + out(f'#endif') + out() + compat_ids = {} + for slot in slots: + if slot.kind == 'compat': + for new_name in slot.equivalents.values(): + compat_ids[new_name] = slot.id + for slot in slots: + if slot.kind == 'compat': + continue + slot_id = slot.id + if compat := compat_ids.get(slot.name): + slot_id = f'_Py_SLOT_COMPAT_VALUE({compat}, {slot_id})' + out(f'#define {slot.name} {slot_id}') + out() + out(f'#define _Py_slot_COUNT {len(slots)}') + out(f'#endif /* _PY_HAVE_SLOTS_GENERATED_H */') + + +def write_private_header(f, slots): + out = CWriter(f) + + def add_case(slot): + out(out(f' case {slot.id}:')) + + slots_by_name = {slot.name: slot for slot in slots} + + out(f'#ifndef _PY_HAVE_INTERNAL_SLOTS_GENERATED_H') + out(f'#define _PY_HAVE_INTERNAL_SLOTS_GENERATED_H') + for kind in 'type', 'mod': + out() + out(f'static inline uint16_t') + out(f'_PySlot_resolve_{kind}_slot(uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + good_slots = [] + for slot in slots: + if slot.kind == 'compat': + new_slot = slots_by_name[slot.equivalents[kind]] + out(f'case {slot.id}:') + out(f' return {new_slot.name};') + elif slot.kind in {kind, 'slot'}: + good_slots.append(f'case {slot.name}:') + for case in good_slots: + out(case) + out(f' return slot_id;') + out(f'default:') + out(f' return Py_slot_invalid;') + out() + out(f'static inline void*') + out(f'_PySlot_type_getslot(PyTypeObject *tp, uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + for slot in slots: + if slot.is_type_field: + field = slot.type_field + table_ident = slot.type_table_ident + if table_ident == 'tp': + out(f'case {slot.name}:') + out(f' return (void*)tp->{field};') + else: + if table_ident == 'ht': + cond = 'tp->tp_flags & Py_TPFLAGS_HEAPTYPE' + val = f'((PyHeapTypeObject*)tp)->{field}' + else: + table = TABLES[table_ident] + cond = f'tp->tp_{table}' + val = f'tp->tp_{table}->{field}' + out(f'case {slot.name}:') + out(f' if (!({cond})) return NULL;') + out(f' return (void*){val};') + out(f'_PySlot_err_bad_slot("PyType_GetSlot", slot_id);') + out(f'return NULL;') + out() + out(f'static inline void') + out(f'_PySlot_heaptype_apply_field_slot(PyHeapTypeObject *ht,', + f'PySlot slot)') + with out.block(): + with out.block('switch (slot.sl_id)'): + for slot in slots: + if slot.is_type_field: + field = slot.type_field + table_ident = slot.type_table_ident + if table_ident == 'ht': + continue + table = TABLES[table_ident] + if slot.dtype == 'func': + functype = f'({slot.functype})' + else: + functype = '' + out(f'case {slot.name}:') + out(f' ht->{table}.{field} = {functype}slot.sl_{slot.dtype};') + out(f' break;') + out() + out(f'static inline _PySlot_DTYPE') + out(f'_PySlot_get_dtype(uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + for slot in slots: + if slot.kind == 'compat': + continue + dtype = slot.dtype + name = slot.name + out(f'case {name}: return _PySlot_DTYPE_{dtype.upper()};') + out(f'default: return _PySlot_DTYPE_VOID;') + out() + out(f'static inline _PySlot_PROBLEM_HANDLING') + out(f'_PySlot_get_duplicate_handling(uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + results = collections.defaultdict(list) + for slot in slots: + if slot.kind == 'compat': + continue + handling = slot.duplicate_handling + results[handling.upper()].append(f'case {slot.name}:') + results.pop('REJECT') + for handling, cases in results.items(): + for case in cases: + out(case) + out(f' return _PySlot_PROBLEM_{handling};') + out(f'default:') + out(f' return _PySlot_PROBLEM_REJECT;') + out() + out(f'static inline _PySlot_PROBLEM_HANDLING') + out(f'_PySlot_get_null_handling(uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + results = collections.defaultdict(list) + for slot in slots: + if slot.kind == 'compat': + continue + handling = slot.null_handling + if handling is None: + if slot.kind != 'compat' and slot.dtype in {'ptr', 'func'}: + handling = 'reject' + else: + handling = 'allow' + results[handling.upper()].append(f'case {slot.name}:') + results.pop('REJECT') + for handling, cases in results.items(): + for case in cases: + out(case) + out(f' return _PySlot_PROBLEM_{handling};') + out(f'default:') + out(f' return _PySlot_PROBLEM_REJECT;') + out() + out(f'static inline bool') + out(f'_PySlot_get_must_be_static(uint16_t slot_id)') + with out.block(): + with out.block('switch (slot_id)'): + cases = [] + for slot in slots: + if slot.must_be_static: + out(f'case {slot.name}: return true;') + out(f'return false;') + out() + out(f'#endif /* _PY_HAVE_INTERNAL_SLOTS_GENERATED_H */') + + +def write_c(f, slots): + out = CWriter(f) + out('#include "Python.h"') + out('#include "pycore_slots.h" // _PySlot_names') + out() + with out.block(f'const char *_PySlot_names[] =', end=';'): + for slot in slots: + out(f'"{slot.name}",') + out('NULL') + + +@contextlib.contextmanager +def replace_file(filename): + file_path = Path(filename) + with io.StringIO() as sio: + yield sio + try: + old_text = file_path.read_text() + except FileNotFoundError: + old_text = None + new_text = sio.getvalue() + if old_text == new_text: + print(f'{filename}: not modified', file=sys.stderr) + else: + print(f'{filename}: writing new content', file=sys.stderr) + file_path.write_text(new_text) + + +def main(argv): + if len(argv) == 1: + # No sens calling this with no arguments. + argv.append('--help') + + parser = argparse.ArgumentParser(prog=argv[0], description=__doc__) + parser.add_argument( + '-i', '--input', default=DEFAULT_INPUT_PATH, + help=f'the input file (default: {DEFAULT_INPUT_PATH})') + parser.add_argument( + '--generate-all', action=argparse.BooleanOptionalAction, + help='write all output files to their default locations') + parser.add_argument( + '-j', '--jsonl', action=argparse.BooleanOptionalAction, + help='write info to stdout in "JSON Lines" format (one JSON per line)') + outfile_group = parser.add_argument_group( + 'output files', + description='By default, no files are generated. Use --generate-all ' + + 'or the options below to generate them.') + outfile_group.add_argument( + '-H', '--public-header', + help='file into which to write the public header') + outfile_group.add_argument( + '-I', '--private-header', + help='file into which to write the private header') + outfile_group.add_argument( + '-C', '--cfile', + help='file into which to write internal C code') + args = parser.parse_args(argv[1:]) + + if args.generate_all: + if args.public_header is None: + args.public_header = DEFAULT_PUBLIC_HEADER_PATH + if args.private_header is None: + args.private_header = DEFAULT_PRIVATE_HEADER_PATH + if args.cfile is None: + args.cfile = DEFAULT_C_PATH + + with open(args.input, 'rb') as f: + slots = parse_slots(f) + + if args.jsonl: + for slot in slots: + print(json.dumps(slot.to_dict())) + + if args.public_header: + with replace_file(args.public_header) as f: + write_public_header(f, slots) + + if args.private_header: + with replace_file(args.private_header) as f: + write_private_header(f, slots) + + if args.cfile: + with replace_file(args.cfile) as f: + write_c(f, slots) + +if __name__ == "__main__": + main(sys.argv) diff --git a/configure b/configure index 6cd7a1900463ee..386f6e16fa2ab7 100755 --- a/configure +++ b/configure @@ -3596,7 +3596,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "$srcdir" != . -a "$srcdir" != "$(pwd)"; then # If we're building out-of-tree, we need to make sure the following # resources get picked up before their $srcdir counterparts. - # Objects/ -> typeslots.inc + # Objects/ -> slots_generated.c # Include/ -> Python.h # (A side effect of this is that these resources will automatically be # regenerated when building out-of-tree, regardless of whether or not diff --git a/configure.ac b/configure.ac index 60511db39fad1e..fe13f3b2fbf61a 100644 --- a/configure.ac +++ b/configure.ac @@ -99,7 +99,7 @@ AC_SUBST([BASECPPFLAGS]) if test "$srcdir" != . -a "$srcdir" != "$(pwd)"; then # If we're building out-of-tree, we need to make sure the following # resources get picked up before their $srcdir counterparts. - # Objects/ -> typeslots.inc + # Objects/ -> slots_generated.c # Include/ -> Python.h # (A side effect of this is that these resources will automatically be # regenerated when building out-of-tree, regardless of whether or not