From a2909692ad5406a5bc41d69304d241b9b721b9bd Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 27 Jun 2026 13:09:54 +0300 Subject: [PATCH 1/2] gh-152192: Fix JUMP_BACKWARD passing a truncated oparg to the jit tracer --- Lib/test/test_capi/test_opt.py | 21 +++++++++++++++++++ ...-06-26-22-38-21.gh-issue-152192.lX2jIM.rst | 2 ++ Modules/_testinternalcapi/test_cases.c.h | 6 ++---- Python/bytecodes.c | 4 ++-- Python/generated_cases.c.h | 6 ++---- 5 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2248920c266aef5..e58630bce3472db 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 000000000000000..153cbeb317a2452 --- /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 76493327ca0a00b..1407c66f0459994 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 8afa0be702357de..846b4b50f8e4152 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 2cdf48c559a292c..8f6b51194849ad3 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, From d1d235444a0fa0e433fbffd2db5bc75c9cfcd70c Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Mon, 29 Jun 2026 14:18:35 +0300 Subject: [PATCH 2/2] added comment note on test --- Lib/test/test_capi/test_opt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index e58630bce3472db..25b2c393e6773de 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -249,6 +249,8 @@ def many_vars(): for opcode, oparg, _, operand in list(ex))) def test_jump_backward_extended_arg(self): + # gh-152192: a JUMP_BACKWARD that needs an EXTENDED_ARG must record its + # deopt target at the EXTENDED_ARG, not the JUMP_BACKWARD. ns = {} src = ("def f(n):\n" " i = 0\n"