Skip to content

Commit 13db9a9

Browse files
committed
GH-126910: Derive JIT unwind info from shim CFI
Generate per-target JIT unwind constants from the compiled shim EH frame, emit dispatcher headers for the unwind info, and update the GDB JIT unwind tests.
1 parent 45cb07a commit 13db9a9

7 files changed

Lines changed: 448 additions & 96 deletions

File tree

Lib/test/test_gdb/test_jit.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,9 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
9292
# py::jit:executor frame would mean the unwinder is
9393
# materializing two native frames for a single logical JIT
9494
# region, or failing to unwind out of the region entirely.
95-
# 2. The unwinder must climb back out of the JIT region into
96-
# the eval loop. Some platforms materialize a real
97-
# _PyJIT_Entry frame between the synthetic executor frame
98-
# and _PyEval_*, while others unwind directly from the
99-
# executor into _PyEval_*. Accept both shapes.
95+
# 2. The unwinder must climb directly back out of the JIT region
96+
# into the eval loop. _PyJIT_Entry only exists to establish the
97+
# physical frame; the synthetic executor FDE collapses it away.
10098
# 3. For tests that assert a specific entry PC, the JIT frame
10199
# is also at #0.
102100
frames = self._extract_backtrace_frames(gdb_output)
@@ -132,32 +130,17 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
132130
f"expected an eval frame after the JIT frame\n"
133131
f"backtrace:\n{backtrace}",
134132
)
135-
between_jit_and_eval = frames_after_jit[:first_eval_offset]
136-
jit_entry_frames = [
137-
frame for frame in between_jit_and_eval
138-
if JIT_ENTRY_SYMBOL in frame
139-
]
140-
self.assertLessEqual(
141-
len(jit_entry_frames), 1,
142-
f"expected at most one {JIT_ENTRY_SYMBOL} frame between the "
143-
f"executor and eval frames\nbacktrace:\n{backtrace}",
144-
)
145-
unexpected_between = [
146-
frame for frame in between_jit_and_eval
147-
if JIT_ENTRY_SYMBOL not in frame
148-
]
133+
unexpected_between = frames_after_jit[:first_eval_offset]
149134
self.assertFalse(
150135
unexpected_between,
151-
"expected only an optional _PyJIT_Entry frame between the "
152-
"executor and eval frames\n"
136+
"expected the executor frame to unwind directly into eval\n"
153137
f"backtrace:\n{backtrace}",
154138
)
155139
relevant_end = max(
156140
i
157141
for i, frame in enumerate(frames)
158142
if (
159143
JIT_EXECUTOR_FRAME in frame
160-
or JIT_ENTRY_SYMBOL in frame
161144
or re.search(EVAL_FRAME_RE, frame)
162145
)
163146
)

Makefile.pre.in

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3210,7 +3210,8 @@ Python/emscripten_trampoline_wasm.c: Python/emscripten_trampoline_inner.wasm
32103210
$(PYTHON_FOR_REGEN) $(srcdir)/Platforms/emscripten/prepare_external_wasm.py $< $@ getWasmTrampolineModule
32113211

32123212
JIT_SHIM_BUILD_OBJS= @JIT_SHIM_BUILD_O@
3213-
JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_SHIM_BUILD_OBJS)
3213+
JIT_UNWIND_INFO_H= $(if $(JIT_OBJS),jit_unwind_info.h $(patsubst jit_stencils-%.h,jit_unwind_info-%.h,@JIT_STENCILS_H@))
3214+
JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_UNWIND_INFO_H) $(JIT_SHIM_BUILD_OBJS)
32143215
JIT_TARGETS= $(JIT_BUILD_TARGETS) $(filter-out $(JIT_SHIM_BUILD_OBJS),$(JIT_OBJS))
32153216
JIT_GENERATED_STAMP= .jit-stamp
32163217

@@ -3238,6 +3239,9 @@ jit_shim-universal2-apple-darwin.o: jit_shim-aarch64-apple-darwin.o jit_shim-x86
32383239
Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@
32393240
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
32403241

3242+
Python/jit_unwind.o: $(srcdir)/Python/jit_unwind.c $(JIT_UNWIND_INFO_H)
3243+
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
3244+
32413245
.PHONY: regen-jit
32423246
regen-jit: $(JIT_TARGETS)
32433247

@@ -3363,7 +3367,7 @@ clean-profile: clean-retain-profile clean-bolt
33633367
# gh-141808: The JIT stencils are deliberately kept in clean-profile
33643368
.PHONY: clean-jit-stencils
33653369
clean-jit-stencils:
3366-
-rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_shim*.o
3370+
-rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_unwind_info*.h jit_shim*.o
33673371

33683372
.PHONY: clean
33693373
clean: clean-profile clean-jit-stencils

Python/jit_unwind.c

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
#include "pycore_jit_unwind.h"
1010
#include "pycore_lock.h"
1111

12+
#if defined(_Py_JIT) && defined(__linux__) && defined(__ELF__)
13+
# include "jit_unwind_info.h"
14+
# if !JIT_UNWIND_INFO_SUPPORTED
15+
# error "JIT unwind info was not generated for this target"
16+
# endif
17+
# define PY_HAVE_JIT_GDB_UNWIND 1
18+
#else
19+
# define PY_HAVE_JIT_GDB_UNWIND 0
20+
#endif
21+
1222
#if defined(PY_HAVE_PERF_TRAMPOLINE) || (defined(__linux__) && defined(__ELF__))
1323

