Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-128563: A new tail-calling interpreter #128718

Open
wants to merge 90 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
2443f21
Add new configure.ac option --enable-tail-call-interp
Fidget-Spinner Jan 4, 2025
fb72fcd
sorta working
Fidget-Spinner Jan 5, 2025
c120a98
fix release builds
Fidget-Spinner Jan 5, 2025
6f753da
split out cold error parts
Fidget-Spinner Jan 5, 2025
4db1853
fix on release builds
Fidget-Spinner Jan 5, 2025
3bab1b7
Garret gets credit for the idea of splitting exits
Fidget-Spinner Jan 5, 2025
e545c55
fix jit build
Fidget-Spinner Jan 5, 2025
166aab0
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 5, 2025
465467f
force on
Fidget-Spinner Jan 5, 2025
52b7ece
ignore out entire interp loop
Fidget-Spinner Jan 6, 2025
b9bedb1
Apply changes from Garret's branch
Fidget-Spinner Jan 6, 2025
f1d3190
Revert "Apply changes from Garret's branch"
Fidget-Spinner Jan 6, 2025
6132746
Autoconf detection
Fidget-Spinner Jan 9, 2025
16db128
cleanup
Fidget-Spinner Jan 9, 2025
fc91ac8
cleanup macros
Fidget-Spinner Jan 9, 2025
982c51d
Make deopt more efficient
Fidget-Spinner Jan 9, 2025
9820340
Cleanup
Fidget-Spinner Jan 9, 2025
5862338
more cleanup
Fidget-Spinner Jan 9, 2025
f5b1c93
Generate everything
Fidget-Spinner Jan 10, 2025
637589e
Revert "Make deopt more efficient"
Fidget-Spinner Jan 10, 2025
5615d6f
Add unknown opcode handlers
Fidget-Spinner Jan 10, 2025
71eba58
cleanup
Fidget-Spinner Jan 10, 2025
e1d5f41
Add to makefile
Fidget-Spinner Jan 10, 2025
b577888
fix debug
Fidget-Spinner Jan 10, 2025
ac80cdd
Fix up generation
Fidget-Spinner Jan 10, 2025
6ae3e03
fix instrumented instructions partially
Fidget-Spinner Jan 10, 2025
a27ee2d
Fix instrumentation completely
Fidget-Spinner Jan 10, 2025
56c6349
Fix tests, move entry_frame into frame
Fidget-Spinner Jan 10, 2025
34dd801
Block tier2
Fidget-Spinner Jan 10, 2025
aa67427
Add tests in cases generator
Fidget-Spinner Jan 10, 2025
54cff79
Leave assert in
Fidget-Spinner Jan 10, 2025
22bdf7d
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 10, 2025
75361d4
Upstream changes
Fidget-Spinner Jan 10, 2025
d93bdaa
📜🤖 Added by blurb_it.
blurb-it[bot] Jan 10, 2025
a68d891
fix mypy
Fidget-Spinner Jan 10, 2025
1310b47
Fix lint
Fidget-Spinner Jan 10, 2025
8a2423d
Merge branch 'tail-call' of github.com:Fidget-Spinner/cpython into ta…
Fidget-Spinner Jan 10, 2025
db110ff
Fix builds, address review
Fidget-Spinner Jan 10, 2025
e0d9e6c
Update tail-call.yml
Fidget-Spinner Jan 10, 2025
5aec067
fix name
Fidget-Spinner Jan 10, 2025
6f7934b
Simplify
Fidget-Spinner Jan 10, 2025
142e56e
Address review
Fidget-Spinner Jan 10, 2025
5bc7d14
Address review again
Fidget-Spinner Jan 10, 2025
195ee87
Address review
Fidget-Spinner Jan 10, 2025
9df3e75
Update 2025-01-10-18-56-20.gh-issue-128563.baDvls.rst
Fidget-Spinner Jan 11, 2025
2b4851e
fix configure auto detection
Fidget-Spinner Jan 12, 2025
1ae8fc0
fix workflow
Fidget-Spinner Jan 12, 2025
c58f285
Test on macOS as well
Fidget-Spinner Jan 12, 2025
dfa2af7
Specify clang-19
Fidget-Spinner Jan 12, 2025
1fc4df5
Try fix Apple
Fidget-Spinner Jan 12, 2025
dec29ed
add to PATH
Fidget-Spinner Jan 12, 2025
8d29895
Fix on x86_64 Apple
Fidget-Spinner Jan 12, 2025
b1ae570
missing "
Fidget-Spinner Jan 12, 2025
7a95b7e
allow computed gotos with tail calls
Fidget-Spinner Jan 12, 2025
d26ef11
add ceval.c to list of paths
Fidget-Spinner Jan 12, 2025
70db5e9
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 13, 2025
eac485f
Fix upstream, fix warnings
Fidget-Spinner Jan 13, 2025
910bd88
cleanup
Fidget-Spinner Jan 13, 2025
c1a6652
Partial GCC 15.0 support
Fidget-Spinner Jan 13, 2025
e198894
Fix test
Fidget-Spinner Jan 13, 2025
29b6237
Fix JIT builds
Fidget-Spinner Jan 13, 2025
92411f0
fix typo
Fidget-Spinner Jan 14, 2025
501f645
half the runners
Fidget-Spinner Jan 14, 2025
7d33768
Update tail-call.yml
Fidget-Spinner Jan 14, 2025
2a860a2
remove unused macro
Fidget-Spinner Jan 16, 2025
a5e7c2a
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 16, 2025
df5d01c
catch up with main
Fidget-Spinner Jan 16, 2025
1b6f969
Address Hugo's review
Fidget-Spinner Jan 17, 2025
e13aab9
Update 2025-01-10-18-56-20.gh-issue-128563.baDvls.rst
Fidget-Spinner Jan 17, 2025
60d0d9b
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 17, 2025
297f5b9
Update generated_tail_call_handlers.c.h
Fidget-Spinner Jan 17, 2025
e4f2147
Make deopts significantly more efficient
Fidget-Spinner Jan 20, 2025
aa3d408
reduce diff
Fidget-Spinner Jan 20, 2025
8f1f0b9
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 20, 2025
82ed698
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 22, 2025
aa1a5f6
Update generated_tail_call_handlers.c.h
Fidget-Spinner Jan 22, 2025
fda19f4
partial fix (waiting for #129112 )
Fidget-Spinner Jan 22, 2025
1a21607
Make it opt-in
Fidget-Spinner Jan 23, 2025
15da9c7
Squashed commit of the following:
Fidget-Spinner Jan 25, 2025
665c0f6
Catch up with labels-as-dsl branch
Fidget-Spinner Jan 25, 2025
09065ee
refactor
Fidget-Spinner Jan 25, 2025
08ce01a
Address review
Fidget-Spinner Jan 25, 2025
e7b791b
fix test
Fidget-Spinner Jan 25, 2025
f9fff87
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 25, 2025
7ea7ddc
Update generated_tail_call_handlers.c.h
Fidget-Spinner Jan 25, 2025
627fb59
Address review by Hugo
Fidget-Spinner Jan 25, 2025
368f60e
Make mypy happy
Fidget-Spinner Jan 25, 2025
68cd6e5
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 26, 2025
b7f5faf
Update generated_tail_call_handlers.c.h
Fidget-Spinner Jan 26, 2025
720a1d0
Merge remote-tracking branch 'upstream/main' into tail-call
Fidget-Spinner Jan 27, 2025
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
7 changes: 6 additions & 1 deletion Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ typedef struct _PyInterpreterFrame {
_PyStackRef *stackpointer;
uint16_t return_offset; /* Only relevant during a function call */
char owner;
char visited;
char visited:4;
char is_entry_frame:4;
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
/* Locals and stack */
_PyStackRef localsplus[1];
} _PyInterpreterFrame;
Expand Down Expand Up @@ -214,6 +215,8 @@ _PyFrame_Initialize(
frame->localsplus[i] = PyStackRef_NULL;
}

frame->is_entry_frame = 0;

#ifdef Py_GIL_DISABLED
// On GIL disabled, we walk the entire stack in GC. Since stacktop
// is not always in sync with the real stack pointer, we have
Expand Down Expand Up @@ -394,6 +397,8 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
frame->visited = 0;
frame->return_offset = 0;

frame->is_entry_frame = 0;

#ifdef Py_GIL_DISABLED
assert(code->co_nlocalsplus == 0);
for (int i = 0; i < code->co_stacksize; i++) {
Expand Down
170 changes: 161 additions & 9 deletions Lib/test/test_generated_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import tempfile
import unittest
import textwrap
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved

from io import StringIO
from test import support
Expand Down Expand Up @@ -36,6 +37,7 @@ def skip_if_different_mount_drives():
import parser
from stack import Local, Stack
import tier1_generator
import tier1_tail_call_generator
import opcode_metadata_generator
import optimizer_generator

Expand Down Expand Up @@ -538,7 +540,7 @@ def test_error_if_plain(self):
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP);
if (cond) goto label;
if (cond) CEVAL_GOTO(label);
DISPATCH();
}
"""
Expand All @@ -555,7 +557,7 @@ def test_error_if_plain_with_comment(self):
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP);
if (cond) goto label;
if (cond) CEVAL_GOTO(label);
// Comment is ok
DISPATCH();
}
Expand All @@ -582,7 +584,7 @@ def test_error_if_pop(self):
right = stack_pointer[-1];
left = stack_pointer[-2];
SPAM(left, right);
if (cond) goto pop_2_label;
if (cond) CEVAL_GOTO(pop_2_label);
res = 0;
stack_pointer[-2] = res;
stack_pointer += -1;
Expand Down Expand Up @@ -611,7 +613,7 @@ def test_error_if_pop_with_result(self):
right = stack_pointer[-1];
left = stack_pointer[-2];
res = SPAM(left, right);
if (cond) goto pop_2_label;
if (cond) CEVAL_GOTO(pop_2_label);
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Expand Down Expand Up @@ -929,7 +931,7 @@ def test_array_error_if(self):
if (oparg == 0) {
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
goto somewhere;
CEVAL_GOTO(somewhere);
}
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
Expand Down Expand Up @@ -1392,7 +1394,7 @@ def test_pop_on_error_peeks(self):
// THIRD
{
// Mark j and k as used
if (cond) goto pop_2_error;
if (cond) CEVAL_GOTO(pop_2_error);
}
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
Expand Down Expand Up @@ -1435,7 +1437,7 @@ def test_push_then_error(self):
stack_pointer[1] = b;
stack_pointer += 2;
assert(WITHIN_STACK_BOUNDS());
goto error;
CEVAL_GOTO(error);
}
}
stack_pointer[0] = a;
Expand All @@ -1462,14 +1464,14 @@ def test_error_if_true(self):
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP1);
goto here;
CEVAL_GOTO(here);
}

TARGET(OP2) {
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP2);
goto there;
CEVAL_GOTO(there);
}
"""
self.run_cases_test(input, output)
Expand Down Expand Up @@ -1739,6 +1741,156 @@ def test_no_escaping_calls_in_branching_macros(self):
with self.assertRaises(SyntaxError):
self.run_cases_test(input, "")

