Skip to content

Commit 520b7ae

Browse files
serhiy-storchakamarkshannonpitrou
authored
bpo-17611. Move unwinding of stack for "pseudo exceptions" from interpreter to compiler. (GH-5006)
Co-authored-by: Mark Shannon <[email protected]> Co-authored-by: Antoine Pitrou <[email protected]>
1 parent 4af8fd5 commit 520b7ae

19 files changed

+4497
-4383
lines changed

Doc/library/dis.rst

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,14 @@ The Python compiler currently generates the following bytecode instructions.
335335
three.
336336

337337

338+
.. opcode:: ROT_FOUR
339+
340+
Lifts second, third and forth stack items one position up, moves top down
341+
to position four.
342+
343+
.. versionadded:: 3.8
344+
345+
338346
.. opcode:: DUP_TOP
339347

340348
Duplicates the reference on top of the stack.
@@ -605,17 +613,6 @@ the original TOS1.
605613
is terminated with :opcode:`POP_TOP`.
606614

607615

608-
.. opcode:: BREAK_LOOP
609-
610-
Terminates a loop due to a :keyword:`break` statement.
611-
612-
613-
.. opcode:: CONTINUE_LOOP (target)
614-
615-
Continues a loop due to a :keyword:`continue` statement. *target* is the
616-
address to jump to (which should be a :opcode:`FOR_ITER` instruction).
617-
618-
619616
.. opcode:: SET_ADD (i)
620617

621618
Calls ``set.add(TOS1[-i], TOS)``. Used to implement set comprehensions.
@@ -676,7 +673,7 @@ iterations of the loop.
676673
.. opcode:: POP_BLOCK
677674

678675
Removes one block from the block stack. Per frame, there is a stack of
679-
blocks, denoting nested loops, try statements, and such.
676+
blocks, denoting :keyword:`try` statements, and such.
680677

681678

682679
.. opcode:: POP_EXCEPT
@@ -687,11 +684,50 @@ iterations of the loop.
687684
popped values are used to restore the exception state.
688685

689686

687+
.. opcode:: POP_FINALLY (preserve_tos)
688+
689+
Cleans up the value stack and the block stack. If *preserve_tos* is not
690+
``0`` TOS first is popped from the stack and pushed on the stack after
691+
perfoming other stack operations:
692+
693+
* If TOS is ``NULL`` or an integer (pushed by :opcode:`BEGIN_FINALLY`
694+
or :opcode:`CALL_FINALLY`) it is popped from the stack.
695+
* If TOS is an exception type (pushed when an exception has been raised)
696+
6 values are popped from the stack, the last three popped values are
697+
used to restore the exception state. An exception handler block is
698+
removed from the block stack.
699+
700+
It is similar to :opcode:`END_FINALLY`, but doesn't change the bytecode
701+
counter nor raise an exception. Used for implementing :keyword:`break`
702+
and :keyword:`return` in the :keyword:`finally` block.
703+
704+
.. versionadded:: 3.8
705+
706+
707+
.. opcode:: BEGIN_FINALLY
708+
709+
Pushes ``NULL`` onto the stack for using it in :opcode:`END_FINALLY`,
710+
:opcode:`POP_FINALLY`, :opcode:`WITH_CLEANUP_START` and
711+
:opcode:`WITH_CLEANUP_FINISH`. Starts the :keyword:`finally` block.
712+
713+
.. versionadded:: 3.8
714+
715+
690716
.. opcode:: END_FINALLY
691717

692718
Terminates a :keyword:`finally` clause. The interpreter recalls whether the
693-
exception has to be re-raised, or whether the function returns, and continues
694-
with the outer-next block.
719+
exception has to be re-raised or execution has to be continued depending on
720+
the value of TOS.
721+
722+
* If TOS is ``NULL`` (pushed by :opcode:`BEGIN_FINALLY`) continue from
723+
the next instruction. TOS is popped.
724+
* If TOS is an integer (pushed by :opcode:`CALL_FINALLY`), sets the
725+
bytecode counter to TOS. TOS is popped.
726+
* If TOS is an exception type (pushed when an exception has been raised)
727+
6 values are popped from the stack, the first three popped values are
728+
used to re-raise the exception and the last three popped values are used
729+
to restore the exception state. An exception handler block is removed
730+
from the block stack.
695731

