Skip to content
Draft
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
158 changes: 99 additions & 59 deletions peps/pep-0831.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Status: Final
Type: Standards Track
Created: 14-Mar-2026
Python-Version: 3.15
Post-History: `13-Apr-2026 <https://discuss.python.org/t/106958>`__
Post-History: `13-Apr-2026 <https://discuss.python.org/t/106958>`__,
`04-Aug-2026 <https://discuss.python.org/t/106958/7>`__
Resolution: `30-Apr-2026 <https://discuss.python.org/t/106958/6>`__

.. canonical-doc:: :external+py3.15:option:`--without-frame-pointers`
Expand All @@ -22,11 +23,12 @@ This PEP proposes two things:
1. **Build CPython with frame pointers by default on platforms that support
them.** The default build configuration is changed to compile the
interpreter with ``-fno-omit-frame-pointer`` and
``-mno-omit-leaf-frame-pointer``. The flags are added to ``CFLAGS``, so they
apply to the interpreter itself and propagate to C extension modules built
against this Python via ``sysconfig``. An opt-out ``configure`` flag
(``--without-frame-pointers``) is provided for deployments that require
maximum raw throughput.
``-mno-omit-leaf-frame-pointer``. The flags are added to ``BASECFLAGS``, so
they apply to the interpreter itself and are recorded in the ``CFLAGS``
exposed by ``sysconfig``. Build backends that consume that value, most
notably Setuptools, propagate the flags to C extension builds. An opt-out
``configure`` flag (``--without-frame-pointers``) is provided for
deployments that require maximum raw throughput.

2. **Strongly recommend that all build systems in the Python ecosystem build
with frame pointers by default.** This PEP recommends that *every* compiled
Expand Down Expand Up @@ -394,14 +396,14 @@ through the interpreter **and** through every extension in the call stack. A
gap at any point, whether in ``_PyEval_EvalFrameDefault`` or in a C extension,
breaks the chain for the entire process.

The need for a continuous chain is precisely why the flags must propagate to
extension builds. If only the interpreter has frame pointers but extensions do
not, the chain is still broken at every C extension boundary. By adding the
flags to ``CFLAGS`` as reported by ``sysconfig``, extension builds that consume
CPython's compiler flags (for example via ``pip install``, Setuptools, or
other build backends) will inherit frame pointers by default. Extensions
and libraries with independent build systems still need to enable the same
flags themselves for the frame-pointer chain to remain continuous.
The need for a continuous chain is precisely why extension builds must also
enable the flags. If only the interpreter has frame pointers but extensions do
not, the chain is still broken at every C extension boundary. Adding the flags
to ``CFLAGS`` as reported by ``sysconfig`` makes them available to Setuptools
and other build backends that deliberately consume CPython's compiler flags.
Extensions and libraries using backends with independent compiler
configuration still need to enable the same flags themselves for the
frame-pointer chain to remain continuous.

The JIT Compiler Needs Frame Pointers to Be Debuggable
------------------------------------------------------
Expand Down Expand Up @@ -477,14 +479,20 @@ The following changes are made to ``configure.ac``::
[BASECFLAGS="$BASECFLAGS -mno-omit-leaf-frame-pointer"])

The flags are prepended to ``BASECFLAGS`` (rather than ``CFLAGS_NODIST``) so
they propagate to third-party builds via ``sysconfig``. This ensures:
they are exposed to third-party builds via ``sysconfig``. This ensures:

1. The flags apply to all ``*.c`` files compiled as part of the interpreter:
the ``python`` binary, ``libpython``, and built-in extension modules under
``Modules/``.
2. The flags **are** written into the ``sysconfig`` data, so that third-party C
extensions built against this Python (via ``pip``, Setuptools, or direct
``sysconfig`` queries) inherit frame pointers by default.
2. The flags **are** written into the ``sysconfig`` data. Setuptools and other
build backends that deliberately consume its ``CFLAGS`` value therefore
apply them to C extension builds by default.

``sysconfig`` describes how CPython itself was built; its ``CFLAGS`` value is
not a generic build-backend interface and may also contain optimisation,
warning, architecture, and other flags that are inappropriate for an extension
build. This PEP therefore does not require build backends to copy that value.
See `Build Backend Guidance`_ for the generic case.

Several architectures need adjustments to produce a walkable frame-pointer
chain:
Expand All @@ -500,9 +508,10 @@ chain:

