https://github.com/python/cpython/commit/ab41a347ebc7e6c3e3f5795c4a24545bfbf92a6e
commit: ab41a347ebc7e6c3e3f5795c4a24545bfbf92a6e
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2026-04-23T11:52:13+02:00
summary:

gh-146636: Improve ABI/feature selection, add new header for it (GH-148302)

Improve ABI/feature selection, add new header for it.

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.

Co-authored-by: Victor Stinner <[email protected]>

files:
A Include/pyabi.h
A Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst
M Include/Python.h
M Include/exports.h
M Include/patchlevel.h
M Include/pyport.h
M Lib/test/test_cext/setup.py
M Makefile.pre.in
M PCbuild/pythoncore.vcxproj
M PCbuild/pythoncore.vcxproj.filters

diff --git a/Include/Python.h b/Include/Python.h
index e6e5cab67e2045..8b76195b320998 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
@@ -46,13 +47,11 @@
 #  endif
 #endif
 
-#if defined(Py_GIL_DISABLED)
-#  if defined(_MSC_VER)
-#    include <intrin.h>             // __readgsqword()
-#  endif
-
-#  if defined(__MINGW32__)
-#    include <intrin.h>             // __readgsqword()
+#if !defined(Py_LIMITED_API)
+#  if defined(Py_GIL_DISABLED)
+#    if defined(_MSC_VER) || defined(__MINGW32__)
+#      include <intrin.h>             // __readgsqword()
+#    endif
 #  endif
 #endif // Py_GIL_DISABLED
 
@@ -67,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/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..8c4ae281a43faf
--- /dev/null
+++ b/Include/pyabi.h
@@ -0,0 +1,121 @@
+/* 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. 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.
+ *
+ * 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
+
+/* Check valid values for target ABI macros.
+ */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 3
+   // Empty Py_LIMITED_API used to work; redefine to
+   // Python 3.2 to be explicit.
+#  undef Py_LIMITED_API
+#  define Py_LIMITED_API 0x03020000
+#endif
+#if defined(Py_TARGET_ABI3T) && Py_TARGET_ABI3T+0 < 0x030f0000
+#  error "Py_TARGET_ABI3T must be 0x030f0000 (3.15) or above"
+#endif
+
+/* Stable ABI for free-threaded builds (abi3t, introduced in PEP 803)
+ * 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)
+#  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
+#else
+#  ifdef _Py_OPAQUE_PYOBJECT
+     // _Py_OPAQUE_PYOBJECT is a private macro; do not define it directly.
+#    error "Define Py_TARGET_ABI3T to 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
+     // (test_cext).
+     //
+     // Clang's poisoning is stricter than GCC's: it looks in `#elif`
+     // expressions after matching `#if`s. We disable it for now.
+     // We also provide an undocumented, unsupported opt-out macro to help
+     // porting to other compilers. Consider reaching out if you use it.
+#    if defined(__GNUC__) && !defined(__clang__) && !defined(_Py_NO_GCC_POISON)
+#      undef Py_GIL_DISABLED
+#      pragma GCC poison Py_GIL_DISABLED
+#    endif
+#  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 62cba4c1421f99..c975921beafb9e 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -58,34 +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
-#endif
-
-
 /**************************************************************************
 Symbols and macros to supply platform-independent interfaces to basic
 C language & library operations whose spellings vary across platforms.
@@ -393,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
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
diff --git a/Makefile.pre.in b/Makefile.pre.in
index f869c1f7c93776..57fce05d476e9e 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1214,6 +1214,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/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`.
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 @@
     <ClInclude Include="..\Include\osmodule.h" />
     <ClInclude Include="..\Include\patchlevel.h" />
     <ClInclude Include="..\Include\py_curses.h" />
+    <ClInclude Include="..\Include\pyabi.h" />
     <ClInclude Include="..\Include\pyatomic.h" />
     <ClInclude Include="..\Include\pybuffer.h" />
     <ClInclude Include="..\Include\pycapsule.h" />
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 @@
     <ClInclude Include="..\Include\py_curses.h">
       <Filter>Include</Filter>
     </ClInclude>
+    <ClInclude Include="..\Include\pyabi.h">
+      <Filter>Include</Filter>
+    </ClInclude>
     <ClInclude Include="..\Include\pyatomic.h">
       <Filter>Include</Filter>
     </ClInclude>

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to