Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ peps/pep-0799.rst @pablogsal
peps/pep-0800.rst @JelleZijlstra
peps/pep-0801.rst @warsaw
peps/pep-0802.rst @AA-Turner
peps/pep-0803.rst @encukou
# ...
peps/pep-2026.rst @hugovk
# ...
Expand Down
322 changes: 322 additions & 0 deletions peps/pep-0803.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
PEP: 803
Title: Stable ABI for Free-Threaded Builds
Author: Petr Viktorin <[email protected]>
Discussions-To: Pending
Status: Draft
Type: Standards Track
Requires: 703, 793, 697
Created: 08-Aug-2025
Python-Version: 3.15


Abstract
========

Stable ABI 3.15 will be compatible with both free-threaded and GIL-enabled
builds.
To allow this, the ``PyObject`` internal structure and related APIs
will be removed from the Limited API, requiring migration to new API
for common tasks like defining modules and most classes.


Terminology
===========

This PEP uses “GIL-enabled build” as an antonym to “free-threaded build”,
that is, an interpreter or extension built without ``Py_GIL_DISABLED``.


Motivation
==========

In its `acceptance post <https://discuss.python.org/t/84319/123>`__
for :pep:`779`, the Steering Council stated that it “expects that Stable ABI
for free-threading should be prepared and defined for Python 3.15”.

This PEP proposes the Stable ABI for free-threading.


Background
----------

Python's Stable ABI, as defined in :pep:`384` and :pep:`652`, provides a way to
compile extension modules that can be loaded on multiple versions of the
CPython interpreter.
Several projects use this to limit the number of *wheels* (binary artefacts)
that need to be built and distributed for each release, and/or to make it
possible to test with pre-release versions of Python.

With free-threading builds (:pep:`703`) being on track to eventually become
the default (:pep:`779`), we need a way to make the Stable ABI available
to those builds.

To build against the Stable ABI, the extension must use a *Limited API*,
that is, only a subset of the functions, structures, etc. that CPython
exposes.
The Limited API is versioned, and building against Limited API 3.X
yields an extension that is ABI-compatible with CPython 3.X and *any* later
version (though bugs in CPython sometimes cause incompatibilities in practice).
Also, the Limited API is not “stable”: newer versions may remove API that
were a part of older versions.

This PEP proposes the most significant such removal to date.


Rationale
=========

The design in this PEP makes several assumptions:

One ABI
A single compiled extension module should support both
free-threaded and GIL-enabled builds.

No backwards compatibility
The new limited API will not support CPython 3.14 and below.
Projects that need this support can build separate extensions specifically for
the 3.14 free-threaded interpreter, and for older stable ABI versions.

API changes are OK
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit vague. Perhaps "Extension authors may need to change their code."

Copy link
Member Author

Choose a reason for hiding this comment

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

That's what the next sentence says. Do you think the 4 words don't make sense as a heading?

The new Limited API may require extension authors to make significant
changes to their code.
Projects that cannot do this (yet) can continue using Limited API 3.14,
which will yield extensions compatible with GIL-enabled builds only.

No extra configuration
We do not introduce new “knobs” that influence what API is available
and what the ABI is compatible with.


Specification
=============


Opaque PyObject
---------------

Version 3.15 of the Limited API will:

- make the following structures *opaque* (or in C terminology, *incomplete
types*):

- :c:type:`PyObject`
- :c:type:`PyVarObject`
- :c:type:`!PyModuleDef_Base`
- :c:type:`PyModuleDef`

- no longer include the following macros:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- no longer include the following macros:
- exclude the following macros:

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to keep the current wording if you don't mind.
AFAIK, “exclude” sounds more like “prevent from being included” or “expel” than a passive “not include”. (And a few dictionaries I checked at least put the definitions in this order.)


- :c:macro:`PyObject_HEAD`
- :c:macro:`!_PyObject_EXTRA_INIT`
- :c:macro:`PyObject_HEAD_INIT`
- :c:macro:`PyObject_VAR_HEAD`

