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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ Tools/unicode/data/
/dist/
/jit_stencils*.h
/jit_unwind_info*.h
/trampoline_ehframe.h*
.jit-stamp
/platform
/profile-clean-stamp
Expand Down
3 changes: 2 additions & 1 deletion Doc/howto/perf_profiling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ relationship between this piece of code and the associated Python function using
samply support on macOS is available starting from Python 3.15.
Check the output of the ``configure`` build step or
check the output of ``python -m sysconfig | grep HAVE_PERF_TRAMPOLINE``
to see if your system is supported.
to see if your system is supported. Building the perf trampoline needs a
Python interpreter, found by ``configure`` as ``PYTHON_FOR_REGEN``.

For example, consider the following script:

Expand Down
5 changes: 5 additions & 0 deletions Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ To build CPython, you will need:

* Support for threads.

* Optionally, a Python interpreter, found by ``configure`` as
``PYTHON_FOR_REGEN``, to build the perf trampoline (see
:doc:`/howto/perf_profiling`). Without it, ``configure`` disables the perf
trampoline.

.. versionchanged:: 3.5
On Windows, Visual Studio 2015 or later is now required.

Expand Down
324 changes: 324 additions & 0 deletions Lib/test/test_perf_profiler.py

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -3185,9 +3185,16 @@ jit_shim-universal2-apple-darwin.o: jit_shim-aarch64-apple-darwin.o jit_shim-x86
Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<

Python/jit_unwind.o: $(srcdir)/Python/jit_unwind.c $(JIT_UNWIND_INFO_H)
TRAMPOLINE_EHFRAME_H = $(if @PERF_TRAMPOLINE_OBJ@,trampoline_ehframe.h)

trampoline_ehframe.h: @PERF_TRAMPOLINE_OBJ@ $(srcdir)/Tools/jit/_trampoline_ehframe.py
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/jit/_trampoline_ehframe.py \
-o $@ @PERF_TRAMPOLINE_OBJ@

Python/jit_unwind.o: $(srcdir)/Python/jit_unwind.c $(JIT_UNWIND_INFO_H) $(TRAMPOLINE_EHFRAME_H)
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<


.PHONY: regen-jit
regen-jit: $(JIT_TARGETS)

Expand Down Expand Up @@ -3289,6 +3296,7 @@ clean-retain-profile: pycremoval
-rm -f Python/frozen_modules/MANIFEST
-find build -type f -a ! -name '*.gc??' -exec rm -f {} ';'
-rm -f Include/pydtrace_probes.h
-rm -f trampoline_ehframe.h trampoline_ehframe.h.tmp
-rm -f profile-gen-stamp
-rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/bin
-rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/lib
Expand Down Expand Up @@ -3463,7 +3471,7 @@ MODULE__SOCKET_DEPS=$(srcdir)/Modules/socketmodule.h $(srcdir)/Modules/addrinfo.
MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_openssl_mem.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h
MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/_testcapi/parts.h $(srcdir)/Modules/_testcapi/util.h
MODULE__TESTLIMITEDCAPI_DEPS=$(srcdir)/Modules/_testlimitedcapi/testcapi_long.h $(srcdir)/Modules/_testlimitedcapi/parts.h $(srcdir)/Modules/_testlimitedcapi/util.h
MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/cursor.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h
MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/cursor.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h $(TRAMPOLINE_EHFRAME_H)
MODULE__SQLITE3_DEPS=$(srcdir)/Modules/_sqlite/connection.h $(srcdir)/Modules/_sqlite/cursor.h $(srcdir)/Modules/_sqlite/microprotocols.h $(srcdir)/Modules/_sqlite/module.h $(srcdir)/Modules/_sqlite/prepare_protocol.h $(srcdir)/Modules/_sqlite/row.h $(srcdir)/Modules/_sqlite/util.h
MODULE__ZSTD_DEPS=$(srcdir)/Modules/_zstd/_zstdmodule.h $(srcdir)/Modules/_zstd/buffer.h $(srcdir)/Modules/_zstd/zstddict.h

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Generate the perf trampoline's unwind information from its compiled assembly
at build time. Building the trampoline now requires a Python interpreter.
119 changes: 119 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_New()
#include "pycore_interpframe.h" // _PyFrame_GetFunction()
#include "pycore_jit.h" // _PyJIT_AddressInJitCode()
#include "pycore_jit_unwind.h" // DWRF_EH_PE_* constants
#include "pycore_object.h" // _PyObject_IsFreed()
#include "pycore_optimizer.h" // _Py_Executor_DependsOn
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
Expand Down Expand Up @@ -2718,6 +2719,121 @@ _testinternalcapi_test_long_numbits_impl(PyObject *module)
Py_RETURN_NONE;
}

