From 34e7e3c100ff28933509743dd0c6ce4d171416c8 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Thu, 24 Mar 2022 02:29:13 +0800 Subject: [PATCH 01/12] Disassembly of quickened code --- Include/opcode.h | 106 ----------------------------- Lib/dis.py | 57 +++++++++------- Lib/opcode.py | 25 ++++++- Objects/codeobject.c | 4 +- Python/makeopcodetargets.py | 5 -- Tools/scripts/generate_opcode_h.py | 24 ++----- 6 files changed, 62 insertions(+), 159 deletions(-) diff --git a/Include/opcode.h b/Include/opcode.h index dfc7b72e3cdc68..7049ce58a6e39a 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -226,10 +226,6 @@ const uint8_t _PyOpcode_Caches[256] = { }; const uint8_t _PyOpcode_Deopt[256] = { - [ASYNC_GEN_WRAP] = ASYNC_GEN_WRAP, - [BEFORE_ASYNC_WITH] = BEFORE_ASYNC_WITH, - [BEFORE_WITH] = BEFORE_WITH, - [BINARY_OP] = BINARY_OP, [BINARY_OP_ADAPTIVE] = BINARY_OP, [BINARY_OP_ADD_FLOAT] = BINARY_OP, [BINARY_OP_ADD_INT] = BINARY_OP, @@ -239,110 +235,36 @@ const uint8_t _PyOpcode_Deopt[256] = { [BINARY_OP_MULTIPLY_INT] = BINARY_OP, [BINARY_OP_SUBTRACT_FLOAT] = BINARY_OP, [BINARY_OP_SUBTRACT_INT] = BINARY_OP, - [BINARY_SUBSCR] = BINARY_SUBSCR, [BINARY_SUBSCR_ADAPTIVE] = BINARY_SUBSCR, [BINARY_SUBSCR_DICT] = BINARY_SUBSCR, [BINARY_SUBSCR_GETITEM] = BINARY_SUBSCR, [BINARY_SUBSCR_LIST_INT] = BINARY_SUBSCR, [BINARY_SUBSCR_TUPLE_INT] = BINARY_SUBSCR, - [BUILD_CONST_KEY_MAP] = BUILD_CONST_KEY_MAP, - [BUILD_LIST] = BUILD_LIST, - [BUILD_MAP] = BUILD_MAP, - [BUILD_SET] = BUILD_SET, - [BUILD_SLICE] = BUILD_SLICE, - [BUILD_STRING] = BUILD_STRING, - [BUILD_TUPLE] = BUILD_TUPLE, - [CACHE] = CACHE, - [CALL] = CALL, [CALL_ADAPTIVE] = CALL, - [CALL_FUNCTION_EX] = CALL_FUNCTION_EX, [CALL_PY_EXACT_ARGS] = CALL, [CALL_PY_WITH_DEFAULTS] = CALL, - [COMPARE_OP] = COMPARE_OP, [COMPARE_OP_ADAPTIVE] = COMPARE_OP, [COMPARE_OP_FLOAT_JUMP] = COMPARE_OP, [COMPARE_OP_INT_JUMP] = COMPARE_OP, [COMPARE_OP_STR_JUMP] = COMPARE_OP, - [CONTAINS_OP] = CONTAINS_OP, - [COPY] = COPY, - [COPY_FREE_VARS] = COPY_FREE_VARS, - [DELETE_ATTR] = DELETE_ATTR, - [DELETE_DEREF] = DELETE_DEREF, - [DELETE_FAST] = DELETE_FAST, - [DELETE_GLOBAL] = DELETE_GLOBAL, - [DELETE_NAME] = DELETE_NAME, - [DELETE_SUBSCR] = DELETE_SUBSCR, - [DICT_MERGE] = DICT_MERGE, - [DICT_UPDATE] = DICT_UPDATE, - [END_ASYNC_FOR] = END_ASYNC_FOR, - [EXTENDED_ARG] = EXTENDED_ARG, - [FORMAT_VALUE] = FORMAT_VALUE, - [FOR_ITER] = FOR_ITER, - [GET_AITER] = GET_AITER, - [GET_ANEXT] = GET_ANEXT, - [GET_AWAITABLE] = GET_AWAITABLE, - [GET_ITER] = GET_ITER, - [GET_LEN] = GET_LEN, - [GET_YIELD_FROM_ITER] = GET_YIELD_FROM_ITER, - [IMPORT_FROM] = IMPORT_FROM, - [IMPORT_NAME] = IMPORT_NAME, - [IMPORT_STAR] = IMPORT_STAR, - [IS_OP] = IS_OP, - [JUMP_ABSOLUTE] = JUMP_ABSOLUTE, [JUMP_ABSOLUTE_QUICK] = JUMP_ABSOLUTE, - [JUMP_FORWARD] = JUMP_FORWARD, - [JUMP_IF_FALSE_OR_POP] = JUMP_IF_FALSE_OR_POP, - [JUMP_IF_NOT_EG_MATCH] = JUMP_IF_NOT_EG_MATCH, - [JUMP_IF_NOT_EXC_MATCH] = JUMP_IF_NOT_EXC_MATCH, - [JUMP_IF_TRUE_OR_POP] = JUMP_IF_TRUE_OR_POP, - [JUMP_NO_INTERRUPT] = JUMP_NO_INTERRUPT, - [KW_NAMES] = KW_NAMES, - [LIST_APPEND] = LIST_APPEND, - [LIST_EXTEND] = LIST_EXTEND, - [LIST_TO_TUPLE] = LIST_TO_TUPLE, - [LOAD_ASSERTION_ERROR] = LOAD_ASSERTION_ERROR, - [LOAD_ATTR] = LOAD_ATTR, [LOAD_ATTR_ADAPTIVE] = LOAD_ATTR, [LOAD_ATTR_INSTANCE_VALUE] = LOAD_ATTR, [LOAD_ATTR_MODULE] = LOAD_ATTR, [LOAD_ATTR_SLOT] = LOAD_ATTR, [LOAD_ATTR_WITH_HINT] = LOAD_ATTR, - [LOAD_BUILD_CLASS] = LOAD_BUILD_CLASS, - [LOAD_CLASSDEREF] = LOAD_CLASSDEREF, - [LOAD_CLOSURE] = LOAD_CLOSURE, - [LOAD_CONST] = LOAD_CONST, [LOAD_CONST__LOAD_FAST] = LOAD_CONST, - [LOAD_DEREF] = LOAD_DEREF, - [LOAD_FAST] = LOAD_FAST, [LOAD_FAST__LOAD_CONST] = LOAD_FAST, [LOAD_FAST__LOAD_FAST] = LOAD_FAST, - [LOAD_GLOBAL] = LOAD_GLOBAL, [LOAD_GLOBAL_ADAPTIVE] = LOAD_GLOBAL, [LOAD_GLOBAL_BUILTIN] = LOAD_GLOBAL, [LOAD_GLOBAL_MODULE] = LOAD_GLOBAL, - [LOAD_METHOD] = LOAD_METHOD, [LOAD_METHOD_ADAPTIVE] = LOAD_METHOD, [LOAD_METHOD_CLASS] = LOAD_METHOD, [LOAD_METHOD_MODULE] = LOAD_METHOD, [LOAD_METHOD_NO_DICT] = LOAD_METHOD, [LOAD_METHOD_WITH_DICT] = LOAD_METHOD, [LOAD_METHOD_WITH_VALUES] = LOAD_METHOD, - [LOAD_NAME] = LOAD_NAME, - [MAKE_CELL] = MAKE_CELL, - [MAKE_FUNCTION] = MAKE_FUNCTION, - [MAP_ADD] = MAP_ADD, - [MATCH_CLASS] = MATCH_CLASS, - [MATCH_KEYS] = MATCH_KEYS, - [MATCH_MAPPING] = MATCH_MAPPING, - [MATCH_SEQUENCE] = MATCH_SEQUENCE, - [NOP] = NOP, - [POP_EXCEPT] = POP_EXCEPT, - [POP_JUMP_IF_FALSE] = POP_JUMP_IF_FALSE, - [POP_JUMP_IF_NONE] = POP_JUMP_IF_NONE, - [POP_JUMP_IF_NOT_NONE] = POP_JUMP_IF_NOT_NONE, - [POP_JUMP_IF_TRUE] = POP_JUMP_IF_TRUE, - [POP_TOP] = POP_TOP, - [PRECALL] = PRECALL, [PRECALL_ADAPTIVE] = PRECALL, [PRECALL_BOUND_METHOD] = PRECALL, [PRECALL_BUILTIN_CLASS] = PRECALL, @@ -359,48 +281,20 @@ const uint8_t _PyOpcode_Deopt[256] = { [PRECALL_NO_KW_TUPLE_1] = PRECALL, [PRECALL_NO_KW_TYPE_1] = PRECALL, [PRECALL_PYFUNC] = PRECALL, - [PREP_RERAISE_STAR] = PREP_RERAISE_STAR, - [PRINT_EXPR] = PRINT_EXPR, - [PUSH_EXC_INFO] = PUSH_EXC_INFO, - [PUSH_NULL] = PUSH_NULL, - [RAISE_VARARGS] = RAISE_VARARGS, - [RERAISE] = RERAISE, - [RESUME] = RESUME, [RESUME_QUICK] = RESUME, - [RETURN_GENERATOR] = RETURN_GENERATOR, - [RETURN_VALUE] = RETURN_VALUE, - [SEND] = SEND, - [SETUP_ANNOTATIONS] = SETUP_ANNOTATIONS, - [SET_ADD] = SET_ADD, - [SET_UPDATE] = SET_UPDATE, - [STORE_ATTR] = STORE_ATTR, [STORE_ATTR_ADAPTIVE] = STORE_ATTR, [STORE_ATTR_INSTANCE_VALUE] = STORE_ATTR, [STORE_ATTR_SLOT] = STORE_ATTR, [STORE_ATTR_WITH_HINT] = STORE_ATTR, - [STORE_DEREF] = STORE_DEREF, - [STORE_FAST] = STORE_FAST, [STORE_FAST__LOAD_FAST] = STORE_FAST, [STORE_FAST__STORE_FAST] = STORE_FAST, - [STORE_GLOBAL] = STORE_GLOBAL, - [STORE_NAME] = STORE_NAME, - [STORE_SUBSCR] = STORE_SUBSCR, [STORE_SUBSCR_ADAPTIVE] = STORE_SUBSCR, [STORE_SUBSCR_DICT] = STORE_SUBSCR, [STORE_SUBSCR_LIST_INT] = STORE_SUBSCR, - [SWAP] = SWAP, - [UNARY_INVERT] = UNARY_INVERT, - [UNARY_NEGATIVE] = UNARY_NEGATIVE, - [UNARY_NOT] = UNARY_NOT, - [UNARY_POSITIVE] = UNARY_POSITIVE, - [UNPACK_EX] = UNPACK_EX, - [UNPACK_SEQUENCE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_ADAPTIVE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_LIST] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_TUPLE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_TWO_TUPLE] = UNPACK_SEQUENCE, - [WITH_EXCEPT_START] = WITH_EXCEPT_START, - [YIELD_VALUE] = YIELD_VALUE, }; #endif /* OPCODE_TABLES */ diff --git a/Lib/dis.py b/Lib/dis.py index 3b7747b03ffb1c..67d28ee01e3623 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -46,7 +46,7 @@ def _try_compile(source, name): c = compile(source, name, 'exec') return c -def dis(x=None, *, file=None, depth=None, show_caches=False): +def dis(x=None, *, file=None, depth=None, show_caches=False, quickened=False): """Disassemble classes, methods, functions, and other compiled objects. With no argument, disassemble the last traceback. @@ -56,7 +56,7 @@ def dis(x=None, *, file=None, depth=None, show_caches=False): in a special attribute. """ if x is None: - distb(file=file, show_caches=show_caches) + distb(file=file, show_caches=show_caches, quickened=quickened) return # Extract functions from methods. if hasattr(x, '__func__'): @@ -77,21 +77,21 @@ def dis(x=None, *, file=None, depth=None, show_caches=False): if isinstance(x1, _have_code): print("Disassembly of %s:" % name, file=file) try: - dis(x1, file=file, depth=depth, show_caches=show_caches) + dis(x1, file=file, depth=depth, show_caches=show_caches, quickened=quickened) 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) + _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, quickened=quickened) elif isinstance(x, (bytes, bytearray)): # Raw bytecode - _disassemble_bytes(x, file=file, show_caches=show_caches) + _disassemble_bytes(x, file=file, show_caches=show_caches, quickened=quickened) elif isinstance(x, str): # Source code - _disassemble_str(x, file=file, depth=depth, show_caches=show_caches) + _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, quickened=quickened) else: raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) -def distb(tb=None, *, file=None, show_caches=False): +def distb(tb=None, *, file=None, show_caches=False, quickened=False): """Disassemble a traceback (default: last traceback).""" if tb is None: try: @@ -99,7 +99,7 @@ def distb(tb=None, *, file=None, show_caches=False): 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) + disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, quickened=quickened) # The inspect module interrogates this dictionary to build its # list of CO_* constants. It is also used by pretty_flags to @@ -301,7 +301,7 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4): return ' '.join(fields).rstrip() -def get_instructions(x, *, first_line=None, show_caches=False): +def get_instructions(x, *, first_line=None, show_caches=False, quickened=False): """Iterator for the opcodes in methods, functions or code Generates a series of Instruction named tuples giving the details of @@ -323,7 +323,8 @@ def get_instructions(x, *, first_line=None, show_caches=False): co.co_names, co.co_consts, linestarts, line_offset, co_positions=co.co_positions(), - show_caches=show_caches) + show_caches=show_caches, + quickened=quickened) def _get_const_value(op, arg, co_consts): """Helper to get the value of the const in a hasconst op. @@ -395,7 +396,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, line_offset=0, exception_entries=(), co_positions=None, - show_caches=False): + show_caches=False, quickened=False): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -460,11 +461,12 @@ def _get_instructions_bytes(code, varname_from_oparg=None, if arg & (1< 0: if depth is not None: depth = depth - 1 @@ -484,13 +486,13 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False): print(file=file) print("Disassembly of %r:" % (x,), file=file) _disassemble_recursive( - x, file=file, depth=depth, show_caches=show_caches + x, file=file, depth=depth, show_caches=show_caches, quickened=quickened ) def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, *, file=None, line_offset=0, exception_entries=(), - co_positions=None, show_caches=False): + co_positions=None, show_caches=False, quickened=False): # Omit the line number column entirely if we have no line number info show_lineno = bool(linestarts) if show_lineno: @@ -511,7 +513,8 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, line_offset=line_offset, exception_entries=exception_entries, co_positions=co_positions, - show_caches=show_caches): + show_caches=show_caches, + quickened=quickened): new_source_line = (show_lineno and instr.starts_line is not None and instr.offset > 0) @@ -543,7 +546,8 @@ def _unpack_opargs(code): extended_arg = 0 for i in range(0, len(code), 2): op = code[i] - if op >= HAVE_ARGUMENT: + oop = op if op not in deoptmap else deoptmap[op] + if oop >= HAVE_ARGUMENT: arg = code[i+1] | extended_arg extended_arg = (arg << 8) if op == EXTENDED_ARG else 0 # The oparg is stored as a signed integer @@ -634,7 +638,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): + def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, quickened=False): self.codeobj = co = _get_code_object(x) if first_line is None: self.first_line = co.co_firstlineno @@ -647,6 +651,7 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False self.current_offset = current_offset self.exception_entries = parse_exception_table(co) self.show_caches = show_caches + self.quickened = quickened def __iter__(self): co = self.codeobj @@ -657,19 +662,20 @@ def __iter__(self): line_offset=self._line_offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches) + show_caches=self.show_caches, + quickened=self.quickened) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self._original_object) @classmethod - def from_traceback(cls, tb, *, show_caches=False): + def from_traceback(cls, tb, *, show_caches=False, quickened=False): """ Construct a Bytecode from the given traceback """ while tb.tb_next: tb = tb.tb_next return cls( - tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches + tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, quickened=quickened ) def info(self): @@ -693,7 +699,8 @@ def dis(self): lasti=offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches) + show_caches=self.show_caches, + quickened=self.quickened) return output.getvalue() diff --git a/Lib/opcode.py b/Lib/opcode.py index 7a52c13579af7c..0e1dfb98f8cd78 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -6,7 +6,7 @@ __all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs", "haslocal", "hascompare", "hasfree", "opname", "opmap", - "HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"] + "HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs", "deoptmap"] # It's a chicken-and-egg I'm afraid: # We're imported before _opcode's made. @@ -34,13 +34,16 @@ opmap = {} opname = ['<%r>' % (op,) for op in range(256)] +deoptmap = {} _inline_cache_entries = [0] * 256 +_empty_slot = [i for i in range(256)] def def_op(name, op, entries=0): opname[op] = name opmap[name] = op _inline_cache_entries[op] = entries + _empty_slot[op] = False def name_op(name, op, entries=0): def_op(name, op, entries) @@ -54,6 +57,13 @@ def jabs_op(name, op, entries=0): def_op(name, op, entries) hasjabs.append(op) +def spec_op(origin_name, spec_name, counter): + op = _empty_slot[counter] + # fill opname + opname[op] = spec_name + opmap[spec_name] = op + deoptmap[op] = opmap[origin_name] + # Instruction opcodes for compiled code # Blank lines correspond to available opcodes @@ -198,8 +208,6 @@ def jabs_op(name, op, entries=0): hasconst.append(172) -del def_op, name_op, jrel_op, jabs_op - _nb_ops = [ ("NB_ADD", "+"), ("NB_AND", "&"), @@ -335,6 +343,14 @@ def jabs_op(name, op, entries=0): _specialized_instructions = [ opcode for family in _specializations.values() for opcode in family ] + +_empty_slot = [i for i in filter(lambda e: e is not False, _empty_slot)] +_specialized_counter = 0 +for basic, family in _specializations.items(): + for specialized in family: + spec_op(basic, specialized, _specialized_counter) + _specialized_counter += 1 + _specialization_stats = [ "success", "failure", @@ -343,3 +359,6 @@ def jabs_op(name, op, entries=0): "miss", "deopt", ] + +del def_op, name_op, jrel_op, jabs_op, spec_op + diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 224493edb19ea3..9edc0442062359 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1165,8 +1165,8 @@ _PyCode_GetCode(PyCodeObject *co) _Py_CODEUNIT *instructions = (_Py_CODEUNIT *)PyBytes_AS_STRING(code); for (int i = 0; i < Py_SIZE(co); i++) { _Py_CODEUNIT instruction = _PyCode_CODE(co)[i]; - int opcode = _PyOpcode_Deopt[_Py_OPCODE(instruction)]; - int caches = _PyOpcode_Caches[opcode]; + int opcode = _Py_OPCODE(instruction); + int caches = _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; instructions[i] = _Py_MAKECODEUNIT(opcode, _Py_OPARG(instruction)); while (caches--) { instructions[++i] = _Py_MAKECODEUNIT(CACHE, 0); diff --git a/Python/makeopcodetargets.py b/Python/makeopcodetargets.py index 3bf2e35ccb6dab..0c8489ae80e422 100755 --- a/Python/makeopcodetargets.py +++ b/Python/makeopcodetargets.py @@ -35,11 +35,6 @@ def write_contents(f): targets[255] = "TARGET_DO_TRACING" for opname, op in opcode.opmap.items(): targets[op] = "TARGET_%s" % opname - next_op = 1 - for opname in opcode._specialized_instructions: - while targets[next_op] != '_unknown_opcode': - next_op += 1 - targets[next_op] = "TARGET_%s" % opname f.write("static void *opcode_targets[256] = {\n") f.write(",\n".join([" &&%s" % s for s in targets])) f.write("\n};\n") diff --git a/Tools/scripts/generate_opcode_h.py b/Tools/scripts/generate_opcode_h.py index 3b79dc6b7359f6..f635b7eda73426 100644 --- a/Tools/scripts/generate_opcode_h.py +++ b/Tools/scripts/generate_opcode_h.py @@ -57,23 +57,17 @@ def main(opcode_py, outfile='Include/opcode.h'): hasconst = opcode['hasconst'] hasjrel = opcode['hasjrel'] hasjabs = opcode['hasjabs'] - used = [ False ] * 256 - next_op = 1 - for name, op in opmap.items(): - used[op] = True + deoptmap = opcode['deoptmap'] with open(outfile, 'w') as fobj: fobj.write(header) for name in opname: - if name in opmap: + if name in opmap and opmap[name] not in deoptmap: fobj.write(DEFINE.format(name, opmap[name])) if name == 'POP_EXCEPT': # Special entry for HAVE_ARGUMENT fobj.write(DEFINE.format("HAVE_ARGUMENT", opcode["HAVE_ARGUMENT"])) - for name in opcode['_specialized_instructions']: - while used[next_op]: - next_op += 1 - fobj.write(DEFINE.format(name, next_op)) - used[next_op] = True + for spec_op in deoptmap: + fobj.write(DEFINE.format(opname[spec_op], spec_op)) fobj.write(DEFINE.format('DO_TRACING', 255)) fobj.write("\nextern const uint8_t _PyOpcode_Caches[256];\n") fobj.write("\nextern const uint8_t _PyOpcode_Deopt[256];\n") @@ -86,15 +80,9 @@ def main(opcode_py, outfile='Include/opcode.h'): if entries: fobj.write(f" [{opname[i]}] = {entries},\n") fobj.write("};\n") - deoptcodes = {} - for basic in opmap: - deoptcodes[basic] = basic - for basic, family in opcode["_specializations"].items(): - for specialized in family: - deoptcodes[specialized] = basic fobj.write("\nconst uint8_t _PyOpcode_Deopt[256] = {\n") - for opt, deopt in sorted(deoptcodes.items()): - fobj.write(f" [{opt}] = {deopt},\n") + for opt, deopt in sorted(deoptmap.items()): + fobj.write(f" [{opname[opt]}] = {opname[deopt]},\n") fobj.write("};\n") fobj.write("#endif /* OPCODE_TABLES */\n") From ceabedbe42823becee2326be86fe56fc151865f4 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 00:31:16 +0800 Subject: [PATCH 02/12] Reset `deoptmap` and fix test_dis --- Lib/dis.py | 34 ++++++++++++++++++------------ Lib/opcode.py | 18 ++++++++-------- Lib/test/test_dis.py | 4 +--- Tools/scripts/generate_opcode_h.py | 8 +++---- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 67d28ee01e3623..f3700793860cc3 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -33,6 +33,10 @@ CACHE = opmap["CACHE"] +def deoptop(op): + name = opname[op] + return opmap[deoptmap[name]] if name in deoptmap else opmap[name] + def _try_compile(source, name): """Attempts to compile the given source, first as an expression and then as a statement if the first approach fails. @@ -428,40 +432,43 @@ def _get_instructions_bytes(code, varname_from_oparg=None, # available, and argrepr to the string representation of argval. # _disassemble_bytes needs the string repr of the # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. + deop = deoptop(op) argval = arg - if op in hasconst: - argval, argrepr = _get_const_info(op, arg, co_consts) - elif op in hasname: - if op == LOAD_GLOBAL: + if deop in hasconst: + argval, argrepr = _get_const_info(deop, arg, co_consts) + elif deop in hasname: + if deop == LOAD_GLOBAL: argval, argrepr = _get_name_info(arg//2, get_name) if (arg & 1) and argrepr: argrepr = "NULL + " + argrepr else: argval, argrepr = _get_name_info(arg, get_name) - elif op in hasjabs: + elif deop in hasjabs: argval = arg*2 argrepr = "to " + repr(argval) - elif op in hasjrel: + elif deop in hasjrel: argval = offset + 2 + arg*2 argrepr = "to " + repr(argval) - elif op in haslocal or op in hasfree: + elif deop in haslocal or deop in hasfree: argval, argrepr = _get_name_info(arg, varname_from_oparg) - elif op in hascompare: + elif deop in hascompare: argval = cmp_op[arg] argrepr = argval - elif op == FORMAT_VALUE: + elif deop == FORMAT_VALUE: argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3] argval = (argval, bool(arg & 0x4)) if argval[1]: if argrepr: argrepr += ', ' argrepr += 'with format' - elif op == MAKE_FUNCTION: + elif deop == MAKE_FUNCTION: argrepr = ', '.join(s for i, s in enumerate(MAKE_FUNCTION_FLAGS) if arg & (1<= HAVE_ARGUMENT: + if deoptop(op) >= HAVE_ARGUMENT: arg = code[i+1] | extended_arg extended_arg = (arg << 8) if op == EXTENDED_ARG else 0 # The oparg is stored as a signed integer diff --git a/Lib/opcode.py b/Lib/opcode.py index 0e1dfb98f8cd78..49d0ff00ba3e83 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -34,16 +34,13 @@ opmap = {} opname = ['<%r>' % (op,) for op in range(256)] -deoptmap = {} _inline_cache_entries = [0] * 256 -_empty_slot = [i for i in range(256)] def def_op(name, op, entries=0): opname[op] = name opmap[name] = op _inline_cache_entries[op] = entries - _empty_slot[op] = False def name_op(name, op, entries=0): def_op(name, op, entries) @@ -344,12 +341,15 @@ def spec_op(origin_name, spec_name, counter): opcode for family in _specializations.values() for opcode in family ] -_empty_slot = [i for i in filter(lambda e: e is not False, _empty_slot)] -_specialized_counter = 0 -for basic, family in _specializations.items(): - for specialized in family: - spec_op(basic, specialized, _specialized_counter) - _specialized_counter += 1 +_empty_slot = [slot for slot, name in enumerate(opname) if name.startswith("<")] +for spec_op, specialized in zip(_empty_slot, _specialized_instructions): + # fill opname and opmap + opname[spec_op] = specialized + opmap[specialized] = spec_op + +deoptmap = { + specialized: base for base, family in _specializations.items() for specialized in family +} _specialization_stats = [ "success", diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 16bfee188e0461..e566b81066a137 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -682,9 +682,7 @@ def test_boundaries(self): def test_widths(self): for opcode, opname in enumerate(dis.opname): - if opname in ('BUILD_MAP_UNPACK_WITH_CALL', - 'BUILD_TUPLE_UNPACK_WITH_CALL', - 'JUMP_IF_NOT_EXC_MATCH'): + if opname in dis.deoptmap or opname == "JUMP_IF_NOT_EXC_MATCH": continue with self.subTest(opname=opname): width = dis._OPNAME_WIDTH diff --git a/Tools/scripts/generate_opcode_h.py b/Tools/scripts/generate_opcode_h.py index f635b7eda73426..3c2b679c7f9fdb 100644 --- a/Tools/scripts/generate_opcode_h.py +++ b/Tools/scripts/generate_opcode_h.py @@ -61,13 +61,13 @@ def main(opcode_py, outfile='Include/opcode.h'): with open(outfile, 'w') as fobj: fobj.write(header) for name in opname: - if name in opmap and opmap[name] not in deoptmap: + if name in opmap and name not in deoptmap: fobj.write(DEFINE.format(name, opmap[name])) if name == 'POP_EXCEPT': # Special entry for HAVE_ARGUMENT fobj.write(DEFINE.format("HAVE_ARGUMENT", opcode["HAVE_ARGUMENT"])) - for spec_op in deoptmap: - fobj.write(DEFINE.format(opname[spec_op], spec_op)) + for spec_name in deoptmap: + fobj.write(DEFINE.format(spec_name, opmap[spec_name])) fobj.write(DEFINE.format('DO_TRACING', 255)) fobj.write("\nextern const uint8_t _PyOpcode_Caches[256];\n") fobj.write("\nextern const uint8_t _PyOpcode_Deopt[256];\n") @@ -82,7 +82,7 @@ def main(opcode_py, outfile='Include/opcode.h'): fobj.write("};\n") fobj.write("\nconst uint8_t _PyOpcode_Deopt[256] = {\n") for opt, deopt in sorted(deoptmap.items()): - fobj.write(f" [{opname[opt]}] = {opname[deopt]},\n") + fobj.write(f" [{opt}] = {deopt},\n") fobj.write("};\n") fobj.write("#endif /* OPCODE_TABLES */\n") From 9cef324859a5c156dbf74dd5faa41e8d81ba8473 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 00:49:18 +0800 Subject: [PATCH 03/12] Clean code --- Lib/dis.py | 8 +++----- Lib/opcode.py | 12 ++---------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index f3700793860cc3..daddccdc3cd36d 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -35,7 +35,7 @@ def deoptop(op): name = opname[op] - return opmap[deoptmap[name]] if name in deoptmap else opmap[name] + return opmap[deoptmap[name]] if name in deoptmap else op def _try_compile(source, name): """Attempts to compile the given source, first as an expression and @@ -427,12 +427,12 @@ def _get_instructions_bytes(code, varname_from_oparg=None, argval = None argrepr = '' positions = Positions(*next(co_positions, ())) + deop = deoptop(op) if arg is not None: # Set argval to the dereferenced value of the argument when # available, and argrepr to the string representation of argval. # _disassemble_bytes needs the string repr of the # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. - deop = deoptop(op) argval = arg if deop in hasconst: argval, argrepr = _get_const_info(deop, arg, co_consts) @@ -466,9 +466,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, if arg & (1< Date: Fri, 25 Mar 2022 01:18:51 +0800 Subject: [PATCH 04/12] Fix assert for deopt code --- Include/opcode.h | 106 +++++++++++++++++++++++++++++ Tools/scripts/generate_opcode_h.py | 5 +- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/Include/opcode.h b/Include/opcode.h index 7049ce58a6e39a..dfc7b72e3cdc68 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -226,6 +226,10 @@ const uint8_t _PyOpcode_Caches[256] = { }; const uint8_t _PyOpcode_Deopt[256] = { + [ASYNC_GEN_WRAP] = ASYNC_GEN_WRAP, + [BEFORE_ASYNC_WITH] = BEFORE_ASYNC_WITH, + [BEFORE_WITH] = BEFORE_WITH, + [BINARY_OP] = BINARY_OP, [BINARY_OP_ADAPTIVE] = BINARY_OP, [BINARY_OP_ADD_FLOAT] = BINARY_OP, [BINARY_OP_ADD_INT] = BINARY_OP, @@ -235,36 +239,110 @@ const uint8_t _PyOpcode_Deopt[256] = { [BINARY_OP_MULTIPLY_INT] = BINARY_OP, [BINARY_OP_SUBTRACT_FLOAT] = BINARY_OP, [BINARY_OP_SUBTRACT_INT] = BINARY_OP, + [BINARY_SUBSCR] = BINARY_SUBSCR, [BINARY_SUBSCR_ADAPTIVE] = BINARY_SUBSCR, [BINARY_SUBSCR_DICT] = BINARY_SUBSCR, [BINARY_SUBSCR_GETITEM] = BINARY_SUBSCR, [BINARY_SUBSCR_LIST_INT] = BINARY_SUBSCR, [BINARY_SUBSCR_TUPLE_INT] = BINARY_SUBSCR, + [BUILD_CONST_KEY_MAP] = BUILD_CONST_KEY_MAP, + [BUILD_LIST] = BUILD_LIST, + [BUILD_MAP] = BUILD_MAP, + [BUILD_SET] = BUILD_SET, + [BUILD_SLICE] = BUILD_SLICE, + [BUILD_STRING] = BUILD_STRING, + [BUILD_TUPLE] = BUILD_TUPLE, + [CACHE] = CACHE, + [CALL] = CALL, [CALL_ADAPTIVE] = CALL, + [CALL_FUNCTION_EX] = CALL_FUNCTION_EX, [CALL_PY_EXACT_ARGS] = CALL, [CALL_PY_WITH_DEFAULTS] = CALL, + [COMPARE_OP] = COMPARE_OP, [COMPARE_OP_ADAPTIVE] = COMPARE_OP, [COMPARE_OP_FLOAT_JUMP] = COMPARE_OP, [COMPARE_OP_INT_JUMP] = COMPARE_OP, [COMPARE_OP_STR_JUMP] = COMPARE_OP, + [CONTAINS_OP] = CONTAINS_OP, + [COPY] = COPY, + [COPY_FREE_VARS] = COPY_FREE_VARS, + [DELETE_ATTR] = DELETE_ATTR, + [DELETE_DEREF] = DELETE_DEREF, + [DELETE_FAST] = DELETE_FAST, + [DELETE_GLOBAL] = DELETE_GLOBAL, + [DELETE_NAME] = DELETE_NAME, + [DELETE_SUBSCR] = DELETE_SUBSCR, + [DICT_MERGE] = DICT_MERGE, + [DICT_UPDATE] = DICT_UPDATE, + [END_ASYNC_FOR] = END_ASYNC_FOR, + [EXTENDED_ARG] = EXTENDED_ARG, + [FORMAT_VALUE] = FORMAT_VALUE, + [FOR_ITER] = FOR_ITER, + [GET_AITER] = GET_AITER, + [GET_ANEXT] = GET_ANEXT, + [GET_AWAITABLE] = GET_AWAITABLE, + [GET_ITER] = GET_ITER, + [GET_LEN] = GET_LEN, + [GET_YIELD_FROM_ITER] = GET_YIELD_FROM_ITER, + [IMPORT_FROM] = IMPORT_FROM, + [IMPORT_NAME] = IMPORT_NAME, + [IMPORT_STAR] = IMPORT_STAR, + [IS_OP] = IS_OP, + [JUMP_ABSOLUTE] = JUMP_ABSOLUTE, [JUMP_ABSOLUTE_QUICK] = JUMP_ABSOLUTE, + [JUMP_FORWARD] = JUMP_FORWARD, + [JUMP_IF_FALSE_OR_POP] = JUMP_IF_FALSE_OR_POP, + [JUMP_IF_NOT_EG_MATCH] = JUMP_IF_NOT_EG_MATCH, + [JUMP_IF_NOT_EXC_MATCH] = JUMP_IF_NOT_EXC_MATCH, + [JUMP_IF_TRUE_OR_POP] = JUMP_IF_TRUE_OR_POP, + [JUMP_NO_INTERRUPT] = JUMP_NO_INTERRUPT, + [KW_NAMES] = KW_NAMES, + [LIST_APPEND] = LIST_APPEND, + [LIST_EXTEND] = LIST_EXTEND, + [LIST_TO_TUPLE] = LIST_TO_TUPLE, + [LOAD_ASSERTION_ERROR] = LOAD_ASSERTION_ERROR, + [LOAD_ATTR] = LOAD_ATTR, [LOAD_ATTR_ADAPTIVE] = LOAD_ATTR, [LOAD_ATTR_INSTANCE_VALUE] = LOAD_ATTR, [LOAD_ATTR_MODULE] = LOAD_ATTR, [LOAD_ATTR_SLOT] = LOAD_ATTR, [LOAD_ATTR_WITH_HINT] = LOAD_ATTR, + [LOAD_BUILD_CLASS] = LOAD_BUILD_CLASS, + [LOAD_CLASSDEREF] = LOAD_CLASSDEREF, + [LOAD_CLOSURE] = LOAD_CLOSURE, + [LOAD_CONST] = LOAD_CONST, [LOAD_CONST__LOAD_FAST] = LOAD_CONST, + [LOAD_DEREF] = LOAD_DEREF, + [LOAD_FAST] = LOAD_FAST, [LOAD_FAST__LOAD_CONST] = LOAD_FAST, [LOAD_FAST__LOAD_FAST] = LOAD_FAST, + [LOAD_GLOBAL] = LOAD_GLOBAL, [LOAD_GLOBAL_ADAPTIVE] = LOAD_GLOBAL, [LOAD_GLOBAL_BUILTIN] = LOAD_GLOBAL, [LOAD_GLOBAL_MODULE] = LOAD_GLOBAL, + [LOAD_METHOD] = LOAD_METHOD, [LOAD_METHOD_ADAPTIVE] = LOAD_METHOD, [LOAD_METHOD_CLASS] = LOAD_METHOD, [LOAD_METHOD_MODULE] = LOAD_METHOD, [LOAD_METHOD_NO_DICT] = LOAD_METHOD, [LOAD_METHOD_WITH_DICT] = LOAD_METHOD, [LOAD_METHOD_WITH_VALUES] = LOAD_METHOD, + [LOAD_NAME] = LOAD_NAME, + [MAKE_CELL] = MAKE_CELL, + [MAKE_FUNCTION] = MAKE_FUNCTION, + [MAP_ADD] = MAP_ADD, + [MATCH_CLASS] = MATCH_CLASS, + [MATCH_KEYS] = MATCH_KEYS, + [MATCH_MAPPING] = MATCH_MAPPING, + [MATCH_SEQUENCE] = MATCH_SEQUENCE, + [NOP] = NOP, + [POP_EXCEPT] = POP_EXCEPT, + [POP_JUMP_IF_FALSE] = POP_JUMP_IF_FALSE, + [POP_JUMP_IF_NONE] = POP_JUMP_IF_NONE, + [POP_JUMP_IF_NOT_NONE] = POP_JUMP_IF_NOT_NONE, + [POP_JUMP_IF_TRUE] = POP_JUMP_IF_TRUE, + [POP_TOP] = POP_TOP, + [PRECALL] = PRECALL, [PRECALL_ADAPTIVE] = PRECALL, [PRECALL_BOUND_METHOD] = PRECALL, [PRECALL_BUILTIN_CLASS] = PRECALL, @@ -281,20 +359,48 @@ const uint8_t _PyOpcode_Deopt[256] = { [PRECALL_NO_KW_TUPLE_1] = PRECALL, [PRECALL_NO_KW_TYPE_1] = PRECALL, [PRECALL_PYFUNC] = PRECALL, + [PREP_RERAISE_STAR] = PREP_RERAISE_STAR, + [PRINT_EXPR] = PRINT_EXPR, + [PUSH_EXC_INFO] = PUSH_EXC_INFO, + [PUSH_NULL] = PUSH_NULL, + [RAISE_VARARGS] = RAISE_VARARGS, + [RERAISE] = RERAISE, + [RESUME] = RESUME, [RESUME_QUICK] = RESUME, + [RETURN_GENERATOR] = RETURN_GENERATOR, + [RETURN_VALUE] = RETURN_VALUE, + [SEND] = SEND, + [SETUP_ANNOTATIONS] = SETUP_ANNOTATIONS, + [SET_ADD] = SET_ADD, + [SET_UPDATE] = SET_UPDATE, + [STORE_ATTR] = STORE_ATTR, [STORE_ATTR_ADAPTIVE] = STORE_ATTR, [STORE_ATTR_INSTANCE_VALUE] = STORE_ATTR, [STORE_ATTR_SLOT] = STORE_ATTR, [STORE_ATTR_WITH_HINT] = STORE_ATTR, + [STORE_DEREF] = STORE_DEREF, + [STORE_FAST] = STORE_FAST, [STORE_FAST__LOAD_FAST] = STORE_FAST, [STORE_FAST__STORE_FAST] = STORE_FAST, + [STORE_GLOBAL] = STORE_GLOBAL, + [STORE_NAME] = STORE_NAME, + [STORE_SUBSCR] = STORE_SUBSCR, [STORE_SUBSCR_ADAPTIVE] = STORE_SUBSCR, [STORE_SUBSCR_DICT] = STORE_SUBSCR, [STORE_SUBSCR_LIST_INT] = STORE_SUBSCR, + [SWAP] = SWAP, + [UNARY_INVERT] = UNARY_INVERT, + [UNARY_NEGATIVE] = UNARY_NEGATIVE, + [UNARY_NOT] = UNARY_NOT, + [UNARY_POSITIVE] = UNARY_POSITIVE, + [UNPACK_EX] = UNPACK_EX, + [UNPACK_SEQUENCE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_ADAPTIVE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_LIST] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_TUPLE] = UNPACK_SEQUENCE, [UNPACK_SEQUENCE_TWO_TUPLE] = UNPACK_SEQUENCE, + [WITH_EXCEPT_START] = WITH_EXCEPT_START, + [YIELD_VALUE] = YIELD_VALUE, }; #endif /* OPCODE_TABLES */ diff --git a/Tools/scripts/generate_opcode_h.py b/Tools/scripts/generate_opcode_h.py index 3c2b679c7f9fdb..c00a9c8e39dd4d 100644 --- a/Tools/scripts/generate_opcode_h.py +++ b/Tools/scripts/generate_opcode_h.py @@ -80,8 +80,11 @@ def main(opcode_py, outfile='Include/opcode.h'): if entries: fobj.write(f" [{opname[i]}] = {entries},\n") fobj.write("};\n") + deoptcodes = {} + for basic in opmap: + deoptcodes[basic] = deoptmap[basic] if basic in deoptmap else basic fobj.write("\nconst uint8_t _PyOpcode_Deopt[256] = {\n") - for opt, deopt in sorted(deoptmap.items()): + for opt, deopt in sorted(deoptcodes.items()): fobj.write(f" [{opt}] = {deopt},\n") fobj.write("};\n") fobj.write("#endif /* OPCODE_TABLES */\n") From bd59be295f539058040e128dbcf7ddc7c87fa94d Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 01:53:03 +0800 Subject: [PATCH 05/12] Revert `co_code` --- Objects/codeobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 9edc0442062359..224493edb19ea3 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1165,8 +1165,8 @@ _PyCode_GetCode(PyCodeObject *co) _Py_CODEUNIT *instructions = (_Py_CODEUNIT *)PyBytes_AS_STRING(code); for (int i = 0; i < Py_SIZE(co); i++) { _Py_CODEUNIT instruction = _PyCode_CODE(co)[i]; - int opcode = _Py_OPCODE(instruction); - int caches = _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; + int opcode = _PyOpcode_Deopt[_Py_OPCODE(instruction)]; + int caches = _PyOpcode_Caches[opcode]; instructions[i] = _Py_MAKECODEUNIT(opcode, _Py_OPARG(instruction)); while (caches--) { instructions[++i] = _Py_MAKECODEUNIT(CACHE, 0); From f72c1fb77ded8006df419ddb779a44494d020ce5 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 02:42:43 +0800 Subject: [PATCH 06/12] Rename kwarg name and use `_co_code_adaptive` --- Lib/dis.py | 76 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index daddccdc3cd36d..2f2a8d0fb255c2 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -7,7 +7,7 @@ from opcode import * from opcode import __all__ as _opcodes_all -from opcode import _nb_ops +from opcode import _nb_ops, _inline_cache_entries __all__ = ["code_info", "dis", "disassemble", "distb", "disco", "findlinestarts", "findlabels", "show_code", @@ -50,7 +50,7 @@ def _try_compile(source, name): c = compile(source, name, 'exec') return c -def dis(x=None, *, file=None, depth=None, show_caches=False, quickened=False): +def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False): """Disassemble classes, methods, functions, and other compiled objects. With no argument, disassemble the last traceback. @@ -60,7 +60,7 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, quickened=False): in a special attribute. """ if x is None: - distb(file=file, show_caches=show_caches, quickened=quickened) + distb(file=file, show_caches=show_caches, adaptive=adaptive) return # Extract functions from methods. if hasattr(x, '__func__'): @@ -81,21 +81,21 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, quickened=False): if isinstance(x1, _have_code): print("Disassembly of %s:" % name, file=file) try: - dis(x1, file=file, depth=depth, show_caches=show_caches, quickened=quickened) + dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive) 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, quickened=quickened) + _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive) elif isinstance(x, (bytes, bytearray)): # Raw bytecode - _disassemble_bytes(x, file=file, show_caches=show_caches, quickened=quickened) + _disassemble_bytes(x, file=file, show_caches=show_caches) elif isinstance(x, str): # Source code - _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, quickened=quickened) + _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive) else: raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) -def distb(tb=None, *, file=None, show_caches=False, quickened=False): +def distb(tb=None, *, file=None, show_caches=False, adaptive=False): """Disassemble a traceback (default: last traceback).""" if tb is None: try: @@ -103,7 +103,7 @@ def distb(tb=None, *, file=None, show_caches=False, quickened=False): 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, quickened=quickened) + disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive) # The inspect module interrogates this dictionary to build its # list of CO_* constants. It is also used by pretty_flags to @@ -305,7 +305,7 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4): return ' '.join(fields).rstrip() -def get_instructions(x, *, first_line=None, show_caches=False, quickened=False): +def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): """Iterator for the opcodes in methods, functions or code Generates a series of Instruction named tuples giving the details of @@ -322,13 +322,13 @@ def get_instructions(x, *, first_line=None, show_caches=False, quickened=False): line_offset = first_line - co.co_firstlineno else: line_offset = 0 - return _get_instructions_bytes(co.co_code, + code_bytes = co._co_code_adaptive.tobytes() if adaptive else co.co_code + return _get_instructions_bytes(code_bytes, co._varname_from_oparg, co.co_names, co.co_consts, linestarts, line_offset, co_positions=co.co_positions(), - show_caches=show_caches, - quickened=quickened) + show_caches=show_caches) def _get_const_value(op, arg, co_consts): """Helper to get the value of the const in a hasconst op. @@ -400,7 +400,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, line_offset=0, exception_entries=(), co_positions=None, - show_caches=False, quickened=False): + show_caches=False): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -416,8 +416,13 @@ def _get_instructions_bytes(code, varname_from_oparg=None, for i in range(start, end): labels.add(target) starts_line = None + cache_counter = 0 for offset, op, arg in _unpack_opargs(code): - if not show_caches and op == CACHE: + 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: starts_line = linestarts.get(offset, None) @@ -428,6 +433,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, argrepr = '' positions = Positions(*next(co_positions, ())) deop = deoptop(op) + cache_counter = _inline_cache_entries[deop] if arg is not None: # Set argval to the dereferenced value of the argument when # available, and argrepr to the string representation of argval. @@ -466,23 +472,22 @@ def _get_instructions_bytes(code, varname_from_oparg=None, if arg & (1< 0: if depth is not None: depth = depth - 1 @@ -491,13 +496,13 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, quic print(file=file) print("Disassembly of %r:" % (x,), file=file) _disassemble_recursive( - x, file=file, depth=depth, show_caches=show_caches, quickened=quickened + x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive ) def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, *, file=None, line_offset=0, exception_entries=(), - co_positions=None, show_caches=False, quickened=False): + co_positions=None, show_caches=False): # Omit the line number column entirely if we have no line number info show_lineno = bool(linestarts) if show_lineno: @@ -518,8 +523,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, line_offset=line_offset, exception_entries=exception_entries, co_positions=co_positions, - show_caches=show_caches, - quickened=quickened): + show_caches=show_caches): new_source_line = (show_lineno and instr.starts_line is not None and instr.offset > 0) @@ -642,7 +646,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, quickened=False): + def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False): self.codeobj = co = _get_code_object(x) if first_line is None: self.first_line = co.co_firstlineno @@ -655,31 +659,30 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False self.current_offset = current_offset self.exception_entries = parse_exception_table(co) self.show_caches = show_caches - self.quickened = quickened + self.adaptive = adaptive def __iter__(self): co = self.codeobj - return _get_instructions_bytes(co.co_code, + return _get_instructions_bytes(co._co_code_adaptive.tobytes() if self.adaptive else co.co_code, co._varname_from_oparg, co.co_names, co.co_consts, self._linestarts, line_offset=self._line_offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches, - quickened=self.quickened) + show_caches=self.show_caches) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self._original_object) @classmethod - def from_traceback(cls, tb, *, show_caches=False, quickened=False): + def from_traceback(cls, tb, *, show_caches=False, adaptive=False): """ Construct a Bytecode from the given traceback """ while tb.tb_next: tb = tb.tb_next return cls( - tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, quickened=quickened + tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive ) def info(self): @@ -694,7 +697,7 @@ def dis(self): else: offset = -1 with io.StringIO() as output: - _disassemble_bytes(co.co_code, + _disassemble_bytes(co._co_code_adaptive.tobytes() if self.adaptive else co.co_code, varname_from_oparg=co._varname_from_oparg, names=co.co_names, co_consts=co.co_consts, linestarts=self._linestarts, @@ -703,8 +706,7 @@ def dis(self): lasti=offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches, - quickened=self.quickened) + show_caches=self.show_caches) return output.getvalue() From c14ab9019d9e95df6b45726efd784f1a1040ea8b Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 02:48:02 +0800 Subject: [PATCH 07/12] Fix test__opcode --- Lib/test/test__opcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 7c1c0cfdb069b8..2a4c0d2eeb656a 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -18,7 +18,7 @@ def test_stack_effect(self): self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE']) self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0) # All defined opcodes - for name, code in dis.opmap.items(): + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): with self.subTest(opname=name): if code < dis.HAVE_ARGUMENT: stack_effect(code) @@ -47,7 +47,7 @@ def test_stack_effect_jump(self): self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0) # All defined opcodes has_jump = dis.hasjabs + dis.hasjrel - for name, code in dis.opmap.items(): + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): with self.subTest(opname=name): if code < dis.HAVE_ARGUMENT: common = stack_effect(code) From 026715a29cc0eb9df26e3bd77a26d9ef9f06d682 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 25 Mar 2022 22:30:44 +0800 Subject: [PATCH 08/12] Add News --- Lib/dis.py | 24 ++++++++++--------- .../2022-03-25-22-18-45.bpo-46841.NUEsXW.rst | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-03-25-22-18-45.bpo-46841.NUEsXW.rst diff --git a/Lib/dis.py b/Lib/dis.py index 2f2a8d0fb255c2..b141cb9ae4e6de 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -33,10 +33,6 @@ CACHE = opmap["CACHE"] -def deoptop(op): - name = opname[op] - return opmap[deoptmap[name]] if name in deoptmap else op - def _try_compile(source, name): """Attempts to compile the given source, first as an expression and then as a statement if the first approach fails. @@ -165,6 +161,13 @@ def _get_code_object(x): raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) +def _deoptop(op): + name = opname[op] + return opmap[deoptmap[name]] if name in deoptmap else op + +def _get_code_array(co, adaptive): + return co._co_code_adaptive.tobytes() if adaptive else co.co_code + def code_info(x): """Formatted details of methods, functions, or code.""" return _format_code_info(_get_code_object(x)) @@ -322,8 +325,7 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): line_offset = first_line - co.co_firstlineno else: line_offset = 0 - code_bytes = co._co_code_adaptive.tobytes() if adaptive else co.co_code - return _get_instructions_bytes(code_bytes, + return _get_instructions_bytes(_get_code_array(co, adaptive), co._varname_from_oparg, co.co_names, co.co_consts, linestarts, line_offset, @@ -432,7 +434,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, argval = None argrepr = '' positions = Positions(*next(co_positions, ())) - deop = deoptop(op) + deop = _deoptop(op) cache_counter = _inline_cache_entries[deop] if arg is not None: # Set argval to the dereferenced value of the argument when @@ -480,7 +482,7 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False): """Disassemble a code object.""" linestarts = dict(findlinestarts(co)) exception_entries = parse_exception_table(co) - _disassemble_bytes(co._co_code_adaptive.tobytes() if adaptive else co.co_code, + _disassemble_bytes(_get_code_array(co, adaptive), lasti, co._varname_from_oparg, co.co_names, co.co_consts, linestarts, file=file, exception_entries=exception_entries, @@ -555,7 +557,7 @@ def _unpack_opargs(code): extended_arg = 0 for i in range(0, len(code), 2): op = code[i] - if deoptop(op) >= HAVE_ARGUMENT: + if _deoptop(op) >= HAVE_ARGUMENT: arg = code[i+1] | extended_arg extended_arg = (arg << 8) if op == EXTENDED_ARG else 0 # The oparg is stored as a signed integer @@ -663,7 +665,7 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False def __iter__(self): co = self.codeobj - return _get_instructions_bytes(co._co_code_adaptive.tobytes() if self.adaptive else co.co_code, + return _get_instructions_bytes(_get_code_array(co, self.adaptive), co._varname_from_oparg, co.co_names, co.co_consts, self._linestarts, @@ -697,7 +699,7 @@ def dis(self): else: offset = -1 with io.StringIO() as output: - _disassemble_bytes(co._co_code_adaptive.tobytes() if self.adaptive else co.co_code, + _disassemble_bytes(_get_code_array(co, self.adaptive), varname_from_oparg=co._varname_from_oparg, names=co.co_names, co_consts=co.co_consts, linestarts=self._linestarts, diff --git a/Misc/NEWS.d/next/Library/2022-03-25-22-18-45.bpo-46841.NUEsXW.rst b/Misc/NEWS.d/next/Library/2022-03-25-22-18-45.bpo-46841.NUEsXW.rst new file mode 100644 index 00000000000000..0e778047593a73 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-03-25-22-18-45.bpo-46841.NUEsXW.rst @@ -0,0 +1 @@ +Disassembly of quickened code. From 2a4bb97b66ad9527b60a11e5b22fdf332cc120fe Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Fri, 1 Apr 2022 23:57:09 +0800 Subject: [PATCH 09/12] Move deoptmap to dis module --- Lib/dis.py | 22 +++++++++++++++++----- Lib/opcode.py | 13 +------------ Python/makeopcodetargets.py | 5 +++++ Tools/scripts/generate_opcode_h.py | 19 ++++++++++++++----- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 8f7a81f98d5825..3947cba2eefdd2 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -7,7 +7,7 @@ from opcode import * from opcode import __all__ as _opcodes_all -from opcode import _nb_ops, _inline_cache_entries +from opcode import _nb_ops, _inline_cache_entries, _specializations, _specialized_instructions __all__ = ["code_info", "dis", "disassemble", "distb", "disco", "findlinestarts", "findlabels", "show_code", @@ -34,6 +34,18 @@ CACHE = opmap["CACHE"] +_all_opname = list(opname) +_all_opmap = dict(opmap) +_empty_slot = [slot for slot, name in enumerate(_all_opname) if name.startswith("<")] +for spec_op, specialized in zip(_empty_slot, _specialized_instructions): + # fill opname and opmap + _all_opname[spec_op] = specialized + _all_opmap[specialized] = spec_op + +deoptmap = { + specialized: base for base, family in _specializations.items() for specialized in family +} + def _try_compile(source, name): """Attempts to compile the given source, first as an expression and then as a statement if the first approach fails. @@ -163,11 +175,11 @@ def _get_code_object(x): type(x).__name__) def _deoptop(op): - name = opname[op] - return opmap[deoptmap[name]] if name in deoptmap else op + name = _all_opname[op] + return _all_opmap[deoptmap[name]] if name in deoptmap else op def _get_code_array(co, adaptive): - return co._co_code_adaptive.tobytes() if adaptive else co.co_code + return co._co_code_adaptive if adaptive else co.co_code def code_info(x): """Formatted details of methods, functions, or code.""" @@ -476,7 +488,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, if arg & (1< Date: Sat, 2 Apr 2022 14:15:45 +0800 Subject: [PATCH 10/12] Add code quicken in test_dis --- Include/internal/pycore_code.h | 2 +- Lib/test/test_dis.py | 100 ++++++++++++++++++++++++++++++++- Modules/_testinternalcapi.c | 15 +++++ 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 8c868bcd5b5cbe..2abe88aa643b15 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -97,7 +97,7 @@ typedef struct { /* We want to compare to zero for efficiency, so we offset values accordingly */ #define QUICKENING_INITIAL_WARMUP_VALUE (-QUICKENING_WARMUP_DELAY) -void _PyCode_Quicken(PyCodeObject *code); +PyAPI_FUNC(void) _PyCode_Quicken(PyCodeObject *code); static inline void _PyCode_Warmup(PyCodeObject *code) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index d1c374673f350e..ff0a3ff610cfd7 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -7,7 +7,7 @@ import sys import types import unittest -from test.support import captured_stdout, requires_debug_ranges +from test.support import captured_stdout, requires_debug_ranges, import_helper, cpython_only from test.support.bytecode_helper import BytecodeTestCase import opcode @@ -583,6 +583,31 @@ def foo(x): _h.__code__.co_firstlineno + 3, ) +def load_test(x, y): + a, b = x, y + return a, b + +dis_load_test_quickened_code = """\ +%3d 0 RESUME_QUICK 0 + +%3d 2 LOAD_FAST__LOAD_FAST 0 (x) + 4 LOAD_FAST 1 (y) + 6 STORE_FAST__STORE_FAST 3 (b) + 8 STORE_FAST__LOAD_FAST 2 (a) + +%3d 10 LOAD_FAST__LOAD_FAST 2 (a) + 12 LOAD_FAST 3 (b) + 14 BUILD_TUPLE 2 + 16 RETURN_VALUE +""" % (load_test.__code__.co_firstlineno, + load_test.__code__.co_firstlineno + 1, + load_test.__code__.co_firstlineno + 2) + +def binary_test(x, y): + a = x + y + b = x - y + return a + b + class DisTestBase(unittest.TestCase): "Common utilities for DisTests and TestDisTraceback" @@ -862,6 +887,79 @@ def check(expected, **kwargs): check(dis_nested_2, depth=None) check(dis_nested_2) + _testinternalcapi = import_helper.import_module('_testinternalcapi') + + @cpython_only + def test_super_instructions(self): + self._testinternalcapi.code_quicken(load_test.__code__) + got = self.get_disassembly(load_test, adaptive=True) + self.do_disassembly_compare(got, dis_load_test_quickened_code, True) + + @cpython_only + def test_binary_specialize(self): + binary_op_quicken = """\ + 0 RESUME_QUICK 0 + + 1 2 LOAD_NAME 0 (a) + 4 LOAD_NAME 1 (b) + 6 %s + 10 RETURN_VALUE +""" + co_int = compile('a + b', "", "eval") + self._testinternalcapi.code_quicken(co_int) + got = self.get_disassembly(co_int, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADAPTIVE 0 (+)", True) + exec(co_int, {}, {'a': 1, 'b': 2}) + got = self.get_disassembly(co_int, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_INT 0 (+)", True) + + co_unicode = compile('a + b', "", "eval") + self._testinternalcapi.code_quicken(co_unicode) + exec(co_unicode, {}, {'a': 'a', 'b': 'b'}) + got = self.get_disassembly(co_unicode, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)", True) + + @cpython_only + def test_load_attr_specialize(self): + load_attr_quicken = """\ + 0 RESUME_QUICK 0 + + 1 2 LOAD_CONST 0 ('a') + 4 %s + 14 RETURN_VALUE +""" + co = compile("'a'.__class__", "", "eval") + self._testinternalcapi.code_quicken(co) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, load_attr_quicken % "LOAD_ATTR_ADAPTIVE 0 (__class__)", True) + exec(co, {}, {}) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, load_attr_quicken % "LOAD_ATTR_SLOT 0 (__class__)", True) + + @cpython_only + def test_call_specialize(self): + call_quicken = """\ + 0 RESUME_QUICK 0 + + 1 2 PUSH_NULL + 4 LOAD_NAME 0 (str) + 6 LOAD_CONST 0 (1) + 8 %s + 12 %s + 22 RETURN_VALUE +""" + co = compile("str(1)", "", "eval") + self._testinternalcapi.code_quicken(co) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, call_quicken % ( + "PRECALL_ADAPTIVE 1", + "CALL_ADAPTIVE 1"), True) + exec(co, {}, {}) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, call_quicken % ( + "PRECALL_NO_KW_STR_1 1", + "CALL_ADAPTIVE 1"), True) + class DisWithFileTests(DisTests): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5d5b3e6b2fd626..880e8b825554aa 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -14,6 +14,7 @@ #include "Python.h" #include "pycore_atomic_funcs.h" // _Py_atomic_int_get() #include "pycore_bitutils.h" // _Py_bswap32() +#include "pycore_code.h" // _PyCode_Quicken #include "pycore_fileutils.h" // _Py_normpath #include "pycore_gc.h" // PyGC_Head #include "pycore_hashtable.h" // _Py_hashtable_new() @@ -491,6 +492,19 @@ decode_locale_ex(PyObject *self, PyObject *args) return res; } +static PyObject * +code_quicken(PyObject *self, PyObject *code) +{ + if (!PyCode_Check(code)) { + PyErr_SetString(PyExc_TypeError, "argument must be a code object"); + return NULL; + } + PyCodeObject *co = (PyCodeObject *) code; + co->co_warmup = 0; + _PyCode_Quicken(co); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"get_configs", get_configs, METH_NOARGS}, @@ -508,6 +522,7 @@ static PyMethodDef TestMethods[] = { {"get_getpath_codeobject", get_getpath_codeobject, METH_NOARGS, NULL}, {"EncodeLocaleEx", encode_locale_ex, METH_VARARGS}, {"DecodeLocaleEx", decode_locale_ex, METH_VARARGS}, + {"code_quicken", code_quicken, METH_O, NULL}, {NULL, NULL} /* sentinel */ }; From e89653894a376bb68d2bf3c7c625ffaaaff7d096 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Wed, 6 Apr 2022 14:11:12 +0800 Subject: [PATCH 11/12] Get quickened in dis --- Include/internal/pycore_code.h | 2 +- Lib/test/test_dis.py | 103 +++++++++++++++++++++++---------- Modules/_testinternalcapi.c | 15 ----- 3 files changed, 73 insertions(+), 47 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 2abe88aa643b15..8c868bcd5b5cbe 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -97,7 +97,7 @@ typedef struct { /* We want to compare to zero for efficiency, so we offset values accordingly */ #define QUICKENING_INITIAL_WARMUP_VALUE (-QUICKENING_WARMUP_DELAY) -PyAPI_FUNC(void) _PyCode_Quicken(PyCodeObject *code); +void _PyCode_Quicken(PyCodeObject *code); static inline void _PyCode_Warmup(PyCodeObject *code) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index ff0a3ff610cfd7..70a8055edd70b2 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -7,7 +7,7 @@ import sys import types import unittest -from test.support import captured_stdout, requires_debug_ranges, import_helper, cpython_only +from test.support import captured_stdout, requires_debug_ranges, cpython_only from test.support.bytecode_helper import BytecodeTestCase import opcode @@ -583,7 +583,7 @@ def foo(x): _h.__code__.co_firstlineno + 3, ) -def load_test(x, y): +def load_test(x, y=0): a, b = x, y return a, b @@ -603,10 +603,37 @@ def load_test(x, y): load_test.__code__.co_firstlineno + 1, load_test.__code__.co_firstlineno + 2) -def binary_test(x, y): - a = x + y - b = x - y - return a + b +def loop_test(): + for i in [1, 2, 3] * 3: + load_test(i) + +dis_loop_test_quickened_code = """\ +%3d 0 RESUME_QUICK 0 + +%3d 2 BUILD_LIST 0 + 4 LOAD_CONST 1 ((1, 2, 3)) + 6 LIST_EXTEND 1 + 8 LOAD_CONST 2 (3) + 10 BINARY_OP_ADAPTIVE 5 (*) + 14 GET_ITER + 16 FOR_ITER 17 (to 52) + 18 STORE_FAST 0 (i) + +%3d 20 LOAD_GLOBAL_MODULE 1 (NULL + load_test) + 32 LOAD_FAST 0 (i) + 34 PRECALL_PYFUNC 1 + 38 CALL_PY_WITH_DEFAULTS 1 + 48 POP_TOP + 50 JUMP_BACKWARD_QUICK 18 (to 16) + +%3d >> 52 LOAD_CONST 0 (None) + 54 RETURN_VALUE +""" % (loop_test.__code__.co_firstlineno, + loop_test.__code__.co_firstlineno + 1, + loop_test.__code__.co_firstlineno + 2, + loop_test.__code__.co_firstlineno + 1,) + +QUICKENING_WARMUP_DELAY = 8 class DisTestBase(unittest.TestCase): "Common utilities for DisTests and TestDisTraceback" @@ -887,11 +914,14 @@ def check(expected, **kwargs): check(dis_nested_2, depth=None) check(dis_nested_2) - _testinternalcapi = import_helper.import_module('_testinternalcapi') + @staticmethod + def code_quicken(f, times=QUICKENING_WARMUP_DELAY): + for _ in range(times): + f() @cpython_only def test_super_instructions(self): - self._testinternalcapi.code_quicken(load_test.__code__) + self.code_quicken(lambda: load_test(0, 0)) got = self.get_disassembly(load_test, adaptive=True) self.do_disassembly_compare(got, dis_load_test_quickened_code, True) @@ -906,35 +936,46 @@ def test_binary_specialize(self): 10 RETURN_VALUE """ co_int = compile('a + b', "", "eval") - self._testinternalcapi.code_quicken(co_int) - got = self.get_disassembly(co_int, adaptive=True) - self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADAPTIVE 0 (+)", True) - exec(co_int, {}, {'a': 1, 'b': 2}) + self.code_quicken(lambda: exec(co_int, {}, {'a': 1, 'b': 2})) got = self.get_disassembly(co_int, adaptive=True) self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_INT 0 (+)", True) co_unicode = compile('a + b', "", "eval") - self._testinternalcapi.code_quicken(co_unicode) - exec(co_unicode, {}, {'a': 'a', 'b': 'b'}) + self.code_quicken(lambda: exec(co_unicode, {}, {'a': 'a', 'b': 'b'})) got = self.get_disassembly(co_unicode, adaptive=True) self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)", True) + binary_subsrc_quicken = """\ + 0 RESUME_QUICK 0 + + 1 2 LOAD_NAME 0 (a) + 4 LOAD_CONST 0 (0) + 6 %s + 16 RETURN_VALUE +""" + co_list = compile('a[0]', "", "eval") + self.code_quicken(lambda: exec(co_list, {}, {'a': [0]})) + got = self.get_disassembly(co_list, adaptive=True) + self.do_disassembly_compare(got, binary_subsrc_quicken % "BINARY_SUBSCR_LIST_INT", True) + + co_dict = compile('a[0]', "", "eval") + self.code_quicken(lambda: exec(co_dict, {}, {'a': {0: '1'}})) + got = self.get_disassembly(co_dict, adaptive=True) + self.do_disassembly_compare(got, binary_subsrc_quicken % "BINARY_SUBSCR_DICT", True) + @cpython_only def test_load_attr_specialize(self): load_attr_quicken = """\ 0 RESUME_QUICK 0 1 2 LOAD_CONST 0 ('a') - 4 %s + 4 LOAD_ATTR_SLOT 0 (__class__) 14 RETURN_VALUE """ co = compile("'a'.__class__", "", "eval") - self._testinternalcapi.code_quicken(co) + self.code_quicken(lambda: exec(co, {}, {})) got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, load_attr_quicken % "LOAD_ATTR_ADAPTIVE 0 (__class__)", True) - exec(co, {}, {}) - got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, load_attr_quicken % "LOAD_ATTR_SLOT 0 (__class__)", True) + self.do_disassembly_compare(got, load_attr_quicken, True) @cpython_only def test_call_specialize(self): @@ -944,21 +985,21 @@ def test_call_specialize(self): 1 2 PUSH_NULL 4 LOAD_NAME 0 (str) 6 LOAD_CONST 0 (1) - 8 %s - 12 %s + 8 PRECALL_NO_KW_STR_1 1 + 12 CALL_ADAPTIVE 1 22 RETURN_VALUE """ co = compile("str(1)", "", "eval") - self._testinternalcapi.code_quicken(co) - got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, call_quicken % ( - "PRECALL_ADAPTIVE 1", - "CALL_ADAPTIVE 1"), True) - exec(co, {}, {}) + self.code_quicken(lambda: exec(co, {}, {})) got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, call_quicken % ( - "PRECALL_NO_KW_STR_1 1", - "CALL_ADAPTIVE 1"), True) + self.do_disassembly_compare(got, call_quicken, True) + + @cpython_only + def test_loop_quicken(self): + # Loop can trigger a quicken where the loop is located + self.code_quicken(loop_test, 1) + got = self.get_disassembly(loop_test, adaptive=True) + self.do_disassembly_compare(got, dis_loop_test_quickened_code, True) class DisWithFileTests(DisTests): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 880e8b825554aa..5d5b3e6b2fd626 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -14,7 +14,6 @@ #include "Python.h" #include "pycore_atomic_funcs.h" // _Py_atomic_int_get() #include "pycore_bitutils.h" // _Py_bswap32() -#include "pycore_code.h" // _PyCode_Quicken #include "pycore_fileutils.h" // _Py_normpath #include "pycore_gc.h" // PyGC_Head #include "pycore_hashtable.h" // _Py_hashtable_new() @@ -492,19 +491,6 @@ decode_locale_ex(PyObject *self, PyObject *args) return res; } -static PyObject * -code_quicken(PyObject *self, PyObject *code) -{ - if (!PyCode_Check(code)) { - PyErr_SetString(PyExc_TypeError, "argument must be a code object"); - return NULL; - } - PyCodeObject *co = (PyCodeObject *) code; - co->co_warmup = 0; - _PyCode_Quicken(co); - Py_RETURN_NONE; -} - static PyMethodDef TestMethods[] = { {"get_configs", get_configs, METH_NOARGS}, @@ -522,7 +508,6 @@ static PyMethodDef TestMethods[] = { {"get_getpath_codeobject", get_getpath_codeobject, METH_NOARGS, NULL}, {"EncodeLocaleEx", encode_locale_ex, METH_VARARGS}, {"DecodeLocaleEx", decode_locale_ex, METH_VARARGS}, - {"code_quicken", code_quicken, METH_O, NULL}, {NULL, NULL} /* sentinel */ }; From 82b053f226439b1a55a59c66599b10b4322780d6 Mon Sep 17 00:00:00 2001 From: penguin-wwy <940375606@qq.com> Date: Tue, 19 Apr 2022 00:40:46 +0800 Subject: [PATCH 12/12] Fix test_dis --- Lib/test/test_dis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index a0ea452e3ff638..f560a5556c8b0e 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -943,7 +943,7 @@ def test_binary_specialize(self): got = self.get_disassembly(co_unicode, adaptive=True) self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)", True) - binary_subsrc_quicken = """\ + binary_subscr_quicken = """\ 0 RESUME_QUICK 0 1 2 LOAD_NAME 0 (a) @@ -954,12 +954,12 @@ def test_binary_specialize(self): co_list = compile('a[0]', "", "eval") self.code_quicken(lambda: exec(co_list, {}, {'a': [0]})) got = self.get_disassembly(co_list, adaptive=True) - self.do_disassembly_compare(got, binary_subsrc_quicken % "BINARY_SUBSCR_LIST_INT", True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_LIST_INT", True) co_dict = compile('a[0]', "", "eval") self.code_quicken(lambda: exec(co_dict, {}, {'a': {0: '1'}})) got = self.get_disassembly(co_dict, adaptive=True) - self.do_disassembly_compare(got, binary_subsrc_quicken % "BINARY_SUBSCR_DICT", True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_DICT", True) @cpython_only def test_load_attr_specialize(self):