696732

697733
.. opcode:: LOAD_BUILD_CLASS
@@ -704,9 +740,9 @@ iterations of the loop.
704740

705741
This opcode performs several operations before a with block starts. First,
706742
it loads :meth:`~object.__exit__` from the context manager and pushes it onto
707-
the stack for later use by :opcode:`WITH_CLEANUP`. Then,
743+
the stack for later use by :opcode:`WITH_CLEANUP_START`. Then,
708744
:meth:`~object.__enter__` is called, and a finally block pointing to *delta*
709-
is pushed. Finally, the result of calling the enter method is pushed onto
745+
is pushed. Finally, the result of calling the ``__enter__()`` method is pushed onto
710746
the stack. The next opcode will either ignore it (:opcode:`POP_TOP`), or
711747
store it in (a) variable(s) (:opcode:`STORE_FAST`, :opcode:`STORE_NAME`, or
712748
:opcode:`UNPACK_SEQUENCE`).
@@ -716,30 +752,31 @@ iterations of the loop.
716752

717753
.. opcode:: WITH_CLEANUP_START
718754

719-
Cleans up the stack when a :keyword:`with` statement block exits. TOS is the
720-
context manager's :meth:`__exit__` bound method. Below TOS are 1--3 values
721-
indicating how/why the finally clause was entered:
755+
Starts cleaning up the stack when a :keyword:`with` statement block exits.
722756

