Skip to content

gh-149800: Generate the perf trampoline .eh_frame from the compiled assembly - #157246

Open
stratakis wants to merge 5 commits into
python:mainfrom
stratakis:trampoline_ehframe
Open

gh-149800: Generate the perf trampoline .eh_frame from the compiled assembly#157246
stratakis wants to merge 5 commits into
python:mainfrom
stratakis:trampoline_ehframe

Conversation

@stratakis

@stratakis stratakis commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

The trampoline's unwind information has been a hand-maintained DWARF block per architecture in Python/jit_unwind.c.

Now all of this can be changed :)

The perf trampoline's unwind information is now produced by the assembler from .cfi directives in the trampoline assembly and extracted into a header at build time, so no architecture needs hand-written DWARF anymore.

Building the perf trampoline now needs a host Python (PYTHON_FOR_REGEN, 3.7 or newer. Wanted 3.9 for the stock Mac interpreter but 3.7 was compatible so why not). Without one, configure disables the trampoline.

Tested on Linux x86_64 (gcc, clang, CET, JIT, tail-call interpreter, free-threaded, LTO, PGO, BOLT, shared, debug, no frame pointers, out-of-tree)

Linux aarch64 (default, PAC, shared, debug, no frame pointers)

macOS arm64 (native, framework, universal2 on both slices), end to end with perf (fp and DWARF call graphs) and samply.

