diff --git a/peps/pep-0831.rst b/peps/pep-0831.rst index 70c401a563c..a6dd012099b 100644 --- a/peps/pep-0831.rst +++ b/peps/pep-0831.rst @@ -9,7 +9,8 @@ Status: Final Type: Standards Track Created: 14-Mar-2026 Python-Version: 3.15 -Post-History: `13-Apr-2026 `__ +Post-History: `13-Apr-2026 `__, + `04-Aug-2026 `__ Resolution: `30-Apr-2026 `__ .. canonical-doc:: :external+py3.15:option:`--without-frame-pointers` @@ -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 @@ -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 ------------------------------------------------------ @@ -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: @@ -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 @@ -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 +---------------------- + +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 --------------------- @@ -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 @@ -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 --------------------------------------- @@ -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 @@ -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 --------------------- @@ -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