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
2 changes: 1 addition & 1 deletion Doc/c-api/perfmaps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Note that holding an :term:`attached thread state` is not required for these API
or ``-2`` on failure to create a lock. Check ``errno`` for more information
about the cause of a failure.

.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, unsigned int code_size, const char *entry_name)
.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, size_t code_size, const char *entry_name)
Write one single entry to the ``/tmp/perf-$pid.map`` file. This function is
thread safe. Here is what an example entry looks like::
Expand Down
2 changes: 1 addition & 1 deletion Include/cpython/ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ typedef struct {
PyAPI_FUNC(int) PyUnstable_PerfMapState_Init(void);
PyAPI_FUNC(int) PyUnstable_WritePerfMapEntry(
const void *code_addr,
unsigned int code_size,
size_t code_size,
const char *entry_name);
PyAPI_FUNC(void) PyUnstable_PerfMapState_Fini(void);
PyAPI_FUNC(int) PyUnstable_CopyPerfMapFile(const char* parent_filename);
Expand Down
6 changes: 5 additions & 1 deletion Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ typedef struct {
void* (*init_state)(void);
// Callback to register every trampoline being created
void (*write_state)(void* state, const void *code_addr,
unsigned int code_size, PyCodeObject* code);
size_t code_size, PyCodeObject* code);
// Callback to free the trampoline state
int (*free_state)(void* state);
} _PyPerf_Callbacks;
Expand All @@ -108,6 +108,10 @@ extern PyStatus _PyPerfTrampoline_AfterFork_Child(void);
#ifdef PY_HAVE_PERF_TRAMPOLINE
extern _PyPerf_Callbacks _Py_perfmap_callbacks;
extern _PyPerf_Callbacks _Py_perfmap_jit_callbacks;
extern void _PyPerfJit_WriteNamedCode(const void *code_addr,
size_t code_size,
const char *entry,
const char *filename);
#endif

static inline PyObject*
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ struct code_arena_st;
struct trampoline_api_st {
void* (*init_state)(void);
void (*write_state)(void* state, const void *code_addr,
unsigned int code_size, PyCodeObject* code);
size_t code_size, PyCodeObject* code);
int (*free_state)(void* state);
void *state;
Py_ssize_t code_padding;
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_jit.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ typedef _Py_CODEUNIT *(*jit_func)(
_PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2
);

_Py_CODEUNIT *_PyJIT(
_Py_CODEUNIT *_PyJIT_Entry(
_PyExecutorObject *executor, _PyInterpreterFrame *frame,
_PyStackRef *stack_pointer, PyThreadState *tstate
);
Expand Down
23 changes: 23 additions & 0 deletions Include/internal/pycore_jit_publish.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef Py_INTERNAL_JIT_PUBLISH_H
#define Py_INTERNAL_JIT_PUBLISH_H

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

#include <stddef.h>

typedef struct _PyJitCodeRegistration _PyJitCodeRegistration;

#ifdef _Py_JIT

_PyJitCodeRegistration *_PyJit_RegisterCode(const void *code_addr,
size_t code_size,
const char *entry,
const char *filename);

void _PyJit_UnregisterCode(_PyJitCodeRegistration *registration);

#endif // _Py_JIT

#endif // Py_INTERNAL_JIT_PUBLISH_H
65 changes: 65 additions & 0 deletions Include/internal/pycore_jit_unwind.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#ifndef Py_INTERNAL_JIT_UNWIND_H
#define Py_INTERNAL_JIT_UNWIND_H

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

#include <stddef.h>
#include <stdint.h>

#if defined(PY_HAVE_PERF_TRAMPOLINE) || (defined(__linux__) && defined(__ELF__))

/* DWARF exception-handling pointer encodings shared by JIT unwind users. */
enum {
DWRF_EH_PE_absptr = 0x00,
DWRF_EH_PE_omit = 0xff,

/* Data type encodings */
DWRF_EH_PE_uleb128 = 0x01,
DWRF_EH_PE_udata2 = 0x02,
DWRF_EH_PE_udata4 = 0x03,
DWRF_EH_PE_udata8 = 0x04,
DWRF_EH_PE_sleb128 = 0x09,
DWRF_EH_PE_sdata2 = 0x0a,
DWRF_EH_PE_sdata4 = 0x0b,
DWRF_EH_PE_sdata8 = 0x0c,
DWRF_EH_PE_signed = 0x08,

/* Reference type encodings */
DWRF_EH_PE_pcrel = 0x10,
DWRF_EH_PE_textrel = 0x20,
DWRF_EH_PE_datarel = 0x30,
DWRF_EH_PE_funcrel = 0x40,
DWRF_EH_PE_aligned = 0x50,
DWRF_EH_PE_indirect = 0x80
};

/* Return the size of the generated .eh_frame data for the given encoding. */
size_t _PyJitUnwind_EhFrameSize(int absolute_addr);

/*
* Build DWARF .eh_frame data for JIT code; returns size written or 0 on error.
* absolute_addr selects the FDE address encoding:
* - 0: PC-relative offsets (perf jitdump synthesized DSO).
* - nonzero: absolute addresses (GDB JIT in-memory ELF).
*/
size_t _PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size,
const void *code_addr, size_t code_size,
int absolute_addr);

void *_PyJitUnwind_GdbRegisterCode(const void *code_addr,
size_t code_size,
const char *entry,
const char *filename);

void _PyJitUnwind_GdbUnregisterCode(void *handle);

void *_PyJitUnwind_GnuBacktraceRegisterCode(const void *code_addr,
size_t code_size);

