Skip to content

Commit 7e1b8a5

Browse files
Address review partially
1 parent 5b79a66 commit 7e1b8a5

File tree

7 files changed

+2514
-586
lines changed

7 files changed

+2514
-586
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
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-11% geometric mean faster on pyperformance (depending on platform), and up to 30% faster on Python-intensive workloads. This interpreter currently only works on newer compilers, such as ``clang-19``. Other compilers will continue using the old interpreter. Patch by Ken Jin, with ideas by Mark Shannon, Garret Gu, Haoran Xu, and Josh Haberman.
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-11% geometric mean faster on pyperformance (depending on platform), and up to 30% faster on Python-intensive workloads. This interpreter currently only works on newer compilers, such as ``clang-19``. Other compilers will continue using the old interpreter. Patch by Ken Jin, with ideas on how to implement this in CPython by Mark Shannon, Garret Gu, Haoran Xu, and Josh Haberman.

Python/ceval_macros.h

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -282,24 +282,6 @@ GETITEM(PyObject *v, Py_ssize_t i) {
282282
#define UPDATE_MISS_STATS(INSTNAME) ((void)0)
283283
#endif
284284

285-
#ifdef Py_TAIL_CALL_INTERP
286-
# define GO_TO_INSTRUCTION_IF(COND, INSTNAME, SIZE) \
287-
if ((COND)) { \
288-
/* This is only a single jump on release builds! */ \
289-
UPDATE_MISS_STATS((INSTNAME)); \
290-
assert(_PyOpcode_Deopt[opcode] == (INSTNAME)); \
291-
Py_MUSTTAIL \
292-
return (INSTRUCTION_TABLE[INSTNAME])(frame, stack_pointer, tstate, next_instr - 1 - SIZE, opcode, oparg); \
293-
}
294-
#else
295-
# define GO_TO_INSTRUCTION_IF(COND, INSTNAME, SIZE) \
296-
if ((COND)) { \
297-
/* This is only a single jump on release builds! */ \
298-
UPDATE_MISS_STATS((INSTNAME)); \
299-
assert(_PyOpcode_Deopt[opcode] == (INSTNAME)); \
300-
goto PREDICTED_##INSTNAME; \
301-
}
302-
#endif
303285

304286
// Try to lock an object in the free threading build, if it's not already
305287
// locked. Use with a DEOPT_IF() to deopt if the object is already locked.

Python/generated_cases.c.h

Lines changed: 1236 additions & 280 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_tail_call_handlers.c.h

Lines changed: 1236 additions & 280 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/cases_generator/generators_common.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,21 @@ def deopt_if(
150150
storage: Storage,
151151
inst: Instruction | None,
152152
) -> bool:
153-
self.out.emit_at("GO_TO_INSTRUCTION_IF", tkn)
153+
self.out.start_line()
154+
self.out.emit("if (")
154155
lparen = next(tkn_iter)
155-
self.emit(lparen)
156156
assert lparen.kind == "LPAREN"
157157
first_tkn = tkn_iter.peek()
158158
emit_to(self.out, tkn_iter, "RPAREN")
159+
self.emit(") {\n")
159160
next(tkn_iter) # Semi colon
160-
self.out.emit(", ")
161161
assert inst is not None
162162
assert inst.family is not None
163-
self.out.emit(inst.family.name)
164-
self.out.emit(", ")
165-
self.out.emit(inst.family.size)
166-
self.out.emit(");\n")
163+
family_name = inst.family.name
164+
self.emit(f"UPDATE_MISS_STATS({family_name});\n")
165+
self.emit(f"assert(_PyOpcode_Deopt[opcode] == ({family_name}));\n")
166+
self.emit(f"goto PREDICTED_{family_name};\n")
167+
self.emit("}\n")
167168
return not always_true(first_tkn)
168169

169170
exit_if = deopt_if

Tools/cases_generator/tier1_generator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ def uses_this(inst: Instruction) -> bool:
129129
for cache in uop.caches:
130130
if cache.name != "unused":
131131
return True
132+
for tkn in uop.body:
133+
if tkn.kind == "IDENTIFIER" and (tkn.text == "DEOPT_IF" or tkn.text == "EXIT_IF"):
134+
return True
132135
return False
133136

134137

Tools/cases_generator/tier1_tail_call_generator.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
CWriter,
1515
Emitter,
1616
TokenIterator,
17+
emit_to,
18+
always_true,
1719
)
1820

1921
from analyzer import (
@@ -67,6 +69,34 @@ def go_to_instruction(
6769
self.emit(f"Py_MUSTTAIL return (INSTRUCTION_TABLE[{name.text}])(frame, stack_pointer, tstate, next_instr - 1 - {size}, opcode, oparg);\n")
6870
return True
6971

72+
def deopt_if(
73+
self,
74+
tkn: Token,
75+
tkn_iter: TokenIterator,
76+
uop: Uop,
77+
storage: Storage,
78+
inst: Instruction | None,
79+
) -> bool:
80+
self.out.start_line()
81+
self.out.emit("if (")
82+
lparen = next(tkn_iter)
83+
assert lparen.kind == "LPAREN"
84+
first_tkn = tkn_iter.peek()
85+
emit_to(self.out, tkn_iter, "RPAREN")
86+
self.emit(") {\n")
87+
next(tkn_iter) # Semi colon
88+
assert inst is not None
89+
assert inst.family is not None
90+
family_name = inst.family.name
91+
self.emit(f"UPDATE_MISS_STATS({family_name});\n")
92+
self.emit(f"assert(_PyOpcode_Deopt[opcode] == ({family_name}));\n")
93+
self.emit(f"Py_MUSTTAIL return _TAIL_CALL_{family_name}(frame, stack_pointer, tstate, this_instr, opcode, oparg);\n")
94+
self.emit("}\n")
95+
return not always_true(first_tkn)
96+
97+
98+
exit_if = deopt_if
99+
70100
class TailCallLabelsEmitter(Emitter):
71101
def __init__(self, out: CWriter):
72102
super().__init__(out)

0 commit comments

Comments
 (0)