Skip to content

Commit e590379

Browse files
authored
gh-90997: bpo-46841: Disassembly of quickened code (GH-32099)
1 parent aa5c0a9 commit e590379

File tree

4 files changed

+206
-39
lines changed

4 files changed

+206
-39
lines changed

Lib/dis.py

+63-36
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from opcode import *
99
from opcode import __all__ as _opcodes_all
10-
from opcode import _nb_ops
10+
from opcode import _nb_ops, _inline_cache_entries, _specializations, _specialized_instructions
1111

1212
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
1313
"findlinestarts", "findlabels", "show_code",
@@ -34,6 +34,18 @@
3434

3535
CACHE = opmap["CACHE"]
3636

37+
_all_opname = list(opname)
38+
_all_opmap = dict(opmap)
39+
_empty_slot = [slot for slot, name in enumerate(_all_opname) if name.startswith("<")]
40+
for spec_op, specialized in zip(_empty_slot, _specialized_instructions):
41+
# fill opname and opmap
42+
_all_opname[spec_op] = specialized
43+
_all_opmap[specialized] = spec_op
44+
45+
deoptmap = {
46+
specialized: base for base, family in _specializations.items() for specialized in family
47+
}
48+
3749
def _try_compile(source, name):
3850
"""Attempts to compile the given source, first as an expression and
3951
then as a statement if the first approach fails.
@@ -47,7 +59,7 @@ def _try_compile(source, name):
4759
c = compile(source, name, 'exec')
4860
return c
4961

50-
def dis(x=None, *, file=None, depth=None, show_caches=False):
62+
def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False):
5163
"""Disassemble classes, methods, functions, and other compiled objects.
5264
5365
With no argument, disassemble the last traceback.
@@ -57,7 +69,7 @@ def dis(x=None, *, file=None, depth=None, show_caches=False):
5769
in a special attribute.
5870
"""
5971
if x is None:
60-
distb(file=file, show_caches=show_caches)
72+
distb(file=file, show_caches=show_caches, adaptive=adaptive)
6173
return
6274
# Extract functions from methods.
6375
if hasattr(x, '__func__'):
@@ -78,29 +90,29 @@ def dis(x=None, *, file=None, depth=None, show_caches=False):
7890
if isinstance(x1, _have_code):
7991
print("Disassembly of %s:" % name, file=file)
8092
try:
81-
dis(x1, file=file, depth=depth, show_caches=show_caches)
93+
dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
8294
except TypeError as msg:
8395
print("Sorry:", msg, file=file)
8496
print(file=file)
8597
elif hasattr(x, 'co_code'): # Code object
86-
_disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches)
98+
_disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
8799
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
88100
_disassemble_bytes(x, file=file, show_caches=show_caches)
89101
elif isinstance(x, str): # Source code
90-
_disassemble_str(x, file=file, depth=depth, show_caches=show_caches)
102+
_disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
91103
else:
92104
raise TypeError("don't know how to disassemble %s objects" %
93105
type(x).__name__)
94106

95-
def distb(tb=None, *, file=None, show_caches=False):
107+
def distb(tb=None, *, file=None, show_caches=False, adaptive=False):
96108
"""Disassemble a traceback (default: last traceback)."""
97109
if tb is None:
98110
try:
99111
tb = sys.last_traceback
100112
except AttributeError:
101113
raise RuntimeError("no last traceback to disassemble") from None
102114
while tb.tb_next: tb = tb.tb_next
103-
disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches)
115+
disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive)
104116

105117
# The inspect module interrogates this dictionary to build its
106118
# list of CO_* constants. It is also used by pretty_flags to
@@ -162,6 +174,13 @@ def _get_code_object(x):
162174
raise TypeError("don't know how to disassemble %s objects" %
163175
type(x).__name__)
164176

177+
def _deoptop(op):
178+
name = _all_opname[op]
179+
return _all_opmap[deoptmap[name]] if name in deoptmap else op
180+
181+
def _get_code_array(co, adaptive):
182+
return co._co_code_adaptive if adaptive else co.co_code
183+
165184
def code_info(x):
166185
"""Formatted details of methods, functions, or code."""
167186
return _format_code_info(_get_code_object(x))
@@ -302,7 +321,7 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
302321
return ' '.join(fields).rstrip()
303322

304323

305-
def get_instructions(x, *, first_line=None, show_caches=False):
324+
def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
306325
"""Iterator for the opcodes in methods, functions or code
307326
308327
Generates a series of Instruction named tuples giving the details of
@@ -319,7 +338,7 @@ def get_instructions(x, *, first_line=None, show_caches=False):
319338
line_offset = first_line - co.co_firstlineno
320339
else:
321340
line_offset = 0
322-
return _get_instructions_bytes(co.co_code,
341+
return _get_instructions_bytes(_get_code_array(co, adaptive),
323342
co._varname_from_oparg,
324343
co.co_names, co.co_consts,
325344
linestarts, line_offset,
@@ -415,8 +434,13 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
415434
for i in range(start, end):
416435
labels.add(target)
417436
starts_line = None
437+
cache_counter = 0
418438
for offset, op, arg in _unpack_opargs(code):
419-
if not show_caches and op == CACHE:
439+
if cache_counter > 0:
440+
if show_caches:
441+
yield Instruction("CACHE", 0, None, None, '',
442+
offset, None, False, None)
443+
cache_counter -= 1
420444
continue
421445
if linestarts is not None:
422446
starts_line = linestarts.get(offset, None)
@@ -426,61 +450,63 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
426450
argval = None
427451
argrepr = ''
428452
positions = Positions(*next(co_positions, ()))
453+
deop = _deoptop(op)
454+
cache_counter = _inline_cache_entries[deop]
429455
if arg is not None:
430456
# Set argval to the dereferenced value of the argument when
431457
# available, and argrepr to the string representation of argval.
432458
# _disassemble_bytes needs the string repr of the
433459
# raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
434460
argval = arg
435-
if op in hasconst:
436-
argval, argrepr = _get_const_info(op, arg, co_consts)
437-
elif op in hasname:
438-
if op == LOAD_GLOBAL:
461+
if deop in hasconst:
462+
argval, argrepr = _get_const_info(deop, arg, co_consts)
463+
elif deop in hasname:
464+
if deop == LOAD_GLOBAL:
439465
argval, argrepr = _get_name_info(arg//2, get_name)
440466
if (arg & 1) and argrepr:
441467
argrepr = "NULL + " + argrepr
442468
else:
443469
argval, argrepr = _get_name_info(arg, get_name)
444-
elif op in hasjabs:
470+
elif deop in hasjabs:
445471
argval = arg*2
446472
argrepr = "to " + repr(argval)
447-
elif op in hasjrel:
448-
signed_arg = -arg if _is_backward_jump(op) else arg
473+
elif deop in hasjrel:
474+
signed_arg = -arg if _is_backward_jump(deop) else arg
449475
argval = offset + 2 + signed_arg*2
450476
argrepr = "to " + repr(argval)
451-
elif op in haslocal or op in hasfree:
477+
elif deop in haslocal or deop in hasfree:
452478
argval, argrepr = _get_name_info(arg, varname_from_oparg)
453-
elif op in hascompare:
479+
elif deop in hascompare:
454480
argval = cmp_op[arg]
455481
argrepr = argval
456-
elif op == FORMAT_VALUE:
482+
elif deop == FORMAT_VALUE:
457483
argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3]
458484
argval = (argval, bool(arg & 0x4))
459485
if argval[1]:
460486
if argrepr:
461487
argrepr += ', '
462488
argrepr += 'with format'
463-
elif op == MAKE_FUNCTION:
489+
elif deop == MAKE_FUNCTION:
464490
argrepr = ', '.join(s for i, s in enumerate(MAKE_FUNCTION_FLAGS)
465491
if arg & (1<<i))
466-
elif op == BINARY_OP:
492+
elif deop == BINARY_OP:
467493
_, argrepr = _nb_ops[arg]
468-
yield Instruction(opname[op], op,
494+
yield Instruction(_all_opname[op], op,
469495
arg, argval, argrepr,
470496
offset, starts_line, is_jump_target, positions)
471497

472-
def disassemble(co, lasti=-1, *, file=None, show_caches=False):
498+
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
473499
"""Disassemble a code object."""
474500
linestarts = dict(findlinestarts(co))
475501
exception_entries = parse_exception_table(co)
476-
_disassemble_bytes(co.co_code, lasti,
477-
co._varname_from_oparg,
502+
_disassemble_bytes(_get_code_array(co, adaptive),
503+
lasti, co._varname_from_oparg,
478504
co.co_names, co.co_consts, linestarts, file=file,
479505
exception_entries=exception_entries,
480506
co_positions=co.co_positions(), show_caches=show_caches)
481507

482-
def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False):
483-
disassemble(co, file=file, show_caches=show_caches)
508+
def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False):
509+
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive)
484510
if depth is None or depth > 0:
485511
if depth is not None:
486512
depth = depth - 1
@@ -489,7 +515,7 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False):
489515
print(file=file)
490516
print("Disassembly of %r:" % (x,), file=file)
491517
_disassemble_recursive(
492-
x, file=file, depth=depth, show_caches=show_caches
518+
x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive
493519
)
494520

