Skip to content

Commit 93a666b

Browse files
authored
gh-90997: Show cached inline values in dis output (#92360)
1 parent a79001e commit 93a666b

File tree

4 files changed

+126
-26
lines changed

4 files changed

+126
-26
lines changed

Lib/dis.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
import io
77

88
from opcode import *
9-
from opcode import __all__ as _opcodes_all
10-
from opcode import _nb_ops, _inline_cache_entries, _specializations, _specialized_instructions
9+
from opcode import (
10+
__all__ as _opcodes_all,
11+
_cache_format,
12+
_inline_cache_entries,
13+
_nb_ops,
14+
_specializations,
15+
_specialized_instructions,
16+
)
1117

1218
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
1319
"findlinestarts", "findlabels", "show_code",
@@ -437,9 +443,6 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
437443
cache_counter = 0
438444
for offset, op, arg in _unpack_opargs(code):
439445
if cache_counter > 0:
440-
if show_caches:
441-
yield Instruction("CACHE", 0, None, None, '',
442-
offset, None, False, None)
443446
cache_counter -= 1
444447
continue
445448
if linestarts is not None:
@@ -494,6 +497,17 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
494497
yield Instruction(_all_opname[op], op,
495498
arg, argval, argrepr,
496499
offset, starts_line, is_jump_target, positions)
500+
if show_caches and cache_counter:
501+
for name, caches in _cache_format[opname[deop]].items():
502+
data = code[offset + 2: offset + 2 + caches * 2]
503+
argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
504+
for _ in range(caches):
505+
offset += 2
506+
yield Instruction(
507+
"CACHE", 0, 0, None, argrepr, offset, None, False, None
508+
)
509+
# Only show the actual value for the first cache entry:
510+
argrepr = ""
497511

498512
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
499513
"""Disassemble a code object."""

Lib/opcode.py

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,20 @@
3535
opmap = {}
3636
opname = ['<%r>' % (op,) for op in range(256)]
3737

38-
_inline_cache_entries = [0] * 256
39-
40-
def def_op(name, op, entries=0):
38+
def def_op(name, op):
4139
opname[op] = name
4240
opmap[name] = op
43-
_inline_cache_entries[op] = entries
4441

45-
def name_op(name, op, entries=0):
46-
def_op(name, op, entries)
42+
def name_op(name, op):
43+
def_op(name, op)
4744
hasname.append(op)
4845

49-
def jrel_op(name, op, entries=0):
50-
def_op(name, op, entries)
46+
def jrel_op(name, op):
47+
def_op(name, op)
5148
hasjrel.append(op)
5249

53-
def jabs_op(name, op, entries=0):
54-
def_op(name, op, entries)
50+
def jabs_op(name, op):
51+
def_op(name, op)
5552
hasjabs.append(op)
5653

5754
# Instruction opcodes for compiled code
@@ -68,7 +65,7 @@ def jabs_op(name, op, entries=0):
6865

6966
def_op('UNARY_INVERT', 15)
7067

71-
def_op('BINARY_SUBSCR', 25, 4)
68+
def_op('BINARY_SUBSCR', 25)
7269

7370
def_op('GET_LEN', 30)
7471
def_op('MATCH_MAPPING', 31)
@@ -86,7 +83,7 @@ def jabs_op(name, op, entries=0):
8683
def_op('BEFORE_WITH', 53)
8784
def_op('END_ASYNC_FOR', 54)
8885

89-
def_op('STORE_SUBSCR', 60, 1)
86+
def_op('STORE_SUBSCR', 60)
9087
def_op('DELETE_SUBSCR', 61)
9188

9289
def_op('GET_ITER', 68)
@@ -110,10 +107,10 @@ def jabs_op(name, op, entries=0):
110107

111108
name_op('STORE_NAME', 90) # Index in name list
112109
name_op('DELETE_NAME', 91) # ""
113-
def_op('UNPACK_SEQUENCE', 92, 1) # Number of tuple items
110+
def_op('UNPACK_SEQUENCE', 92) # Number of tuple items
114111
jrel_op('FOR_ITER', 93)
115112
def_op('UNPACK_EX', 94)
116-
name_op('STORE_ATTR', 95, 4) # Index in name list
113+
name_op('STORE_ATTR', 95) # Index in name list
117114
name_op('DELETE_ATTR', 96) # ""
118115
name_op('STORE_GLOBAL', 97) # ""
119116
name_op('DELETE_GLOBAL', 98) # ""
@@ -125,8 +122,8 @@ def jabs_op(name, op, entries=0):
125122
def_op('BUILD_LIST', 103) # Number of list items
126123
def_op('BUILD_SET', 104) # Number of set items
127124
def_op('BUILD_MAP', 105) # Number of dict entries
128-
name_op('LOAD_ATTR', 106, 4) # Index in name list
129-
def_op('COMPARE_OP', 107, 2) # Comparison operator
125+
name_op('LOAD_ATTR', 106) # Index in name list
126+
def_op('COMPARE_OP', 107) # Comparison operator
130127
hascompare.append(107)
131128
name_op('IMPORT_NAME', 108) # Index in name list
132129
name_op('IMPORT_FROM', 109) # Index in name list
@@ -135,12 +132,12 @@ def jabs_op(name, op, entries=0):
135132
jrel_op('JUMP_IF_TRUE_OR_POP', 112) # ""
136133
jrel_op('POP_JUMP_FORWARD_IF_FALSE', 114)
137134
jrel_op('POP_JUMP_FORWARD_IF_TRUE', 115)
138-
name_op('LOAD_GLOBAL', 116, 5) # Index in name list
135+
name_op('LOAD_GLOBAL', 116) # Index in name list
139136
def_op('IS_OP', 117)
140137
def_op('CONTAINS_OP', 118)
141138
def_op('RERAISE', 119)
142139
def_op('COPY', 120)
143-
def_op('BINARY_OP', 122, 1)
140+
def_op('BINARY_OP', 122)
144141
jrel_op('SEND', 123) # Number of bytes to skip
145142
def_op('LOAD_FAST', 124) # Local variable number
146143
haslocal.append(124)
@@ -185,15 +182,15 @@ def jabs_op(name, op, entries=0):
185182
def_op('BUILD_CONST_KEY_MAP', 156)
186183
def_op('BUILD_STRING', 157)
187184

188-
name_op('LOAD_METHOD', 160, 10)
185+
name_op('LOAD_METHOD', 160)
189186

190187
def_op('LIST_EXTEND', 162)
191188
def_op('SET_UPDATE', 163)
192189
def_op('DICT_MERGE', 164)
193190
def_op('DICT_UPDATE', 165)
194-
def_op('PRECALL', 166, 1)
191+
def_op('PRECALL', 166)
195192

196-
def_op('CALL', 171, 4)
193+
def_op('CALL', 171)
197194
def_op('KW_NAMES', 172)
198195
hasconst.append(172)
199196

@@ -352,3 +349,59 @@ def jabs_op(name, op, entries=0):
352349
"miss",
353350
"deopt",
354351
]
352+
353+
_cache_format = {
354+
"LOAD_GLOBAL": {
355+
"counter": 1,
356+
"index": 1,
357+
"module_keys_version": 2,
358+
"builtin_keys_version": 1,
359+
},
360+
"BINARY_OP": {
361+
"counter": 1,
362+
},
363+
"UNPACK_SEQUENCE": {
364+
"counter": 1,
365+
},
366+
"COMPARE_OP": {
367+
"counter": 1,
368+
"mask": 1,
369+
},
370+
"BINARY_SUBSCR": {
371+
"counter": 1,
372+
"type_version": 2,
373+
"func_version": 1,
374+
},
375+
"LOAD_ATTR": {
376+
"counter": 1,
377+
"version": 2,
378+
"index": 1,
379+
},
380+
"STORE_ATTR": {
381+
"counter": 1,
382+
"version": 2,
383+
"index": 1,
384+
},
385+
"LOAD_METHOD": {
386+
"counter": 1,
387+
"type_version": 2,
388+
"dict_offset": 1,
389+
"keys_version": 2,
390+
"descr": 4,
391+
},
392+
"CALL": {
393+
"counter": 1,
394+
"func_version": 2,
395+
"min_args": 1,
396+
},
397+
"PRECALL": {
398+
"counter": 1,
399+
},
400+
"STORE_SUBSCR": {
401+
"counter": 1,
402+
},
403+
}
404+
405+
_inline_cache_entries = [
406+
sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256)
407+
]

