https://github.com/python/cpython/commit/4996b99695a04c247de5b1fd5a38674a9d0775c6
commit: 4996b99695a04c247de5b1fd5a38674a9d0775c6
branch: main
author: Mark Shannon <[email protected]>
committer: markshannon <[email protected]>
date: 2026-06-01T17:52:40+01:00
summary:
GH-150478: Add "show_jit" option to `dis.dis` to show jit entry points
(GH-150554)
* Shows `ENTER_EXECUTOR` instructions
files:
A Misc/NEWS.d/next/Library/2026-05-28-23-38-46.gh-issue-150478.qEr0E-.rst
M Lib/dis.py
M Lib/test/test_dis.py
diff --git a/Lib/dis.py b/Lib/dis.py
index cb32a4c0c7d303..318729091fa098 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 8be104585814f4..c75992761d1334 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 00000000000000..0ceae5e313977a
--- /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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]