Skip to content

Commit 0c74fc8

Browse files
encukouzooba
andauthored
gh-137210: Add a struct, slot & function for checking an extension's ABI (GH-137212)
Co-authored-by: Steve Dower <[email protected]>
1 parent c1a9c23 commit 0c74fc8

24 files changed

+654
-8
lines changed

Doc/c-api/module.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,28 @@ The available slot types are:
388388
389389
.. versionadded:: 3.13
390390
391+
.. c:macro:: Py_mod_abi
392+
393+
A pointer to a :c:struct:`PyABIInfo` structure that describes the ABI that
394+
the extension is using.
395+
396+
When the module is loaded, the :c:struct:`!PyABIInfo` in this slot is checked
397+
using :c:func:`PyABIInfo_Check`.
398+
399+
A suitable :c:struct:`!PyABIInfo` variable can be defined using the
400+
:c:macro:`PyABIInfo_VAR` macro, as in:
401+
402+
.. code-block:: c
403+
404+
PyABIInfo_VAR(abi_info);
405+
406+
static PyModuleDef_Slot mymodule_slots[] = {
407+
{Py_mod_abi, &abi_info},
408+
...
409+
};
410+
411+
.. versionadded:: 3.15
412+
391413
392414
.. _moduledef-dynamic:
393415

Doc/c-api/stable.rst

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
.. _stable:
44

5-
***************
6-
C API Stability
7-
***************
5+
***********************
6+
C API and ABI Stability
7+
***********************
88

99
Unless documented otherwise, Python's C API is covered by the Backwards
1010
Compatibility Policy, :pep:`387`.
@@ -199,6 +199,162 @@ This is the case with Windows and macOS releases from ``python.org`` and many
199199
third-party distributors.
200200

201201

