Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import dis
import itertools
import sys
import textwrap
Expand Down Expand Up @@ -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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to clarify a little bit about this test.

When the jit counter is enabled we enter into _JIT op with original oparg = 299
Then oparg truncates with buggy loop

while (oparg > 255) {
       oparg >>= 8
       insert_exec_at--;
}

oparg becomes 1, and we pass it to _PyJit_TryInitializeTracing

In _PyJit_TryInitializeTracing we store this broken oparg

tracer->prev_state.instr_oparg = oparg;

And call ENTER_TRACING()

In next calls code uses TRACE_RECORD which in turn calls _PyJit_translate_single_bytecode_to_trace. This is the point where shift exists

    // Rewind EXTENDED_ARG so that we see the whole thing.
    // We must point to the first EXTENDED_ARG when deopting.
    int oparg = tracer->prev_state.instr_oparg;
    int opcode = this_instr->op.code;
    int rewind_oparg = oparg;
    while (rewind_oparg > 255) {
        rewind_oparg >>= 8;
        target--;
    }

We get the broken oparg from tracer->prev_state.instr_oparg and the loop does not executes because 1 < 255. target does not decrements and the instruction is not correct (should be EXTENDED_ARG but actually JUMP_BACKWARD)

P.S: I may not be fully correct in the internals but the test catches the shift on the buggy build

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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a truncated ``oparg`` being passed to JIT trace initialization for a
``JUMP_BACKWARD`` with an ``EXTENDED_ARG``.
6 changes: 2 additions & 4 deletions Modules/_testinternalcapi/test_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 2 additions & 4 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading