gh-143183: Rewind stop tracing to previous target (GH-143187)
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index d090b22..ea1606f 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -3306,6 +3306,48 @@ class B: ...
for i in range(TIER2_THRESHOLD * 10):
f1()
+ def test_143183(self):
+ # https://github.com/python/cpython/issues/143183
+
+ result = script_helper.run_python_until_end('-c', textwrap.dedent(f"""
+ def f1():
+ class AsyncIter:
+ def __init__(self):
+ self.limit = 0
+ self.count = 0
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ if self.count >= self.limit:
+ ...
+ self.count += 1j
+
+ class AsyncCtx:
+ async def async_for_driver():
+ try:
+ for _ in range({TIER2_THRESHOLD}):
+ try:
+ async for _ in AsyncIter():
+ ...
+ except TypeError:
+ ...
+ except Exception:
+ ...
+
+ c = async_for_driver()
+ while True:
+ try:
+ c.send(None)
+ except StopIteration:
+ break
+
+ for _ in range({TIER2_THRESHOLD // 40}):
+ f1()
+ """), PYTHON_JIT="1")
+ self.assertEqual(result[0].rc, 0, result)
+
def global_identity(x):
return x
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-26-11-00-44.gh-issue-143183.rhxzZr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-26-11-00-44.gh-issue-143183.rhxzZr.rst
new file mode 100644
index 0000000..bee2eb6
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-26-11-00-44.gh-issue-143183.rhxzZr.rst
@@ -0,0 +1 @@
+Fix a bug in the JIT when dealing with unsupported control-flow or operations.
diff --git a/Python/optimizer.c b/Python/optimizer.c
index 5e97f20..b497ac6 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -625,6 +625,7 @@ _PyJit_translate_single_bytecode_to_trace(
int trace_length = _tstate->jit_tracer_state.prev_state.code_curr_size;
_PyUOpInstruction *trace = _tstate->jit_tracer_state.code_buffer;
int max_length = _tstate->jit_tracer_state.prev_state.code_max_size;
+ int exit_op = stop_tracing_opcode == 0 ? _EXIT_TRACE : stop_tracing_opcode;
_Py_CODEUNIT *this_instr = _tstate->jit_tracer_state.prev_state.instr;
_Py_CODEUNIT *target_instr = this_instr;
@@ -691,8 +692,11 @@ _PyJit_translate_single_bytecode_to_trace(
}
if (stop_tracing_opcode != 0) {
- ADD_TO_TRACE(stop_tracing_opcode, 0, 0, target);
- goto done;
+ // gh-143183: It's important we rewind to the last known proper target.
+ // The current target might be garbage as stop tracing usually indicates
+ // we are in something that we can't trace.
+ DPRINTF(2, "Told to stop tracing\n");
+ goto unsupported;
}
DPRINTF(2, "%p %d: %s(%d) %d %d\n", old_code, target, _PyOpcode_OpName[opcode], oparg, needs_guard_ip, old_stack_level);
@@ -703,10 +707,6 @@ _PyJit_translate_single_bytecode_to_trace(
}
#endif
- if (opcode == ENTER_EXECUTOR) {
- goto full;
- }
-
if (!_tstate->jit_tracer_state.prev_state.dependencies_still_valid) {
goto full;
}
@@ -720,11 +720,6 @@ _PyJit_translate_single_bytecode_to_trace(
if (oparg > 0xFFFF) {
DPRINTF(2, "Unsupported: oparg too large\n");
- goto unsupported;
- }
-
- // TODO (gh-140277): The constituent use one extra stack slot. So we need to check for headroom.
- if (opcode == BINARY_OP_SUBSCR_GETITEM && old_stack_level + 1 > old_code->co_stacksize) {
unsupported:
{
// Rewind to previous instruction and replace with _EXIT_TRACE.
@@ -738,7 +733,7 @@ _PyJit_translate_single_bytecode_to_trace(
int32_t old_target = (int32_t)uop_get_target(curr);
curr++;
trace_length++;
- curr->opcode = _EXIT_TRACE;
+ curr->opcode = exit_op;
curr->format = UOP_FORMAT_TARGET;
curr->target = old_target;
}
@@ -746,6 +741,7 @@ _PyJit_translate_single_bytecode_to_trace(
}
}
+
if (opcode == NOP) {
return 1;
}