@@ -265,6 +265,7 @@ def show_code(co, *, file=None):
265
265
'argval' ,
266
266
'argrepr' ,
267
267
'offset' ,
268
+ 'start_offset' ,
268
269
'starts_line' ,
269
270
'is_jump_target' ,
270
271
'positions'
@@ -278,6 +279,10 @@ def show_code(co, *, file=None):
278
279
_Instruction .argval .__doc__ = "Resolved arg value (if known), otherwise same as arg"
279
280
_Instruction .argrepr .__doc__ = "Human readable description of operation argument"
280
281
_Instruction .offset .__doc__ = "Start index of operation within bytecode sequence"
282
+ _Instruction .start_offset .__doc__ = (
283
+ "Start index of operation within bytecode sequence, including extended args if present; "
284
+ "otherwise equal to Instruction.offset"
285
+ )
281
286
_Instruction .starts_line .__doc__ = "Line started by this opcode (if any), otherwise None"
282
287
_Instruction .is_jump_target .__doc__ = "True if other code jumps to here, otherwise False"
283
288
_Instruction .positions .__doc__ = "dis.Positions object holding the span of source code covered by this instruction"
@@ -288,8 +293,26 @@ def show_code(co, *, file=None):
288
293
_OPNAME_WIDTH = 20
289
294
_OPARG_WIDTH = 5
290
295
296
+ def _get_jump_target (op , arg , offset ):
297
+ """Gets the bytecode offset of the jump target if this is a jump instruction.
298
+
299
+ Otherwise return None.
300
+ """
301
+ deop = _deoptop (op )
302
+ caches = _inline_cache_entries [deop ]
303
+ if deop in hasjrel :
304
+ if _is_backward_jump (deop ):
305
+ arg = - arg
306
+ target = offset + 2 + arg * 2
307
+ target += 2 * caches
308
+ elif deop in hasjabs :
309
+ target = arg * 2
310
+ else :
311
+ target = None
312
+ return target
313
+
291
314
class Instruction (_Instruction ):
292
- """Details for a bytecode operation
315
+ """Details for a bytecode operation.
293
316
294
317
Defined fields:
295
318
opname - human readable name for operation
@@ -298,14 +321,55 @@ class Instruction(_Instruction):
298
321
argval - resolved arg value (if known), otherwise same as arg
299
322
argrepr - human readable description of operation argument
300
323
offset - start index of operation within bytecode sequence
324
+ start_offset - start index of operation within bytecode sequence including extended args if present;
325
+ otherwise equal to Instruction.offset
301
326
starts_line - line started by this opcode (if any), otherwise None
302
327
is_jump_target - True if other code jumps to here, otherwise False
303
328
positions - Optional dis.Positions object holding the span of source code
304
329
covered by this instruction
305
330
"""
306
331
332
+ @property
333
+ def oparg (self ):
334
+ """Alias for Instruction.arg."""
335
+ return self .arg
336
+
337
+ @property
338
+ def baseopcode (self ):
339
+ """Numeric code for the base operation if operation is specialized.
340
+
341
+ Otherwise equal to Instruction.opcode.
342
+ """
343
+ return _deoptop (self .opcode )
344
+
345
+ @property
346
+ def baseopname (self ):
347
+ """Human readable name for the base operation if operation is specialized.
348
+
349
+ Otherwise equal to Instruction.opname.
350
+ """
351
+ return opname [self .baseopcode ]
352
+
353
+ @property
354
+ def cache_offset (self ):
355
+ """Start index of the cache entries following the operation."""
356
+ return self .offset + 2
357
+
358
+ @property
359
+ def end_offset (self ):
360
+ """End index of the cache entries following the operation."""
361
+ return self .cache_offset + _inline_cache_entries [self .opcode ]* 2
362
+
363
+ @property
364
+ def jump_target (self ):
365
+ """Bytecode index of the jump target if this is a jump operation.
366
+
367
+ Otherwise return None.
368
+ """
369
+ return _get_jump_target (self .opcode , self .arg , self .offset )
370
+
307
371
def _disassemble (self , lineno_width = 3 , mark_as_current = False , offset_width = 4 ):
308
- """Format instruction details for inclusion in disassembly output
372
+ """Format instruction details for inclusion in disassembly output.
309
373
310
374
*lineno_width* sets the width of the line number field (0 omits it)
311
375
*mark_as_current* inserts a '-->' marker arrow as part of the line
@@ -335,12 +399,19 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
335
399
fields .append (self .opname .ljust (_OPNAME_WIDTH ))
336
400
# Column: Opcode argument
337
401
if self .arg is not None :
338
- fields .append (repr (self .arg ).rjust (_OPARG_WIDTH ))
402
+ arg = repr (self .arg )
403
+ # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into
404
+ # the space reserved for oparg. This results in fewer misaligned opargs
405
+ # in the disassembly output.
406
+ opname_excess = max (0 , len (self .opname ) - _OPNAME_WIDTH )
407
+ fields .append (repr (self .arg ).rjust (_OPARG_WIDTH - opname_excess ))
339
408
# Column: Opcode argument details
340
409
if self .argrepr :
341
410
fields .append ('(' + self .argrepr + ')' )
342
411
return ' ' .join (fields ).rstrip ()
343
412
413
+ def __str__ (self ):
414
+ return self ._disassemble ()
344
415
345
416
def get_instructions (x , * , first_line = None , show_caches = False , adaptive = False ):
346
417
"""Iterator for the opcodes in methods, functions or code
@@ -454,7 +525,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
454
525
for i in range (start , end ):
455
526
labels .add (target )
456
527
starts_line = None
457
- for offset , op , arg in _unpack_opargs (code ):
528
+ for offset , start_offset , op , arg in _unpack_opargs (code ):
458
529
if linestarts is not None :
459
530
starts_line = linestarts .get (offset , None )
460
531
if starts_line is not None :
@@ -526,7 +597,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
526
597
argrepr = _intrinsic_2_descs [arg ]
527
598
yield Instruction (_all_opname [op ], op ,
528
599
arg , argval , argrepr ,
529
- offset , starts_line , is_jump_target , positions )
600
+ offset , start_offset , starts_line , is_jump_target , positions )
530
601
caches = _inline_cache_entries [deop ]
531
602
if not caches :
532
603
continue
@@ -546,7 +617,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
546
617
else :
547
618
argrepr = ""
548
619
yield Instruction (
549
- "CACHE" , CACHE , 0 , None , argrepr , offset , None , False ,
620
+ "CACHE" , CACHE , 0 , None , argrepr , offset , offset , None , False ,
550
621
Positions (* next (co_positions , ()))
551
622
)
552
623
@@ -632,6 +703,7 @@ def _disassemble_str(source, **kwargs):
632
703
633
704
def _unpack_opargs (code ):
634
705
extended_arg = 0
706
+ extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction
635
707
caches = 0
636
708
for i in range (0 , len (code ), 2 ):
637
709
# Skip inline CACHE entries:
@@ -652,7 +724,13 @@ def _unpack_opargs(code):
652
724
else :
653
725
arg = None
654
726
extended_arg = 0
655
- yield (i , op , arg )
727
+ if deop == EXTENDED_ARG :
728
+ extended_args_offset += 1
729
+ yield (i , i , op , arg )
730
+ else :
731
+ start_offset = i - extended_args_offset * 2
732
+ yield (i , start_offset , op , arg )
733
+ extended_args_offset = 0
656
734
657
735
def findlabels (code ):
658
736
"""Detect all offsets in a byte code which are jump targets.
@@ -661,18 +739,10 @@ def findlabels(code):
661
739
662
740
"""
663
741
labels = []
664
- for offset , op , arg in _unpack_opargs (code ):
742
+ for offset , _ , op , arg in _unpack_opargs (code ):
665
743
if arg is not None :
666
- deop = _deoptop (op )
667
- caches = _inline_cache_entries [deop ]
668
- if deop in hasjrel :
669
- if _is_backward_jump (deop ):
670
- arg = - arg
671
- label = offset + 2 + arg * 2
672
- label += 2 * caches
673
- elif deop in hasjabs :
674
- label = arg * 2
675
- else :
744
+ label = _get_jump_target (op , arg , offset )
745
+ if label is None :
676
746
continue
677
747
if label not in labels :
678
748
labels .append (label )
@@ -701,7 +771,7 @@ def _find_imports(co):
701
771
702
772
consts = co .co_consts
703
773
names = co .co_names
704
- opargs = [(op , arg ) for _ , op , arg in _unpack_opargs (co .co_code )
774
+ opargs = [(op , arg ) for _ , _ , op , arg in _unpack_opargs (co .co_code )
705
775
if op != EXTENDED_ARG ]
706
776
for i , (op , oparg ) in enumerate (opargs ):
707
777
if op == IMPORT_NAME and i >= 2 :
@@ -723,7 +793,7 @@ def _find_store_names(co):
723
793
}
724
794
725
795
names = co .co_names
726
- for _ , op , arg in _unpack_opargs (co .co_code ):
796
+ for _ , _ , op , arg in _unpack_opargs (co .co_code ):
727
797
if op in STORE_OPS :
728
798
yield names [arg ]
729
799
0 commit comments