From d6b814eb848497863ba0d4dae884214434da09bc Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 27 May 2026 15:16:34 +0100 Subject: [PATCH] Add show_jit option to dis to show ENTER_EXECUTOR instructions --- Lib/dis.py | 47 +++++++++++-------- Lib/test/test_dis.py | 34 +++++++++++++- ...-05-28-23-38-46.gh-issue-150478.qEr0E-.rst | 3 ++ 3 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst diff --git a/Lib/dis.py b/Lib/dis.py index cb32a4c0c7d3037..318729091fa098c 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -84,7 +84,7 @@ def _try_compile(source, name): return compile(source, name, 'exec') def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, - show_offsets=False, show_positions=False): + show_offsets=False, show_positions=False, show_jit=False): """Disassemble classes, methods, functions, and other compiled objects. With no argument, disassemble the last traceback. @@ -95,7 +95,8 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, """ if x is None: distb(file=file, show_caches=show_caches, adaptive=adaptive, - show_offsets=show_offsets, show_positions=show_positions) + show_offsets=show_offsets, show_positions=show_positions, + show_jit=show_jit) return # Extract functions from methods. if hasattr(x, '__func__'): @@ -116,12 +117,14 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, if isinstance(x1, _have_code): print("Disassembly of %s:" % name, file=file) try: - dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions) + dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) except TypeError as msg: print("Sorry:", msg, file=file) print(file=file) elif hasattr(x, 'co_code'): # Code object - _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions) + _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) elif isinstance(x, (bytes, bytearray)): # Raw bytecode labels_map = _make_labels_map(x) label_width = 4 + len(str(len(labels_map))) @@ -132,12 +135,13 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, arg_resolver = ArgResolver(labels_map=labels_map) _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) elif isinstance(x, str): # Source code - _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions) + _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) else: raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) -def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False): +def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False, show_jit=False): """Disassemble a traceback (default: last traceback).""" if tb is None: try: @@ -148,7 +152,8 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets except AttributeError: raise RuntimeError("no last traceback to disassemble") from None while tb.tb_next: tb = tb.tb_next - disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions) + disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) # The inspect module interrogates this dictionary to build its # list of CO_* constants. It is also used by pretty_flags to @@ -216,14 +221,14 @@ def _deoptop(op): name = _all_opname[op] return _all_opmap[deoptmap[name]] if name in deoptmap else op -def _get_code_array(co, adaptive): +def _get_code_array(co, adaptive, show_jit): if adaptive: code = co._co_code_adaptive res = [] found = False for i in range(0, len(code), 2): op, arg = code[i], code[i+1] - if op == ENTER_EXECUTOR: + if op == ENTER_EXECUTOR and not show_jit: try: ex = get_executor(co, i) except (ValueError, RuntimeError): @@ -656,7 +661,7 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = 'not in' if argval else 'in' return argval, argrepr -def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False): +def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False, show_jit=False): """Iterator for the opcodes in methods, functions or code Generates a series of Instruction named tuples giving the details of @@ -679,7 +684,7 @@ def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False): names=co.co_names, varname_from_oparg=co._varname_from_oparg, labels_map=_make_labels_map(original_code)) - return _get_instructions_bytes(_get_code_array(co, adaptive), + return _get_instructions_bytes(_get_code_array(co, adaptive, show_jit), linestarts=linestarts, line_offset=line_offset, co_positions=co.co_positions(), @@ -792,6 +797,8 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N positions = Positions(*next(co_positions, ())) deop = _deoptop(op) op = code[offset] + if op == ENTER_EXECUTOR: + arg = code[offset+1] if arg_resolver: argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset) @@ -820,7 +827,7 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, - show_offsets=False, show_positions=False): + show_offsets=False, show_positions=False, show_jit=False): """Disassemble a code object.""" linestarts = dict(findlinestarts(co)) exception_entries = _parse_exception_table(co) @@ -840,12 +847,12 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, names=co.co_names, varname_from_oparg=co._varname_from_oparg, labels_map=labels_map) - _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, + _disassemble_bytes(_get_code_array(co, adaptive, show_jit), lasti, linestarts, exception_entries=exception_entries, co_positions=co.co_positions(), original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) -def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False): - disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions) +def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False, show_jit=False): + disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) if depth is None or depth > 0: if depth is not None: depth = depth - 1 @@ -855,7 +862,8 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adap print("Disassembly of %r:" % (x,), file=file) _disassemble_recursive( x, file=file, depth=depth, show_caches=show_caches, - adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions + adaptive=adaptive, show_offsets=show_offsets, + show_positions=show_positions, show_jit=show_jit ) @@ -1054,7 +1062,7 @@ class Bytecode: Iterating over this yields the bytecode operations as Instruction instances. """ - def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False): + def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False, show_jit=False): self.codeobj = co = _get_code_object(x) if first_line is None: self.first_line = co.co_firstlineno @@ -1070,6 +1078,7 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False self.adaptive = adaptive self.show_offsets = show_offsets self.show_positions = show_positions + self.show_jit = show_jit def __iter__(self): co = self.codeobj @@ -1079,7 +1088,7 @@ def __iter__(self): names=co.co_names, varname_from_oparg=co._varname_from_oparg, labels_map=labels_map) - return _get_instructions_bytes(_get_code_array(co, self.adaptive), + return _get_instructions_bytes(_get_code_array(co, self.adaptive, self.show_jit), linestarts=self._linestarts, line_offset=self._line_offset, co_positions=co.co_positions(), @@ -1111,7 +1120,7 @@ def dis(self): else: offset = -1 with io.StringIO() as output: - code = _get_code_array(co, self.adaptive) + code = _get_code_array(co, self.adaptive, self.show_jit) offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 if self.show_positions: lineno_width = _get_positions_width(co) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 8be104585814f4a..c75992761d1334f 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -15,7 +15,8 @@ import unittest from test.support import (captured_stdout, requires_debug_ranges, requires_specialization, cpython_only, - os_helper, import_helper, reset_code) + os_helper, import_helper, reset_code, + requires_jit_enabled) from test.support.bytecode_helper import BytecodeTestCase @@ -1481,6 +1482,37 @@ def f(): self.assertEqual(assem_op, assem_cache) + @cpython_only + @requires_specialization + @requires_jit_enabled + def test_show_jit(self): + def loop(n): + for i in range(n): + pass + for _ in range(10): + loop(500) + line = loop.__code__.co_firstlineno + loop_dis = f"""\ +{line} RESUME_CHECK_JIT 0 + +{line+1} LOAD_GLOBAL_BUILTIN 1 (range + NULL) + LOAD_FAST_BORROW 0 (n) + CALL_BUILTIN_CLASS 1 + GET_ITER 0 + L1: FOR_ITER_RANGE 3 (to L2) + STORE_FAST 1 (i) + +{line+2} ENTER_EXECUTOR 0 + +{line+1} L2: END_FOR + POP_ITER + LOAD_COMMON_CONSTANT 7 (None) + RETURN_VALUE +""" + got = self.get_disassembly(loop, adaptive=True, show_jit=True) + self.do_disassembly_compare(got, loop_dis) + + class DisWithFileTests(DisTests): # Run the tests again, using the file arg instead of print diff --git a/Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst b/Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst new file mode 100644 index 000000000000000..0ceae5e313977a4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst @@ -0,0 +1,3 @@ +Add ``show_jit`` option to ``dis.dis`` to show JIT entry points in the +bytecode. This is useful for visualizing where JIT traces are entered from +the interpreter.