723-
* SECOND = ``None``
724-
* (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval
725-
* SECOND = ``WHY_*``; no retval below it
726-
* (SECOND, THIRD, FOURTH) = exc_info()
757+
At the top of the stack are either ``NULL`` (pushed by
758+
:opcode:`BEGIN_FINALLY`) or 6 values pushed if an exception has been
759+
raised in the with block. Below is the context manager's
760+
:meth:`~object.__exit__` or :meth:`~object.__aexit__` bound method.
727761

728-
In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise
729-
``TOS(None, None, None)``. Pushes SECOND and result of the call
730-
to the stack.
762+
If TOS is ``NULL``, calls ``SECOND(None, None, None)``,
763+
removes the function from the stack, leaving TOS, and pushes ``None``
764+
to the stack. Otherwise calls ``SEVENTH(TOP, SECOND, THIRD)``,
765+
shifts the bottom 3 values of the stack down, replaces the empty spot
766+
with ``NULL`` and pushes TOS. Finally pushes the result of the call.
731767

732768

733769
.. opcode:: WITH_CLEANUP_FINISH
734770

735-
Pops exception type and result of 'exit' function call from the stack.
771+
Finishes cleaning up the stack when a :keyword:`with` statement block exits.
736772

737-
If the stack represents an exception, *and* the function call returns a
738-
'true' value, this information is "zapped" and replaced with a single
739-
``WHY_SILENCED`` to prevent :opcode:`END_FINALLY` from re-raising the
740-
exception. (But non-local gotos will still be resumed.)
773+
TOS is result of ``__exit__()`` or ``__aexit__()`` function call pushed
774+
by :opcode:`WITH_CLEANUP_START`. SECOND is ``None`` or an exception type
775+
(pushed when an exception has been raised).
741776

742-
.. XXX explain the WHY stuff!
777+
Pops two values from the stack. If SECOND is not None and TOS is true
778+
unwinds the EXCEPT_HANDLER block which was created when the exception
779+
was caught and pushes ``NULL`` to the stack.
743780

744781

745782
All of the following opcodes use their arguments.
@@ -987,22 +1024,19 @@ All of the following opcodes use their arguments.
9871024
Loads the global named ``co_names[namei]`` onto the stack.
9881025

9891026

990-
.. opcode:: SETUP_LOOP (delta)
991-
992-
Pushes a block for a loop onto the block stack. The block spans from the
993-
current instruction with a size of *delta* bytes.
994-
1027+
.. opcode:: SETUP_FINALLY (delta)
9951028

996-
.. opcode:: SETUP_EXCEPT (delta)
1029+
Pushes a try block from a try-finally or try-except clause onto the block
1030+
stack. *delta* points to the finally block or the first except block.
9971031

998-
Pushes a try block from a try-except clause onto the block stack. *delta*
999-
points to the first except block.
10001032

1033+
.. opcode:: CALL_FINALLY (delta)
10011034

1002-
.. opcode:: SETUP_FINALLY (delta)
1035+
Pushes the address of the next instruction onto the stack and increments
1036+
bytecode counter by *delta*. Used for calling the finally block as a
1037+
"subroutine".
10031038

1004-
Pushes a try block from a try-except clause onto the block stack. *delta*
1005-
points to the finally block.
1039+
.. versionadded:: 3.8
10061040

10071041

10081042
.. opcode:: LOAD_FAST (var_num)

Doc/whatsnew/3.8.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,21 @@ Changes in the Python API
137137
:func:`dbm.dumb.open` with flags ``'r'`` and ``'w'`` no longer creates
138138
a database if it does not exist.
139139
(Contributed by Serhiy Storchaka in :issue:`32749`.)
140+
141+
142+
CPython bytecode changes
143+
------------------------
144+
145+
* The interpreter loop has been simplified by moving the logic of unrolling
146+
the stack of blocks into the compiler. The compiler emits now explicit
147+
instructions for adjusting the stack of values and calling the cleaning
148+
up code for :keyword:`break`, :keyword:`continue` and :keyword:`return`.
149+
150+
Removed opcodes :opcode:`BREAK_LOOP`, :opcode:`CONTINUE_LOOP`,
151+
:opcode:`SETUP_LOOP` and :opcode:`SETUP_EXCEPT`. Added new opcodes
152+
:opcode:`ROT_FOUR`, :opcode:`BEGIN_FINALLY`, :opcode:`CALL_FINALLY` and
153+
:opcode:`POP_FINALLY`. Changed the behavior of :opcode:`END_FINALLY`
154+
and :opcode:`WITH_CLEANUP_START`.
155+
156+
(Contributed by Mark Shannon, Antoine Pitrou and Serhiy Storchaka in
157+
:issue:`17611`.)

Include/opcode.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212
#define ROT_THREE 3
1313
#define DUP_TOP 4
1414
#define DUP_TOP_TWO 5
15+
#define ROT_FOUR 6
1516
#define NOP 9
1617
#define UNARY_POSITIVE 10
1718
#define UNARY_NEGATIVE 11
@@ -32,6 +33,7 @@ extern "C" {
3233
#define GET_AITER 50
3334
#define GET_ANEXT 51
3435
#define BEFORE_ASYNC_WITH 52
36+
#define BEGIN_FINALLY 53
3537
#define INPLACE_ADD 55
3638
#define INPLACE_SUBTRACT 56
3739
#define INPLACE_MULTIPLY 57
@@ -55,7 +57,6 @@ extern "C" {
5557
#define INPLACE_AND 77
5658
#define INPLACE_XOR 78
5759
#define INPLACE_OR 79
58-
#define BREAK_LOOP 80
5960
#define WITH_CLEANUP_START 81
6061
#define WITH_CLEANUP_FINISH 82
6162
#define RETURN_VALUE 83
@@ -92,9 +93,6 @@ extern "C" {
9293
#define POP_JUMP_IF_FALSE 114
9394
#define POP_JUMP_IF_TRUE 115
9495
#define LOAD_GLOBAL 116
95-
#define CONTINUE_LOOP 119
96-
#define SETUP_LOOP 120
97-
#define SETUP_EXCEPT 121
9896
#define SETUP_FINALLY 122
9997
#define LOAD_FAST 124
10098
#define STORE_FAST 125
@@ -127,6 +125,8 @@ extern "C" {
127125
#define BUILD_TUPLE_UNPACK_WITH_CALL 158
128126
#define LOAD_METHOD 160
129127
#define CALL_METHOD 161
128+
#define CALL_FINALLY 162
129+
#define POP_FINALLY 163
130130

131131
/* EXCEPT_HANDLER is a special, implicit block type which is created when
132132
entering an except handler. It is not an opcode but we define it here

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ def _write_atomic(path, data, mode=0o666):
246246
# Python 3.7a2 3391 (update GET_AITER #31709)
247247
# Python 3.7a4 3392 (PEP 552: Deterministic pycs #31650)
248248
# Python 3.7b1 3393 (remove STORE_ANNOTATION opcode #32550)
249+
# Python 3.8a1 3400 (move frame block handling to compiler #17611)
249250
#
250251
# MAGIC must change whenever the bytecode emitted by the compiler may no
251252
# longer be understood by older implementations of the eval loop (usually
@@ -254,7 +255,7 @@ def _write_atomic(path, data, mode=0o666):
254255
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
255256
# in PC/launcher.c must also be updated.
256257

257-
MAGIC_NUMBER = (3393).to_bytes(2, 'little') + b'\r\n'
258+
MAGIC_NUMBER = (3400).to_bytes(2, 'little') + b'\r\n'
258259
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
259260

260261
_PYCACHE = '__pycache__'

Lib/opcode.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def jabs_op(name, op):
6060
def_op('ROT_THREE', 3)
6161
def_op('DUP_TOP', 4)
6262
def_op('DUP_TOP_TWO', 5)
63+
def_op('ROT_FOUR', 6)
6364

6465
def_op('NOP', 9)
6566
def_op('UNARY_POSITIVE', 10)
@@ -86,6 +87,7 @@ def jabs_op(name, op):
8687
def_op('GET_AITER', 50)
8788
def_op('GET_ANEXT', 51)
8889
def_op('BEFORE_ASYNC_WITH', 52)
90+
def_op('BEGIN_FINALLY', 53)
8991

9092
def_op('INPLACE_ADD', 55)
9193
def_op('INPLACE_SUBTRACT', 56)
@@ -113,10 +115,8 @@ def jabs_op(name, op):
113115
def_op('INPLACE_AND', 77)
114116
def_op('INPLACE_XOR', 78)
115117
def_op('INPLACE_OR', 79)
116-
def_op('BREAK_LOOP', 80)
117118
def_op('WITH_CLEANUP_START', 81)
118119
def_op('WITH_CLEANUP_FINISH', 82)
119-
120120
def_op('RETURN_VALUE', 83)
121121
def_op('IMPORT_STAR', 84)
122122
def_op('SETUP_ANNOTATIONS', 85)
@@ -158,10 +158,7 @@ def jabs_op(name, op):
158158

159159
name_op('LOAD_GLOBAL', 116) # Index in name list
160160

161-
jabs_op('CONTINUE_LOOP', 119) # Target address
162-
jrel_op('SETUP_LOOP', 120) # Distance to target address
163-
jrel_op('SETUP_EXCEPT', 121) # ""
164-
jrel_op('SETUP_FINALLY', 122) # ""
161+
jrel_op('SETUP_FINALLY', 122) # Distance to target address
165162

166163
def_op('LOAD_FAST', 124) # Local variable number
167164
haslocal.append(124)
@@ -213,5 +210,7 @@ def jabs_op(name, op):
213210

214211
name_op('LOAD_METHOD', 160)
215212
def_op('CALL_METHOD', 161)
213+
jrel_op('CALL_FINALLY', 162)
214+
def_op('POP_FINALLY', 163)
216215

217216
del def_op, name_op, jrel_op, jabs_op

0 commit comments

Comments
 (0)