]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-146636: Improve ABI/feature selection, add new header for it (GH-148302)
authorPetr Viktorin <encukou@gmail.com>
Thu, 23 Apr 2026 09:52:13 +0000 (11:52 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Apr 2026 09:52:13 +0000 (11:52 +0200)
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 <vstinner@python.org>
Include/Python.h
Include/exports.h
Include/patchlevel.h
Include/pyabi.h [new file with mode: 0644]
Include/pyport.h
Lib/test/test_cext/setup.py
Makefile.pre.in
Misc/NEWS.d/next/C_API/2026-04-09-14-45-44.gh-issue-148267.p84kG_.rst [new file with mode: 0644]
PCbuild/pythoncore.vcxproj
PCbuild/pythoncore.vcxproj.filters

index e6e5cab67e2045bca3ae95618e3e2aef3b59eb0b..8b76195b32099836f7d212b157158be9bdcf4a8e 100644 (file)
@@ -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
 #  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"
index 97a674ec2403a4e231d278e90b08a3b618802753..a863ecb33078aba2369f860a9e8f0ea39a672f75 100644 (file)
@@ -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)
 /* 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 */
 
index 9f5c36230a7e45d90cef7ce66480210225782fb6..974246f896e10b149c03a7ea43f43d2afe98e282 100644 (file)
 #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 (file)
index 0000000..8c4ae28
--- /dev/null
@@ -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
index 62cba4c1421f99f25a500130598f5f8cc391399f..c975921beafb9e0e87c242be678f0e704bdcd3f6 100644 (file)
 #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
index 7262a110d83415d3971733fceaa82a0ad004a796..25fe50df603883fbea9228198994d3bc3de6a8b6 100644 (file)
@@ -18,6 +18,11 @@ if not support.MS_WINDOWS:
         # 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
index f869c1f7c937764e7c71a4c0dbaf4eaf41e4ed45..57fce05d476e9ec1596e0333f83527d0aa37617f 100644 (file)
@@ -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 (file)
index 0000000..1ec1afd
--- /dev/null
@@ -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`.
index 61bee29c0af3d6f21f5768776db0cf91c637db26..fe70e02536bbb600fcde24ba6f90ce28a935caea 100644 (file)
     <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" />
index 664788e69af19a362f466d3400f25e10a091f3e8..629f063861de9a1614e95dba060d97dabb702d6b 100644 (file)
     <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>