class TestGeneratedTailCallErorHandlers(unittest.TestCase):
def setUp(self) -> None:
super().setUp()
self.maxDiff = None

self.temp_dir = tempfile.gettempdir()
self.temp_input_filename = os.path.join(self.temp_dir, "input.txt")
self.temp_output_filename = os.path.join(self.temp_dir, "output.txt")
self.temp_metadata_filename = os.path.join(self.temp_dir, "metadata.txt")
self.temp_pymetadata_filename = os.path.join(self.temp_dir, "pymetadata.txt")
self.temp_executor_filename = os.path.join(self.temp_dir, "executor.txt")

def tearDown(self) -> None:
for filename in [
self.temp_input_filename,
self.temp_output_filename,
self.temp_metadata_filename,
self.temp_pymetadata_filename,
self.temp_executor_filename,
]:
try:
os.remove(filename)
except:
pass
super().tearDown()

def run_cases_test(self, input: str, expected: str):
with open(self.temp_input_filename, "w+") as temp_input:
temp_input.write(textwrap.dedent(input))
temp_input.flush()

with handle_stderr():
tier1_tail_call_generator.generate_label_handlers_from_files(
self.temp_input_filename, self.temp_output_filename
)

with open(self.temp_output_filename) as temp_output:
lines = temp_output.readlines()
while lines and lines[0].startswith(("// ", "#", " #", "\n")):
lines.pop(0)
while lines and lines[-1].startswith(("#", "\n")):
lines.pop(-1)
actual = "".join(lines)

