]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-150478: Add "show_jit" option to `dis.dis` to show jit entry points (GH-150554)
authorMark Shannon <Mark.Shannon@arm.com>
Mon, 1 Jun 2026 16:52:40 +0000 (17:52 +0100)
committerGitHub <noreply@github.com>
Mon, 1 Jun 2026 16:52:40 +0000 (17:52 +0100)
* Shows `ENTER_EXECUTOR` instructions

Lib/dis.py
Lib/test/test_dis.py
Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst [new file with mode: 0644]

index cb32a4c0c7d30373bd4a3da9c54f4a6fa717c11f..318729091fa098c9967f1edd9779ce624fc81da6 100644 (file)
@@ -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 @@ class ArgResolver:
                 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 @@ class Bytecode:
         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 @@ class Bytecode:
                                    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 @@ class Bytecode:
         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)
index 8be104585814f4a7661a817e7a546fe57ee62459..c75992761d1334fa54da8723e47a9fa336540aaa 100644 (file)
@@ -15,7 +15,8 @@ import types
 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 @@ class DisTests(DisTestBase):
         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 (file)
index 0000000..0ceae5e
--- /dev/null
@@ -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.