Skip to content

Commit d8b54c9

Browse files
committed
Produce cleaner bytecode for 'with' and 'async with' by generating separate code for normal and exceptional paths.
Remove BEGIN_FINALLY, END_FINALLY, CALL_FINALLY and POP_FINALLY bytecodes. Implement finally blocks by code duplication. Reimplement frame.lineno setter using line numbers rather than bytecode offsets.
1 parent 25b804a commit d8b54c9

File tree

13 files changed

+4702
-4680
lines changed

13 files changed

+4702
-4680
lines changed

Doc/library/dis.rst

+9-76
Original file line numberDiff line numberDiff line change
@@ -695,50 +695,21 @@ iterations of the loop.
695695
popped values are used to restore the exception state.
696696

697697

698-
.. opcode:: POP_FINALLY (preserve_tos)
698+
.. opcode:: RERAISE
699699

700-
Cleans up the value stack and the block stack. If *preserve_tos* is not
701-
``0`` TOS first is popped from the stack and pushed on the stack after
702-
perfoming other stack operations:
700+
Re-raises the exception currently on top of the stack.
703701

704-
* If TOS is ``NULL`` or an integer (pushed by :opcode:`BEGIN_FINALLY`
705-
or :opcode:`CALL_FINALLY`) it is popped from the stack.
706-
* If TOS is an exception type (pushed when an exception has been raised)
707-
6 values are popped from the stack, the last three popped values are
708-
used to restore the exception state. An exception handler block is
709-
removed from the block stack.
702+
.. versionadded:: 3.8
710703

711-
It is similar to :opcode:`END_FINALLY`, but doesn't change the bytecode
712-
counter nor raise an exception. Used for implementing :keyword:`break`,
713-
:keyword:`continue` and :keyword:`return` in the :keyword:`finally` block.
714704

715-
.. versionadded:: 3.8
716-
717-
718-
.. opcode:: BEGIN_FINALLY
719-
720-
Pushes ``NULL`` onto the stack for using it in :opcode:`END_FINALLY`,
721-
:opcode:`POP_FINALLY`, :opcode:`WITH_CLEANUP_START` and
722-
:opcode:`WITH_CLEANUP_FINISH`. Starts the :keyword:`finally` block.
723-
724-
.. versionadded:: 3.8
705+
.. opcode:: WITH_EXCEPT_START
725706

707+
Calls the function in position 7 on the stack with the top three
708+
items on the stack as arguments.
709+
Used to implement the call ``exit(*exc_info())`` when an exception
710+
has occurred in a :keyword:`with` statement.
726711

727-
.. opcode:: END_FINALLY
728-
729-
Terminates a :keyword:`finally` clause. The interpreter recalls whether the
730-
exception has to be re-raised or execution has to be continued depending on
731-
the value of TOS.
732-
733-
* If TOS is ``NULL`` (pushed by :opcode:`BEGIN_FINALLY`) continue from
734-
the next instruction. TOS is popped.
735-
* If TOS is an integer (pushed by :opcode:`CALL_FINALLY`), sets the
736-
bytecode counter to TOS. TOS is popped.
737-
* If TOS is an exception type (pushed when an exception has been raised)
738-
6 values are popped from the stack, the first three popped values are
739-
used to re-raise the exception and the last three popped values are used
740-
to restore the exception state. An exception handler block is removed
741-
from the block stack.
712+
.. versionadded:: 3.8
742713

743714

744715
.. opcode:: LOAD_BUILD_CLASS
@@ -761,35 +732,6 @@ iterations of the loop.
761732
.. versionadded:: 3.2
762733

763734

764-
.. opcode:: WITH_CLEANUP_START
765-
766-
Starts cleaning up the stack when a :keyword:`with` statement block exits.
767-
768-
At the top of the stack are either ``NULL`` (pushed by
769-
:opcode:`BEGIN_FINALLY`) or 6 values pushed if an exception has been
770-
raised in the with block. Below is the context manager's
771-
:meth:`~object.__exit__` or :meth:`~object.__aexit__` bound method.
772-
773-
If TOS is ``NULL``, calls ``SECOND(None, None, None)``,
774-
removes the function from the stack, leaving TOS, and pushes ``None``
775-
to the stack. Otherwise calls ``SEVENTH(TOP, SECOND, THIRD)``,
776-
shifts the bottom 3 values of the stack down, replaces the empty spot
777-
with ``NULL`` and pushes TOS. Finally pushes the result of the call.
778-
779-
780-
.. opcode:: WITH_CLEANUP_FINISH
781-
782-
Finishes cleaning up the stack when a :keyword:`with` statement block exits.
783-
784-
TOS is result of ``__exit__()`` or ``__aexit__()`` function call pushed
785-
by :opcode:`WITH_CLEANUP_START`. SECOND is ``None`` or an exception type
786-
(pushed when an exception has been raised).
787-
788-
Pops two values from the stack. If SECOND is not None and TOS is true
789-
unwinds the EXCEPT_HANDLER block which was created when the exception
790-
was caught and pushes ``NULL`` to the stack.
791-
792-
793735
All of the following opcodes use their arguments.
794736

