Skip to content

Commit 95fcf68

Browse files
Copilotpablogsal
authored andcommitted
Add support for Python 3.14 tail call interpreter patterns
Implement support for Python 3.14's tail call interpreter in Memray. The changes enable Memray to correctly identify Python frame boundaries when native stack traces contain LLVM-generated tail call functions. Signed-off-by: Pablo Galindo Salgado <[email protected]>
1 parent 98c8dd3 commit 95fcf68

File tree

5 files changed

+109
-3
lines changed

5 files changed

+109
-3
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: UV Python Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test_uv_python:
17+
name: "Test with UV Python 3.14"
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v5
21+
22+
- name: Install uv and set Python 3.14
23+
uses: astral-sh/setup-uv@v6
24+
with:
25+
python-version: "3.14"
26+
27+
- name: Create virtual environment
28+
run: |
29+
uv venv --python 3.14
30+
31+
- name: Set up system dependencies
32+
run: |
33+
sudo apt-get update
34+
sudo apt-get install -qy \
35+
pkg-config \
36+
libdebuginfod-dev \
37+
libunwind-dev \
38+
liblz4-dev \
39+
gdb \
40+
npm
41+
42+
- name: Install Python dependencies
43+
run: |
44+
uv pip install --upgrade pip cython pkgconfig
45+
uv pip install -r requirements-test.txt
46+
47+
- name: Build package
48+
run: |
49+
make build-js
50+
uv pip install -e .
51+
52+
- name: Disable ptrace security restrictions
53+
run: |
54+
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
55+
56+
- name: Run tests
57+
run: |
58+
uv run pytest -vvv --log-cli-level=info tests

news/836.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for Python 3.14's tail call interpreter. Memray now correctly identifies Python frame boundaries in native stack traces when Python 3.14 is built with the tail call interpreter enabled (``--with-tail-call-interp``), recognizing LLVM-generated tail call functions alongside traditional ``_PyEval_EvalFrameDefault`` functions.

src/memray/_memray.pyx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,12 @@ cdef hybrid_stack_trace(
247247

248248
for native_frame in native_stack:
249249
symbol = native_frame[0]
250-
if pidx >= 0 and "_PyEval_EvalFrameDefault" in symbol:
250+
# Check for Python frame boundaries: traditional _PyEval_EvalFrameDefault
251+
# or Python 3.14 tail call interpreter LLVM-generated functions
252+
is_python_frame_boundary = "_PyEval_EvalFrameDefault" in symbol or (
253+
symbol.startswith("_TAIL_CALL_") and ".llvm." in symbol
254+
)
255+
if pidx >= 0 and is_python_frame_boundary:
251256
while True:
252257
# If we're not keeping all frames and we've reached the
253258
# first one we want to keep, remove frames above it.
@@ -272,7 +277,8 @@ cdef hybrid_stack_trace(
272277
# We ran out of native frames without using up all of our Python
273278
# frames. We've seen this happen on stripped interpreters on Alpine
274279
# Linux in CI. Presumably this indicates that unwinding failed to
275-
# symbolify some of the calls to _PyEval_EvalFrameDefault.
280+
# symbolify some of the Python frame boundaries (_PyEval_EvalFrameDefault
281+
# or Python 3.14 tail call interpreter functions).
276282
return python_stack
277283
assert hidx == -1
278284

@@ -1642,7 +1648,12 @@ def get_symbolic_support():
16421648
locations = unwindHere()
16431649
for location in locations:
16441650
function, file, line = location.split(":")
1645-
if function != "_PyEval_EvalFrameDefault":
1651+
# Check for Python frame boundaries: traditional _PyEval_EvalFrameDefault
1652+
# or Python 3.14 tail call interpreter LLVM-generated functions
1653+
is_python_frame_boundary = function == "_PyEval_EvalFrameDefault" or (
1654+
function.startswith("_TAIL_CALL_") and ".llvm." in function
1655+
)
1656+
if not is_python_frame_boundary:
16461657
continue
16471658
if not file:
16481659
return SymbolicSupport.FUNCTION_NAME_ONLY

src/memray/reporters/frame_tools.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
def _is_cpython_internal_symbol(symbol: str, file: str) -> bool:
3939
if "PyEval_EvalFrameEx" in symbol or "_PyEval_EvalFrameDefault" in symbol:
4040
is_candidate = True
41+
elif symbol.startswith("_TAIL_CALL_") and ".llvm." in symbol:
42+
# Python 3.14 tail call interpreter uses LLVM-generated functions
43+
is_candidate = True
4144
elif symbol.startswith(("PyEval", "_Py")):
4245
is_candidate = True
4346
elif "vectorcall" in symbol.lower():

tests/unit/test_frame_tools.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,32 @@ class TestFrameFiltering:
3131
),
3232
True,
3333
],
34+
# Python 3.14 tail call interpreter LLVM-generated functions
35+
[
36+
(
37+
"_TAIL_CALL_INSTRUMENTED_CALL.llvm.10282351651392433962",
38+
"/src/python/python3.14/Python/ceval.c",
39+
100,
40+
),
41+
True,
42+
],
43+
[
44+
(
45+
"_TAIL_CALL_POP_TOP.llvm.123456789",
46+
"/src/python/python3.14/Python/ceval.c",
47+
50,
48+
),
49+
True,
50+
],
3451
[("somefunc", "myapp.py", 100), False],
3552
[("function_code_fastcall", "myapp.py", 100), False],
53+
# _TAIL_CALL_ pattern should not match without .llvm.
54+
[
55+
("_TAIL_CALL_SOMETHING", "/src/python/python3.14/Python/ceval.c", 100),
56+
False,
57+
],
58+
# _TAIL_CALL_ pattern should not match in non-CPython files
59+
[("_TAIL_CALL_SOMETHING.llvm.123", "myapp.py", 100), False],
3660
],
3761
)
3862
def test_cpython_internal_calls(self, frame, expected):
@@ -51,6 +75,15 @@ def test_cpython_internal_calls(self, frame, expected):
5175
),
5276
False,
5377
],
78+
# Python 3.14 tail call interpreter frames should not be interesting
79+
[
80+
(
81+
"_TAIL_CALL_INSTRUMENTED_CALL.llvm.10282351651392433962",
82+
"/src/python/python3.14/Python/ceval.c",
83+
100,
84+
),
85+
False,
86+
],
5487
[
5588
(
5689
"PyArg_ParseTuple",

0 commit comments

Comments
 (0)