void _PyJitUnwind_GnuBacktraceUnregisterCode(void *handle);

#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || (defined(__linux__) && defined(__ELF__))

#endif // Py_INTERNAL_JIT_UNWIND_H
2 changes: 2 additions & 0 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#endif

#include "pycore_typedefs.h" // _PyInterpreterFrame
#include "pycore_jit_publish.h"
#include "pycore_uop.h" // _PyUOpInstruction
#include "pycore_uop_ids.h"
#include "pycore_stackref.h" // _PyStackRef
Expand Down Expand Up @@ -198,6 +199,7 @@ typedef struct _PyExecutorObject {
uint32_t code_size;
size_t jit_size;
void *jit_code;
_PyJitCodeRegistration *jit_registration;
_PyExitData exits[1];
} _PyExecutorObject;

Expand Down
62 changes: 55 additions & 7 deletions Lib/test/test_frame_pointer_unwind.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def _frame_pointers_expected(machine):
return None


def _build_stack_and_unwind():
def _build_stack_and_unwind(unwinder):
import operator

def build_stack(n, unwinder, warming_up_caller=False):
Expand All @@ -90,7 +90,7 @@ def build_stack(n, unwinder, warming_up_caller=False):
result = operator.call(build_stack, n - 1, unwinder, warming_up)
return result

stack = build_stack(10, _testinternalcapi.manual_frame_pointer_unwind)
stack = build_stack(10, unwinder)
return stack


Expand All @@ -113,8 +113,9 @@ def _classify_stack(stack, jit_enabled):
return annotated, python_frames, jit_frames, other_frames


def _annotate_unwind():
stack = _build_stack_and_unwind()
def _annotate_unwind(unwinder_name="manual_frame_pointer_unwind"):
unwinder = getattr(_testinternalcapi, unwinder_name)
stack = _build_stack_and_unwind(unwinder)
jit_enabled = hasattr(sys, "_jit") and sys._jit.is_enabled()
jit_backend = _testinternalcapi.get_jit_backend()
ranges = _testinternalcapi.get_jit_code_ranges() if jit_enabled else []
Expand All @@ -133,13 +134,14 @@ def _annotate_unwind():
"jit_frames": jit_frames,
"other_frames": other_frames,
"jit_backend": jit_backend,
"unwinder": unwinder_name,
})


def _manual_unwind_length(**env):
def _unwind_result(unwinder_name, **env):
code = (
"from test.test_frame_pointer_unwind import _annotate_unwind; "
"print(_annotate_unwind());"
f"print(_annotate_unwind({unwinder_name!r}));"
)
run_env = os.environ.copy()
run_env.update(env)
Expand Down Expand Up @@ -198,7 +200,7 @@ def test_manual_unwind_respects_frame_pointers(self):

for env, using_jit in envs:
with self.subTest(env=env):
result = _manual_unwind_length(**env)
result = _unwind_result("manual_frame_pointer_unwind", **env)
jit_frames = result["jit_frames"]
python_frames = result.get("python_frames", 0)
jit_backend = result.get("jit_backend")
Expand Down Expand Up @@ -241,5 +243,51 @@ def test_manual_unwind_respects_frame_pointers(self):
)


@support.requires_gil_enabled("test requires the GIL enabled")
@unittest.skipIf(support.is_wasi, "test not supported on WASI")
@unittest.skipUnless(sys.platform == "linux", "GNU backtrace unwinding test requires Linux")
class GnuBacktraceUnwindTests(unittest.TestCase):

def setUp(self):
super().setUp()
try:
_testinternalcapi.gnu_backtrace_unwind()
except RuntimeError as exc:
if "not supported" in str(exc):
self.skipTest("gnu backtrace unwinding not supported on this platform")
raise

def test_gnu_backtrace_unwinds_through_jit_frames(self):
jit_available = hasattr(sys, "_jit") and sys._jit.is_available()
envs = [({"PYTHON_JIT": "0"}, False)]
if jit_available:
envs.append(({"PYTHON_JIT": "1"}, True))

for env, using_jit in envs:
with self.subTest(env=env):
result = _unwind_result("gnu_backtrace_unwind", **env)
python_frames = result.get("python_frames", 0)
jit_frames = result.get("jit_frames", 0)
jit_backend = result.get("jit_backend")

self.assertGreater(
python_frames,
0,
f"expected to find Python frames in GNU backtrace with env {env}",
)
if using_jit and jit_backend == "jit":
self.assertGreater(
jit_frames,
0,
f"expected GNU backtrace to include JIT frames with env {env}",
)
else:
self.assertEqual(
jit_frames,
0,
f"unexpected JIT frames counted in GNU backtrace with env {env}",
)


if __name__ == "__main__":
unittest.main()
27 changes: 27 additions & 0 deletions Lib/test/test_gdb/gdb_jit_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Sample script for use by test_gdb.test_jit

import _testinternalcapi
import operator


WARMUP_ITERATIONS = _testinternalcapi.TIER2_THRESHOLD + 10


def jit_bt_hot(depth, warming_up_caller=False):
if depth == 0:
if not warming_up_caller:
id(42)
return

for iteration in range(WARMUP_ITERATIONS):
operator.call(
jit_bt_hot,
depth - 1,
warming_up_caller or iteration + 1 != WARMUP_ITERATIONS,
)


# Warm the shared shim once without hitting builtin_id so the real run uses
# the steady-state shim path when GDB breaks inside id(42).
jit_bt_hot(1, warming_up_caller=True)
jit_bt_hot(1)
Loading
Loading