Skip to content

Commit

Permalink
GH-90997: Wrap yield from/await in a virtual try/except StopIteration (
Browse files Browse the repository at this point in the history
  • Loading branch information
brandtbucher committed Aug 19, 2022
1 parent 2d9f252 commit 5bfb3c3
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 106 deletions.
21 changes: 18 additions & 3 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,17 @@ the original TOS1.
.. versionchanged:: 3.11
Exception representation on the stack now consist of one, not three, items.


.. opcode:: CLEANUP_THROW

Handles an exception raised during a :meth:`~generator.throw` or
:meth:`~generator.close` call through the current frame. If TOS is an
instance of :exc:`StopIteration`, pop three values from the stack and push
its ``value`` member. Otherwise, re-raise TOS.

.. versionadded:: 3.12


.. opcode:: BEFORE_ASYNC_WITH

Resolves ``__aenter__`` and ``__aexit__`` from the object on top of the
Expand Down Expand Up @@ -1344,10 +1355,14 @@ iterations of the loop.
.. versionadded:: 3.11


.. opcode:: SEND
.. opcode:: SEND (delta)

Equivalent to ``TOS = TOS1.send(TOS)``. Used in ``yield from`` and ``await``
statements.

Sends ``None`` to the sub-generator of this generator.
Used in ``yield from`` and ``await`` statements.
If the call raises :exc:`StopIteration`, pop both items, push the
exception's ``value`` attribute, and increment the bytecode counter by
*delta*.

.. versionadded:: 3.11

Expand Down
28 changes: 14 additions & 14 deletions Include/internal/pycore_opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 43 additions & 42 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,10 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.12a1 3505 (Specialization/Cache for FOR_ITER)
# Python 3.12a1 3506 (Add BINARY_SLICE and STORE_SLICE instructions)
# Python 3.12a1 3507 (Set lineno of module's RESUME to 0)
# Python 3.12a1 3508 (Add CLEANUP_THROW)

# Python 3.13 will start with 3550

#
# MAGIC must change whenever the bytecode emitted by the compiler may no
# longer be understood by older implementations of the eval loop (usually
# due to the addition of new opcodes).
Expand All @@ -424,7 +424,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3507).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3508).to_bytes(2, 'little') + b'\r\n'

_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

Expand Down
1 change: 1 addition & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def pseudo_op(name, op, real_ops):
def_op('BEFORE_ASYNC_WITH', 52)
def_op('BEFORE_WITH', 53)
def_op('END_ASYNC_FOR', 54)
def_op('CLEANUP_THROW', 55)

def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61)
Expand Down
17 changes: 11 additions & 6 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,26 +507,31 @@ async def _asyncwith(c):
LOAD_CONST 0 (None)
RETURN_VALUE
%3d >> PUSH_EXC_INFO
%3d >> CLEANUP_THROW
JUMP_BACKWARD 24 (to 22)
>> CLEANUP_THROW
JUMP_BACKWARD 9 (to 56)
>> PUSH_EXC_INFO
WITH_EXCEPT_START
GET_AWAITABLE 2
LOAD_CONST 0 (None)
>> SEND 3 (to 82)
>> SEND 4 (to 92)
YIELD_VALUE 6
RESUME 3
JUMP_BACKWARD_NO_INTERRUPT 4 (to 74)
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 86)
JUMP_BACKWARD_NO_INTERRUPT 4 (to 82)
>> CLEANUP_THROW
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 96)
RERAISE 2
>> POP_TOP
POP_EXCEPT
POP_TOP
POP_TOP
JUMP_BACKWARD 19 (to 58)
JUMP_BACKWARD 24 (to 58)
>> COPY 3
POP_EXCEPT
RERAISE 1
ExceptionTable:
2 rows
6 rows
""" % (_asyncwith.__code__.co_firstlineno,
_asyncwith.__code__.co_firstlineno + 1,
_asyncwith.__code__.co_firstlineno + 2,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Compile virtual :keyword:`try`/:keyword:`except` blocks to handle exceptions
raised during :meth:`~generator.close` or :meth:`~generator.throw` calls
through a suspended frame.
21 changes: 1 addition & 20 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,26 +485,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
}
Py_DECREF(yf);
if (!ret) {
PyObject *val;
/* Pop subiterator from stack */
assert(gen->gi_frame_state < FRAME_CLEARED);
ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);
assert(ret == yf);
Py_DECREF(ret);
// XXX: Performing this jump ourselves is awkward and problematic.
// See https://github.com/python/cpython/pull/31968.
/* Termination repetition of SEND loop */
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
/* Backup to SEND */
assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND);
int jump = _Py_OPARG(frame->prev_instr[-1]);
frame->prev_instr += jump - 1;
if (_PyGen_FetchStopIterationValue(&val) == 0) {
ret = gen_send(gen, val);
Py_DECREF(val);
} else {
ret = gen_send_ex(gen, Py_None, 1, 0);
}
ret = gen_send_ex(gen, Py_None, 1, 0);
}
return ret;
}
Expand Down
Loading

0 comments on commit 5bfb3c3

Please sign in to comment.