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