Closed
Description
We would like try-except to run fastest when no exception is raised ('the happy path'). Currently the code is laid out such that in the happy path we need to jump over the except block (see faster-cpython/ideas#226).
We can avoid this jump if we place the except block at the end of the function's bytecode block. To do this, the compiler will detect "cold" basic blocks (those only reachable from exception targets) and reorder the blocks so that the cold blocks are at the end.
Example:
def f():
try:
x = "try"
except:
x = "except"
finally:
x = "finally"
return x
Becomes:
1 0 RESUME 0
2 2 NOP
3 4 LOAD_CONST 1 ('try')
6 STORE_FAST 0 (x)
7 >> 8 LOAD_CONST 3 ('finally')
10 STORE_FAST 0 (x)
8 12 LOAD_FAST 0 (x)
14 RETURN_VALUE
>> 16 PUSH_EXC_INFO
4 18 POP_TOP
5 20 LOAD_CONST 2 ('except')
22 STORE_FAST 0 (x)
24 POP_EXCEPT
26 JUMP_BACKWARD 10 (to 8)
>> 28 COPY 3
30 POP_EXCEPT
32 RERAISE 1
>> 34 PUSH_EXC_INFO
7 36 LOAD_CONST 3 ('finally')
38 STORE_FAST 0 (x)
8 40 LOAD_FAST 0 (x)
42 SWAP 2
44 POP_TOP
46 SWAP 2
48 POP_EXCEPT
50 RETURN_VALUE
>> 52 COPY 3
54 POP_EXCEPT
56 RERAISE 1
ExceptionTable:
4 to 6 -> 16 [0]
16 to 22 -> 28 [1] lasti
24 to 32 -> 34 [0]
34 to 46 -> 52 [1] lasti
instead of:
1 0 RESUME 0
2 2 NOP
3 4 LOAD_CONST 1 ('try')
6 STORE_FAST 0 (x)
8 JUMP_FORWARD 9 (to 28) <-- this jump was removed
>> 10 PUSH_EXC_INFO
4 12 POP_TOP
5 14 LOAD_CONST 2 ('except')
16 STORE_FAST 0 (x)
18 POP_EXCEPT
20 JUMP_FORWARD 3 (to 28)
>> 22 COPY 3
24 POP_EXCEPT
26 RERAISE 1
7 >> 28 LOAD_CONST 3 ('finally')
30 STORE_FAST 0 (x)
8 32 LOAD_FAST 0 (x)
34 RETURN_VALUE
>> 36 PUSH_EXC_INFO
7 38 LOAD_CONST 3 ('finally')
40 STORE_FAST 0 (x)
8 42 LOAD_FAST 0 (x)
44 SWAP 2
46 POP_TOP
48 SWAP 2
50 POP_EXCEPT
52 RETURN_VALUE
>> 54 COPY 3
56 POP_EXCEPT
58 RERAISE 1
ExceptionTable:
4 to 6 -> 10 [0]
8 to 8 -> 36 [0]
10 to 16 -> 22 [1] lasti
18 to 26 -> 36 [0]
36 to 48 -> 54 [1] lasti