From 1bb08c2ffa6592926d61bc9963d0b4542f0e6c33 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Sep 2025 11:10:05 +0200 Subject: [PATCH 1/4] Make Py_{SIZE,IS_TYPE,SET_SIZE} regular functions in stable ABI Group them together with Py_TYPE & Py_SET_TYPE to cut down on repetitive preprocessor macros. Format repetitive definitions in object.c more concisely. Py_SET_TYPE is still left out of the Limited API. --- Doc/data/stable_abi.dat | 3 ++ Include/object.h | 85 ++++++++++++++++-------------- Lib/test/test_stable_abi_ctypes.py | 3 ++ Misc/stable_abi.toml | 11 ++++ Objects/object.c | 31 +++++------ PC/python3dll.c | 3 ++ 6 files changed, 79 insertions(+), 57 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 3f51254494c654..e4c644f0cd1c6c 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -867,6 +867,7 @@ func,Py_GetPlatform,3.2,, func,Py_GetRecursionLimit,3.2,, func,Py_GetVersion,3.2,, data,Py_HasFileSystemDefaultEncoding,3.2,, +func,Py_IS_TYPE,3.15,, func,Py_IncRef,3.2,, func,Py_Initialize,3.2,, func,Py_InitializeEx,3.2,, @@ -886,6 +887,8 @@ func,Py_PACK_VERSION,3.14,, func,Py_REFCNT,3.14,, func,Py_ReprEnter,3.2,, func,Py_ReprLeave,3.2,, +func,Py_SET_SIZE,3.15,, +func,Py_SIZE,3.15,, func,Py_SetProgramName,3.2,, func,Py_SetPythonHome,3.2,, func,Py_SetRecursionLimit,3.2,, diff --git a/Include/object.h b/Include/object.h index 9585f4a1d67a52..7e5de7f6e38118 100644 --- a/Include/object.h +++ b/Include/object.h @@ -265,56 +265,41 @@ _Py_IsOwnedByCurrentThread(PyObject *ob) } #endif -// Py_TYPE() implementation for the stable ABI -PyAPI_FUNC(PyTypeObject*) Py_TYPE(PyObject *ob); - -#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030e0000 - // Stable ABI implements Py_TYPE() as a function call - // on limited C API version 3.14 and newer. -#else - static inline PyTypeObject* _Py_TYPE(PyObject *ob) - { - return ob->ob_type; - } - #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 - # define Py_TYPE(ob) _Py_TYPE(_PyObject_CAST(ob)) - #else - # define Py_TYPE(ob) _Py_TYPE(ob) - #endif -#endif - PyAPI_DATA(PyTypeObject) PyLong_Type; PyAPI_DATA(PyTypeObject) PyBool_Type; +/* Definitions for the stable ABI */ +PyAPI_FUNC(PyTypeObject*) Py_TYPE(PyObject *ob); +PyAPI_FUNC(Py_ssize_t) Py_SIZE(PyObject *ob); +PyAPI_FUNC(int) Py_IS_TYPE(PyObject *ob, PyTypeObject *type); +PyAPI_FUNC(void) Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size); + #ifndef _Py_OPAQUE_PYOBJECT + +static inline +PyTypeObject* _Py_TYPE_impl(PyObject *ob) +{ + return ob->ob_type; +} + // bpo-39573: The Py_SET_SIZE() function must be used to set an object size. -static inline Py_ssize_t Py_SIZE(PyObject *ob) { +static inline Py_ssize_t +_Py_SIZE_impl(PyObject *ob) +{ assert(Py_TYPE(ob) != &PyLong_Type); assert(Py_TYPE(ob) != &PyBool_Type); return _PyVarObject_CAST(ob)->ob_size; } -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 -# define Py_SIZE(ob) Py_SIZE(_PyObject_CAST(ob)) -#endif -#endif // !defined(_Py_OPAQUE_PYOBJECT) -static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { +static inline int +_Py_IS_TYPE_impl(PyObject *ob, PyTypeObject *type) +{ return Py_TYPE(ob) == type; } -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 -# define Py_IS_TYPE(ob, type) Py_IS_TYPE(_PyObject_CAST(ob), (type)) -#endif - - -#ifndef _Py_OPAQUE_PYOBJECT -static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { - ob->ob_type = type; -} -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 -# define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type) -#endif -static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) { +static inline void +_Py_SET_SIZE_impl(PyVarObject *ob, Py_ssize_t size) +{ assert(Py_TYPE(_PyObject_CAST(ob)) != &PyLong_Type); assert(Py_TYPE(_PyObject_CAST(ob)) != &PyBool_Type); #ifdef Py_GIL_DISABLED @@ -323,9 +308,31 @@ static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) { ob->ob_size = size; #endif } -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 -# define Py_SET_SIZE(ob, size) Py_SET_SIZE(_PyVarObject_CAST(ob), (size)) + +static inline void +Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 11) +// Non-limited API & limited API 3.11 & below: the user can omit casts +# define Py_TYPE(ob) _Py_TYPE_impl(_PyObject_CAST(ob)) +# define Py_SIZE(ob) _Py_SIZE_impl(_PyObject_CAST(ob)) +# define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl(_PyObject_CAST(ob), (type)) +# define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl(_PyVarObject_CAST(ob), (size)) +# define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type) +#elif Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 15) +# if Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 14) +// Py_TYPE() is a function call on Limited API 3.14 and newer +# define Py_TYPE(ob) _Py_TYPE_impl(ob) +# endif +// These are function calls on Limited API 3.15 and newer: +# define Py_SIZE(ob) _Py_SIZE_impl(ob) +# define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl((ob), (type)) +# define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl((ob), (size)) #endif + #endif // !defined(_Py_OPAQUE_PYOBJECT) diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index cbec7e43a7c9fb..6cf0ece361c3cc 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -895,6 +895,7 @@ def test_windows_feature_macros(self): "Py_GetRecursionLimit", "Py_GetVersion", "Py_HasFileSystemDefaultEncoding", + "Py_IS_TYPE", "Py_IncRef", "Py_Initialize", "Py_InitializeEx", @@ -914,6 +915,8 @@ def test_windows_feature_macros(self): "Py_REFCNT", "Py_ReprEnter", "Py_ReprLeave", + "Py_SET_SIZE", + "Py_SIZE", "Py_SetPath", "Py_SetProgramName", "Py_SetPythonHome", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index d651e0fac111b1..db7ba4359bb893 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2519,8 +2519,10 @@ added = '3.13' [function.Py_TYPE] + # Before 3.14, this was a macro that accessed the PyObject member added = '3.14' [function.Py_REFCNT] + # Before 3.14, this was a macro that accessed the PyObject member added = '3.14' [function.PyIter_NextItem] added = '3.14' @@ -2606,3 +2608,12 @@ added = '3.15' [const.PyABIInfo_FREETHREADING_AGNOSTIC] added = '3.15' +[function.Py_SIZE] + # Before 3.15, this was a macro that accessed the PyObject member + added = '3.15' +[function.Py_IS_TYPE] + # Before 3.15, this was a macro that accessed the PyObject member + added = '3.15' +[function.Py_SET_SIZE] + # Before 3.15, this was a macro that accessed the PyObject member + added = '3.15' diff --git a/Objects/object.c b/Objects/object.c index 0540112d7d2acf..c25ed3710f38a9 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3361,24 +3361,6 @@ Py_GetConstantBorrowed(unsigned int constant_id) return Py_GetConstant(constant_id); } - -// Py_TYPE() implementation for the stable ABI -#undef Py_TYPE -PyTypeObject* -Py_TYPE(PyObject *ob) -{ - return _Py_TYPE(ob); -} - - -// Py_REFCNT() implementation for the stable ABI -#undef Py_REFCNT -Py_ssize_t -Py_REFCNT(PyObject *ob) -{ - return _Py_REFCNT(ob); -} - int PyUnstable_IsImmortal(PyObject *op) { @@ -3405,3 +3387,16 @@ _PyObject_VisitType(PyObject *op, visitproc visit, void *arg) Py_VISIT(tp); return 0; } + +// Implementations for the stable ABI +// Keep these at the end. +#undef Py_TYPE +#undef Py_REFCNT +#undef Py_SIZE +#undef Py_IS_TYPE +#undef Py_SET_SIZE +PyTypeObject* Py_TYPE(PyObject *ob) { return _Py_TYPE_impl(ob); } +Py_ssize_t Py_REFCNT(PyObject *ob) { return _Py_REFCNT(ob); } +Py_ssize_t Py_SIZE(PyObject *o) { return _Py_SIZE_impl(o); } +int Py_IS_TYPE(PyObject *o, PyTypeObject *t) { return _Py_IS_TYPE_impl(o, t); } +void Py_SET_SIZE(PyVarObject *o, Py_ssize_t s) { _Py_SET_SIZE_impl(o, s); } diff --git a/PC/python3dll.c b/PC/python3dll.c index 05c86e6d5924d4..c98b7c200bf28d 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -71,6 +71,7 @@ EXPORT_FUNC(Py_IncRef) EXPORT_FUNC(Py_Initialize) EXPORT_FUNC(Py_InitializeEx) EXPORT_FUNC(Py_Is) +EXPORT_FUNC(Py_IS_TYPE) EXPORT_FUNC(Py_IsFalse) EXPORT_FUNC(Py_IsFinalizing) EXPORT_FUNC(Py_IsInitialized) @@ -86,10 +87,12 @@ EXPORT_FUNC(Py_PACK_VERSION) EXPORT_FUNC(Py_REFCNT) EXPORT_FUNC(Py_ReprEnter) EXPORT_FUNC(Py_ReprLeave) +EXPORT_FUNC(Py_SET_SIZE) EXPORT_FUNC(Py_SetPath) EXPORT_FUNC(Py_SetProgramName) EXPORT_FUNC(Py_SetPythonHome) EXPORT_FUNC(Py_SetRecursionLimit) +EXPORT_FUNC(Py_SIZE) EXPORT_FUNC(Py_TYPE) EXPORT_FUNC(Py_VaBuildValue) EXPORT_FUNC(Py_XNewRef) From 0ee604a2b411934fdae6fd6e87dc85b9c1e8fd79 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Sep 2025 16:05:40 +0200 Subject: [PATCH 2/4] Adjust comments --- Include/object.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Include/object.h b/Include/object.h index 7e5de7f6e38118..f721d64b91f8f5 100644 --- a/Include/object.h +++ b/Include/object.h @@ -316,21 +316,24 @@ Py_SET_TYPE(PyObject *ob, PyTypeObject *type) } #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 11) -// Non-limited API & limited API 3.11 & below: the user can omit casts +// Non-limited API & limited API 3.11 & below: use static inline functions and +// use _PyObject_CAST so that users don't need their own casts # define Py_TYPE(ob) _Py_TYPE_impl(_PyObject_CAST(ob)) # define Py_SIZE(ob) _Py_SIZE_impl(_PyObject_CAST(ob)) # define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl(_PyObject_CAST(ob), (type)) # define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl(_PyVarObject_CAST(ob), (size)) # define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type) #elif Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 15) -# if Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 14) -// Py_TYPE() is a function call on Limited API 3.14 and newer -# define Py_TYPE(ob) _Py_TYPE_impl(ob) -# endif -// These are function calls on Limited API 3.15 and newer: +// Limited API 3.11-3.14: use static inline functions, without casts # define Py_SIZE(ob) _Py_SIZE_impl(ob) # define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl((ob), (type)) # define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl((ob), (size)) +# if Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 14) +// Py_TYPE() is static inline only on Limited API 3.13 and below +# define Py_TYPE(ob) _Py_TYPE_impl(ob) +# endif +#else +// Limited API 3.15+: use function calls #endif #endif // !defined(_Py_OPAQUE_PYOBJECT) From b5b8c1ed7b442b70e9932961b102abf5cc33c758 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Sep 2025 16:32:09 +0200 Subject: [PATCH 3/4] Add blurb --- .../next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst diff --git a/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst new file mode 100644 index 00000000000000..46565972d0af39 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst @@ -0,0 +1,2 @@ +Expose the functions :c:func:`Py_SIZE`, :c:func:`Py_IS_TYPE` and +:c:func:`SET_SIZE` in the Stable ABI. From b522757edb4a4771a12c907d133f6238dbe9043c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Sep 2025 16:47:05 +0200 Subject: [PATCH 4/4] Fix reference --- .../next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst index 46565972d0af39..039c25b8ce2b3e 100644 --- a/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst +++ b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst @@ -1,2 +1,2 @@ Expose the functions :c:func:`Py_SIZE`, :c:func:`Py_IS_TYPE` and -:c:func:`SET_SIZE` in the Stable ABI. +:c:func:`Py_SET_SIZE` in the Stable ABI.