This is an intentional design choice. For profiling data to be useful, the
frame-pointer chain must be continuous through the entire call stack. A gap at
any C extension boundary is as harmful as a gap in the interpreter itself. By
propagating the flags, CPython establishes frame pointers as the ecosystem-wide
default for the Python stack.
any C extension boundary is as harmful as a gap in the interpreter itself.
Exposing the flags to compatible build backends, together with the ecosystem
recommendation in this PEP, starts establishing frame pointers as the default
for the whole Python stack.

``-mno-omit-leaf-frame-pointer`` preserves the frame pointer even in leaf
functions. Without it, the compiler may drop the frame pointer in any function
Expand All @@ -524,35 +533,59 @@ level and wish to avoid double-specification, analogous to Fedora's per-package
``%undefine _include_frame_pointers`` macro.

Extension authors who wish to override the default for a specific module can
pass ``-fomit-frame-pointer`` in their ``extra_compile_args`` or via
environment variables; the last flag on the command line wins under GCC and
Clang.
pass ``-fomit-frame-pointer`` through their backend's compiler options (for
example, Setuptools' ``extra_compile_args``) or via environment variables; the
last flag on the command line wins under GCC and Clang.

Ecosystem Impact
----------------

Because the flags are in ``CFLAGS``, they propagate automatically to consumers
that build against CPython's reported compiler flags, such as C extensions
built via ``pip``, Setuptools, or direct ``sysconfig`` queries. Those
consumers need take no additional action to benefit from this change.

Not all compiled code in the Python ecosystem inherits CPython's ``CFLAGS``.
Rust extensions built with ``pyo3`` or ``maturin``, C++ libraries with their
own build systems, and embedding applications that compile CPython from source
each manage their own compiler flags. This PEP recommends that all such
projects also enable ``-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer``
in their builds. A frame-pointer chain is only as strong as its weakest
link: a single library in the call stack without frame pointers breaks the
chain for the entire process, regardless of whether CPython and every other
library has them. The goal is that every native component in a Python process
participates in the frame-pointer chain, so that ``perf record`` and eBPF
profilers produce complete, useful flame graphs out of the box.
The ``CFLAGS`` change automatically affects only consumers of CPython's
reported compiler flags. Setuptools does this for C extension builds, as do
backends that emulate its behaviour or query ``sysconfig`` directly. ``pip``
is a build frontend and does not itself determine which compiler flags a build
backend uses. Other backends may intentionally manage their toolchain without
using CPython's ``CFLAGS`` and therefore do not inherit this setting.

Rust extensions built with ``pyo3`` or ``maturin``, C and C++ extensions using
independent build systems, and embedding applications each manage their own
compiler flags. This PEP recommends that all such projects also enable
``-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer`` in their builds. A
frame-pointer chain is only as strong as its weakest link: a single library in
the call stack without frame pointers breaks the chain for the entire process,
regardless of whether CPython and every other library has them. The goal is
that every native component in a Python process participates in the
frame-pointer chain, so that ``perf record`` and eBPF profilers produce
complete, useful flame graphs out of the box.