202+
ABI Checking
203+
============
204+
205+
.. versionadded:: next
206+
207+
Python includes a rudimentary check for ABI compatibility.
208+
209+
This check is not comprehensive.
210+
It only guards against common cases of incompatible modules being
211+
installed for the wrong interpreter.
212+
It also does not take :ref:`platform incompatibilities <stable-abi-platform>`
213+
into account.
214+
It can only be done after an extension is successfully loaded.
215+
216+
Despite these limitations, it is recommended that extension modules use this
217+
mechanism, so that detectable incompatibilities raise exceptions rather than
218+
crash.
219+
220+
Most modules can use this check via the :c:data:`Py_mod_abi`
221+
slot and the :c:macro:`PyABIInfo_VAR` macro, for example like this:
222+
223+
.. code-block:: c
224+
225+
PyABIInfo_VAR(abi_info);
226+
227+
static PyModuleDef_Slot mymodule_slots[] = {
228+
{Py_mod_abi, &abi_info},
229+
...
230+
};
231+
232+
233+
The full API is described below for advanced use cases.
234+
235+
.. c:function:: int PyABIInfo_Check(PyABIInfo *info, const char *module_name)
236+
237+
Verify that the given *info* is compatible with the currently running
238+
interpreter.
239+
240+
Return 0 on success. On failure, raise an exception and return -1.
241+
242+
If the ABI is incompatible, the raised exception will be :py:exc:`ImportError`.
243+
244+
The *module_name* argument can be ``NULL``, or point to a NUL-terminated
245+
UTF-8-encoded string used for error messages.
246+
247+
Note that if *info* describes the ABI that the current code uses (as defined
248+
by :c:macro:`PyABIInfo_VAR`, for example), using any other Python C API
249+
may lead to crashes.
250+
In particular, it is not safe to examine the raised exception.
251+
252+
.. versionadded:: next
253+
254+
.. c:macro:: PyABIInfo_VAR(NAME)
255+
256+
Define a static :c:struct:`PyABIInfo` variable with the given *NAME* that
257+
describes the ABI that the current code will use.
258+
This macro expands to:
259+
260+
.. code-block:: c
261+
262+
static PyABIInfo NAME = {
263+
1, 0,
264+
PyABIInfo_DEFAULT_FLAGS,
265+
PY_VERSION_HEX,
266+
PyABIInfo_DEFAULT_ABI_VERSION
267+
}
268+
269+
.. versionadded:: next
270+
271+
.. c:type:: PyABIInfo
272+
273+
.. c:member:: uint8_t abiinfo_major_version
274+
275+
The major version of :c:struct:`PyABIInfo`. Can be set to:
276+
277+
* ``0`` to skip all checking, or
278+
* ``1`` to specify this version of :c:struct:`!PyABIInfo`.
279+
280+
.. c:member:: uint8_t abiinfo_minor_version
281+
282+
The major version of :c:struct:`PyABIInfo`.
283+
Must be set to ``0``; larger values are reserved for backwards-compatible
284+
future versions of :c:struct:`!PyABIInfo`.
285+
286+
.. c:member:: uint16_t flags
287+
288+
.. c:namespace:: NULL
289+
290+
This field is usually set to the following macro:
291+
292+
.. c:macro:: PyABIInfo_DEFAULT_FLAGS
293+
294+
Default flags, based on current values of macros such as
295+
:c:macro:`Py_LIMITED_API` and :c:macro:`Py_GIL_DISABLED`.
296+
297+
Alternately, the field can be set to to the following flags, combined
298+
by bitwise OR.
299+
Unused bits must be set to zero.
300+
301+
ABI variant -- one of:
302+
303+
.. c:macro:: PyABIInfo_STABLE
304+
305+
Specifies that the stable ABI is used.
306+
307+
.. c:macro:: PyABIInfo_INTERNAL
308+
309+
Specifies ABI specific to a particular build of CPython.
310+
Internal use only.
311+
312+
Free-threading compatibility -- one of:
313+
314+
.. c:macro:: PyABIInfo_FREETHREADED
315+
316+
Specifies ABI compatible with free-threading builds of CPython.
317+
(That is, ones compiled with :option:`--disable-gil`; with ``t``
318+
in :py:data:`sys.abiflags`)
319+
320+
.. c:macro:: PyABIInfo_GIL
321+
322+
Specifies ABI compatible with non-free-threading builds of CPython
323+
(ones compiled *without* :option:`--disable-gil`).
324+
325+
.. c:member:: uint32_t build_version
326+
327+
The version of the Python headers used to build the code, in the format
328+
used by :c:macro:`PY_VERSION_HEX`.
329+
330+
This can be set to ``0`` to skip any checks related to this field.
331+
This option is meant mainly for projects that do not use the CPython
332+
headers directly, and do not emulate a specific version of them.
333+
334+
.. c:member:: uint32_t abi_version
335+
336+
The ABI version.
337+
338+
For the Stable ABI, this field should be the value of
339+
:c:macro:`Py_LIMITED_API`
340+
(except if :c:macro:`Py_LIMITED_API` is ``3``; use
341+
:c:expr:`Py_PACK_VERSION(3, 2)` in that case).
342+
343+
Otherwise, it should be set to :c:macro:`PY_VERSION_HEX`.
344+
345+
It can also be set to ``0`` to skip any checks related to this field.
346+
347+
.. c:namespace:: NULL
348+
349+
.. c:macro:: PyABIInfo_DEFAULT_ABI_VERSION
350+
351+
The value that should be used for this field, based on current
352+
values of macros such as :c:macro:`Py_LIMITED_API`,
353+
:c:macro:`PY_VERSION_HEX` and :c:macro:`Py_GIL_DISABLED`.
354+
355+
.. versionadded:: next
356+
357+
202358
.. _limited-api-list:
203359

204360
Contents of Limited API

Doc/data/stable_abi.dat

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/using/configure.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ General Options
293293

294294
.. option:: --disable-gil
295295

296+
.. c:macro:: Py_GIL_DISABLED
297+
:no-typesetting:
298+
296299
Enables support for running Python without the :term:`global interpreter
297300
lock` (GIL): free threading build.
298301

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,11 @@ New features
670670
a string. See the documentation for caveats.
671671
(Contributed by Petr Viktorin in :gh:`131510`)
672672

673+
* Add API for checking an extension module's ABI compatibility:
674+
:c:data:`Py_mod_abi`, :c:func:`PyABIInfo_Check`, :c:macro:`PyABIInfo_VAR`
675+
and :c:data:`Py_mod_abi`.
676+
(Contributed by Petr Viktorin in :gh:`137210`)
677+
673678

674679
Porting to Python 3.15
675680
----------------------

Include/cpython/modsupport.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,15 @@ typedef struct _PyArg_Parser {
2424

2525
PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *,
2626
struct _PyArg_Parser *, ...);
27+
28+
#ifdef Py_BUILD_CORE
29+
// Internal; defined here to avoid explicitly including pycore_modsupport.h
30+
#define _Py_INTERNAL_ABI_SLOT \
31+
{Py_mod_abi, (void*) &(PyABIInfo) { \
32+
.abiinfo_major_version = 1, \
33+
.abiinfo_minor_version = 0, \
34+
.flags = PyABIInfo_INTERNAL, \
35+
.build_version = PY_VERSION_HEX, \
36+
.abi_version = PY_VERSION_HEX }} \
37+
///////////////////////////////////////////////////////
38+
#endif