1424
#if defined(__linux__)
@@ -205,11 +215,17 @@ static void elfctx_append_uleb128(ELFObjectContext* ctx, uint32_t v) {
205215
// =============================================================================
206216

207217
static void elf_init_ehframe_perf(ELFObjectContext* ctx);
218+
#if PY_HAVE_JIT_GDB_UNWIND
208219
static void elf_init_ehframe_gdb(ELFObjectContext* ctx);
220+
#endif
209221

210222
static inline void elf_init_ehframe(ELFObjectContext* ctx, int absolute_addr) {
211223
if (absolute_addr) {
224+
#if PY_HAVE_JIT_GDB_UNWIND
212225
elf_init_ehframe_gdb(ctx);
226+
#else
227+
Py_UNREACHABLE();
228+
#endif
213229
}
214230
else {
215231
elf_init_ehframe_perf(ctx);
@@ -614,21 +630,15 @@ static void elf_init_ehframe_perf(ELFObjectContext* ctx) {
614630
/*
615631
* Build .eh_frame data for the GDB JIT interface.
616632
*
617-
* The CIE's initial CFI hard-codes the steady-state frame layout for the
618-
* executor region:
619-
*
620-
* x86_64: CFA = %rbp + 16, return-to-_PyJIT_Entry PC at cfa-72
621-
* AArch64: CFA = x29 + 96, caller x29 at cfa-96, caller x30 at cfa-88
622-
*
623-
* The executor runs inside the frame established by _PyJIT_Entry. On AArch64
624-
* we collapse that state into a single synthetic executor frame that unwinds
625-
* directly into _PyEval_*. On x86_64 the normal call into the executor leaves
626-
* a real return slot back into _PyJIT_Entry, so the executor FDE materializes
627-
* _PyJIT_Entry as the caller frame. Executor stencils never touch the frame
628-
* pointer — enforced by Tools/jit/_optimizers.py _validate() and
629-
* -mframe-pointer=reserved — so the steady-state rule is valid at every PC
630-
* and the FDE body is empty.
633+
* The executor runs inside the frame established by _PyJIT_Entry, but the
634+
* synthetic executor FDE collapses that state into a single logical JIT frame
635+
* that unwinds directly into _PyEval_*. Executor stencils never touch the
636+
* frame pointer - enforced by Tools/jit/_optimizers.py _validate() and
637+
* -mframe-pointer=reserved - so the steady-state rule is valid at every PC
638+
* and the FDE body is empty. Tools/jit/_targets.py derives the initial CFI
639+
* rules from the row active at the executor call in the compiled shim object.
631640
*/
641+
#if PY_HAVE_JIT_GDB_UNWIND
632642
static void elf_init_ehframe_gdb(ELFObjectContext* ctx) {
633643
int fde_ptr_enc = DWRF_EH_PE_absptr;
634644
uint8_t* p = ctx->p;
@@ -638,34 +648,20 @@ static void elf_init_ehframe_gdb(ELFObjectContext* ctx) {
638648
DWRF_U32(0); // CIE ID
639649
DWRF_U8(DWRF_CIE_VERSION);
640650
DWRF_STR("zR"); // aug data length + FDE ptr encoding follow
641-
#ifdef __x86_64__
642-
DWRF_UV(1); // x86_64: 1 byte per instruction
643-
#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__)
644-
DWRF_UV(4); // AArch64: 4 bytes per instruction
645-
#endif
646-
DWRF_SV(-(int64_t)sizeof(uintptr_t));
647-
DWRF_U8(DWRF_REG_RA);
651+
DWRF_UV(JIT_UNWIND_CODE_ALIGNMENT_FACTOR);
652+
DWRF_SV(JIT_UNWIND_DATA_ALIGNMENT_FACTOR);
653+
DWRF_U8(JIT_UNWIND_RA_REG);
648654
DWRF_UV(1); // Augmentation data length
649655
DWRF_U8(fde_ptr_enc); // FDE pointer encoding
650656

651657
/* Executor steady-state rule (our invariant, not the compiler's). */
652-
#ifdef __x86_64__
653-
DWRF_U8(DWRF_CFA_def_cfa); // CFA = %rbp + 16
654-
DWRF_UV(DWRF_REG_BP);
655-
DWRF_UV(16);
656-
DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA);
657-
DWRF_UV(9); // return-to-_PyJIT_Entry at cfa-72
658-
#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__)
659-
DWRF_U8(DWRF_CFA_def_cfa); // CFA = x29 + 96
660-
DWRF_UV(DWRF_REG_FP);
661-
DWRF_UV(96);
662-
DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP);
663-
DWRF_UV(12); // caller x29 at cfa-96
664-
DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA);
665-
DWRF_UV(11); // caller x30 at cfa-88
666-
#else
667-
# error "Unsupported target architecture"
668-
#endif
658+
DWRF_U8(DWRF_CFA_def_cfa);
659+
DWRF_UV(JIT_UNWIND_CFA_REG);
660+
DWRF_UV(JIT_UNWIND_CFA_OFFSET);
661+
DWRF_U8(DWRF_CFA_offset | JIT_UNWIND_FP_REG);
662+
DWRF_UV(JIT_UNWIND_FP_OFFSET);
663+
DWRF_U8(DWRF_CFA_offset | JIT_UNWIND_RA_REG);
664+
DWRF_UV(JIT_UNWIND_RA_OFFSET);
669665
DWRF_ALIGNNOP(sizeof(uintptr_t));
670666
)
671667

@@ -679,6 +675,7 @@ static void elf_init_ehframe_gdb(ELFObjectContext* ctx) {
679675

680676
ctx->p = p;
681677
}
678+
#endif
682679

683680
#if defined(__linux__) && defined(__ELF__)
684681
enum {

0 commit comments

Comments
 (0)