Skip to content

gh-137210: Add a struct, slot & function for checking an extension's ABI #137212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
Add docs
  • Loading branch information
encukou committed Jul 29, 2025
commit c5740af9a3e16bc75d0ff403777cceaae50cffcc
22 changes: 22 additions & 0 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,28 @@ The available slot types are:

.. versionadded:: 3.13

.. c:macro:: Py_mod_abi

A pointer to a :c:struct:`PyABIInfo` structure that describes the ABI that
the extension is using.

When the module is loaded, the :c:struct:`!PyABIInfo` in this slot is checked
using :c:func:`PyABIInfo_Check`.

A suitable :c:struct:`!PyABIInfo` variable can be defined using the
:c:macro:`PyABIInfo_VAR` macro, as in:

.. code-block:: c

PyABIInfo_VAR(abi_info);

static PyModuleDef_Slot mymodule_slots[] = {
{Py_mod_abi, &abi_info},
...
};

.. versionadded:: 3.15


.. _moduledef-dynamic:

Expand Down
156 changes: 153 additions & 3 deletions Doc/c-api/stable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

.. _stable:

***************
C API Stability
***************
***********************
C API and ABI Stability
***********************

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


ABI Checking
============

.. versionadded:: next

Python includes a rudimentary check for ABI compatibility.

This check is not comprehensive.
It only guards against common cases of incompatible modules being
installed for the wrong interpreter.
It also does not take :ref:`platform incompatibilities <stable-abi-platform>`
into account.
It can only be done after an extension is successfully loaded.

Despite these limitations, it is recommended that extension modules use this
mechanism, so that detectable incompatibilities raise exceptions rather than
crash.

Most modules can use this check via the :c:data:`Py_mod_abi`
slot and the :c:macro:`PyABIInfo_VAR` macro.
The full API is described below for advanced use cases.

.. c:function:: int PyABIInfo_Check(PyABIInfo *info, const char *module_name)

Verify that the given *info* is compatible with the currently running
interpreter.

Return 0 on success. On failure, raise an exception and return -1.

If the ABI is incompatible, the raised exception will be :py:exc:`ImportError`.

The *module_name* argument can be ``NULL``, or point to a NUL-terminated
UTF-8-encoded string used for error messages.

Note that if *info* describes the ABI taht the current code uses (as defined
by :c:macro:`PyABIInfo_VAR`, for example), using any other Python C API
may lead to crashes.
In particular, it is not safe to examine the raised exception.

.. versionadded:: next

.. c:macro:: PyABIInfo_VAR(NAME)

Define a static :c:struct:`PyABIInfo` variable with the given *NAME* that
describes the ABI that the current code will use.
This macro expands to:

.. code-block:: c

static PyABIInfo NAME = {
1, 0,
PyABIInfo_DEFAULT_FLAGS,
PY_VERSION_HEX,
PyABIInfo_DEFAULT_ABI_VERSION
}

.. versionadded:: next

.. c:type:: PyABIInfo

.. c:member:: uint8_t PyABIInfo.abiinfo_major_version

The major version of :c:struct:`PyABIInfo`. Can be set to:

* ``0`` to skip all checking, or
* ``1`` to specify this version of :c:struct:`!PyABIInfo`.

.. c:member:: uint8_t PyABIInfo.abiinfo_minor_version

The major version of :c:struct:`PyABIInfo`.
Must be set to ``0``; larger values are reserved for backwards-compatible
future versions of :c:struct:`!PyABIInfo`.

.. c:member:: uint16_t PyABIInfo.flags

.. c:namespace:: NULL

This field is usually set to the following macro:

.. c:macro:: PyABIInfo_DEFAULT_FLAGS

Default flags, based on current values of macros such as
:c:macro:`Py_LIMITED_API` and :c:macro:`Py_GIL_DISABLED`.

Alternately, the field can be set to to the following flags, combined
by bitwise OR.
Unused bits must be set to zero.

ABI variant -- one of:

.. c:macro:: PyABIInfo_STABLE

Specifies that the stable ABI is used.

.. c:macro:: PyABIInfo_INTERNAL

Specifies ABI specific to a particular build of CPython.
Internal use only.

Free-threading compatibility -- one of:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still planning to get rid of these options once FT is merged, right? Maybe we ought to use abiflags here (i.e. a string) instead, for future extensibility? This isn't going to be checked anywhere that it needs to be bitflags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you encode being compatible with both GIL and FT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, we can remove them when there are no GIL-only extensions around any more. That would be a lot of time after FT is the only option.


.. c:macro:: PyABIInfo_FREETHREADED

Specifies ABI compatible with free-threading builds of CPython.
(That is, ones compiled with :option:`--disable-gil`; with ``t``
in :py:data:`sys.abiflags`)

.. c:macro:: PyABIInfo_GIL

Specifies ABI compatible with non-free-threading builds of CPython
(ones compiled *without* :option:`--disable-gil`).

.. c:macro:: PyABIInfo_FREETHREADING_AGNOSTIC

Specifies ABI compatible with both free-threading and non-free-threading
builds.

.. c:member:: uint32_t PyABIInfo.build_version

The version of the Python headers used to build the code, in the format
used by :c:macro:`PY_VERSION_HEX`.

This can be set to ``0`` to skip any checks related to this field.
This option is meant mainly for projects that do not use the CPython
headers directly, and do not emulate a specific version of them.

.. c:member:: uint32_t PyABIInfo.abi_version

The ABI version.

For the Stable ABI, this field should be the value of
:c:macro:`Py_LIMITED_API`
(except if :c:macro:`Py_LIMITED_API` is ``3``; use
:c:expr:`Py_PACK_VERSION(3, 2)` in that case).
Comment on lines +329 to +330
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems easier for us to just check this on our side than to expect users to get it right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And isn't the legacy value 1 rather than 3?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, users should use PyABIInfo_DEFAULT_ABI_VERSION or PyABIInfo_VAR -- those are our side too.
I prefer the data to be simpler, but if you want, we can keep the exception in.

The legacy value is 3. (Not that it matters that much; it's always compared to a Python version.)


Otherwise, it should be set to :c:macro:`PY_VERSION_HEX`.

It can also be set to ``0`` to skip any checks related to this field.

.. c:namespace:: NULL

.. c:macro:: PyABIInfo_DEFAULT_ABI_VERSION

The value that should be used for this field, based on current
values of macros such as :c:macro:`Py_LIMITED_API`,
:c:macro:`PY_VERSION_HEX` and :c:macro:`Py_GIL_DISABLED`.

.. versionadded:: next


.. _limited-api-list:

Contents of Limited API
Expand Down