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
10 changes: 10 additions & 0 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ remove it.
Usually, there is only one variable of this type for each extension module
defined this way.

In the :ref:`Stable ABI <stable-abi>` for free-threaded builds (``abi3t``),
this struct is opaque, and unusable in practice.
Copy link
Copy Markdown
Contributor

@ngoldbaum ngoldbaum Apr 3, 2026

Choose a reason for hiding this comment

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

This is all covered above, but for people who land here with a direct link to this section, you could add a cross-reference suggesting the PyModExport API instead?

The struct, including all members, is part of Stable ABI for
non-free-threaded builds (``abi3``).

.. c:member:: PyModuleDef_Base m_base

Always initialize this member to :c:macro:`PyModuleDef_HEAD_INIT`:
Expand All @@ -695,6 +700,11 @@ remove it.

The type of :c:member:`!PyModuleDef.m_base`.

In the :ref:`Stable ABI <stable-abi>` for Free-Threaded Builds
(``abi3t``), this struct is opaque, and unusable in practice.
The struct is part of Stable ABI for
non-free-threaded builds (``abi3``).

.. c:macro:: PyModuleDef_HEAD_INIT

The required initial value for :c:member:`!PyModuleDef.m_base`.
Expand Down
264 changes: 167 additions & 97 deletions Doc/c-api/stable.rst

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions Doc/c-api/structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ under :ref:`reference counting <countingrefs>`.
The members must not be accessed directly; instead use macros such as
:c:macro:`Py_REFCNT` and :c:macro:`Py_TYPE`.

In the :ref:`Stable ABI <stable-abi>` for Free-Threaded Builds (``abi3t``),
this struct is opaque; its size and layout may change between
Python versions.
In Stable ABI for non-free-threaded builds (``abi3``), the
:c:member:`!ob_refcnt` and :c:member:`!ob_type` fields are available,
but using them directly is discouraged.

.. c:member:: Py_ssize_t ob_refcnt

The object's reference count, as returned by :c:macro:`Py_REFCNT`.
Expand Down Expand Up @@ -72,6 +79,19 @@ under :ref:`reference counting <countingrefs>`.
instead use macros such as :c:macro:`Py_SIZE`, :c:macro:`Py_REFCNT` and
:c:macro:`Py_TYPE`.

In the :ref:`Stable ABI <stable-abi>` for Free-Threaded Builds (``abi3t``),
this struct is opaque; its size and layout may change between
Python versions.
In Stable ABI for non-free-threaded builds (``abi3``), the
:c:member:`!ob_base` and :c:member:`!ob_size` fields are available,
but using them directly is discouraged.

.. c:member:: PyObject ob_base

Common object header.
Typically, this field is not accessed directly; instead
:c:type:`!PyVarObject` can be cast to :c:type:`PyObject`.

.. c:member:: Py_ssize_t ob_size

A size field, whose contents should be considered an object's internal
Expand Down
4 changes: 2 additions & 2 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 32 additions & 10 deletions Doc/tools/extensions/c_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,17 @@ def _stable_abi_annotation(
reftype="ref",
refexplicit="False",
)
struct_abi_kind = record.struct_abi_kind
if struct_abi_kind in {"opaque", "members"}:
ref_node += nodes.Text(sphinx_gettext("Limited API"))
else:
ref_node += nodes.Text(sphinx_gettext("Stable ABI"))
ref_node += nodes.Text(sphinx_gettext("Stable ABI"))
emph_node += ref_node
struct_abi_kind = record.struct_abi_kind
if struct_abi_kind == "opaque":
emph_node += nodes.Text(" " + sphinx_gettext("(as an opaque struct)"))
elif struct_abi_kind == "full-abi":
emph_node += nodes.Text(
" " + sphinx_gettext("(including all members)")
)
elif struct_abi_kind in {"members", "abi3t-opaque"}:
emph_node += nodes.Text(" " + sphinx_gettext("(see below)"))
if record.ifdef_note:
emph_node += nodes.Text(f" {record.ifdef_note}")
if stable_added == "3.2":
Expand All @@ -271,11 +270,7 @@ def _stable_abi_annotation(
" " + sphinx_gettext("since version %s") % stable_added
)
emph_node += nodes.Text(".")
if struct_abi_kind == "members":
msg = " " + sphinx_gettext(
"(Only some members are part of the stable ABI.)"
)
emph_node += nodes.Text(msg)

return emph_node


Expand Down Expand Up @@ -378,6 +373,32 @@ def run(self) -> list[nodes.Node]:
return [node]


class VersionHexCheatsheet(SphinxDirective):
"""Show results of Py_PACK_VERSION(3, x) for a few relevant Python versions

This is useful for defining version before Python.h is included.
It should auto-update with the version being documented, so it must be an
extension.
"""
has_content = False
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True

def run(self) -> list[nodes.Node]:
content = [
".. code-block:: c",
"",
]
current_minor = int(self.config.version.removeprefix('3.'))
for minor in range(current_minor - 5, current_minor + 1):
value = (3 << 24) | (minor << 16)
content.append(f' {value:#x} /* Py_PACK_VERSION(3.{minor}) */')
node = nodes.paragraph()
self.state.nested_parse(StringList(content), 0, node)
return [node]