- no longer include these function-like macros:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- no longer include these function-like macros:
- exclude these function-like macros:


- :c:func:`Py_SIZE`
- :c:func:`Py_SET_SIZE`
- :c:func:`Py_SET_TYPE`


Implications
^^^^^^^^^^^^

Making the ``PyObject``, ``PyVarObject`` and ``PyModuleDef`` structures
opaque means:

- Their fields may not be accessed.

For example, instead of ``o->ob_type``, extensions must use
``Py_TYPE(o)``.
This usage has been the preferred practice for some time.

- Their size and alignment will not be available.
Expressions such as ``sizeof(PyObject)`` will no longer work.

- They cannot be embedded in other structures.
This mainly affects instance structs of extension-defined types,
which will need to be defined using API added in :pep:`697` -- that is,
using a ``struct`` *without* ``PyObject`` (or other base class struct) at
the beginning, with :c:func:`PyObject_GetTypeData` calls needed to access
the memory.

- Variables of these types cannot be created.
This mainly affects static ``PyModuleDef`` variables needed to define
extension modules.
Extensions will need to switch to API added in :pep:`793`.

The following functions will become unusable in practice (in the new Limited
API), since an extension cannot create valid, statically allocated, input
for them. To ease the transition for extension developers, they will not yet be removed from the Limited API:

- :c:func:`PyModuleDef_Init`
- :c:func:`PyModule_Create`, :c:func:`PyModule_Create2`
- :c:func:`PyModule_FromDefAndSpec`, :c:func:`PyModule_FromDefAndSpec2`


New Export Hook (PEP 793)
-------------------------

Implementation of this PEP requires :pep:`793` (``PyModExport``:
A new entry point for C extension modules) to be
accepted, providing a new “export hook” for defining extension modules.
Using the new hook will become mandatory in Limited API 3.15.


Runtime ABI checks
------------------

Users -- or rather the tools they use for building and installing extensions --
will continue to be responsible for not putting incompatible extensions on
Python's import paths.
Comment on lines +169 to +171
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Users -- or rather the tools they use for building and installing extensions --
will continue to be responsible for not putting incompatible extensions on
Python's import paths.
Users -- or rather the tools they use for building and installing extensions --
will continue to be responsible for preventing incompatible extensions on
Python's import paths.

Copy link
Member Author

Choose a reason for hiding this comment

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

The tools can only be responsible for their own actions; they're not expected to prevent anything else from messing things up.

This decision makes sense since tools typically have much richer metadata than what CPython
can check.

However, CPython will add a line of defense against outdated or misconfigured
tools, or human mistakes, in the form of a new *module slot* containing
basic ABI information.
Comment on lines +175 to +177
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
However, CPython will add a line of defense against outdated or misconfigured
tools, or human mistakes, in the form of a new *module slot* containing
basic ABI information.
However, CPython will help defend against outdated or misconfigured
tools, or human mistakes, in the form of a new *module slot* containing
basic ABI information.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think my wording better suggests that we're adding a distinct mechanism.

This information will be checked when a module is loaded, and incompatible
extensions will be rejected.
Comment on lines +178 to +179
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
This information will be checked when a module is loaded, and incompatible
extensions will be rejected.
Checks will be done when a module is loaded, and incompatible
extensions will be rejected.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to say which information is checked (with “this” referring to the previous sentence).

The specifics are left to the C API working group
(see `issue 72 <https://github.com/capi-workgroup/decisions/issues/72>`__).

This slot will become *mandatory* with the new export hook added in
:pep:`793`.
(The PEP currently says “there are no required slots”; it will be updated.)


Check for older ``abi3``
^^^^^^^^^^^^^^^^^^^^^^^^