Lib/test/test_dis.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,37 @@ def test_loop_quicken(self):
10111011
got = self.get_disassembly(loop_test, adaptive=True)
10121012
self.do_disassembly_compare(got, dis_loop_test_quickened_code, True)
10131013

1014+
def get_cached_values(self, quickened, adaptive):
1015+
def f():
1016+
l = []
1017+
for i in range(42):
1018+
l.append(i)
1019+
if quickened:
1020+
self.code_quicken(f)
1021+
else:
1022+
# "copy" the code to un-quicken it:
1023+
f.__code__ = f.__code__.replace()
1024+
for instruction in dis.get_instructions(
1025+
f, show_caches=True, adaptive=adaptive
1026+
):
1027+
if instruction.opname == "CACHE":
1028+
yield instruction.argrepr
1029+
1030+
@cpython_only
1031+
def test_show_caches(self):
1032+
for quickened in (False, True):
1033+
for adaptive in (False, True):
1034+
with self.subTest(f"{quickened=}, {adaptive=}"):
1035+
if quickened and adaptive:
1036+
pattern = r"^(\w+: \d+)?$"
1037+
else:
1038+
pattern = r"^(\w+: 0)?$"
1039+
caches = list(self.get_cached_values(quickened, adaptive))
1040+
for cache in caches:
1041+
self.assertRegex(cache, pattern)
1042+
self.assertEqual(caches.count(""), 8)
1043+
self.assertEqual(len(caches), 25)
1044+
10141045

10151046
class DisWithFileTests(DisTests):
10161047

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Show the actual named values stored in inline caches when
2+
``show_caches=True`` is passed to :mod:`dis` utilities.

0 commit comments

Comments
 (0)