795737
.. opcode:: STORE_NAME (namei)
@@ -1041,15 +983,6 @@ All of the following opcodes use their arguments.
1041983
stack. *delta* points to the finally block or the first except block.
1042984

1043985

1044-
.. opcode:: CALL_FINALLY (delta)
1045-
1046-
Pushes the address of the next instruction onto the stack and increments
1047-
bytecode counter by *delta*. Used for calling the finally block as a
1048-
"subroutine".
1049-
1050-
.. versionadded:: 3.8
1051-
1052-
1053986
.. opcode:: LOAD_FAST (var_num)
1054987

1055988
Pushes a reference to the local ``co_varnames[var_num]`` onto the stack.

Include/opcode.h

+2-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ def _write_atomic(path, data, mode=0o666):
259259
# this might affected the first line number #32911)
260260
# Python 3.8a1 3400 (move frame block handling to compiler #17611)
261261
# Python 3.8a1 3401 (add END_ASYNC_FOR #33041)
262+
# Python 3.8a1 3402 (simplified bytecode for with blocks #32949)
263+
# Python 3.8a1 3403 (remove BEGIN_FINALLY, END_FINALLY, CALL_FINALLY, POP_FINALLY bytecodes #33387)
262264
#
263265
# MAGIC must change whenever the bytecode emitted by the compiler may no
264266
# longer be understood by older implementations of the eval loop (usually
@@ -267,7 +269,7 @@ def _write_atomic(path, data, mode=0o666):
267269
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
268270
# in PC/launcher.c must also be updated.
269271

270-
MAGIC_NUMBER = (3401).to_bytes(2, 'little') + b'\r\n'
272+
MAGIC_NUMBER = (3403).to_bytes(2, 'little') + b'\r\n'
271273
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
272274

273275
_PYCACHE = '__pycache__'

Lib/opcode.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ def jabs_op(name, op):
8484
def_op('INPLACE_FLOOR_DIVIDE', 28)
8585
def_op('INPLACE_TRUE_DIVIDE', 29)
8686

87+
def_op('RERAISE', 48)
88+
def_op('WITH_EXCEPT_START', 49)
8789
def_op('GET_AITER', 50)
8890
def_op('GET_ANEXT', 51)
8991
def_op('BEFORE_ASYNC_WITH', 52)
90-
def_op('BEGIN_FINALLY', 53)
92+
9193
def_op('END_ASYNC_FOR', 54)
9294
def_op('INPLACE_ADD', 55)
9395
def_op('INPLACE_SUBTRACT', 56)
@@ -115,14 +117,13 @@ def jabs_op(name, op):
115117
def_op('INPLACE_AND', 77)
116118
def_op('INPLACE_XOR', 78)
117119
def_op('INPLACE_OR', 79)
118-
def_op('WITH_CLEANUP_START', 81)
119-
def_op('WITH_CLEANUP_FINISH', 82)
120+
120121
def_op('RETURN_VALUE', 83)
121122
def_op('IMPORT_STAR', 84)
122123
def_op('SETUP_ANNOTATIONS', 85)
123124
def_op('YIELD_VALUE', 86)
124125
def_op('POP_BLOCK', 87)
125-
def_op('END_FINALLY', 88)
126+
126127
def_op('POP_EXCEPT', 89)
127128

128129
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
@@ -210,7 +211,5 @@ def jabs_op(name, op):
210211

211212
name_op('LOAD_METHOD', 160)
212213
def_op('CALL_METHOD', 161)
213-
jrel_op('CALL_FINALLY', 162)
214-
def_op('POP_FINALLY', 163)
215214

216215
del def_op, name_op, jrel_op, jabs_op

Lib/test/test_dis.py