Extension authors who observe an unacceptable regression in a specific module
can opt out per-extension via ``extra_compile_args`` (see `Extension Build
can opt out through backend-specific compiler options (see `Extension Build
Impact`_). Distributions that already enable frame pointers system-wide
(Fedora, Ubuntu, Arch Linux) need take no action.

Build Backend Guidance
----------------------
Comment on lines +566 to +567

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.

In addition to guidance to build backends, should there be guidance for tools such as cibuildwheel which specify config to build backends?

For example, should they add an option to enable frame pointers in backends, and perhaps make it the default at some point?


Build backends should enable frame pointers through the native compiler or
build-system interface they control. They should not blindly copy the complete
``sysconfig`` ``CFLAGS`` value: it describes the CPython build and can include
unrelated optimisation, warning, link-time optimisation, or architecture
options. It also cannot describe the corresponding C++ options because
``sysconfig`` does not define a ``CXXFLAGS`` counterpart.

:pep:`739`'s ``build-details.json`` is the modern, static mechanism for
communicating build requirements to generic tooling, including when the target
interpreter cannot be executed during cross-compilation. Version 1.0 does not
define a field for compiler options that extensions must or should use.
Standardising such a field requires a future version of that format. Once
available, generic build backends should prefer that dedicated field over
extracting flags from ``sysconfig``.

Consequently, the accepted CPython change exposes the flags to existing
``sysconfig`` consumers and this PEP strongly recommends the same default to
all other build systems; it does not cause every :pep:`517` build backend to
inherit CPython's build-time choice automatically.

Documentation Updates
---------------------

Expand Down Expand Up @@ -766,9 +799,9 @@ Why Not Use ``CFLAGS_NODIST`` Instead of ``BASECFLAGS``
-------------------------------------------------------

CPython's build system provides ``CFLAGS_NODIST`` specifically for flags that
should apply to the interpreter but not propagate to extension module builds
via ``sysconfig``. Using ``CFLAGS_NODIST`` would confine the overhead to the
interpreter itself.
should apply to the interpreter but not appear in the compiler flags exposed
for extension builds via ``sysconfig``. Using ``CFLAGS_NODIST`` would confine
the overhead to the interpreter itself.

This PEP deliberately chooses ``BASECFLAGS`` over ``CFLAGS_NODIST`` because frame
pointers are only useful when the chain is continuous. Unlike debugging aids
Expand All @@ -788,7 +821,9 @@ not exhibit the same call density and sees negligible overhead.
As Gregory Szorc (``python-build-standalone`` creator) noted: "Turning the
corner on the long tail of compiled extensions having frame pointers will take
years. So the sooner we start..." [#pbs992]_ Propagating the flags via
``BASECFLAGS`` is how CPython starts that process.
``BASECFLAGS`` makes them available to existing ``sysconfig`` consumers;
the recommendation to other build systems is how CPython starts the wider
process.

Alternatives to Frame-Pointer Unwinding
---------------------------------------
Expand Down Expand Up @@ -866,11 +901,12 @@ AMD EPYC 9654 (x86-64) 1.8%
Intel Xeon Platinum 8480 (x86-64) 1.5%
===================================== =======================

This overhead applies to both the interpreter and to C extensions that inherit
the flags via ``sysconfig``. Detailed microarchitectural analysis shows the
overhead is purely from additional instructions (frame-pointer prologues in
~6,000 helper functions), with no pathological cache, TLB, or branch-prediction
effects (see `Detailed Performance Analysis of CPython with Frame Pointers`_).
This overhead applies to the interpreter and to C extensions whose build
backend inherits the flags via ``sysconfig``. Detailed microarchitectural
analysis shows the overhead is purely from additional instructions
(frame-pointer prologues in ~6,000 helper functions), with no pathological
cache, TLB, or branch-prediction effects (see
`Detailed Performance Analysis of CPython with Frame Pointers`_).
Typical C extension code does not exhibit the same density of small function
calls as the CPython runtime. Numerically intensive extensions (NumPy, SciPy)
typically spend their hot loops in BLAS/LAPACK or vectorised intrinsics that
Expand All @@ -890,16 +926,16 @@ unacceptable may use ``--without-frame-pointers``.
Extension Build Impact
----------------------

C extensions built against Python 3.15+ will inherit
C extensions built against Python 3.15+ with Setuptools, or another backend
that consumes ``sysconfig`` compiler flags, will inherit
``-fno-omit-frame-pointer`` and ``-mno-omit-leaf-frame-pointer`` in their
default ``CFLAGS`` from ``sysconfig``. This is the same mechanism by which
extensions already inherit ``-O2``, warning flags, and other compilation
defaults.
default ``CFLAGS``. Extensions built with other backends do not necessarily
inherit those flags; see `Build Backend Guidance`_.

Extensions that set their own ``CFLAGS`` or use ``extra_compile_args`` in
``setup.py`` / ``pyproject.toml`` can override this default. The last flag on
the command line wins, so appending ``-fomit-frame-pointer`` is sufficient to
opt out on a per-extension basis.
Extensions that set their own ``CFLAGS`` or backend-specific compiler options
can override this default. With Setuptools, for example, an extension can use
``extra_compile_args``. The last flag on the command line wins, so appending
``-fomit-frame-pointer`` is sufficient to opt out on a per-extension basis.

Build Reproducibility
---------------------
Expand Down Expand Up @@ -953,7 +989,11 @@ they do not remove the need for frame pointers as the default baseline.
Change History
==============

None at this time.
04-Aug-2026
Clarified that ``sysconfig`` flag propagation applies to Setuptools and
other deliberate consumers rather than to build backends generically, and
documented ``build-details.json`` as the appropriate future interface for
dedicated extension compiler requirements.


Footnotes
Expand Down
Loading