#if defined(PY_HAVE_PERF_TRAMPOLINE)
#include "trampoline_ehframe.h"

/* Structural checks on the generated perf trampoline .eh_frame and the
* field offsets Python/jit_unwind.c patches. Raises instead of assert()
* so the checks also run in release builds. */
static PyObject *
test_trampoline_ehframe(PyObject *self, PyObject *Py_UNUSED(args))
{
#define CHECK(cond, msg) \
do { \
if (!(cond)) { \
PyErr_SetString(PyExc_AssertionError, \
"trampoline_ehframe: " msg); \
return NULL; \
} \
} while (0)

const uint8_t *data = _trampoline_ehframe;
size_t size = TRAMPOLINE_EHFRAME_SIZE;
size_t field_size = TRAMPOLINE_EHFRAME_FDE_FIELD_SIZE;

CHECK(field_size == 4 || field_size == 8, "unsupported FDE field size");
CHECK(size >= 8 + 8 + 2 * field_size + 1, "data too small for a CIE and an FDE");

/* CIE: length, CIE_id == 0, version 1, augmentation "zR". */
uint32_t cie_length;
memcpy(&cie_length, data, sizeof(cie_length));
CHECK(cie_length > 0 && cie_length < size, "bad CIE length");

uint32_t cie_id;
memcpy(&cie_id, data + 4, sizeof(cie_id));
CHECK(cie_id == 0, "first entry is not a CIE");

CHECK(data[8] == 1, "CIE version is not 1");
CHECK(data[9] == 'z' && data[10] == 'R' && data[11] == '\0',
"CIE augmentation is not \"zR\"");

/* Exactly one FDE must follow the CIE, and the walk below must stay
* inside the data. */
size_t cie_total = 4 + cie_length;
CHECK(cie_total + 8 + 2 * field_size <= size,
"no room for an FDE after the CIE");

/* FDE pointer encoding byte: skip version(1), "zR\0"(3), code_align
* (ULEB128), data_align (SLEB128), RA column (1 byte in version 1),
* augmentation data length (ULEB128). */
size_t pos = 12;
while (pos < cie_total && (data[pos] & 0x80)) { /* code alignment */
pos++;
}
pos++;
while (pos < cie_total && (data[pos] & 0x80)) { /* data alignment */
pos++;
}
pos++;
pos++; /* RA column */
/* Augmentation data length (ULEB128) must be 1: the encoding byte. */
uint32_t aug_length = 0;
int shift = 0;
while (pos < cie_total && (data[pos] & 0x80)) {
CHECK(shift < 28, "CIE augmentation data length is too long");
aug_length |= (uint32_t)(data[pos] & 0x7f) << shift;
shift += 7;
pos++;
}
CHECK(pos < cie_total, "CIE augmentation data length is truncated");
CHECK((uint32_t)(data[pos] & 0x7f) <= (UINT32_MAX >> shift),
"CIE augmentation data length overflows");
aug_length |= (uint32_t)(data[pos] & 0x7f) << shift;
pos++;
CHECK(aug_length == 1 && pos < cie_total,
"CIE augmentation data length is not 1");
uint8_t fde_enc = data[pos];
CHECK(fde_enc == (DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4)
|| fde_enc == (DWRF_EH_PE_pcrel | DWRF_EH_PE_absptr),
"unsupported FDE pointer encoding");
size_t enc_field_size = (fde_enc & 0x0f) == DWRF_EH_PE_sdata4 ? 4 : 8;
CHECK(enc_field_size == field_size,
"FDE field size does not match the CIE pointer encoding");

/* The FDE ends exactly at the end of the data. */
uint32_t fde_length;
memcpy(&fde_length, data + cie_total, sizeof(fde_length));
CHECK(fde_length > 0, "FDE length is zero");
CHECK(cie_total + 4 + fde_length == size,
"FDE does not end at the end of the data");

/* The CIE pointer is the distance from its own field back to the CIE. */
uint32_t fde_cie_ptr;
memcpy(&fde_cie_ptr, data + cie_total + 4, sizeof(fde_cie_ptr));
CHECK(fde_cie_ptr == cie_total + 4, "FDE CIE pointer does not point at the CIE");

/* The runtime patches initial_location and address_range at the
* recorded offsets. They must be the two fields after the CIE pointer
* and must be zeroed placeholders in the header. */
CHECK(TRAMPOLINE_EHFRAME_FDE_PC_OFFSET == cie_total + 8,
"FDE initial_location offset is wrong");
CHECK(TRAMPOLINE_EHFRAME_FDE_RANGE_OFFSET
== TRAMPOLINE_EHFRAME_FDE_PC_OFFSET + field_size,
"FDE address_range offset is wrong");
for (size_t i = 0; i < 2 * field_size; i++) {
CHECK(data[TRAMPOLINE_EHFRAME_FDE_PC_OFFSET + i] == 0,
"FDE placeholder fields are not zero");
}
/* The FDE's own augmentation data length must follow and be 0. */
size_t fde_aug_offset = TRAMPOLINE_EHFRAME_FDE_RANGE_OFFSET + field_size;
CHECK(fde_aug_offset < size, "FDE augmentation data length byte is missing");
CHECK(data[fde_aug_offset] == 0, "FDE augmentation data length is not 0");

#undef CHECK
Py_RETURN_NONE;
}
#endif /* PY_HAVE_PERF_TRAMPOLINE */