+51-30
Original file line numberDiff line numberDiff line change
@@ -274,32 +274,34 @@ def bug1333982(x=[]):
274274
--> 6 BINARY_TRUE_DIVIDE
275275
8 POP_TOP
276276
10 POP_BLOCK
277-
12 JUMP_FORWARD 40 (to 54)
277+
12 JUMP_FORWARD 44 (to 58)
278278
279279
%3d >> 14 DUP_TOP
280280
16 LOAD_GLOBAL 0 (Exception)
281281
18 COMPARE_OP 10 (exception match)
282-
20 POP_JUMP_IF_FALSE 52
282+
20 POP_JUMP_IF_FALSE 56
283283
22 POP_TOP
284284
24 STORE_FAST 0 (e)
285285
26 POP_TOP
286-
28 SETUP_FINALLY 10 (to 40)
286+
28 SETUP_FINALLY 18 (to 48)
287287
288288
%3d 30 LOAD_FAST 0 (e)
289289
32 LOAD_ATTR 1 (__traceback__)
290290
34 STORE_FAST 1 (tb)
291291
36 POP_BLOCK
292-
38 BEGIN_FINALLY
293-
>> 40 LOAD_CONST 0 (None)
292+
38 POP_EXCEPT
293+
40 LOAD_CONST 0 (None)
294294
42 STORE_FAST 0 (e)
295295
44 DELETE_FAST 0 (e)
296-
46 END_FINALLY
297-
48 POP_EXCEPT
298-
50 JUMP_FORWARD 2 (to 54)
299-
>> 52 END_FINALLY
300-
301-
%3d >> 54 LOAD_FAST 1 (tb)
302-
56 RETURN_VALUE
296+
46 JUMP_FORWARD 10 (to 58)
297+
>> 48 LOAD_CONST 0 (None)
298+
50 STORE_FAST 0 (e)
299+
52 DELETE_FAST 0 (e)
300+
54 RERAISE
301+
>> 56 RERAISE
302+
303+
%3d >> 58 LOAD_FAST 1 (tb)
304+
60 RETURN_VALUE
303305
""" % (TRACEBACK_CODE.co_firstlineno + 1,
304306
TRACEBACK_CODE.co_firstlineno + 2,
305307
TRACEBACK_CODE.co_firstlineno + 3,
@@ -740,7 +742,7 @@ async def async_def():
740742
Argument count: 0
741743
Kw-only arguments: 0
742744
Number of locals: 2
743-
Stack size: 10
745+
Stack size: 9
744746
Flags: OPTIMIZED, NEWLOCALS, NOFREE, COROUTINE
745747
Constants:
746748
0: None
@@ -965,7 +967,7 @@ def jumpy():
965967
Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=96, starts_line=None, is_jump_target=False),
966968
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=98, starts_line=None, is_jump_target=False),
967969
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=100, starts_line=None, is_jump_target=False),
968-
Instruction(opname='SETUP_FINALLY', opcode=122, arg=70, argval=174, argrepr='to 174', offset=102, starts_line=20, is_jump_target=True),
970+
Instruction(opname='SETUP_FINALLY', opcode=122, arg=98, argval=202, argrepr='to 202', offset=102, starts_line=20, is_jump_target=True),
969971
Instruction(opname='SETUP_FINALLY', opcode=122, arg=12, argval=118, argrepr='to 118', offset=104, starts_line=None, is_jump_target=False),
970972
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=106, starts_line=21, is_jump_target=False),
971973
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=108, starts_line=None, is_jump_target=False),
@@ -985,29 +987,43 @@ def jumpy():
985987
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=136, starts_line=None, is_jump_target=False),
986988
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=138, starts_line=None, is_jump_target=False),
987989
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=140, starts_line=None, is_jump_target=False),
988-
Instruction(opname='JUMP_FORWARD', opcode=110, arg=26, argval=170, argrepr='to 170', offset=142, starts_line=None, is_jump_target=False),
989-
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=144, starts_line=None, is_jump_target=True),
990+
Instruction(opname='JUMP_FORWARD', opcode=110, arg=46, argval=190, argrepr='to 190', offset=142, starts_line=None, is_jump_target=False),
991+
Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=144, starts_line=None, is_jump_target=True),
990992
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=146, starts_line=25, is_jump_target=True),
991-
Instruction(opname='SETUP_WITH', opcode=143, arg=14, argval=164, argrepr='to 164', offset=148, starts_line=None, is_jump_target=False),
993+
Instruction(opname='SETUP_WITH', opcode=143, arg=24, argval=174, argrepr='to 174', offset=148, starts_line=None, is_jump_target=False),
992994
Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=150, starts_line=None, is_jump_target=False),
993995
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=152, starts_line=26, is_jump_target=False),
994996
Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=154, starts_line=None, is_jump_target=False),
995997
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=156, starts_line=None, is_jump_target=False),
996998
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=158, starts_line=None, is_jump_target=False),
997999
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=160, starts_line=None, is_jump_target=False),
998-
Instruction(opname='BEGIN_FINALLY', opcode=53, arg=None, argval=None, argrepr='', offset=162, starts_line=None, is_jump_target=False),
999-
Instruction(opname='WITH_CLEANUP_START', opcode=81, arg=None, argval=None, argrepr='', offset=164, starts_line=None, is_jump_target=True),
1000-
Instruction(opname='WITH_CLEANUP_FINISH', opcode=82, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False),
1001-
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=168, starts_line=None, is_jump_target=False),
1002-
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=170, starts_line=None, is_jump_target=True),
1003-
Instruction(opname='BEGIN_FINALLY', opcode=53, arg=None, argval=None, argrepr='', offset=172, starts_line=None, is_jump_target=False),
1004-
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=174, starts_line=28, is_jump_target=True),
1005-
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=176, starts_line=None, is_jump_target=False),
1006-
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=178, starts_line=None, is_jump_target=False),
1007-
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=180, starts_line=None, is_jump_target=False),
1008-
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),
1009-
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=184, starts_line=None, is_jump_target=False),
1010-
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=186, starts_line=None, is_jump_target=False),
1000+
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=162, starts_line=None, is_jump_target=False),
1001+
Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=164, starts_line=None, is_jump_target=False),
1002+
Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False),
1003+
Instruction(opname='CALL_FUNCTION', opcode=131, arg=3, argval=3, argrepr='', offset=168, starts_line=None, is_jump_target=False),
1004+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=170, starts_line=None, is_jump_target=False),
1005+
Instruction(opname='JUMP_FORWARD', opcode=110, arg=16, argval=190, argrepr='to 190', offset=172, starts_line=None, is_jump_target=False),
1006+
Instruction(opname='WITH_EXCEPT_START', opcode=49, arg=None, argval=None, argrepr='', offset=174, starts_line=None, is_jump_target=True),
1007+
Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=180, argval=180, argrepr='', offset=176, starts_line=None, is_jump_target=False),
1008+
Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=178, starts_line=None, is_jump_target=False),
1009+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=180, starts_line=None, is_jump_target=True),
1010+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),
1011+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=184, starts_line=None, is_jump_target=False),
1012+
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=186, starts_line=None, is_jump_target=False),
1013+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=188, starts_line=None, is_jump_target=False),
1014+
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=190, starts_line=None, is_jump_target=True),
1015+
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=192, starts_line=28, is_jump_target=False),
1016+
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=194, starts_line=None, is_jump_target=False),
1017+
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=196, starts_line=None, is_jump_target=False),
1018+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=198, starts_line=None, is_jump_target=False),
1019+
Instruction(opname='JUMP_FORWARD', opcode=110, arg=10, argval=212, argrepr='to 212', offset=200, starts_line=None, is_jump_target=False),
1020+
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=202, starts_line=None, is_jump_target=True),
1021+
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=204, starts_line=None, is_jump_target=False),
1022+
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=206, starts_line=None, is_jump_target=False),
1023+
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=208, starts_line=None, is_jump_target=False),
1024+
Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=210, starts_line=None, is_jump_target=False),
1025+
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=212, starts_line=None, is_jump_target=True),
1026+
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=214, starts_line=None, is_jump_target=False)
10111027
]
10121028

10131029
# One last piece of inspect fodder to check the default line number handling
@@ -1020,6 +1036,10 @@ def simple(): pass
10201036

10211037
class InstructionTests(BytecodeTestCase):
10221038

1039+
def __init__(self, *args):
1040+
super().__init__(*args)
1041+
self.maxDiff = None
1042+
10231043
def test_default_first_line(self):
10241044
actual = dis.get_instructions(simple)
10251045
self.assertEqual(list(actual), expected_opinfo_simple)
@@ -1051,6 +1071,7 @@ def test_jumpy(self):
10511071
# get_instructions has its own tests above, so can rely on it to validate
10521072
# the object oriented API
10531073
class BytecodeTests(unittest.TestCase):
1074+
10541075
def test_instantiation(self):
10551076
# Test with function, method, code string and code object
10561077
for obj in [_f, _C(1).__init__, "a=1", _f.__code__]:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Removed WITH_CLEANUP_START, WITH_CLEANUP_FINISH, BEGIN_FINALLY, END_FINALLY, CALL_FINALLY and POP_FINALLY bytecodes.
2+
Replaced with RERAISE and WITH_EXCEPT_FINISH bytecodes.
3+
The compiler now generates different code for exceptional and non-exceptional branches for 'with' and 'try-except'
4+
statements. For 'try-finally' statements the 'finally' block is replicated for each exit from the 'try' body.
5+

0 commit comments

Comments
 (0)