diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2248920c266aef..e58630bce3472d 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1,4 +1,5 @@ import contextlib +import dis import itertools import sys import textwrap @@ -247,6 +248,26 @@ def many_vars(): self.assertTrue(any((opcode, oparg, operand) == ("_LOAD_FAST_BORROW", 259, 0) for opcode, oparg, _, operand in list(ex))) + def test_jump_backward_extended_arg(self): + ns = {} + src = ("def f(n):\n" + " i = 0\n" + " while i < n:\n" + " i += 1\n" + + "".join(f" a = {j}\n" for j in range(140))) + exec(src, ns) + f = ns["f"] + + instrs = list(dis.get_instructions(f)) + ext, jb = next((p, i) for p, i in zip(instrs, instrs[1:]) + if i.opname == "JUMP_BACKWARD" and p.opname == "EXTENDED_ARG") + + f(TIER2_THRESHOLD + 1) + ex = _opcode.get_executor(f.__code__, ext.offset) + set_ips = {t for op, _, t, _ in ex if op == "_SET_IP"} + self.assertIn(ext.offset // 2, set_ips) + self.assertNotIn(jb.offset // 2, set_ips) + def test_unspecialized_unpack(self): # An example of an unspecialized opcode def testfunc(x): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst new file mode 100644 index 00000000000000..153cbeb317a245 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst @@ -0,0 +1,2 @@ +Fix a truncated ``oparg`` being passed to JIT trace initialization for a +``JUMP_BACKWARD`` with an ``EXTENDED_ARG``. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 76493327ca0a00..1407c66f045999 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -8582,8 +8582,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, @@ -11553,8 +11552,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 8afa0be702357d..846b4b50f8e415 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3560,8 +3560,8 @@ dummy_func( next_instr->op.code != ENTER_EXECUTOR) { /* Back up over EXTENDED_ARGs so executor is inserted at the correct place */ _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + // gh-152192: count with a temporary. oparg must stay intact, it's passed to the tracer below + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 2cdf48c559a292..8f6b51194849ad 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -8581,8 +8581,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, @@ -11550,8 +11549,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at,