diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index fd4221f0816d24..1e195f0c62fce2 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -351,9 +351,10 @@ _PyEval_SpecialMethodCanSuggest(PyObject *self, int oparg); #define _PY_EVAL_PLEASE_STOP_BIT (1U << 5) #define _PY_EVAL_EXPLICIT_MERGE_BIT (1U << 6) #define _PY_EVAL_JIT_INVALIDATE_COLD_BIT (1U << 7) +#define _PY_EVAL_JIT_FORCE_TIER1_BIT (1U << 8) /* Reserve a few bits for future use */ -#define _PY_EVAL_EVENTS_BITS 8 +#define _PY_EVAL_EVENTS_BITS 9 #define _PY_EVAL_EVENTS_MASK ((1 << _PY_EVAL_EVENTS_BITS)-1) static inline void diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index aaa5050208ced9..d3f631fcc6f2ea 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1,5 +1,6 @@ import contextlib import itertools +import subprocess import sys import textwrap import unittest @@ -9,7 +10,7 @@ import _opcode -from test.support import (script_helper, requires_specialization, +from test.support import (SHORT_TIMEOUT, script_helper, requires_specialization, import_helper, Py_GIL_DISABLED, requires_jit_enabled, reset_code) @@ -5994,6 +5995,51 @@ def __init__(self, name): PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE="1") self.assertEqual(result[0].rc, 0, result) + def test_side_exit_to_executor_makes_progress(self): + script = textwrap.dedent(""" + classes = [] + + def make_iter(): + class It: + def __init__(self): + self.i = 0 + + def __iter__(self): + return self + + def __next__(self): + self.i += 1 + if self.i > 1000: + raise StopIteration + return self.i + + classes.append(It) + return It() + + def f(n): + for outer in range(n): + for x in make_iter(): + pass + + f(200) + """) + env = os.environ.copy() + env.update({ + "PYTHON_JIT": "1", + "PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE": "1", + }) + try: + result = subprocess.run( + [sys.executable, "-X", "faulthandler", "-c", script], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + timeout=SHORT_TIMEOUT, + ) + except subprocess.TimeoutExpired as exc: + self.fail(f"subprocess timed out: {exc}") + self.assertEqual(result.returncode, 0, result) + def test_for_iter_gen_cleared_frame_does_not_crash(self): # See: https://github.com/python/cpython/issues/145197 result = script_helper.run_python_until_end('-c', textwrap.dedent(""" diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 3bd489122da9d4..fa89b81043500f 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -6297,6 +6297,16 @@ dummy_func( if (target->op.code == ENTER_EXECUTOR) { PyCodeObject *code = _PyFrame_GetCode(frame); executor = code->co_executors->executors[target->op.arg]; + _PyExecutorObject *previous_executor = _PyExecutor_FromExit(exit); + if (!exit->is_control_flow) { + int chain_depth = previous_executor->vm_data.chain_depth + 1; + if (executor == previous_executor || + chain_depth % MAX_CHAIN_DEPTH == 0) { + /* Avoid tier-2 self-link/wrap that bypasses the eval breaker. */ + _Py_set_eval_breaker_bit(tstate, _PY_EVAL_JIT_FORCE_TIER1_BIT); + GOTO_TIER_ONE(target); + } + } Py_INCREF(executor); assert(tstate->jit_exit == exit); exit->executor = executor; diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 2425bc1b39f0dc..2cf3656fd84102 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -1403,6 +1403,10 @@ _Py_HandlePending(PyThreadState *tstate) } #ifdef _Py_TIER2 + if ((breaker & _PY_EVAL_JIT_FORCE_TIER1_BIT) != 0) { + _Py_unset_eval_breaker_bit(tstate, _PY_EVAL_JIT_FORCE_TIER1_BIT); + } + if ((breaker & _PY_EVAL_JIT_INVALIDATE_COLD_BIT) != 0) { _Py_unset_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT); _Py_Executors_InvalidateCold(tstate->interp); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index b6a2821db3007e..b666c126a36897 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -23656,6 +23656,16 @@ if (target->op.code == ENTER_EXECUTOR) { PyCodeObject *code = _PyFrame_GetCode(frame); executor = code->co_executors->executors[target->op.arg]; + _PyExecutorObject *previous_executor = _PyExecutor_FromExit(exit); + if (!exit->is_control_flow) { + int chain_depth = previous_executor->vm_data.chain_depth + 1; + if (executor == previous_executor || + chain_depth % MAX_CHAIN_DEPTH == 0) { + _Py_set_eval_breaker_bit(tstate, _PY_EVAL_JIT_FORCE_TIER1_BIT); + SET_CURRENT_CACHED_VALUES(0); + GOTO_TIER_ONE(target); + } + } Py_INCREF(executor); assert(tstate->jit_exit == exit); exit->executor = executor;