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

--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to