495521
def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
@@ -548,7 +574,7 @@ def _unpack_opargs(code):
548574
extended_arg = 0
549575
for i in range(0, len(code), 2):
550576
op = code[i]
551-
if op >= HAVE_ARGUMENT:
577+
if _deoptop(op) >= HAVE_ARGUMENT:
552578
arg = code[i+1] | extended_arg
553579
extended_arg = (arg << 8) if op == EXTENDED_ARG else 0
554580
# The oparg is stored as a signed integer
@@ -641,7 +667,7 @@ class Bytecode:
641667
642668
Iterating over this yields the bytecode operations as Instruction instances.
643669
"""
644-
def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False):
670+
def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False):
645671
self.codeobj = co = _get_code_object(x)
646672
if first_line is None:
647673
self.first_line = co.co_firstlineno
@@ -654,10 +680,11 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False
654680
self.current_offset = current_offset
655681
self.exception_entries = parse_exception_table(co)
656682
self.show_caches = show_caches
683+
self.adaptive = adaptive
657684

658685
def __iter__(self):
659686
co = self.codeobj
660-
return _get_instructions_bytes(co.co_code,
687+
return _get_instructions_bytes(_get_code_array(co, self.adaptive),
661688
co._varname_from_oparg,
662689
co.co_names, co.co_consts,
663690
self._linestarts,
@@ -671,12 +698,12 @@ def __repr__(self):
671698
self._original_object)
672699

673700
@classmethod
674-
def from_traceback(cls, tb, *, show_caches=False):
701+
def from_traceback(cls, tb, *, show_caches=False, adaptive=False):
675702
""" Construct a Bytecode from the given traceback """
676703
while tb.tb_next:
677704
tb = tb.tb_next
678705
return cls(
679-
tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches
706+
tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive
680707
)
681708

682709
def info(self):
@@ -691,7 +718,7 @@ def dis(self):
691718
else:
692719
offset = -1
693720
with io.StringIO() as output:
694-
_disassemble_bytes(co.co_code,
721+
_disassemble_bytes(_get_code_array(co, self.adaptive),
695722
varname_from_oparg=co._varname_from_oparg,
696723
names=co.co_names, co_consts=co.co_consts,
697724
linestarts=self._linestarts,

Lib/test/test__opcode.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_stack_effect(self):
1818
self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
1919
self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
2020
# All defined opcodes
21-
for name, code in dis.opmap.items():
21+
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
2222
with self.subTest(opname=name):
2323
if code < dis.HAVE_ARGUMENT:
2424
stack_effect(code)
@@ -47,7 +47,7 @@ def test_stack_effect_jump(self):
4747
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
4848
# All defined opcodes
4949
has_jump = dis.hasjabs + dis.hasjrel
50-
for name, code in dis.opmap.items():
50+
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
5151
with self.subTest(opname=name):
5252
if code < dis.HAVE_ARGUMENT:
5353
common = stack_effect(code)

0 commit comments

Comments
 (0)