From b4a278ff91777967209c3547a7f1d595f2165010 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 15:34:56 +0000 Subject: [PATCH 01/17] create the InstructionFormatter and use it --- Lib/dis.py | 105 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 8d3885d2526b70..84c7e09d06c82d 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -476,47 +476,72 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field """ + return InstructionFormatter(lineno_width=lineno_width, + offset_width=offset_width, + label_width=label_width).format(self, mark_as_current) + + def __str__(self): + return self._disassemble() + + +class InstructionFormatter: + def __init__(self, lineno_width=3, offset_width=0, label_width=0): + """Create and InstructionFormatter + + *lineno_width* sets the width of the line number field (0 omits it) + *mark_as_current* inserts a '-->' marker arrow as part of the line + *offset_width* sets the width of the instruction offset field + *label_width* sets the width of the label field + """ + self.lineno_width = lineno_width + self.offset_width = offset_width + self.label_width = label_width + + def format(self, instr, mark_as_current=False): + """Format instruction details for inclusion in disassembly output.""" + lineno_width = self.lineno_width + offset_width = self.offset_width + label_width = self.label_width + fields = [] # Column: Source code line number if lineno_width: - if self.starts_line: - lineno_fmt = "%%%dd" if self.line_number is not None else "%%%ds" + if instr.starts_line: + lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" lineno_fmt = lineno_fmt % lineno_width - lineno = self.line_number if self.line_number is not None else '--' + lineno = instr.line_number if instr.line_number is not None else '--' fields.append(lineno_fmt % lineno) else: fields.append(' ' * lineno_width) # Column: Label - if self.label is not None: - lbl = f"L{self.label}:" + if instr.label is not None: + lbl = f"L{instr.label}:" fields.append(f"{lbl:>{label_width}}") else: fields.append(' ' * label_width) # Column: Instruction offset from start of code sequence if offset_width > 0: - fields.append(f"{repr(self.offset):>{offset_width}} ") + fields.append(f"{repr(instr.offset):>{offset_width}} ") # Column: Current instruction indicator if mark_as_current: fields.append('-->') else: fields.append(' ') # Column: Opcode name - fields.append(self.opname.ljust(_OPNAME_WIDTH)) + fields.append(instr.opname.ljust(_OPNAME_WIDTH)) # Column: Opcode argument - if self.arg is not None: - arg = repr(self.arg) + if instr.arg is not None: + arg = repr(instr.arg) # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into # the space reserved for oparg. This results in fewer misaligned opargs # in the disassembly output. - opname_excess = max(0, len(self.opname) - _OPNAME_WIDTH) - fields.append(repr(self.arg).rjust(_OPARG_WIDTH - opname_excess)) + opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH) + fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess)) # Column: Opcode argument details - if self.argrepr: - fields.append('(' + self.argrepr + ')') + if instr.argrepr: + fields.append('(' + instr.argrepr + ')') return ' '.join(fields).rstrip() - def __str__(self): - return self._disassemble() def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): """Iterator for the opcodes in methods, functions or code @@ -617,7 +642,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, original_code=None): + show_caches=False, original_code=None, labels_map=None): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -633,23 +658,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, co_positions = co_positions or iter(()) get_name = None if names is None else names.__getitem__ - def make_labels_map(original_code, exception_entries): - jump_targets = set(findlabels(original_code)) - labels = set(jump_targets) - for start, end, target, _, _ in exception_entries: - labels.add(start) - labels.add(end) - labels.add(target) - labels = sorted(labels) - labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))} - for e in exception_entries: - e.start_label = labels_map[e.start] - e.end_label = labels_map[e.end] - e.target_label = labels_map[e.target] - return labels_map - - labels_map = make_labels_map(original_code, exception_entries) - label_width = 4 + len(str(len(labels_map))) + labels_map = labels_map or _make_labels_map(original_code, exception_entries) exceptions_map = {} for start, end, target, _, _ in exception_entries: @@ -726,6 +735,23 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adap adaptive=adaptive, show_offsets=show_offsets ) + +def _make_labels_map(original_code, exception_entries): + jump_targets = set(findlabels(original_code)) + labels = set(jump_targets) + for start, end, target, _, _ in exception_entries: + labels.add(start) + labels.add(end) + labels.add(target) + labels = sorted(labels) + labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))} + for e in exception_entries: + e.start_label = labels_map[e.start] + e.end_label = labels_map[e.end] + e.target_label = labels_map[e.target] + return labels_map + + def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, *, file=None, line_offset=0, exception_entries=(), @@ -758,6 +784,13 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, else: offset_width = 0 + labels_map = _make_labels_map(original_code or code, exception_entries) + label_width = 4 + len(str(len(labels_map))) + + instr_formatter = InstructionFormatter(lineno_width=lineno_width, + offset_width=offset_width, + label_width=label_width) + label_width = -1 for instr in _get_instructions_bytes(code, varname_from_oparg, names, co_consts, linestarts, @@ -765,7 +798,8 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, exception_entries=exception_entries, co_positions=co_positions, show_caches=show_caches, - original_code=original_code): + original_code=original_code, + labels_map=labels_map): new_source_line = (show_lineno and instr.starts_line and instr.offset > 0) @@ -779,8 +813,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) label_width = getattr(instr, 'label_width', label_width) assert label_width >= 0 - print(instr._disassemble(lineno_width, is_current_instr, offset_width, label_width), - file=file) + print(instr_formatter.format(instr, is_current_instr), file=file) if exception_entries: print("ExceptionTable:", file=file) for entry in exception_entries: From 07c27a9197ee13ad447056a5a398b22dbd5413f0 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 16:59:24 +0000 Subject: [PATCH 02/17] move more stuff to the formatter --- Lib/dis.py | 75 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 84c7e09d06c82d..22dfc8397a88b9 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -476,8 +476,7 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field """ - return InstructionFormatter(lineno_width=lineno_width, - offset_width=offset_width, + return InstructionFormatter(offset_width=offset_width, label_width=label_width).format(self, mark_as_current) def __str__(self): @@ -485,17 +484,47 @@ def __str__(self): class InstructionFormatter: - def __init__(self, lineno_width=3, offset_width=0, label_width=0): + + NO_LINENO = ' --' + + def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, + linestarts=None, line_offset=0): """Create and InstructionFormatter *lineno_width* sets the width of the line number field (0 omits it) *mark_as_current* inserts a '-->' marker arrow as part of the line *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field + + *linestarts* dictionary mapping offset to lineno, for offsets that + start a new line """ - self.lineno_width = lineno_width + self.file = file self.offset_width = offset_width self.label_width = label_width + self.linestarts = linestarts + + # Omit the line number column entirely if we have no line number info + if bool(linestarts): + linestarts_ints = [line for line in linestarts.values() if line is not None] + show_lineno = len(linestarts_ints) > 0 + else: + show_lineno = False + + if show_lineno: + maxlineno = max(linestarts_ints) + line_offset + if maxlineno >= 1000: + lineno_width = len(str(maxlineno)) + else: + lineno_width = 3 + + if lineno_width < len(self.NO_LINENO) and None in linestarts.values(): + lineno_width = len(self.NO_LINENO) + else: + lineno_width = 0 + + self.lineno_width = lineno_width + def format(self, instr, mark_as_current=False): """Format instruction details for inclusion in disassembly output.""" @@ -503,13 +532,19 @@ def format(self, instr, mark_as_current=False): offset_width = self.offset_width label_width = self.label_width + new_source_line = (lineno_width > 0 and + instr.starts_line and + instr.offset > 0) + if new_source_line: + print(file=self.file) + fields = [] # Column: Source code line number if lineno_width: if instr.starts_line: lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" lineno_fmt = lineno_fmt % lineno_width - lineno = instr.line_number if instr.line_number is not None else '--' + lineno = self.NO_LINENO if instr.line_number is None else instr.line_number fields.append(lineno_fmt % lineno) else: fields.append(' ' * lineno_width) @@ -758,23 +793,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, co_positions=None, show_caches=False, original_code=None, show_offsets=False): # Omit the line number column entirely if we have no line number info - if bool(linestarts): - linestarts_ints = [line for line in linestarts.values() if line is not None] - show_lineno = len(linestarts_ints) > 0 - else: - show_lineno = False - if show_lineno: - maxlineno = max(linestarts_ints) + line_offset - if maxlineno >= 1000: - lineno_width = len(str(maxlineno)) - else: - lineno_width = 3 - - if lineno_width < len(str(None)) and None in linestarts.values(): - lineno_width = len(str(None)) - else: - lineno_width = 0 if show_offsets: maxoffset = len(code) - 2 if maxoffset >= 10000: @@ -787,11 +806,12 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, labels_map = _make_labels_map(original_code or code, exception_entries) label_width = 4 + len(str(len(labels_map))) - instr_formatter = InstructionFormatter(lineno_width=lineno_width, + instr_formatter = InstructionFormatter(file=file, offset_width=offset_width, - label_width=label_width) + label_width=label_width, + linestarts=linestarts, + line_offset=line_offset) - label_width = -1 for instr in _get_instructions_bytes(code, varname_from_oparg, names, co_consts, linestarts, line_offset=line_offset, @@ -800,19 +820,12 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, show_caches=show_caches, original_code=original_code, labels_map=labels_map): - new_source_line = (show_lineno and - instr.starts_line and - instr.offset > 0) - if new_source_line: - print(file=file) if show_caches: is_current_instr = instr.offset == lasti else: # Each CACHE takes 2 bytes is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) - label_width = getattr(instr, 'label_width', label_width) - assert label_width >= 0 print(instr_formatter.format(instr, is_current_instr), file=file) if exception_entries: print("ExceptionTable:", file=file) From 85c46ba56161ff50d4eca9c374af4bc9e8d13f90 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 18:17:37 +0000 Subject: [PATCH 03/17] fix comment --- Lib/dis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/dis.py b/Lib/dis.py index 22dfc8397a88b9..42159eda3b1dd2 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -491,13 +491,14 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, linestarts=None, line_offset=0): """Create and InstructionFormatter + *file* where to write the output *lineno_width* sets the width of the line number field (0 omits it) - *mark_as_current* inserts a '-->' marker arrow as part of the line *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field *linestarts* dictionary mapping offset to lineno, for offsets that start a new line + *line_offset* the line number (within the code unit) """ self.file = file self.offset_width = offset_width From c46273030cd1a1398bf8734a5b3f9580d0a2cba2 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 18:47:02 +0000 Subject: [PATCH 04/17] make the formatter print the exception table too --- Lib/dis.py | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 42159eda3b1dd2..669221e0af2f70 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -476,20 +476,20 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field """ - return InstructionFormatter(offset_width=offset_width, - label_width=label_width).format(self, mark_as_current) + return Formatter(offset_width=offset_width, + label_width=label_width).format(self, mark_as_current) def __str__(self): return self._disassemble() -class InstructionFormatter: +class Formatter: NO_LINENO = ' --' def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, - linestarts=None, line_offset=0): - """Create and InstructionFormatter + linestarts=None, exception_entries=None, line_offset=0): + """Create a Formatter *file* where to write the output *lineno_width* sets the width of the line number field (0 omits it) @@ -504,6 +504,7 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, self.offset_width = offset_width self.label_width = label_width self.linestarts = linestarts + self.exception_entries = exception_entries # Omit the line number column entirely if we have no line number info if bool(linestarts): @@ -527,7 +528,7 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, self.lineno_width = lineno_width - def format(self, instr, mark_as_current=False): + def print_instruction(self, instr, mark_as_current=False): """Format instruction details for inclusion in disassembly output.""" lineno_width = self.lineno_width offset_width = self.offset_width @@ -576,7 +577,18 @@ def format(self, instr, mark_as_current=False): # Column: Opcode argument details if instr.argrepr: fields.append('(' + instr.argrepr + ')') - return ' '.join(fields).rstrip() + print(' '.join(fields).rstrip(), file=self.file) + + def print_exception_table(self): + file = self.file + if self.exception_entries: + print("ExceptionTable:", file=file) + for entry in self.exception_entries: + lasti = " lasti" if entry.lasti else "" + start = entry.start_label + end = entry.end_label + target = entry.target_label + print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): @@ -807,11 +819,12 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, labels_map = _make_labels_map(original_code or code, exception_entries) label_width = 4 + len(str(len(labels_map))) - instr_formatter = InstructionFormatter(file=file, - offset_width=offset_width, - label_width=label_width, - linestarts=linestarts, - line_offset=line_offset) + formatter = Formatter(file=file, + offset_width=offset_width, + label_width=label_width, + linestarts=linestarts, + line_offset=line_offset, + exception_entries=exception_entries) for instr in _get_instructions_bytes(code, varname_from_oparg, names, co_consts, linestarts, @@ -827,15 +840,9 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, # Each CACHE takes 2 bytes is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) - print(instr_formatter.format(instr, is_current_instr), file=file) - if exception_entries: - print("ExceptionTable:", file=file) - for entry in exception_entries: - lasti = " lasti" if entry.lasti else "" - start = entry.start_label - end = entry.end_label - target = entry.target_label - print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) + formatter.print_instruction(instr, is_current_instr) + + formatter.print_exception_table() def _disassemble_str(source, **kwargs): """Compile the source string, then disassemble the code object.""" From 7a1ee26f0b9fb14ed0f8990a14c2b15ec3f6ff6b Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 19:07:31 +0000 Subject: [PATCH 05/17] remove unused code --- Lib/dis.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 669221e0af2f70..104495dad74921 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -410,7 +410,7 @@ def _get_argval_argrepr(op, arg, offset, co_consts, names, varname_from_oparg, def _create(cls, op, arg, offset, start_offset, starts_line, line_number, positions, co_consts=None, varname_from_oparg=None, names=None, - labels_map=None, exceptions_map=None): + labels_map=None): argval, argrepr = cls._get_argval_argrepr( op, arg, offset, @@ -419,8 +419,6 @@ def _create(cls, op, arg, offset, start_offset, starts_line, line_number, instr = Instruction(_all_opname[op], op, arg, argval, argrepr, offset, start_offset, starts_line, line_number, label, positions) - instr.label_width = 4 + len(str(len(labels_map))) - instr.exc_handler = exceptions_map.get(offset, None) return instr @property @@ -498,6 +496,7 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, *linestarts* dictionary mapping offset to lineno, for offsets that start a new line + *exception_entries* the exception table (list of ExceptionEntry) *line_offset* the line number (within the code unit) """ self.file = file @@ -708,11 +707,6 @@ def _get_instructions_bytes(code, varname_from_oparg=None, labels_map = labels_map or _make_labels_map(original_code, exception_entries) - exceptions_map = {} - for start, end, target, _, _ in exception_entries: - exceptions_map[start] = labels_map[target] - exceptions_map[end] = -1 - starts_line = False local_line_number = None line_number = None @@ -732,7 +726,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, yield Instruction._create(op, arg, offset, start_offset, starts_line, line_number, positions, co_consts=co_consts, varname_from_oparg=varname_from_oparg, names=names, - labels_map=labels_map, exceptions_map=exceptions_map) + labels_map=labels_map) caches = _get_cache_size(_all_opname[deop]) if not caches: From f96f0faea0bd64009ab08e2661c5eb528c3eaf0b Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 19:29:18 +0000 Subject: [PATCH 06/17] fix Instruction.__str__ (and add test for it) --- Lib/dis.py | 9 +++++++-- Lib/test/test_dis.py | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 104495dad74921..284a7050c1d7d5 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -474,8 +474,13 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field """ - return Formatter(offset_width=offset_width, - label_width=label_width).format(self, mark_as_current) + output = io.StringIO() + formatter = Formatter(file=output, + lineno_width=lineno_width, + offset_width=offset_width, + label_width=label_width) + formatter.print_instruction(self, mark_as_current) + return output.getvalue() def __str__(self): return self._disassemble() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 349790ecd7d075..aacc20ac3e8f32 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1785,6 +1785,12 @@ def __init__(self, *args): super().__init__(*args) self.maxDiff = None + def test_instruction_str(self): + # smoke test for __str__ + instrs = dis.get_instructions(simple) + for instr in instrs: + str(instr) + def test_default_first_line(self): actual = dis.get_instructions(simple) self.assertInstructionsEqual(list(actual), expected_opinfo_simple) From 11e7af866594dee43e11470dd203fac7260a4e1c Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 19:31:21 +0000 Subject: [PATCH 07/17] inline Instruction._disassemble --- Lib/dis.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 284a7050c1d7d5..3bb5ae55efd499 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -465,26 +465,15 @@ def is_jump_target(self): """True if other code jumps to here, otherwise False""" return self.label is not None - def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, - label_width=0): - """Format instruction details for inclusion in disassembly output. - - *lineno_width* sets the width of the line number field (0 omits it) - *mark_as_current* inserts a '-->' marker arrow as part of the line - *offset_width* sets the width of the instruction offset field - *label_width* sets the width of the label field - """ + def __str__(self): output = io.StringIO() formatter = Formatter(file=output, - lineno_width=lineno_width, - offset_width=offset_width, - label_width=label_width) - formatter.print_instruction(self, mark_as_current) + lineno_width=3, + offset_width=0, + label_width=0) + formatter.print_instruction(self, False) return output.getvalue() - def __str__(self): - return self._disassemble() - class Formatter: From 8964fcb52b0a2f44baa6f422627dd940d11f56db Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 20:18:20 +0000 Subject: [PATCH 08/17] move _get_argval_argrepr out of Instruction so that Instruction is more generic --- Lib/dis.py | 140 +++++++++++++++++++++---------------------- Lib/test/test_dis.py | 2 +- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 3bb5ae55efd499..175db4a81b06bb 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -336,83 +336,13 @@ class Instruction(_Instruction): covered by this instruction """ - @staticmethod - def _get_argval_argrepr(op, arg, offset, co_consts, names, varname_from_oparg, - labels_map): - get_name = None if names is None else names.__getitem__ - argval = None - argrepr = '' - 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. - argval = arg - 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 = f"{argrepr} + NULL" - elif deop == LOAD_ATTR: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - elif deop == LOAD_SUPER_ATTR: - argval, argrepr = _get_name_info(arg//4, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - else: - argval, argrepr = _get_name_info(arg, get_name) - elif deop in hasjabs: - argval = arg*2 - argrepr = f"to L{labels_map[argval]}" - elif deop in hasjrel: - signed_arg = -arg if _is_backward_jump(deop) else arg - argval = offset + 2 + signed_arg*2 - caches = _get_cache_size(_all_opname[deop]) - argval += 2 * caches - if deop == ENTER_EXECUTOR: - argval += 2 - argrepr = f"to L{labels_map[argval]}" - elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): - arg1 = arg >> 4 - arg2 = arg & 15 - val1, argrepr1 = _get_name_info(arg1, varname_from_oparg) - val2, argrepr2 = _get_name_info(arg2, varname_from_oparg) - argrepr = argrepr1 + ", " + argrepr2 - argval = val1, val2 - elif deop in haslocal or deop in hasfree: - argval, argrepr = _get_name_info(arg, varname_from_oparg) - elif deop in hascompare: - argval = cmp_op[arg >> 5] - argrepr = argval - if arg & 16: - argrepr = f"bool({argrepr})" - elif deop == CONVERT_VALUE: - argval = (None, str, repr, ascii)[arg] - argrepr = ('', 'str', 'repr', 'ascii')[arg] - elif deop == SET_FUNCTION_ATTRIBUTE: - argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) - if arg & (1< L{target} [{entry.depth}]{lasti}", file=file) +def _get_argval_argrepr(op, arg, offset, co_consts, names, varname_from_oparg, + labels_map): + get_name = None if names is None else names.__getitem__ + argval = None + argrepr = '' + 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. + argval = arg + 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 = f"{argrepr} + NULL" + elif deop == LOAD_ATTR: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + elif deop == LOAD_SUPER_ATTR: + argval, argrepr = _get_name_info(arg//4, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + else: + argval, argrepr = _get_name_info(arg, get_name) + elif deop in hasjabs: + argval = arg*2 + argrepr = f"to L{labels_map[argval]}" + elif deop in hasjrel: + signed_arg = -arg if _is_backward_jump(deop) else arg + argval = offset + 2 + signed_arg*2 + caches = _get_cache_size(_all_opname[deop]) + argval += 2 * caches + if deop == ENTER_EXECUTOR: + argval += 2 + argrepr = f"to L{labels_map[argval]}" + elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): + arg1 = arg >> 4 + arg2 = arg & 15 + val1, argrepr1 = _get_name_info(arg1, varname_from_oparg) + val2, argrepr2 = _get_name_info(arg2, varname_from_oparg) + argrepr = argrepr1 + ", " + argrepr2 + argval = val1, val2 + elif deop in haslocal or deop in hasfree: + argval, argrepr = _get_name_info(arg, varname_from_oparg) + elif deop in hascompare: + argval = cmp_op[arg >> 5] + argrepr = argval + if arg & 16: + argrepr = f"bool({argrepr})" + elif deop == CONVERT_VALUE: + argval = (None, str, repr, ascii)[arg] + argrepr = ('', 'str', 'repr', 'ascii')[arg] + elif deop == SET_FUNCTION_ATTRIBUTE: + argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) + if arg & (1< Date: Mon, 4 Dec 2023 20:23:12 +0000 Subject: [PATCH 09/17] inline the Instruction._create function --- Lib/dis.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 175db4a81b06bb..c1e89034fc6497 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -336,21 +336,6 @@ class Instruction(_Instruction): covered by this instruction """ - @classmethod - def _create(cls, op, arg, offset, start_offset, starts_line, line_number, - positions, - co_consts=None, varname_from_oparg=None, names=None, - labels_map=None): - - argval, argrepr = _get_argval_argrepr( - op, arg, offset, - co_consts, names, varname_from_oparg, labels_map) - label = labels_map.get(offset, None) - instr = Instruction(_all_opname[op], op, arg, argval, argrepr, - offset, start_offset, starts_line, line_number, - label, positions) - return instr - @property def oparg(self): """Alias for Instruction.arg.""" @@ -715,10 +700,13 @@ def _get_instructions_bytes(code, varname_from_oparg=None, deop = _deoptop(op) op = code[offset] - yield Instruction._create(op, arg, offset, start_offset, starts_line, line_number, - positions, co_consts=co_consts, - varname_from_oparg=varname_from_oparg, names=names, - labels_map=labels_map) + argval, argrepr = _get_argval_argrepr( + op, arg, offset, + co_consts, names, varname_from_oparg, labels_map) + + yield Instruction(_all_opname[op], op, arg, argval, argrepr, + offset, start_offset, starts_line, line_number, + labels_map.get(offset, None), positions) caches = _get_cache_size(_all_opname[deop]) if not caches: From 0b6ae887d53e74ad73e82d4d5d92cbe9da3ea66c Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 22:43:54 +0000 Subject: [PATCH 10/17] extract print_instructions --- Lib/dis.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index c1e89034fc6497..187f126782c8f6 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -800,14 +800,20 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, line_offset=line_offset, exception_entries=exception_entries) - for instr in _get_instructions_bytes(code, varname_from_oparg, names, - co_consts, linestarts, - line_offset=line_offset, - exception_entries=exception_entries, - co_positions=co_positions, - show_caches=show_caches, - original_code=original_code, - labels_map=labels_map): + instrs = _get_instructions_bytes(code, varname_from_oparg, names, + co_consts, linestarts, + line_offset=line_offset, + exception_entries=exception_entries, + co_positions=co_positions, + show_caches=show_caches, + original_code=original_code, + labels_map=labels_map) + + print_instructions(instrs, formatter, show_caches=show_caches, lasti=lasti) + + +def print_instructions(instrs, formatter, show_caches=False, lasti=-1): + for instr in instrs: if show_caches: is_current_instr = instr.offset == lasti else: @@ -815,7 +821,6 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) formatter.print_instruction(instr, is_current_instr) - formatter.print_exception_table() def _disassemble_str(source, **kwargs): From 70b79ba4b82244899a58b4b5cb144d444f3c7520 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 23:09:22 +0000 Subject: [PATCH 11/17] _get_instructions_bytes doesn't take the exception table --- Lib/dis.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 187f126782c8f6..b2516b9bd24f72 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -584,13 +584,16 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): line_offset = first_line - co.co_firstlineno else: line_offset = 0 + + original_code = co.co_code + labels_map = _make_labels_map(original_code) return _get_instructions_bytes(_get_code_array(co, adaptive), co._varname_from_oparg, co.co_names, co.co_consts, linestarts, line_offset, co_positions=co.co_positions(), show_caches=show_caches, - original_code=co.co_code) + original_code=original_code) def _get_const_value(op, arg, co_consts): """Helper to get the value of the const in a hasconst op. @@ -665,7 +668,7 @@ def _is_backward_jump(op): def _get_instructions_bytes(code, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, line_offset=0, - exception_entries=(), co_positions=None, + co_positions=None, show_caches=False, original_code=None, labels_map=None): """Iterate over the instructions in a bytecode string. @@ -682,7 +685,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None, co_positions = co_positions or iter(()) get_name = None if names is None else names.__getitem__ - labels_map = labels_map or _make_labels_map(original_code, exception_entries) + labels_map = labels_map or _make_labels_map(original_code) starts_line = False local_line_number = None @@ -758,7 +761,7 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adap ) -def _make_labels_map(original_code, exception_entries): +def _make_labels_map(original_code, exception_entries=()): jump_targets = set(findlabels(original_code)) labels = set(jump_targets) for start, end, target, _, _ in exception_entries: @@ -803,7 +806,6 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, instrs = _get_instructions_bytes(code, varname_from_oparg, names, co_consts, linestarts, line_offset=line_offset, - exception_entries=exception_entries, co_positions=co_positions, show_caches=show_caches, original_code=original_code, @@ -960,15 +962,17 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False def __iter__(self): co = self.codeobj + original_code = co.co_code + labels_map = _make_labels_map(original_code, self.exception_entries) return _get_instructions_bytes(_get_code_array(co, self.adaptive), 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, - original_code=co.co_code) + original_code=original_code, + labels_map=labels_map) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, From c7ead5e0a4586a6e30f5212d0632c6a04934d5e6 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Dec 2023 23:42:49 +0000 Subject: [PATCH 12/17] add ArgResolver --- Lib/dis.py | 187 ++++++++++++++++++++++--------------------- Lib/test/test_dis.py | 9 ++- 2 files changed, 102 insertions(+), 94 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index b2516b9bd24f72..3694c733463e44 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -498,73 +498,80 @@ def print_exception_table(self): target = entry.target_label print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) -def _get_argval_argrepr(op, arg, offset, co_consts, names, varname_from_oparg, - labels_map): - get_name = None if names is None else names.__getitem__ - argval = None - argrepr = '' - 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. - argval = arg - 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 = f"{argrepr} + NULL" - elif deop == LOAD_ATTR: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - elif deop == LOAD_SUPER_ATTR: - argval, argrepr = _get_name_info(arg//4, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - else: - argval, argrepr = _get_name_info(arg, get_name) - elif deop in hasjabs: - argval = arg*2 - argrepr = f"to L{labels_map[argval]}" - elif deop in hasjrel: - signed_arg = -arg if _is_backward_jump(deop) else arg - argval = offset + 2 + signed_arg*2 - caches = _get_cache_size(_all_opname[deop]) - argval += 2 * caches - if deop == ENTER_EXECUTOR: - argval += 2 - argrepr = f"to L{labels_map[argval]}" - elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): - arg1 = arg >> 4 - arg2 = arg & 15 - val1, argrepr1 = _get_name_info(arg1, varname_from_oparg) - val2, argrepr2 = _get_name_info(arg2, varname_from_oparg) - argrepr = argrepr1 + ", " + argrepr2 - argval = val1, val2 - elif deop in haslocal or deop in hasfree: - argval, argrepr = _get_name_info(arg, varname_from_oparg) - elif deop in hascompare: - argval = cmp_op[arg >> 5] - argrepr = argval - if arg & 16: - argrepr = f"bool({argrepr})" - elif deop == CONVERT_VALUE: - argval = (None, str, repr, ascii)[arg] - argrepr = ('', 'str', 'repr', 'ascii')[arg] - elif deop == SET_FUNCTION_ATTRIBUTE: - argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) - if arg & (1<> 4 + arg2 = arg & 15 + val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg) + val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg) + argrepr = argrepr1 + ", " + argrepr2 + argval = val1, val2 + elif deop in haslocal or deop in hasfree: + argval, argrepr = _get_name_info(arg, self.varname_from_oparg) + elif deop in hascompare: + argval = cmp_op[arg >> 5] + argrepr = argval + if arg & 16: + argrepr = f"bool({argrepr})" + elif deop == CONVERT_VALUE: + argval = (None, str, repr, ascii)[arg] + argrepr = ('', 'str', 'repr', 'ascii')[arg] + elif deop == SET_FUNCTION_ATTRIBUTE: + argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) + if arg & (1< Date: Tue, 5 Dec 2023 11:00:14 +0000 Subject: [PATCH 13/17] remove obsolete comment --- Lib/dis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/dis.py b/Lib/dis.py index 3694c733463e44..f2cd54467fba15 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -787,7 +787,6 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, *, file=None, line_offset=0, exception_entries=(), co_positions=None, show_caches=False, original_code=None, show_offsets=False): - # Omit the line number column entirely if we have no line number info if show_offsets: maxoffset = len(code) - 2 From f72700933fdae5ecd4b9c919c68c3eafccd189c4 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Tue, 5 Dec 2023 11:13:17 +0000 Subject: [PATCH 14/17] shorter offset_width code --- Lib/dis.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index f2cd54467fba15..c0dbe30bd26ab7 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -788,14 +788,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, co_positions=None, show_caches=False, original_code=None, show_offsets=False): - if show_offsets: - maxoffset = len(code) - 2 - if maxoffset >= 10000: - offset_width = len(str(maxoffset)) - else: - offset_width = 4 - else: - offset_width = 0 + offset_width = len(str(max(len(code) - 2, 9999))) if show_offsets else 0 labels_map = _make_labels_map(original_code or code, exception_entries) label_width = 4 + len(str(len(labels_map))) From fb801f2d0bdabdeff8b9e13ee1c728ed85206ec6 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 7 Dec 2023 13:45:32 +0000 Subject: [PATCH 15/17] Formatter doesn't need the exception_entries field --- Lib/dis.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index c0dbe30bd26ab7..fd262adff43ff0 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -395,7 +395,7 @@ class Formatter: NO_LINENO = ' --' def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, - linestarts=None, exception_entries=None, line_offset=0): + linestarts=None, line_offset=0): """Create a Formatter *file* where to write the output @@ -412,7 +412,6 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, self.offset_width = offset_width self.label_width = label_width self.linestarts = linestarts - self.exception_entries = exception_entries # Omit the line number column entirely if we have no line number info if bool(linestarts): @@ -487,11 +486,11 @@ def print_instruction(self, instr, mark_as_current=False): fields.append('(' + instr.argrepr + ')') print(' '.join(fields).rstrip(), file=self.file) - def print_exception_table(self): + def print_exception_table(self, exception_entries): file = self.file - if self.exception_entries: + if exception_entries: print("ExceptionTable:", file=file) - for entry in self.exception_entries: + for entry in exception_entries: lasti = " lasti" if entry.lasti else "" start = entry.start_label end = entry.end_label @@ -797,8 +796,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, offset_width=offset_width, label_width=label_width, linestarts=linestarts, - line_offset=line_offset, - exception_entries=exception_entries) + line_offset=line_offset) arg_resolver = ArgResolver(co_consts, names, varname_from_oparg, labels_map) instrs = _get_instructions_bytes(code, linestarts=linestarts, @@ -809,10 +807,11 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, labels_map=labels_map, arg_resolver=arg_resolver) - print_instructions(instrs, formatter, show_caches=show_caches, lasti=lasti) + print_instructions(instrs, exception_entries, formatter, + show_caches=show_caches, lasti=lasti) -def print_instructions(instrs, formatter, show_caches=False, lasti=-1): +def print_instructions(instrs, exception_entries, formatter, show_caches=False, lasti=-1): for instr in instrs: if show_caches: is_current_instr = instr.offset == lasti @@ -821,7 +820,7 @@ def print_instructions(instrs, formatter, show_caches=False, lasti=-1): is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) formatter.print_instruction(instr, is_current_instr) - formatter.print_exception_table() + formatter.print_exception_table(exception_entries) def _disassemble_str(source, **kwargs): """Compile the source string, then disassemble the code object.""" From 322ca098ea766ed75ba114b6e137ee03adc3954f Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 7 Dec 2023 17:19:19 +0000 Subject: [PATCH 16/17] remove obsolete comment --- Lib/dis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/dis.py b/Lib/dis.py index fd262adff43ff0..d04b64d90ca346 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -405,7 +405,6 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, *linestarts* dictionary mapping offset to lineno, for offsets that start a new line - *exception_entries* the exception table (list of ExceptionEntry) *line_offset* the line number (within the code unit) """ self.file = file From 3943eb20c78373eec9c61a227cd217379ca3af6b Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 7 Dec 2023 18:14:11 +0000 Subject: [PATCH 17/17] Formatter doesn't need linestarts --- Lib/dis.py | 54 ++++++++++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index d04b64d90ca346..efa935c5a6a0b6 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -382,20 +382,15 @@ def is_jump_target(self): def __str__(self): output = io.StringIO() - formatter = Formatter(file=output, - lineno_width=3, - offset_width=0, - label_width=0) + formatter = Formatter(file=output) formatter.print_instruction(self, False) return output.getvalue() class Formatter: - NO_LINENO = ' --' - - def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, - linestarts=None, line_offset=0): + def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, + line_offset=0): """Create a Formatter *file* where to write the output @@ -403,35 +398,12 @@ def __init__(self, file=None, lineno_width=3, offset_width=0, label_width=0, *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field - *linestarts* dictionary mapping offset to lineno, for offsets that - start a new line *line_offset* the line number (within the code unit) """ self.file = file + self.lineno_width = lineno_width self.offset_width = offset_width self.label_width = label_width - self.linestarts = linestarts - - # Omit the line number column entirely if we have no line number info - if bool(linestarts): - linestarts_ints = [line for line in linestarts.values() if line is not None] - show_lineno = len(linestarts_ints) > 0 - else: - show_lineno = False - - if show_lineno: - maxlineno = max(linestarts_ints) + line_offset - if maxlineno >= 1000: - lineno_width = len(str(maxlineno)) - else: - lineno_width = 3 - - if lineno_width < len(self.NO_LINENO) and None in linestarts.values(): - lineno_width = len(self.NO_LINENO) - else: - lineno_width = 0 - - self.lineno_width = lineno_width def print_instruction(self, instr, mark_as_current=False): @@ -452,7 +424,7 @@ def print_instruction(self, instr, mark_as_current=False): if instr.starts_line: lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" lineno_fmt = lineno_fmt % lineno_width - lineno = self.NO_LINENO if instr.line_number is None else instr.line_number + lineno = _NO_LINENO if instr.line_number is None else instr.line_number fields.append(lineno_fmt % lineno) else: fields.append(' ' * lineno_width) @@ -779,6 +751,20 @@ def _make_labels_map(original_code, exception_entries=()): e.target_label = labels_map[e.target] return labels_map +_NO_LINENO = ' --' + +def _get_lineno_width(linestarts): + if linestarts is None: + return 0 + maxlineno = max(filter(None, linestarts.values()), default=-1) + if maxlineno == -1: + # Omit the line number column entirely if we have no line number info + return 0 + lineno_width = max(3, len(str(maxlineno))) + if lineno_width < len(_NO_LINENO) and None in linestarts.values(): + lineno_width = len(_NO_LINENO) + return lineno_width + def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, names=None, co_consts=None, linestarts=None, @@ -792,9 +778,9 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, label_width = 4 + len(str(len(labels_map))) formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), offset_width=offset_width, label_width=label_width, - linestarts=linestarts, line_offset=line_offset) arg_resolver = ArgResolver(co_consts, names, varname_from_oparg, labels_map)