self.assertEqual(actual.strip(), textwrap.dedent(expected).strip())

def test_correctly_finds_pyeval_framedefault(self):
input = """
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{

/* END_BASE_INTERPRETER */
}
"""
output = """
"""
self.run_cases_test(input, output)

def test_simple_label(self):
input = """
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{

TAIL_CALL_TARGET(error):
DO_THING();
/* END_BASE_INTERPRETER */
}
"""
output = """
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS);

Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS)
{
DO_THING();
/* END_BASE_INTERPRETER */
}
"""
self.run_cases_test(input, output)

def test_fallthrough_label(self):
input = """
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{

TAIL_CALL_TARGET(error):
DO_THING();
TAIL_CALL_TARGET(fallthrough):
DO_THING2();
/* END_BASE_INTERPRETER */
}
"""
output = """
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_fallthrough(TAIL_CALL_PARAMS);

Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS)
{
DO_THING();
CEVAL_GOTO(fallthrough);
}

Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_fallthrough(TAIL_CALL_PARAMS)
{
DO_THING2();
/* END_BASE_INTERPRETER */
}
"""
self.run_cases_test(input, output)

def test_transform_gotos(self):
input = """
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{

TAIL_CALL_TARGET(error):
if (thing) {
goto fallthrough;
}
DO_THING();
TAIL_CALL_TARGET(fallthrough):
DO_THING2();
/* END_BASE_INTERPRETER */
}
"""
output = """
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_fallthrough(TAIL_CALL_PARAMS);

Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS)
{
if (thing) {
CEVAL_GOTO(fallthrough);
}
DO_THING();
CEVAL_GOTO(fallthrough);
}

Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_fallthrough(TAIL_CALL_PARAMS)
{
DO_THING2();
/* END_BASE_INTERPRETER */
}
"""
self.run_cases_test(input, output)