Additionally, in free-threaded builds, :c:func:`PyModuleDef_Init` will detect
extensions using the pre-free-threading Stable ABI, emit an informative
message when one is loaded, *and* raise an exception.
(Implementation note: A message will be printed before raising the exception, because extensions
that attempt to handle an exception using incompatible ABI will likely crash
and lose the exception's message.)

This check for older ``abi3`` relies on internal bit patterns and may be removed in future CPython
versions, if the internal object layout needs to change.


Wheel tags
----------

PyPA build tools should not need changes: if they allow the user to set the
limited API version, setting it to 3.15 should define :c:macro:`Py_LIMITED_API`
to ``0x030f0000``.
The resulting wheel should be tagged with the Python-ABI tag ``cp315-abi3``.

Installers and other tools should assume that wheels with a *python tag*
``cp315`` and above and ABI tag ``abi3`` are compatible with free-threaded
builds.


New API
-------

Implementing this PEP will make it possible to build extensions that
can be successfully loaded on free-threaded Python, but not necessarily ones
that are thread-safe without a GIL.

Limited API to allow thread-safety without a GIL -- presumably ``PyMutex``, ``PyCriticalSection``, and
similar -- will be added via the C API working group, or in a follow-up PEP.


Backwards Compatibility
=======================

Limited API 3.15 will not be backwards-compatible with older CPython releases,
due to removed structs and functions.

Extension authors who cannot switch may continue to use Limited API 3.14
and below.
For compatibility with free-threaded builds, they can compile using
version-specific ABI -- for example, compile on CPython 3.15 without defining
``Py_LIMITED_API``.


Security Implications
=====================

None known.


How to Teach This
=================

A porting guide will need to explain how to move to APIs added in
:pep:`697` (Limited C API for Extending Opaque Types)
and :pep:`793` (``PyModExport``).


Reference Implementation
========================

This PEP combines several pieces, implemented individually:

- Opaque ``PyObject`` is available in CPython main branch after defining the
``_Py_OPAQUE_PYOBJECT`` macro.
Implemented in GitHub pull request `#136505 <https://github.com/python/cpython/pull/136505>`__.
- For ``PyModExport``, see :pep:`793`.
- For a version-checking slot, see GitHub pull request `#137212 <https://github.com/python/cpython/pull/137212>`__.
- For a check for older ``abi3``, see GitHub pull request `#137957 <https://github.com/python/cpython/pull/137957>`__.
- For wheel tags, there is no implementation yet.
- A porting guide is not yet written.


Rejected Ideas
==============


Add an alternative stable ABI for free-threading
------------------------------------------------

It would be possible to:

- Keep the current stable ABI (“``abi3``”) unchanged (except additions, as done
in each release). Extensions would need no code changes and builds would be
compatible with old and new GIL-enabled CPython versions.
- Add a new stable ABI (“``abi3t``”) specifically for free-threading.
Extensions would need no code changes and builds would be
compatible with free-threaded CPython (3.14 and above).
- Defining an additional macro (“``Py_OPAQUE_PYOBJECT``”) would make
``PyObject`` opaque as in this PEP. Extensions would need code changes as in
this PEP, and compiled extensions (“``abi3.abi3t``”) would be compatible with
all builds of CPython 3.15+.

This scheme was rejected as too complex.
It would also make the free-threading memory layout of ``PyObject`` part
of the stable ABI, preventing future adjustments.


Shim for compatibility with CPython 3.14
----------------------------------------

The issue that prevents compatibility with Python 3.14 is that with
opaque ``PyObject`` and ``PyModuleDef``, it is not feasible to initialize
an extension module.
The solution, :pep:`793`, is only being added in Python 3.15.

It is possible to work around this using the fact that the 3.14 ABIs (both
free-threading and GIL-enabled) are “frozen”, so it is possible for an
extension to query the running interpreter, and for 3.14, use
a ``struct`` definition corresponding to the detected build's ``PyModuleDef``.

This is too onerous to support and test in CPython's Limited API.
It would also require adding a new wheel tag (e.g. ``abi3t``) that all install
tools would need to recognize. (This PEP's ``cp315-abi3`` is incompatible
with Python 3.14.)


Open Issues
===========

[See discussion for now.]


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.