[Python-checkins] gh-90997: Show cached inline values in `dis` output (#92360)

pablogsal webhook-mailer at python.org
Fri May 6 10:18:29 EDT 2022


https://github.com/python/cpython/commit/93a666b5a56919a3633a3897dfdb9bddfb9614f0
commit: 93a666b5a56919a3633a3897dfdb9bddfb9614f0
branch: main
author: Brandt Bucher <brandtbucher at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2022-05-06T15:18:09+01:00
summary:

gh-90997: Show cached inline values in `dis` output  (#92360)

files:
A Misc/NEWS.d/next/Library/2022-05-05-17-35-01.gh-issue-90997.UV5_s0.rst
M Lib/dis.py
M Lib/opcode.py
M Lib/test/test_dis.py

diff --git a/Lib/dis.py b/Lib/dis.py
index c0e5367afb55a..53c62694decaa 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -6,8 +6,14 @@
 import io
 
 from opcode import *
-from opcode import __all__ as _opcodes_all
-from opcode import _nb_ops, _inline_cache_entries, _specializations, _specialized_instructions
+from opcode import (
+    __all__ as _opcodes_all,
+    _cache_format,
+    _inline_cache_entries,
+    _nb_ops,
+    _specializations,
+    _specialized_instructions,
+)
 
 __all__ = ["code_info", "dis", "disassemble", "distb", "disco",
            "findlinestarts", "findlabels", "show_code",
@@ -437,9 +443,6 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
     cache_counter = 0
     for offset, op, arg in _unpack_opargs(code):
         if cache_counter > 0:
-            if show_caches:
-                yield Instruction("CACHE", 0, None, None, '',
-                                  offset, None, False, None)
             cache_counter -= 1
             continue
         if linestarts is not None:
@@ -494,6 +497,17 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
         yield Instruction(_all_opname[op], op,
                           arg, argval, argrepr,
                           offset, starts_line, is_jump_target, positions)
+        if show_caches and cache_counter:
+            for name, caches in _cache_format[opname[deop]].items():
+                data = code[offset + 2: offset + 2 + caches * 2]
+                argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
+                for _ in range(caches):
+                    offset += 2
+                    yield Instruction(
+                        "CACHE", 0, 0, None, argrepr, offset, None, False, None
+                    )
+                    # Only show the actual value for the first cache entry:
+                    argrepr = ""
 
 def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
     """Disassemble a code object."""
diff --git a/Lib/opcode.py b/Lib/opcode.py
index 6c3862707c747..310582874dc8f 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -35,23 +35,20 @@
 opmap = {}
 opname = ['<%r>' % (op,) for op in range(256)]
 
-_inline_cache_entries = [0] * 256
-
-def def_op(name, op, entries=0):
+def def_op(name, op):
     opname[op] = name
     opmap[name] = op
-    _inline_cache_entries[op] = entries
 
-def name_op(name, op, entries=0):
-    def_op(name, op, entries)
+def name_op(name, op):
+    def_op(name, op)
     hasname.append(op)
 
-def jrel_op(name, op, entries=0):
-    def_op(name, op, entries)
+def jrel_op(name, op):
+    def_op(name, op)
     hasjrel.append(op)
 
-def jabs_op(name, op, entries=0):
-    def_op(name, op, entries)
+def jabs_op(name, op):
+    def_op(name, op)
     hasjabs.append(op)
 
 # Instruction opcodes for compiled code
@@ -68,7 +65,7 @@ def jabs_op(name, op, entries=0):
 
 def_op('UNARY_INVERT', 15)
 
-def_op('BINARY_SUBSCR', 25, 4)
+def_op('BINARY_SUBSCR', 25)
 
 def_op('GET_LEN', 30)
 def_op('MATCH_MAPPING', 31)
@@ -86,7 +83,7 @@ def jabs_op(name, op, entries=0):
 def_op('BEFORE_WITH', 53)
 def_op('END_ASYNC_FOR', 54)
 
-def_op('STORE_SUBSCR', 60, 1)
+def_op('STORE_SUBSCR', 60)
 def_op('DELETE_SUBSCR', 61)
 
 def_op('GET_ITER', 68)
@@ -110,10 +107,10 @@ def jabs_op(name, op, entries=0):
 
 name_op('STORE_NAME', 90)       # Index in name list
 name_op('DELETE_NAME', 91)      # ""
-def_op('UNPACK_SEQUENCE', 92, 1)   # Number of tuple items
+def_op('UNPACK_SEQUENCE', 92)   # Number of tuple items
 jrel_op('FOR_ITER', 93)
 def_op('UNPACK_EX', 94)
-name_op('STORE_ATTR', 95, 4)       # Index in name list
+name_op('STORE_ATTR', 95)       # Index in name list
 name_op('DELETE_ATTR', 96)      # ""
 name_op('STORE_GLOBAL', 97)     # ""
 name_op('DELETE_GLOBAL', 98)    # ""
@@ -125,8 +122,8 @@ def jabs_op(name, op, entries=0):
 def_op('BUILD_LIST', 103)       # Number of list items
 def_op('BUILD_SET', 104)        # Number of set items
 def_op('BUILD_MAP', 105)        # Number of dict entries
-name_op('LOAD_ATTR', 106, 4)       # Index in name list
-def_op('COMPARE_OP', 107, 2)       # Comparison operator
+name_op('LOAD_ATTR', 106)       # Index in name list
+def_op('COMPARE_OP', 107)       # Comparison operator
 hascompare.append(107)
 name_op('IMPORT_NAME', 108)     # Index in name list
 name_op('IMPORT_FROM', 109)     # Index in name list
@@ -135,12 +132,12 @@ def jabs_op(name, op, entries=0):
 jrel_op('JUMP_IF_TRUE_OR_POP', 112)  # ""
 jrel_op('POP_JUMP_FORWARD_IF_FALSE', 114)
 jrel_op('POP_JUMP_FORWARD_IF_TRUE', 115)
-name_op('LOAD_GLOBAL', 116, 5)     # Index in name list
+name_op('LOAD_GLOBAL', 116)     # Index in name list
 def_op('IS_OP', 117)
 def_op('CONTAINS_OP', 118)
 def_op('RERAISE', 119)
 def_op('COPY', 120)
-def_op('BINARY_OP', 122, 1)
+def_op('BINARY_OP', 122)
 jrel_op('SEND', 123) # Number of bytes to skip
 def_op('LOAD_FAST', 124)        # Local variable number
 haslocal.append(124)
@@ -185,15 +182,15 @@ def jabs_op(name, op, entries=0):
 def_op('BUILD_CONST_KEY_MAP', 156)
 def_op('BUILD_STRING', 157)
 
-name_op('LOAD_METHOD', 160, 10)
+name_op('LOAD_METHOD', 160)
 
 def_op('LIST_EXTEND', 162)
 def_op('SET_UPDATE', 163)
 def_op('DICT_MERGE', 164)
 def_op('DICT_UPDATE', 165)
-def_op('PRECALL', 166, 1)
+def_op('PRECALL', 166)
 
-def_op('CALL', 171, 4)
+def_op('CALL', 171)
 def_op('KW_NAMES', 172)
 hasconst.append(172)
 
@@ -352,3 +349,59 @@ def jabs_op(name, op, entries=0):
     "miss",
     "deopt",
 ]
+
+_cache_format = {
+    "LOAD_GLOBAL": {
+        "counter": 1,
+        "index": 1,
+        "module_keys_version": 2,
+        "builtin_keys_version": 1,
+    },
+    "BINARY_OP": {
+        "counter": 1,
+    },
+    "UNPACK_SEQUENCE": {
+        "counter": 1,
+    },
+    "COMPARE_OP": {
+        "counter": 1,
+        "mask": 1,
+    },
+    "BINARY_SUBSCR": {
+        "counter": 1,
+        "type_version": 2,
+        "func_version": 1,
+    },
+    "LOAD_ATTR": {
+        "counter": 1,
+        "version": 2,
+        "index": 1,
+    },
+    "STORE_ATTR": {
+        "counter": 1,
+        "version": 2,
+        "index": 1,
+    },
+    "LOAD_METHOD": {
+        "counter": 1,
+        "type_version": 2,
+        "dict_offset": 1,
+        "keys_version": 2,
+        "descr": 4,
+    },
+    "CALL": {
+        "counter": 1,
+        "func_version": 2,
+        "min_args": 1,
+    },
+    "PRECALL": {
+        "counter": 1,
+    },
+    "STORE_SUBSCR": {
+        "counter": 1,
+    },
+}
+
+_inline_cache_entries = [
+    sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256)
+]
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index b8d1c542bae90..202b99829f213 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1011,6 +1011,37 @@ def test_loop_quicken(self):
         got = self.get_disassembly(loop_test, adaptive=True)
         self.do_disassembly_compare(got, dis_loop_test_quickened_code, True)
 
+    def get_cached_values(self, quickened, adaptive):
+        def f():
+            l = []
+            for i in range(42):
+                l.append(i)
+        if quickened:
+            self.code_quicken(f)
+        else:
+            # "copy" the code to un-quicken it:
+            f.__code__ = f.__code__.replace()
+        for instruction in dis.get_instructions(
+            f, show_caches=True, adaptive=adaptive
+        ):
+            if instruction.opname == "CACHE":
+                yield instruction.argrepr
+
+    @cpython_only
+    def test_show_caches(self):
+        for quickened in (False, True):
+            for adaptive in (False, True):
+                with self.subTest(f"{quickened=}, {adaptive=}"):
+                    if quickened and adaptive:
+                        pattern = r"^(\w+: \d+)?$"
+                    else:
+                        pattern = r"^(\w+: 0)?$"
+                    caches = list(self.get_cached_values(quickened, adaptive))
+                    for cache in caches:
+                        self.assertRegex(cache, pattern)
+                    self.assertEqual(caches.count(""), 8)
+                    self.assertEqual(len(caches), 25)
+
 
 class DisWithFileTests(DisTests):
 
diff --git a/Misc/NEWS.d/next/Library/2022-05-05-17-35-01.gh-issue-90997.UV5_s0.rst b/Misc/NEWS.d/next/Library/2022-05-05-17-35-01.gh-issue-90997.UV5_s0.rst
new file mode 100644
index 0000000000000..a653bebfaf4a4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-05-05-17-35-01.gh-issue-90997.UV5_s0.rst
@@ -0,0 +1,2 @@
+Show the actual named values stored in inline caches when
+``show_caches=True`` is passed to :mod:`dis` utilities.



More information about the Python-checkins mailing list