class TestGeneratedAbstractCases(unittest.TestCase):
def setUp(self) -> None:
super().setUp()
Expand Down
9 changes: 8 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1985,7 +1985,7 @@ Objects/mimalloc/page.o: $(srcdir)/Objects/mimalloc/page-queue.c
.PHONY: regen-cases
regen-cases: \
regen-opcode-ids regen-opcode-targets regen-uop-ids regen-opcode-metadata-py \
regen-generated-cases regen-executor-cases regen-optimizer-cases \
regen-generated-cases regen-generated-tail-call-handlers regen-executor-cases regen-optimizer-cases \
regen-opcode-metadata regen-uop-metadata

.PHONY: regen-opcode-ids
Expand Down Expand Up @@ -2018,6 +2018,12 @@ regen-generated-cases:
-o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c
$(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new

.PHONY: regen-generated-tail-call-handlers
regen-generated-tail-call-handlers:
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier1_tail_call_generator.py \
-o $(srcdir)/Python/generated_tail_call_handlers.c.h.new $(srcdir)/Python/bytecodes.c
$(UPDATE_FILE) $(srcdir)/Python/generated_tail_call_handlers.c.h $(srcdir)/Python/generated_tail_call_handlers.c.h.new

.PHONY: regen-executor-cases
regen-executor-cases:
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_generator.py \
Expand Down Expand Up @@ -2055,6 +2061,7 @@ Python/ceval.o: \
$(srcdir)/Python/ceval_macros.h \
$(srcdir)/Python/condvar.h \
$(srcdir)/Python/generated_cases.c.h \
$(srcdir)/Python/generated_tail_call_handlers.c.h \
$(srcdir)/Python/executor_cases.c.h \
$(srcdir)/Python/opcode_targets.h

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A new type of interpreter has been added to CPython. This interpreter uses tail calls for its instruction handlers. Preliminary benchmark results suggest 7.4-14.7% geometric mean faster on pyperformance (depending on platform), and up to 45% faster on Python-intensive workloads. Patch by Ken Jin, with ideas by Garret Gu, Haoran Xu, and Josh Haberman.
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading