https://github.com/python/cpython/commit/d0a6e15108a4b2805fa08d50201357c322e8d98e
commit: d0a6e15108a4b2805fa08d50201357c322e8d98e
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-08-25T09:38:28+09:00
summary:

[3.15] gh-154701: prevent executor self-links in JIT cold exits (GH-155323) 
(#155832)

(cherry picked from commit 716cbae06c7d9d641626dfdb783f3959edf470a4)

Co-authored-by: Hai Zhu <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>

* gh-154701: adapt JIT cold exit backport to 3.15

---------

Co-authored-by: Hai Zhu <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-10-14-56.gh-issue-154701.zulh2S.rst
M Include/internal/pycore_optimizer.h
M Include/internal/pycore_uop_metadata.h
M Lib/test/test_capi/test_opt.py
M Python/bytecodes.c
M Python/executor_cases.c.h

diff --git a/Include/internal/pycore_optimizer.h 
b/Include/internal/pycore_optimizer.h
index 8c35c4416fe3c8c..25e04171f4d5189 100644
--- a/Include/internal/pycore_optimizer.h
+++ b/Include/internal/pycore_optimizer.h
@@ -206,7 +206,7 @@ typedef struct _PyExecutorObject {
 PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset);
 
 int _Py_ExecutorInit(_PyExecutorObject *, const _PyBloomFilter *);
-void _Py_ExecutorDetach(_PyExecutorObject *);
+PyAPI_FUNC(void) _Py_ExecutorDetach(_PyExecutorObject *);
 PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void 
*obj);
 
 /* We use a bloomfilter with k = 6, m = 256
diff --git a/Include/internal/pycore_uop_metadata.h 
b/Include/internal/pycore_uop_metadata.h
index 6713e9bc95f942d..55b7f5160201683 100644
--- a/Include/internal/pycore_uop_metadata.h
+++ b/Include/internal/pycore_uop_metadata.h
@@ -413,7 +413,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
     [_ERROR_POP_N] = HAS_ARG_FLAG | HAS_SYNC_SP_FLAG,
     [_SPILL_OR_RELOAD] = 0,
     [_TIER2_RESUME_CHECK] = HAS_PERIODIC_FLAG,
-    [_COLD_EXIT] = HAS_SYNC_SP_FLAG,
+    [_COLD_EXIT] = HAS_ESCAPES_FLAG | HAS_SYNC_SP_FLAG,
     [_COLD_DYNAMIC_EXIT] = HAS_SYNC_SP_FLAG,
     [_GUARD_CODE_VERSION__PUSH_FRAME] = HAS_EXIT_FLAG,
     [_GUARD_CODE_VERSION_YIELD_VALUE] = HAS_EXIT_FLAG,
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index b2439dfdeb7a221..06271a0e2732754 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -5,13 +5,14 @@
 import unittest
 import gc
 import os
+import subprocess
 import types
 
 import _opcode
 
 from test.support import (script_helper, requires_specialization,
                           import_helper, Py_GIL_DISABLED, requires_jit_enabled,
-                          reset_code)
+                          reset_code, SHORT_TIMEOUT)
 
 _testinternalcapi = import_helper.import_module("_testinternalcapi")
 
@@ -6200,6 +6201,31 @@ def __exit__(self, e, v, t): ...
                 f1()
         """), PYTHON_JIT="1")
 
+    def test_for_iter_side_exit_does_not_self_link(self):
+        subprocess.run([sys.executable, "-c", textwrap.dedent("""
+            from _testinternalcapi import TIER2_THRESHOLD
+
+            def exhaust(iterator):
+                for _ in iterator:
+                    pass
+
+            values = range(TIER2_THRESHOLD)
+            # After the initial trace, MAX_CHAIN_DEPTH side exits cause the 
final
+            # executor to be installed at FOR_ITER.
+            warmup_iterators = (
+                iter(set(values)),
+                iter(dict.fromkeys(values)),
+                iter(values),
+                enumerate(values),
+                zip(values, values),
+            )
+            for iterator in warmup_iterators:
+                exhaust(iterator)
+
+            # A different iterator type must not link that executor to itself.
+            exhaust(map(bool, values))
+        """)], check=True, timeout=SHORT_TIMEOUT)
+
 def global_identity(x):
     return x
 
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-10-14-56.gh-issue-154701.zulh2S.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-10-14-56.gh-issue-154701.zulh2S.rst
new file mode 100644
index 000000000000000..c32803e71cbf993
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-10-14-56.gh-issue-154701.zulh2S.rst
@@ -0,0 +1 @@
+Fix an infinite loop in JIT when a ``FOR_ITER`` side exit links an executor 
back to itself.
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 4f0d0227cc2eb04..f841e12793a8001 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -6308,6 +6308,10 @@ dummy_func(
             if (target->op.code == ENTER_EXECUTOR) {
                 PyCodeObject *code = _PyFrame_GetCode(frame);
                 executor = code->co_executors->executors[target->op.arg];
+                if (executor == _PyExecutor_FromExit(exit)) {
+                    _Py_ExecutorDetach(executor);
+                    GOTO_TIER_ONE(target);
+                }
                 Py_INCREF(executor);
                 assert(tstate->jit_exit == exit);
                 exit->executor = executor;
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 7644c2d38d7f068..dc5c68794e8a5f5 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -23676,6 +23676,13 @@
             if (target->op.code == ENTER_EXECUTOR) {
                 PyCodeObject *code = _PyFrame_GetCode(frame);
                 executor = code->co_executors->executors[target->op.arg];
+                if (executor == _PyExecutor_FromExit(exit)) {
+                    _PyFrame_SetStackPointer(frame, stack_pointer);
+                    _Py_ExecutorDetach(executor);
+                    stack_pointer = _PyFrame_GetStackPointer(frame);
+                    SET_CURRENT_CACHED_VALUES(0);
+                    GOTO_TIER_ONE(target);
+                }
                 Py_INCREF(executor);
                 assert(tstate->jit_exit == exit);
                 exit->executor = executor;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to