Plus the test suite ofc which was passing for me (let's see what the CI says though).

This can also be backported into 3.15 together with #149894 and #150364 if needed.

Fixes: #149800

THe PR and testing has been assisted by various frontier models, mainly by Fable 5.1 but also each each iteration reviewed in addition by the gpt 6 astra model.

Making it a draft for now, I believe it's ready but I'd also like to test an rpm build first, plus adding another architecture such as s390x or ppc64le to verify things work as intended.

Let the assembler emit an .eh_frame for the trampoline, matching the
frame layout jit_unwind.c describes.
…iled assembly

Extract the .eh_frame the assembler emits for the trampoline object at
build time into trampoline_ehframe.h and patch only the FDE address
fields at runtime. Building with the perf trampoline now needs a host
Python, and configure disables the trampoline with a warning when none
is usable.
Check the FDEs in jitdump files against their code load records, cover
the generator's parsers, and validate the header structure from C.
@python-cla-bot

python-cla-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34482344 | 📁 Comparing 2d6fb15 against main (69a6612)

  🔍 Preview build  

3 files changed
± howto/perf_profiling.html
± using/configure.html
± whatsnew/changelog.html

@stratakis
stratakis marked this pull request as ready for review September 10, 2026 22:48
@stratakis

Copy link
Copy Markdown
Contributor Author

Undrafting, this should be ready. The failed CI jobs must be flakiness as they passed on the previous commit, the last one just skips a specific test on BOLT builds.

Did some extra verification here:

A Fedora Rawhide RPM build of python3.15 with this on top of the split and the macOS fix (so basically a 3.15 backport), which contains the multitude of Fedora compiler flags.

Tested on aarch64 and x86_64 the main, debug, free-threading and free-threading-debug interpreters . On all eight the trampoline is enabled, the jitdumps written by -X perf_jit have correctly patched FDEs, test_perf_profiler and test_perfmaps pass, and perf unwinds through every trampoline in both fp and DWARF mode.

Also I've tested with adding ppc64le support and deployed it on the ppc64le Rawhide buildbot (source build not RPM). Granted the non-fp path is not exercised on ppc64le but nontheless it works if I add this patch on top:

ppc64le patch

diff --git a/Makefile.pre.in b/Makefile.pre.in
index f9f9a5db5d4..07f494e38c5 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -3133,6 +3133,9 @@ Python/asm_trampoline_aarch64.o: $(srcdir)/Python/asm_trampoline_aarch64.S
 Python/asm_trampoline_riscv64.o: $(srcdir)/Python/asm_trampoline_riscv64.S
 	$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
 
+Python/asm_trampoline_ppc64le.o: $(srcdir)/Python/asm_trampoline_ppc64le.S
+	$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
+
 # On macOS universal2 builds, $(PY_CORE_CFLAGS) contains "-arch arm64 -arch x86_64",
 # which would produce fat .o files containing both architectures for each .S input.
 # lipo -create then refuses to combine them because they share architectures.
diff --git a/Python/asm_trampoline_ppc64le.S b/Python/asm_trampoline_ppc64le.S
new file mode 100644
index 00000000000..c85e6b59b31
--- /dev/null
+++ b/Python/asm_trampoline_ppc64le.S
@@ -0,0 +1,27 @@
+    .text
+#if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
+    .abiversion 2
+    .globl  _Py_trampoline_func_start
+_Py_trampoline_func_start:
+    .cfi_startproc
+    mflr    0
+    std     0, 16(1)
+    stdu    1, -32(1)
+    .cfi_def_cfa_offset 32
+    .cfi_offset 65, 16
+    mr      12, 6
+    mtctr   6
+    std     2, 24(1)
+    bctrl
+    ld      2, 24(1)
+    addi    1, 1, 32
+    .cfi_def_cfa_offset 0
+    ld      0, 16(1)
+    mtlr    0
+    .cfi_restore 65
+    blr
+    .cfi_endproc
+    .globl  _Py_trampoline_func_end
+_Py_trampoline_func_end:
+#endif
+    .section .note.GNU-stack,"",@progbits
diff --git a/Python/perf_jit_trampoline.c b/Python/perf_jit_trampoline.c
index 21beead742a..1709fda8681 100644
--- a/Python/perf_jit_trampoline.c
+++ b/Python/perf_jit_trampoline.c
@@ -124,6 +124,8 @@
 #    define EM_AARCH64  183
 #  elif defined(__riscv)
 #    define EM_RISCV    243
+#  elif defined(__powerpc64__)
+#    define EM_PPC64    21
 #  endif
 #endif
 
@@ -158,6 +160,8 @@ static uint64_t GetElfMachineArchitecture(void) {
     return EM_ARM;
 #elif defined(__riscv)
     return EM_RISCV;
+#elif defined(__powerpc64__)
+    return EM_PPC64;
 #else
     Py_UNREACHABLE();  // Unsupported architecture - should never reach here
     return 0;
diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c
index fa1cb785b41..620196ab81b 100644
--- a/Python/perf_trampoline.c
+++ b/Python/perf_trampoline.c
@@ -147,7 +147,8 @@ any DWARF information available for them).
 #include <sys/time.h>           // gettimeofday()
 
 
-#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
+#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) \
+    || defined(__powerpc64__)
 #define PY_HAVE_INVALIDATE_ICACHE
 
 #if defined(__clang__) || defined(__GNUC__)
diff --git a/Tools/jit/_trampoline_ehframe.py b/Tools/jit/_trampoline_ehframe.py
index a87488bde25..135dae1146f 100644
--- a/Tools/jit/_trampoline_ehframe.py
+++ b/Tools/jit/_trampoline_ehframe.py
@@ -24,6 +24,7 @@
 _SHT_NOBITS = 8
 _EM_X86_64 = 62
 _EM_AARCH64 = 183
+_EM_PPC64 = 21
 
 # Mach-O constants, see llvm/BinaryFormat/MachO.h.
 _MH_MAGIC_64 = 0xFEEDFACF
@@ -62,6 +63,7 @@
 _ELF_ARCH_MACROS = {
     _EM_X86_64: "__x86_64__",
     _EM_AARCH64: "__aarch64__",
+    _EM_PPC64: "__powerpc64__",
 }
 _MACHO_ARCH_MACROS = {
     _CPU_TYPE_X86_64: "__x86_64__",
diff --git a/configure b/configure
index 1ebf1f795b0..109f2e962c0 100755
--- a/configure
+++ b/configure
@@ -14636,6 +14636,9 @@ case $PLATFORM_TRIPLET in #(
   aarch64-linux-musl) :
     perf_trampoline=yes
                          PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o ;; #(
+  powerpc64le-linux-gnu) :
+    perf_trampoline=yes
+                            PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_ppc64le.o ;; #(
   darwin) :
     case $MACOSX_DEPLOYMENT_TARGET in #(
   10.[0-9]|10.1[0-1]) :
diff --git a/configure.ac b/configure.ac
index b44c7d931d3..d21a05565d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3922,6 +3922,8 @@ AS_CASE([$PLATFORM_TRIPLET],
                          PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o],
   [aarch64-linux-musl], [perf_trampoline=yes
                          PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o],
+  [powerpc64le-linux-gnu], [perf_trampoline=yes
+                            PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_ppc64le.o],
   [darwin], [AS_CASE([$MACOSX_DEPLOYMENT_TARGET],
                 [[10.[0-9]|10.1[0-1]]], [perf_trampoline=no],
                 [perf_trampoline=yes

builds, tests pass, perf back-chain and DWARF mode both unwind through the trampolines.

Did also a s390x compilation (with the relevant code added similarly as the ppc64le one) on RHEL10 (no access to Fedora s390x for now), builds with and without -mbackchain, tests pass on both, DWARF mode unwinds through the trampolines on both. Frame pointer mode doesn't work due to a kernel bug there but overall I think this covers a wider area of testing that functionality.

So if that approach seems sound, it should be fairly easy to add s390x, ppc64le, RISC-V support.

@stratakis

Copy link
Copy Markdown
Contributor Author

cc @diegorusso @pablogsal

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor perf trampoline proposal: split the assembly per architecture, auto-generate DWARF unwind data

1 participant