Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0f70a02

Browse files
committed
Add more convenience properties to dis.Instruction
Adds start_offset, cache_offset, end_offset, baseopcode, baseopname, jump_target and oparg to dis.Instruction. Also slightly improves the disassembly output by allowing opnames to overflow into the space reserved for opargs.
1 parent 6c4124d commit 0f70a02

3 files changed

Lines changed: 421 additions & 193 deletions

File tree

Doc/library/dis.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,23 @@ details of bytecode instructions as :class:`Instruction` instances:
342342
human readable name for operation
343343

344344

345+
.. data:: baseopcode
346+
347+
numeric code for the base operation if operation is specialized. Otherwise equal to :data:`opcode`
348+
349+
350+
.. data:: baseopname
351+
352+
human readable name for the base operation if operation is specialized. Otherwise equal to :data:`opname`
353+
354+
345355
.. data:: arg
346356

347357
numeric argument to operation (if any), otherwise ``None``
348358

359+
.. data:: oparg
360+
361+
alias for :data:`arg`
349362

350363
.. data:: argval
351364

@@ -363,6 +376,22 @@ details of bytecode instructions as :class:`Instruction` instances:
363376
start index of operation within bytecode sequence
364377

365378

379+
.. data:: start_offset
380+
381+
start index of operation within bytecode sequence including prefixed ``EXTENDED_ARG`` operations if present.
382+
Otherwise equal to :data:`offset`
383+
384+
385+
.. data:: cache_offset
386+
387+
start index of the cache entries following the operation
388+
389+
390+
.. data:: end_offset
391+
392+
end index of the cache entries following the operation
393+
394+
366395
.. data:: starts_line
367396

368397
line started by this opcode (if any), otherwise ``None``
@@ -373,6 +402,11 @@ details of bytecode instructions as :class:`Instruction` instances:
373402
``True`` if other code jumps to here, otherwise ``False``
374403

375404

405+
.. data:: jump_target
406+
407+
bytecode index of the jump target if this is a jump operation, otherwise ``None``
408+
409+
376410
.. data:: positions
377411

378412
:class:`dis.Positions` object holding the
@@ -384,6 +418,10 @@ details of bytecode instructions as :class:`Instruction` instances:
384418

385419
Field ``positions`` is added.
386420

421+
.. versionchanged:: 3.12
422+
423+
Fields ``start_offset``, ``cache_offset``, ``end_offset``, ``baseopname``, ``baseopcode``, ``jump_target`` and ``oparg`` are added.
424+
387425

388426
.. class:: Positions
389427

Lib/dis.py

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def show_code(co, *, file=None):
258258
'argval',
259259
'argrepr',
260260
'offset',
261+
'start_offset',
261262
'starts_line',
262263
'is_jump_target',
263264
'positions'
@@ -271,6 +272,8 @@ def show_code(co, *, file=None):
271272
_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg"
272273
_Instruction.argrepr.__doc__ = "Human readable description of operation argument"
273274
_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"
275+
_Instruction.start_offset.__doc__ = "Start index of operation within bytecode sequence including extended args if present. " \
276+
"Otherwise equal to Instruction.offset"
274277
_Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
275278
_Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
276279
_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"
@@ -281,6 +284,23 @@ def show_code(co, *, file=None):
281284
_OPNAME_WIDTH = 20
282285
_OPARG_WIDTH = 5
283286

287+
def _get_jump_target(op, arg, offset):
288+
"""Gets the bytecode offset of the jump target if this is a jump instruction,
289+
otherwise returns None
290+
"""
291+
deop = _deoptop(op)
292+
caches = _inline_cache_entries[deop]
293+
if deop in hasjrel:
294+
if _is_backward_jump(deop):
295+
arg = -arg
296+
target = offset + 2 + arg*2
297+
target += 2 * caches
298+
elif deop in hasjabs:
299+
target = arg*2
300+
else:
301+
target = None
302+
return target
303+
284304
class Instruction(_Instruction):
285305
"""Details for a bytecode operation
286306
@@ -291,12 +311,48 @@ class Instruction(_Instruction):
291311
argval - resolved arg value (if known), otherwise same as arg
292312
argrepr - human readable description of operation argument
293313
offset - start index of operation within bytecode sequence
314+
start_offset - start index of operation within bytecode sequence including extended args if present.
315+
Otherwise equal to Instruction.offset
294316
starts_line - line started by this opcode (if any), otherwise None
295317
is_jump_target - True if other code jumps to here, otherwise False
296318
positions - Optional dis.Positions object holding the span of source code
297319
covered by this instruction
298320
"""
299321

