From a9eac0f241e21f7231672bfe6c252d997f5c0ee8 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 14 Nov 2025 20:46:45 +0000 Subject: [PATCH 1/5] JIT: _EXIT_TRACE to ENTER_EXECUTOR rather than _DEOPT --- Include/internal/pycore_optimizer.h | 2 +- Python/bytecodes.c | 4 ++-- Python/generated_cases.c.h | 4 ++-- Python/optimizer.c | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 653285a2c6b79b..0307a174e77346 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -362,7 +362,7 @@ PyAPI_FUNC(int) _PyDumpExecutors(FILE *out); extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp); #endif -int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, bool stop_tracing); +int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, int stop_tracing_opcode); int _PyJit_TryInitializeTracing(PyThreadState *tstate, _PyInterpreterFrame *frame, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2c798855a71f55..018f2942831582 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5644,7 +5644,7 @@ dummy_func( bool stop_tracing = (opcode == WITH_EXCEPT_START || opcode == RERAISE || opcode == CLEANUP_THROW || opcode == PUSH_EXC_INFO || opcode == INTERPRETER_EXIT); - int full = !_PyJit_translate_single_bytecode_to_trace(tstate, frame, next_instr, stop_tracing); + int full = !_PyJit_translate_single_bytecode_to_trace(tstate, frame, next_instr, stop_tracing ? _DEOPT : 0); if (full) { LEAVE_TRACING(); int err = stop_tracing_and_jit(tstate, frame); @@ -5684,7 +5684,7 @@ dummy_func( #if _Py_TIER2 assert(IS_JIT_TRACING()); int opcode = next_instr->op.code; - _PyJit_translate_single_bytecode_to_trace(tstate, frame, NULL, true); + _PyJit_translate_single_bytecode_to_trace(tstate, frame, NULL, _EXIT_TRACE); LEAVE_TRACING(); int err = stop_tracing_and_jit(tstate, frame); ERROR_IF(err < 0); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a984da6dc912a2..30b72f58bd65f4 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -12268,7 +12268,7 @@ JUMP_TO_LABEL(error); opcode == RERAISE || opcode == CLEANUP_THROW || opcode == PUSH_EXC_INFO || opcode == INTERPRETER_EXIT); _PyFrame_SetStackPointer(frame, stack_pointer); - int full = !_PyJit_translate_single_bytecode_to_trace(tstate, frame, next_instr, stop_tracing); + int full = !_PyJit_translate_single_bytecode_to_trace(tstate, frame, next_instr, stop_tracing ? _DEOPT : 0); stack_pointer = _PyFrame_GetStackPointer(frame); if (full) { LEAVE_TRACING(); @@ -12314,7 +12314,7 @@ JUMP_TO_LABEL(error); assert(IS_JIT_TRACING()); int opcode = next_instr->op.code; _PyFrame_SetStackPointer(frame, stack_pointer); - _PyJit_translate_single_bytecode_to_trace(tstate, frame, NULL, true); + _PyJit_translate_single_bytecode_to_trace(tstate, frame, NULL, _EXIT_TRACE); stack_pointer = _PyFrame_GetStackPointer(frame); LEAVE_TRACING(); _PyFrame_SetStackPointer(frame, stack_pointer); diff --git a/Python/optimizer.c b/Python/optimizer.c index 65007a256d0c3b..081d159687f0a3 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -574,7 +574,7 @@ _PyJit_translate_single_bytecode_to_trace( PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, - bool stop_tracing) + int stop_tracing_opocde) { #ifdef Py_DEBUG @@ -637,8 +637,8 @@ _PyJit_translate_single_bytecode_to_trace( goto full; } - if (stop_tracing) { - ADD_TO_TRACE(_DEOPT, 0, 0, target); + if (stop_tracing_opocde != 0) { + ADD_TO_TRACE(stop_tracing_opocde, 0, 0, target); goto done; } From 2cf4f585f2010a73dc1803e48206ff66698466bf Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 14 Nov 2025 22:07:42 +0000 Subject: [PATCH 2/5] Add a test --- Lib/test/test_capi/test_opt.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index f06c6cbda2976c..554716f6984669 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -40,6 +40,17 @@ def get_first_executor(func): pass return None +def get_all_executors(func): + code = func.__code__ + co_code = code.co_code + execs = [] + for i in range(0, len(co_code), 2): + try: + execs.append(_opcode.get_executor(code, i)) + except ValueError: + pass + return execs + def iter_opnames(ex): for item in ex: @@ -2629,6 +2640,18 @@ def gen(): next(g) """ % _testinternalcapi.SPECIALIZATION_THRESHOLD)) + def test_executor_side_exits_create_another_executor(self): + def f(): + for x in range(TIER2_THRESHOLD + 3): + for y in range(TIER2_THRESHOLD + 3): + z = x + y + + f() + # Inner loop warms up first. + # Outer loop warms up later, linking to the innter one. + # Therefore, at least two executors. + self.assertGreaterEqual(len(get_all_executors(f)), 2) + def global_identity(x): return x From ca89c3ca323135f5d7b836f07abfb251a64d6c79 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 14 Nov 2025 22:24:39 +0000 Subject: [PATCH 3/5] spelling --- Lib/test/test_capi/test_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 554716f6984669..8f0defdaba202c 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2648,7 +2648,7 @@ def f(): f() # Inner loop warms up first. - # Outer loop warms up later, linking to the innter one. + # Outer loop warms up later, linking to the inner one. # Therefore, at least two executors. self.assertGreaterEqual(len(get_all_executors(f)), 2) From 2d735c3f45cb1ea318ac0187912c0f63826f7f92 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 15 Nov 2025 14:24:25 +0000 Subject: [PATCH 4/5] Fix test --- Lib/test/test_capi/test_opt.py | 23 ++++++++++++++++++----- Python/optimizer.c | 6 +++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 8f0defdaba202c..19734fa909ad34 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -43,13 +43,13 @@ def get_first_executor(func): def get_all_executors(func): code = func.__code__ co_code = code.co_code - execs = [] + executors = [] for i in range(0, len(co_code), 2): try: - execs.append(_opcode.get_executor(code, i)) + executors.append(_opcode.get_executor(code, i)) except ValueError: pass - return execs + return executors def iter_opnames(ex): @@ -2647,10 +2647,23 @@ def f(): z = x + y f() + all_executors = get_all_executors(f) # Inner loop warms up first. # Outer loop warms up later, linking to the inner one. - # Therefore, at least two executors. - self.assertGreaterEqual(len(get_all_executors(f)), 2) + # Therefore, we have at least two executors. + self.assertGreaterEqual(len(all_executors), 2) + for executor in all_executors: + opnames = list(get_opnames(executor)) + # Assert all executors first terminator ends in + # _JUMP_TO_TOP or _EXIT_TRACE, not _DEOPT + for idx, op in enumerate(opnames): + if op == "_EXIT_TRACE" or op == "_JUMP_TO_TOP": + break + elif op == "_DEOPT": + self.fail(f"_DEOPT encountered first at executor" + f" {executor} at offset {idx} rather" + f" than expected _EXIT_TRACE") + def global_identity(x): diff --git a/Python/optimizer.c b/Python/optimizer.c index 081d159687f0a3..9db894f0bf054a 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -574,7 +574,7 @@ _PyJit_translate_single_bytecode_to_trace( PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, - int stop_tracing_opocde) + int stop_tracing_opcode) { #ifdef Py_DEBUG @@ -637,8 +637,8 @@ _PyJit_translate_single_bytecode_to_trace( goto full; } - if (stop_tracing_opocde != 0) { - ADD_TO_TRACE(stop_tracing_opocde, 0, 0, target); + if (stop_tracing_opcode != 0) { + ADD_TO_TRACE(stop_tracing_opcode, 0, 0, target); goto done; } From 93c922633247eb9a1e0dac8087b2a4ca2917c7f1 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 15 Nov 2025 15:10:37 +0000 Subject: [PATCH 5/5] Update test_opt.py --- Lib/test/test_capi/test_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 19734fa909ad34..25372fee58e0d7 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2655,7 +2655,7 @@ def f(): for executor in all_executors: opnames = list(get_opnames(executor)) # Assert all executors first terminator ends in - # _JUMP_TO_TOP or _EXIT_TRACE, not _DEOPT + # _EXIT_TRACE or _JUMP_TO_TOP, not _DEOPT for idx, op in enumerate(opnames): if op == "_EXIT_TRACE" or op == "_JUMP_TO_TOP": break