Include/internal/pycore_unicodeobject.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ extern int _PyUnicode_FormatAdvancedWriter(
8282
Py_ssize_t start,
8383
Py_ssize_t end);
8484

85+
/* PyUnicodeWriter_Format, with va_list instead of `...` */
86+
extern int _PyUnicodeWriter_FormatV(
87+
PyUnicodeWriter *writer,
88+
const char *format,
89+
va_list vargs);
90+
8591
/* --- UTF-7 Codecs ------------------------------------------------------- */
8692

8793
extern PyObject* _PyUnicode_EncodeUTF7(

Include/modsupport.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,72 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def,
134134

135135
#endif /* New in 3.5 */
136136

137+
/* ABI info & checking (new in 3.15) */
138+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030f0000
139+
typedef struct PyABIInfo {
140+
uint8_t abiinfo_major_version;
141+
uint8_t abiinfo_minor_version;
142+
uint16_t flags;
143+
uint32_t build_version;
144+
uint32_t abi_version;
145+
} PyABIInfo;
146+
#define PyABIInfo_STABLE 0x0001
147+
#define PyABIInfo_GIL 0x0002
148+
#define PyABIInfo_FREETHREADED 0x0004
149+
#define PyABIInfo_INTERNAL 0x0008
150+
151+
#define PyABIInfo_FREETHREADING_AGNOSTIC (PyABIInfo_GIL|PyABIInfo_FREETHREADED)
152+
153+
PyAPI_FUNC(int) PyABIInfo_Check(PyABIInfo *info, const char *module_name);
154+
155+
// Define the defaults
156+
#ifdef Py_LIMITED_API
157+
#define _PyABIInfo_DEFAULT_FLAG_STABLE PyABIInfo_STABLE
158+
#if Py_LIMITED_API == 3
159+
#define PyABIInfo_DEFAULT_ABI_VERSION _Py_PACK_VERSION(3, 2)
160+
#else
161+
#define PyABIInfo_DEFAULT_ABI_VERSION Py_LIMITED_API
162+
#endif
163+
#else
164+
#define _PyABIInfo_DEFAULT_FLAG_STABLE 0
165+
#define PyABIInfo_DEFAULT_ABI_VERSION PY_VERSION_HEX
166+
#endif
167+
#if defined(Py_LIMITED_API) && defined(_Py_OPAQUE_PYOBJECT)
168+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADING_AGNOSTIC
169+
#elif defined(Py_GIL_DISABLED)
170+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADED
171+
#else
172+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_GIL
173+
#endif
174+
#if defined(Py_BUILD_CORE)
175+
#define _PyABIInfo_DEFAULT_FLAG_INTERNAL PyABIInfo_INTERNAL
176+
#else
177+
#define _PyABIInfo_DEFAULT_FLAG_INTERNAL 0
178+
#endif
179+
180+
#define PyABIInfo_DEFAULT_FLAGS ( \
181+
_PyABIInfo_DEFAULT_FLAG_STABLE \
182+
| _PyABIInfo_DEFAULT_FLAG_FT \
183+
| _PyABIInfo_DEFAULT_FLAG_INTERNAL \
184+
) \
185+
/////////////////////////////////////////////////////////
186+
187+
#define _PyABIInfo_DEFAULT() { \
188+
1, 0, \
189+
PyABIInfo_DEFAULT_FLAGS, \
190+
PY_VERSION_HEX, \
191+
PyABIInfo_DEFAULT_ABI_VERSION } \
192+
/////////////////////////////////////////////////////////
193+
194+
#define PyABIInfo_VAR(NAME) \
195+
static PyABIInfo NAME = _PyABIInfo_DEFAULT;
196+
197+
#undef _PyABIInfo_DEFAULT_STABLE
198+
#undef _PyABIInfo_DEFAULT_FT
199+
#undef _PyABIInfo_DEFAULT_INTERNAL
200+
201+
#endif /* ABI info (new in 3.15) */
202+
137203
#ifndef Py_LIMITED_API
138204
# define Py_CPYTHON_MODSUPPORT_H
139205
# include "cpython/modsupport.h"

Include/moduleobject.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ struct PyModuleDef_Slot {
8181
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
8282
# define Py_mod_gil 4
8383
#endif
84+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
85+
# define Py_mod_abi 5
86+
#endif
8487

8588

8689
#ifndef Py_LIMITED_API
87-
#define _Py_mod_LAST_SLOT 4
90+
#define _Py_mod_LAST_SLOT 5
8891
#endif
8992

9093
#endif /* New in 3.5 */

0 commit comments

Comments
 (0)