322+
@property
323+
def oparg(self):
324+
"""Alias for Instruction.arg"""
325+
return self.arg
326+
327+
@property
328+
def baseopcode(self):
329+
"""numeric code for the base operation if operation is specialized.
330+
Otherwise equal to Instruction.opcode
331+
"""
332+
return _deoptop(self.opcode)
333+
334+
@property
335+
def baseopname(self):
336+
"""human readable name for the base operation if operation is specialized.
337+
Otherwise equal to Instruction.opname
338+
"""
339+
return opname[self.baseopcode]
340+
341+
@property
342+
def cache_offset(self):
343+
"""start index of the cache entries following the operation"""
344+
return self.offset + 2
345+
346+
@property
347+
def end_offset(self):
348+
"""end index of the cache entries following the operation"""
349+
return self.cache_offset + _inline_cache_entries[self.opcode]*2
350+
351+
@property
352+
def jump_target(self):
353+
"""bytecode index of the jump target if this is a jump operation, otherwise None"""
354+
return _get_jump_target(self.opcode, self.arg, self.offset)
355+
300356
def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
301357
"""Format instruction details for inclusion in disassembly output
302358
@@ -328,12 +384,23 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
328384
fields.append(self.opname.ljust(_OPNAME_WIDTH))
329385
# Column: Opcode argument
330386
if self.arg is not None:
331-
fields.append(repr(self.arg).rjust(_OPARG_WIDTH))
387+
arg = repr(self.arg)
388+
# If opname is longer than _OPNAME_WIDTH, but the total length together with
389+
# oparg is less than _OPNAME_WIDTH + _OPARG_WIDTH (with at least one space in between),
390+
# we allow opname to overflow into the space reserved for oparg.
391+
# This results in fewer misaligned opargs in the disassembly output
392+
opname_excess = max(0, len(self.opname) - _OPNAME_WIDTH)
393+
if opname_excess + len(arg) < _OPARG_WIDTH:
394+
fields.append(arg.rjust(_OPARG_WIDTH - opname_excess))
395+
else:
396+
fields.append(arg.rjust(_OPARG_WIDTH))
332397
# Column: Opcode argument details
333398
if self.argrepr:
334399
fields.append('(' + self.argrepr + ')')
335400
return ' '.join(fields).rstrip()
336401

402+
def __str__(self):
403+
return self._disassemble()
337404

338405
def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
339406
"""Iterator for the opcodes in methods, functions or code
@@ -448,7 +515,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
448515
for i in range(start, end):
449516
labels.add(target)
450517
starts_line = None
451-
for offset, op, arg in _unpack_opargs(code):
518+
for offset, start_offset, op, arg in _unpack_opargs(code):
452519
if linestarts is not None:
453520
starts_line = linestarts.get(offset, None)
454521
if starts_line is not None:
@@ -509,7 +576,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
509576
_, argrepr = _nb_ops[arg]
510577
yield Instruction(_all_opname[op], op,
511578
arg, argval, argrepr,
512-
offset, starts_line, is_jump_target, positions)
579+
offset, start_offset, starts_line, is_jump_target, positions)
513580
caches = _inline_cache_entries[deop]
514581
if not caches:
515582
continue
@@ -529,7 +596,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
529596
else:
530597
argrepr = ""
531598
yield Instruction(
532-
"CACHE", CACHE, 0, None, argrepr, offset, None, False,
599+
"CACHE", CACHE, 0, None, argrepr, offset, offset, None, False,
533600
Positions(*next(co_positions, ()))
534601
)
535602

@@ -615,6 +682,7 @@ def _disassemble_str(source, **kwargs):
615682

616683
def _unpack_opargs(code):
617684
extended_arg = 0
685+
extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction
618686
caches = 0
619687
for i in range(0, len(code), 2):
620688
# Skip inline CACHE entries:
@@ -635,7 +703,13 @@ def _unpack_opargs(code):
635703
else:
636704
arg = None
637705
extended_arg = 0
638-
yield (i, op, arg)
706+
if deop == EXTENDED_ARG:
707+
extended_args_offset += 1
708+
yield (i, i, op, arg)
709+
else:
710+
start_offset = i - extended_args_offset*2
711+
yield (i, start_offset, op, arg)
712+
extended_args_offset = 0
639713

640714
def findlabels(code):
641715
"""Detect all offsets in a byte code which are jump targets.
@@ -644,18 +718,10 @@ def findlabels(code):
644718
645719
"""
646720
labels = []
647-
for offset, op, arg in _unpack_opargs(code):
721+
for offset, _, op, arg in _unpack_opargs(code):
648722
if arg is not None:
649-
deop = _deoptop(op)
650-
caches = _inline_cache_entries[deop]
651-
if deop in hasjrel:
652-
if _is_backward_jump(deop):
653-
arg = -arg
654-
label = offset + 2 + arg*2
655-
label += 2 * caches
656-
elif deop in hasjabs:
657-
label = arg*2
658-
else:
723+
label = _get_jump_target(op, arg, offset)
724+
if label is None:
659725
continue
660726
if label not in labels:
661727
labels.append(label)
@@ -684,7 +750,7 @@ def _find_imports(co):
684750

685751
consts = co.co_consts
686752
names = co.co_names
687-
opargs = [(op, arg) for _, op, arg in _unpack_opargs(co.co_code)
753+
opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code)
688754
if op != EXTENDED_ARG]
689755
for i, (op, oparg) in enumerate(opargs):
690756
if op == IMPORT_NAME and i >= 2:
@@ -706,7 +772,7 @@ def _find_store_names(co):
706772
}
707773

708774
names = co.co_names
709-
for _, op, arg in _unpack_opargs(co.co_code):
775+
for _, _, op, arg in _unpack_opargs(co.co_code):
710776
if op in STORE_OPS:
711777
yield names[arg]
712778

0 commit comments

Comments
 (0)