class CorrespondingTypeSlot(SphinxDirective):
"""Type slot annotations

Expand Down Expand Up @@ -443,6 +464,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value("stable_abi_file", "", "env", types={str})
app.add_config_value("threadsafety_file", "", "env", types={str})
app.add_directive("limited-api-list", LimitedAPIList)
app.add_directive("version-hex-cheatsheet", VersionHexCheatsheet)
app.add_directive("corresponding-type-slot", CorrespondingTypeSlot)
app.connect("builder-inited", init_annotations)
app.connect("doctree-read", add_annotations)
Expand Down
36 changes: 36 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Summary -- Release highlights
<whatsnew315-typeform>`
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
<whatsnew315-pybyteswriter>`
* :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
* :ref:`The JIT compiler has been significantly upgraded <whatsnew315-jit>`
* :ref:`Improved error messages <whatsnew315-improved-error-messages>`
* :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter
Expand Down Expand Up @@ -381,6 +382,41 @@ agen() for x in a)``.

(Contributed by Adam Hartz in :gh:`143055`.)

.. _whatsnew315-abi3t:

:pep:`803`: ``abi3t`` -- Stable ABI for Free-Threaded Builds
------------------------------------------------------------

C extensions that target the :ref:`Stable ABI <stable-abi>` can now be
compiled for the new *Stable ABI for Free-Threaded Builds* (also known
as ``abi3t``), which makes them compatible with
:term:`free-threaded <free-threaded build>` builds of CPython.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
:term:`free-threaded <free-threaded build>` builds of CPython.
:term:`free-threaded builds <free-threaded build>` of CPython.

This usually requires some non-trivial changes to the source code;
specifically:

- Switching to API introduced in :pep:`697` (Python 3.12), such as
negative :c:member:`~PyType_Spec.basicsize` and
:c:func:`PyObject_GetTypeData`, rather than making :c:type:`PyObject`
part of the instance struct; and
- Switching from a ``PyInit_`` function to a new export hook,
:c:func:`PyModExport_* <PyModExport_modulename>`, introduced for this
purpose in :pep:`793`.

Note that Stable ABI does not offer all the functionality that CPython
has to offer.
Extensions that cannot switch to ``abi3t`` should continue to build for
the existing Stable ABI (``abi3``) and the version-specific ABI for
free-threading (``cp315t``) separately.

Stable ABI for Free-Threaded Builds should typically
be selected in a build tool (such as Setuptools, meson-python, Cython,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe split this between build backends and bindings generators? Both probably need configuration.

It's a little confusing to group Cython in with meson-python.

Scikit-build-core, Maturin, and similar).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't really need "and similar" after "such as".

Suggested change
Scikit-build-core, Maturin, and similar).
scikit-build-core, Maturin).

At the time of writing this entry, these tools did **not** support ``abi3t``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
At the time of writing this entry, these tools did **not** support ``abi3t``.
At the time of writing, these tools did **not** support ``abi3t``.

If this is the case for your tool, compile for ``cp315t`` separately.
If not using a build tool -- or when writing such a tool -- you can select
``abi3t`` by setting the macro :c:macro:`!Py_TARGET_ABI3T` as discussed
in :ref:`abi3-compiling`.


.. _whatsnew315-improved-error-messages:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement :pep:`803` -- ``abi3t``: Stable ABI for Free-Threaded Builds.
19 changes: 14 additions & 5 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file lists the contents of the Limited API and Stable ABI.
# This file lists the contents of Limited API and Stable ABI.
# Please append new items at the end.

# The syntax of this file is not fixed.
Expand Down Expand Up @@ -46,15 +46,24 @@
# - 'opaque': No members are part of the ABI, nor is the size. The Limited
# API only handles these via pointers. The C definition should be
# incomplete (opaque).
# - 'members': Only specific members are part of the stable ABI.
# The struct's size may change, so it can't be used in arrays.
# - 'abi3t-opaque': 'full-abi' in abi3; 'opaque' in abi3t.
# For docs, the generated annotation refers to details that need to
# be added to the ReST file manually.
# - 'members':
# - 'opaque' in abi3t.
# - In abi3, only specific members are part of the stable ABI.
# The struct's size may change, so it can't be used in arrays.
# Do not add new structs of this kind without an extremely good reason.
# For docs, the generated annotation refers to details that need to
# be added to the ReST file manually.
# - members: For `struct` with struct_abi_kind = 'members', a list of the
# exposed members.
# - doc: for `feature_macro`, the blurb added in documentation
# - windows: for `feature_macro`, this macro is defined on Windows.
# (This info is used to generate the DLL manifest and needs to be available
# on all platforms.)
# - abi3t_opaque: In abi3t, this struct is opaque (as if `struct_abi_kind`
# was 'opaque' and `members` was missing).

# Removing items from this file is generally not allowed, and additions should
# be considered with that in mind. See the devguide for exact rules:
Expand Down Expand Up @@ -107,10 +116,10 @@
struct_abi_kind = 'full-abi'
[struct.PyModuleDef_Base]
added = '3.2'
struct_abi_kind = 'full-abi'
struct_abi_kind = 'abi3t-opaque'
[struct.PyModuleDef]
added = '3.2'
struct_abi_kind = 'full-abi'
struct_abi_kind = 'abi3t-opaque'
[struct.PyStructSequence_Field]
added = '3.2'
struct_abi_kind = 'full-abi'
Expand Down
2 changes: 0 additions & 2 deletions Tools/check-c-api-docs/ignored_c_api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ Py_HasFileSystemDefaultEncoding
Py_UTF8Mode
# pyhash.h
Py_HASH_EXTERNAL
# modsupport.h
PyABIInfo_FREETHREADING_AGNOSTIC
# object.h
Py_INVALID_SIZE
# pyexpat.h
Expand Down
Loading