Strange disassembly

Terry Reedy tjreedy at udel.edu
Fri Jun 18 14:03:00 EDT 2021


On 6/18/2021 6:04 AM, Chris Angelico wrote:
>>>> sys.version
> '3.10.0b2+ (heads/3.10:33a7a24288, Jun  9 2021, 20:47:39) [GCC 8.3.0]'
>>>> def chk(x):
> ...     if not(0 < x < 10): raise Exception

0 < x < 10 == 0 < x and x < 10, except that 'x' is evaluated once.

not(_) == (not 0 < x) or (not x < 10)
       [== x <= 0 or 10 <= x]

>>>> dis.dis(chk)
>    2           0 LOAD_CONST               1 (0)
>                2 LOAD_FAST                0 (x)

stack = 0 x.  Since compare will remove both, must duplicate x and move 
duplicate out of the way.

>                4 DUP_TOP
>                6 ROT_THREE

stack = x 0 x

>                8 COMPARE_OP               0 (<)

test 0 < x, remove both, leaving stack = x

>               10 POP_JUMP_IF_FALSE       11 (to 22)

if false, not 0<x, so 'or' is true, so raise.
But must first remove unneeded duplicate!

>               12 LOAD_CONST               2 (10)
>               14 COMPARE_OP               0 (<)

Raise exception if false, making not x < 10 true
So if true, jump to normal exit at end. Stack is empty.

>               16 POP_JUMP_IF_TRUE        14 (to 28)
>               18 LOAD_GLOBAL              0 (Exception)
>               20 RAISE_VARARGS             >          >>   22 POP_TOP

Must first remove unneeded duplicate of x!

>               24 LOAD_GLOBAL              0 (Exception)
>               26 RAISE_VARARGS            1
>          >>   28 LOAD_CONST               0 (None)
>               30 RETURN_VALUE

> Why are there two separate bytecode blocks for the "raise Exception"?

Because one block must POP_TOP and other must not.

> I'd have thought that the double condition would still be evaluated as
> one thing, or at least that the jump destinations for both the
> early-abort and the main evaluation should be the same.

To reuse the exception block with POP_TOP, could jump over it after the 
2nd compare.

 >               14 COMPARE_OP               0 (<)
 >               16 POP_JUMP_IF_TRUE        14 (to 28)
                 18 JUMP                    (to 24)
                 20 NOP (#to avoid renumbering)
 >          >>   22 POP_TOP
 >               24 LOAD_GLOBAL              0 (Exception)
 >               26 RAISE_VARARGS            1

For the simplest and fasted bytecode, write normal logic and let x be 
reloaded instead of duplicated and rotated.

 >>> import dis
 >>> def f(x):
...     if x <= 0 or 10 <= x: raise Exception
...
...
 >>> dis.dis(f)
   2           0 LOAD_FAST                0 (x)
               2 LOAD_CONST               1 (0)
               4 COMPARE_OP               1 (<=)
               6 POP_JUMP_IF_TRUE         8 (to 16)
               8 LOAD_CONST               2 (10)
              10 LOAD_FAST                0 (x)
              12 COMPARE_OP               1 (<=)
              14 POP_JUMP_IF_FALSE       10 (to 20)
         >>   16 LOAD_GLOBAL              0 (Exception)
              18 RAISE_VARARGS            1
         >>   20 LOAD_CONST               0 (None)
              22 RETURN_VALUE
 >>>


-- 
Terry Jan Reedy



More information about the Python-list mailing list