From 1116e1ece97d7498c886d7a73ad4aa800d180eed Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 5 Apr 2026 23:13:50 +0200 Subject: [PATCH 1/7] gh-146636: abi3t: Test that Python.h avoids checking Py_GIL_DISABLED Add a test that Python headers themselves don't use Py_GIL_DISABLED in abi3t: abi3 and abi3t ought to be the same except the _Py_OPAQUE_PYOBJECT differences. This is done using the GCC-only poison pragma. --- Include/pyport.h | 11 +++++++++++ Lib/test/test_cext/setup.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/Include/pyport.h b/Include/pyport.h index 62cba4c1421f99..e6b98c070bbe55 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -83,6 +83,17 @@ // locking needed in for free-threaded interpreters builds. # define Py_GIL_DISABLED # endif +# if defined(_Py_IS_TESTCEXT) +// When compiling for abi3t, contents of Python.h should not depend +// on Py_GIL_DISABLED. +// We ask GCC to error if it sees the macro from this point on. +// Since users are free to the macro, and there's no way to undo the poisoning +// at the end of Python.h, we only do this in a test module. +# ifdef __GNUC__ +# undef Py_GIL_DISABLED +# pragma GCC poison Py_GIL_DISABLED +# endif +# endif #endif diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py index 7262a110d83415..25fe50df603883 100644 --- a/Lib/test/test_cext/setup.py +++ b/Lib/test/test_cext/setup.py @@ -18,6 +18,11 @@ # The purpose of test_cext extension is to check that building a C # extension using the Python C API does not emit C compiler warnings. '-Werror', + # Enable extra checks for header files, which: + # - need to be enabled somewhere inside Python headers (rather than + # before including Python.h) + # - should not be checked for user code + '-D_Py_IS_TESTCEXT', ] # C compiler flags for GCC and clang From 458129871d16c583828ff39af653dd3658bc3b0c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 9 Apr 2026 14:39:35 +0200 Subject: [PATCH 2/7] Move ABI/feature selection macros to their own header --- Include/Python.h | 9 ++-- Include/patchlevel.h | 28 ---------- Include/pyabi.h | 82 ++++++++++++++++++++++++++++++ Include/pyport.h | 39 -------------- Makefile.pre.in | 1 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 ++ 7 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 Include/pyabi.h diff --git a/Include/Python.h b/Include/Python.h index e6e5cab67e2045..56c2df4382084a 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -9,10 +9,11 @@ // is not needed. -// Include Python header files -#include "patchlevel.h" -#include "pyconfig.h" -#include "pymacconfig.h" +// Include Python configuration headers +#include "patchlevel.h" // the Python version +#include "pyconfig.h" // information from configure +#include "pymacconfig.h" // overrides for pyconfig +#include "pyabi.h" // feature/ABI selection // Include standard header files diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 9f5c36230a7e45..974246f896e10b 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -61,32 +61,4 @@ #define PYTHON_ABI_VERSION 3 #define PYTHON_ABI_STRING "3" - -/* Stable ABI for free-threaded builds (introduced in PEP 803) - is enabled by one of: - - Py_TARGET_ABI3T, or - - Py_LIMITED_API and Py_GIL_DISABLED. - "Output" macros to be used internally: - - Py_LIMITED_API (defines the subset of API we expose) - - _Py_OPAQUE_PYOBJECT (additionally hides what's ABI-incompatible between - free-threaded & GIL) - (Don't use Py_TARGET_ABI3T directly: it's currently only used to set these - 2 macros. It's also available for users' convenience.) - */ -#if defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED) \ - && !defined(Py_TARGET_ABI3T) -# define Py_TARGET_ABI3T Py_LIMITED_API -#endif -#if defined(Py_TARGET_ABI3T) -# define _Py_OPAQUE_PYOBJECT -# if !defined(Py_LIMITED_API) -# define Py_LIMITED_API Py_TARGET_ABI3T -# elif Py_LIMITED_API > Py_TARGET_ABI3T - // if both are defined, use the *lower* version, - // i.e. maximum compatibility -# undef Py_LIMITED_API -# define Py_LIMITED_API Py_TARGET_ABI3T -# endif -#endif - #endif //_Py_PATCHLEVEL_H diff --git a/Include/pyabi.h b/Include/pyabi.h new file mode 100644 index 00000000000000..0dddd25b626724 --- /dev/null +++ b/Include/pyabi.h @@ -0,0 +1,82 @@ +/* Macros that restrict available definitions and select implementations + * to match an ABI stability promise: + * + * - internal API/ABI (may change at any time) -- Py_BUILD_CORE* + * - general CPython API/ABI (may change in 3.x.0) -- default + * - Stable ABI: abi3, abi3t (long-term stable) -- Py_LIMITED_API, + * Py_TARGET_ABI3T, _Py_OPAQUE_PYOBJECT + * - Free-threading (incompatible with non-free-threading builds) + * -- Py_GIL_DISABLED + */ + +#ifndef _Py_PYABI_H +#define _Py_PYABI_H + +/* Defines to build Python and its standard library: + * + * - Py_BUILD_CORE: Build Python core. Give access to Python internals, but + * should not be used by third-party modules. + * - Py_BUILD_CORE_BUILTIN: Build a Python stdlib module as a built-in module. + * - Py_BUILD_CORE_MODULE: Build a Python stdlib module as a dynamic library. + * + * Py_BUILD_CORE_BUILTIN and Py_BUILD_CORE_MODULE imply Py_BUILD_CORE. + * + * On Windows, Py_BUILD_CORE_MODULE exports "PyInit_xxx" symbol, whereas + * Py_BUILD_CORE_BUILTIN does not. + */ +#if defined(Py_BUILD_CORE_BUILTIN) && !defined(Py_BUILD_CORE) +# define Py_BUILD_CORE +#endif +#if defined(Py_BUILD_CORE_MODULE) && !defined(Py_BUILD_CORE) +# define Py_BUILD_CORE +#endif + +/* Stable ABI for free-threaded builds (abi3t, introduced in PEP 803) + is enabled by the user setting one of: + - Py_TARGET_ABI3T, or + - Py_LIMITED_API and Py_GIL_DISABLED. + + These affect set the following, which Python.h should use internally: + - Py_LIMITED_API (defines the subset of API we expose) + - _Py_OPAQUE_PYOBJECT (additionally hides what's ABI-incompatible between + free-threaded & GIL) + + (Don't use Py_TARGET_ABI3T directly. It's currently only used to set these + 2 macro, and defined for users' convenience.) + */ +#if defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED) \ + && !defined(Py_TARGET_ABI3T) +# define Py_TARGET_ABI3T Py_LIMITED_API +#endif +#if defined(Py_TARGET_ABI3T) +# define _Py_OPAQUE_PYOBJECT +# if !defined(Py_LIMITED_API) +# define Py_LIMITED_API Py_TARGET_ABI3T +# elif Py_LIMITED_API > Py_TARGET_ABI3T + // if both are defined, use the *lower* version, + // i.e. maximum compatibility +# undef Py_LIMITED_API +# define Py_LIMITED_API Py_TARGET_ABI3T +# endif +#endif + +#if defined(Py_TARGET_ABI3T) +# if !defined(Py_GIL_DISABLED) +// Define Py_GIL_DISABLED for users' needs. Users check this macro to see +// whether they need extra synchronization. +# define Py_GIL_DISABLED +# endif +# if defined(_Py_IS_TESTCEXT) +// When compiling for abi3t, contents of Python.h should not depend +// on Py_GIL_DISABLED. +// We ask GCC to error if it sees the macro from this point on. +// Since users are free to the macro, and there's no way to undo the poisoning +// at the end of Python.h, we only do this in a test module. +# ifdef __GNUC__ +# undef Py_GIL_DISABLED +# pragma GCC poison Py_GIL_DISABLED +# endif +# endif +#endif + +#endif // _Py_PYABI_H diff --git a/Include/pyport.h b/Include/pyport.h index e6b98c070bbe55..97b1e576b7ad02 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -58,45 +58,6 @@ #endif -/* Defines to build Python and its standard library: - * - * - Py_BUILD_CORE: Build Python core. Give access to Python internals, but - * should not be used by third-party modules. - * - Py_BUILD_CORE_BUILTIN: Build a Python stdlib module as a built-in module. - * - Py_BUILD_CORE_MODULE: Build a Python stdlib module as a dynamic library. - * - * Py_BUILD_CORE_BUILTIN and Py_BUILD_CORE_MODULE imply Py_BUILD_CORE. - * - * On Windows, Py_BUILD_CORE_MODULE exports "PyInit_xxx" symbol, whereas - * Py_BUILD_CORE_BUILTIN does not. - */ -#if defined(Py_BUILD_CORE_BUILTIN) && !defined(Py_BUILD_CORE) -# define Py_BUILD_CORE -#endif -#if defined(Py_BUILD_CORE_MODULE) && !defined(Py_BUILD_CORE) -# define Py_BUILD_CORE -#endif - -#if defined(Py_TARGET_ABI3T) -# if !defined(Py_GIL_DISABLED) -// Define Py_GIL_DISABLED for users' needs. This macro is used to enable -// locking needed in for free-threaded interpreters builds. -# define Py_GIL_DISABLED -# endif -# if defined(_Py_IS_TESTCEXT) -// When compiling for abi3t, contents of Python.h should not depend -// on Py_GIL_DISABLED. -// We ask GCC to error if it sees the macro from this point on. -// Since users are free to the macro, and there's no way to undo the poisoning -// at the end of Python.h, we only do this in a test module. -# ifdef __GNUC__ -# undef Py_GIL_DISABLED -# pragma GCC poison Py_GIL_DISABLED -# endif -# endif -#endif - - /************************************************************************** Symbols and macros to supply platform-independent interfaces to basic C language & library operations whose spellings vary across platforms. diff --git a/Makefile.pre.in b/Makefile.pre.in index 354580aa482d25..efe1865da20431 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1212,6 +1212,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/osdefs.h \ $(srcdir)/Include/osmodule.h \ $(srcdir)/Include/patchlevel.h \ + $(srcdir)/Include/pyabi.h \ $(srcdir)/Include/pyatomic.h \ $(srcdir)/Include/pybuffer.h \ $(srcdir)/Include/pycapsule.h \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 61bee29c0af3d6..fe70e02536bbb6 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -359,6 +359,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 664788e69af19a..629f063861de9a 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -156,6 +156,9 @@ Include + + Include + Include From e1b750050381e418ff1f85bc104d35b72ae69b92 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 9 Apr 2026 14:51:51 +0200 Subject: [PATCH 3/7] Update check for --- Include/Python.h | 12 +++++------- .../2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst | 2 ++ 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst diff --git a/Include/Python.h b/Include/Python.h index 56c2df4382084a..3a8ef3e07b38dd 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -47,13 +47,11 @@ # endif #endif -#if defined(Py_GIL_DISABLED) -# if defined(_MSC_VER) -# include // __readgsqword() -# endif - -# if defined(__MINGW32__) -# include // __readgsqword() +#if !defined(Py_LIMITED_API) +# if defined(Py_GIL_DISABLED) +# if defined(_MSC_VER) || defined(__MINGW32__) +# include // __readgsqword() +# endif # endif #endif // Py_GIL_DISABLED diff --git a/Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst b/Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst new file mode 100644 index 00000000000000..1ec1afd2cbfeb9 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst @@ -0,0 +1,2 @@ +Using :c:macro:`Py_LIMITED_API` on a non-Windows free-threaded build no +longer needs an extra :c:macro:`Py_GIL_DISABLED`. From 247bd4693be60164acc3eeed4bfdc7e3e285627d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 9 Apr 2026 15:39:33 +0200 Subject: [PATCH 4/7] Typo --- Include/pyabi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/pyabi.h b/Include/pyabi.h index 0dddd25b626724..06e94a3a78988f 100644 --- a/Include/pyabi.h +++ b/Include/pyabi.h @@ -42,7 +42,7 @@ free-threaded & GIL) (Don't use Py_TARGET_ABI3T directly. It's currently only used to set these - 2 macro, and defined for users' convenience.) + 2 macros, and defined for users' convenience.) */ #if defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED) \ && !defined(Py_TARGET_ABI3T) From 78354491ba54b5ac6f8dee6237539acd8c12db40 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 9 Apr 2026 15:46:11 +0200 Subject: [PATCH 5/7] Move Py_BUILD_CORE undefines, and include exports.h directly from Python.h --- Include/Python.h | 1 + Include/exports.h | 8 ++++---- Include/pyabi.h | 15 +++++++++++++++ Include/pyport.h | 11 ----------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Include/Python.h b/Include/Python.h index 3a8ef3e07b38dd..8b76195b320998 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -66,6 +66,7 @@ __pragma(warning(disable: 4201)) // Include Python header files #include "pyport.h" +#include "exports.h" #include "pymacro.h" #include "pymath.h" #include "pymem.h" diff --git a/Include/exports.h b/Include/exports.h index 97a674ec2403a4..a863ecb33078ab 100644 --- a/Include/exports.h +++ b/Include/exports.h @@ -36,7 +36,7 @@ #define Py_LOCAL_SYMBOL #endif /* module init functions outside the core must be exported */ - #if defined(Py_BUILD_CORE) + #if defined(_PyEXPORTS_CORE) #define _PyINIT_EXPORTED_SYMBOL Py_EXPORTED_SYMBOL #else #define _PyINIT_EXPORTED_SYMBOL __declspec(dllexport) @@ -64,13 +64,13 @@ /* only get special linkage if built as shared or platform is Cygwin */ #if defined(Py_ENABLE_SHARED) || defined(__CYGWIN__) # if defined(HAVE_DECLSPEC_DLL) -# if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# if defined(_PyEXPORTS_CORE) && !defined(_PyEXPORTS_CORE_MODULE) /* module init functions inside the core need no external linkage */ /* except for Cygwin to handle embedding */ # if !defined(__CYGWIN__) # define _PyINIT_FUNC_DECLSPEC # endif /* __CYGWIN__ */ -# else /* Py_BUILD_CORE */ +# else /* _PyEXPORTS_CORE */ /* Building an extension module, or an embedded situation */ /* public Python functions and data are imported */ /* Under Cygwin, auto-import functions to prevent compilation */ @@ -80,7 +80,7 @@ # define PyAPI_FUNC(RTYPE) Py_IMPORTED_SYMBOL RTYPE # endif /* !__CYGWIN__ */ # define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE -# endif /* Py_BUILD_CORE */ +# endif /* _PyEXPORTS_CORE */ # endif /* HAVE_DECLSPEC_DLL */ #endif /* Py_ENABLE_SHARED */ diff --git a/Include/pyabi.h b/Include/pyabi.h index 06e94a3a78988f..23dcddca4e2f93 100644 --- a/Include/pyabi.h +++ b/Include/pyabi.h @@ -79,4 +79,19 @@ # endif #endif +// The internal C API must not be used with the limited C API: make sure +// that Py_BUILD_CORE* macros are not defined in this case. +// But, keep the "original" values, under different names, for "exports.h" +#ifdef Py_BUILD_CORE +# define _PyEXPORTS_CORE +#endif +#ifdef Py_BUILD_CORE_MODULE +# define _PyEXPORTS_CORE_MODULE +#endif +#ifdef Py_LIMITED_API +# undef Py_BUILD_CORE +# undef Py_BUILD_CORE_BUILTIN +# undef Py_BUILD_CORE_MODULE +#endif + #endif // _Py_PYABI_H diff --git a/Include/pyport.h b/Include/pyport.h index 97b1e576b7ad02..c975921beafb9e 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -365,17 +365,6 @@ extern "C" { # define Py_NO_INLINE #endif -#include "exports.h" - -#ifdef Py_LIMITED_API - // The internal C API must not be used with the limited C API: make sure - // that Py_BUILD_CORE macro is not defined in this case. These 3 macros are - // used by exports.h, so only undefine them afterwards. -# undef Py_BUILD_CORE -# undef Py_BUILD_CORE_BUILTIN -# undef Py_BUILD_CORE_MODULE -#endif - /* limits.h constants that may be missing */ #ifndef INT_MAX From 2ad80d83948f376134ef91a850a1955cc063bc2c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 9 Apr 2026 16:02:59 +0200 Subject: [PATCH 6/7] Comments --- Include/pyabi.h | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Include/pyabi.h b/Include/pyabi.h index 23dcddca4e2f93..46574e4b1a59ec 100644 --- a/Include/pyabi.h +++ b/Include/pyabi.h @@ -14,8 +14,8 @@ /* Defines to build Python and its standard library: * - * - Py_BUILD_CORE: Build Python core. Give access to Python internals, but - * should not be used by third-party modules. + * - Py_BUILD_CORE: Build Python core. Gives access to Python internals; should + * not be used by third-party modules. * - Py_BUILD_CORE_BUILTIN: Build a Python stdlib module as a built-in module. * - Py_BUILD_CORE_MODULE: Build a Python stdlib module as a dynamic library. * @@ -32,17 +32,17 @@ #endif /* Stable ABI for free-threaded builds (abi3t, introduced in PEP 803) - is enabled by the user setting one of: - - Py_TARGET_ABI3T, or - - Py_LIMITED_API and Py_GIL_DISABLED. - - These affect set the following, which Python.h should use internally: - - Py_LIMITED_API (defines the subset of API we expose) - - _Py_OPAQUE_PYOBJECT (additionally hides what's ABI-incompatible between - free-threaded & GIL) - - (Don't use Py_TARGET_ABI3T directly. It's currently only used to set these - 2 macros, and defined for users' convenience.) + * is enabled by one of: + * - Py_TARGET_ABI3T, or + * - Py_LIMITED_API and Py_GIL_DISABLED. + * + * These affect set the following, which Python.h should use internally: + * - Py_LIMITED_API (defines the subset of API we expose) + * - _Py_OPAQUE_PYOBJECT (additionally hides what's ABI-incompatible between + * free-threaded & GIL) + * + * (Don't use Py_TARGET_ABI3T directly. It's currently only used to set these + * 2 macros, and defined for users' convenience.) */ #if defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED) \ && !defined(Py_TARGET_ABI3T) @@ -62,16 +62,16 @@ #if defined(Py_TARGET_ABI3T) # if !defined(Py_GIL_DISABLED) -// Define Py_GIL_DISABLED for users' needs. Users check this macro to see -// whether they need extra synchronization. + // Define Py_GIL_DISABLED for users' needs. Users check this macro to see + // whether they need extra synchronization. # define Py_GIL_DISABLED # endif # if defined(_Py_IS_TESTCEXT) -// When compiling for abi3t, contents of Python.h should not depend -// on Py_GIL_DISABLED. -// We ask GCC to error if it sees the macro from this point on. -// Since users are free to the macro, and there's no way to undo the poisoning -// at the end of Python.h, we only do this in a test module. + // When compiling for abi3t, contents of Python.h should not depend + // on Py_GIL_DISABLED. + // We ask GCC to error if it sees the macro from this point on. + // Since users are free to the macro, and there's no way to undo the + // poisoning at the end of Python.h, we only do this in a test module. # ifdef __GNUC__ # undef Py_GIL_DISABLED # pragma GCC poison Py_GIL_DISABLED @@ -79,9 +79,10 @@ # endif #endif -// The internal C API must not be used with the limited C API: make sure -// that Py_BUILD_CORE* macros are not defined in this case. -// But, keep the "original" values, under different names, for "exports.h" +/* The internal C API must not be used with the limited C API: make sure + * that Py_BUILD_CORE* macros are not defined in this case. + * But, keep the "original" values, under different names, for "exports.h" + */ #ifdef Py_BUILD_CORE # define _PyEXPORTS_CORE #endif From 7c9d92006e8d24fda1f5a249bcc3918779d52e7a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 10 Apr 2026 13:14:38 +0200 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Victor Stinner --- Include/pyabi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Include/pyabi.h b/Include/pyabi.h index 46574e4b1a59ec..361c6036fb2cbc 100644 --- a/Include/pyabi.h +++ b/Include/pyabi.h @@ -67,11 +67,12 @@ # define Py_GIL_DISABLED # endif # if defined(_Py_IS_TESTCEXT) - // When compiling for abi3t, contents of Python.h should not depend + // When compiling for abi3t, contents of Python.h should not depend // on Py_GIL_DISABLED. // We ask GCC to error if it sees the macro from this point on. // Since users are free to the macro, and there's no way to undo the - // poisoning at the end of Python.h, we only do this in a test module. + // poisoning at the end of Python.h, we only do this in a test module + // (test_cext). # ifdef __GNUC__ # undef Py_GIL_DISABLED # pragma GCC poison Py_GIL_DISABLED