static PyObject *
compile_perf_trampoline_entry(PyObject *self, PyObject *args)
{
Expand Down Expand Up @@ -3353,6 +3469,9 @@ static PyMethodDef module_functions[] = {
{"interpreter_refcount_linked", interpreter_refcount_linked, METH_O},
{"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS},
{"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS},
#if defined(PY_HAVE_PERF_TRAMPOLINE)
{"test_trampoline_ehframe", test_trampoline_ehframe, METH_NOARGS},
#endif
{"get_crossinterp_data", _PyCFunction_CAST(get_crossinterp_data),
METH_VARARGS | METH_KEYWORDS},
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
Expand Down
15 changes: 15 additions & 0 deletions Python/asm_trampoline_aarch64.S
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,28 @@ __Py_trampoline_func_start:
.globl _Py_trampoline_func_start
_Py_trampoline_func_start:
#endif
.cfi_startproc
SIGN_LR
#if defined(__ARM_FEATURE_PAC_DEFAULT)
.cfi_negate_ra_state
#endif
stp x29, x30, [sp, -16]!
.cfi_def_cfa_offset 16
.cfi_offset x29, -16
.cfi_offset x30, -8
mov x29, sp
.cfi_def_cfa_register x29
blr x3
ldp x29, x30, [sp], 16
.cfi_restore x30
.cfi_restore x29
.cfi_def_cfa sp, 0
VERIFY_LR
#if defined(__ARM_FEATURE_PAC_DEFAULT)
.cfi_negate_ra_state
#endif
ret
.cfi_endproc
#if defined(__APPLE__)
.globl __Py_trampoline_func_end
__Py_trampoline_func_end:
Expand Down
6 changes: 6 additions & 0 deletions Python/asm_trampoline_x86_64.S
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ __Py_trampoline_func_start:
.globl _Py_trampoline_func_start
_Py_trampoline_func_start:
#endif
.cfi_startproc
#if defined(__CET__) && (__CET__ & 1)
endbr64
#endif
push %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
call *%rcx
pop %rbp
.cfi_def_cfa %rsp, 8
ret
.cfi_endproc
#if defined(__APPLE__)
.globl __Py_trampoline_func_end
__Py_trampoline_func_end:
Expand Down
Loading
Loading