Skip to content

GH-96793: Implement PEP 479 in bytecode. #99006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,15 @@ the original TOS1.
.. versionadded:: 3.12


.. opcode:: STOPITERATION_ERROR

Handles a StopIteration raised in a generator or coroutine.
If TOS is an instance of :exc:`StopIteration`, or :exc:`StopAsyncIteration`
replace it with a :exc:`RuntimeError`.

.. versionadded:: 3.12


.. opcode:: BEFORE_ASYNC_WITH

Resolves ``__aenter__`` and ``__aexit__`` from the object on top of the
Expand Down
26 changes: 13 additions & 13 deletions Include/internal/pycore_opcode.h

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

77 changes: 39 additions & 38 deletions Include/opcode.h

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

3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.12a1 3508 (Add CLEANUP_THROW)
# Python 3.12a1 3509 (Conditional jumps only jump forward)
# Python 3.12a1 3510 (FOR_ITER leaves iterator on the stack)
# Python 3.12a1 3511 (Add STOPITERATION_ERROR instruction)

# Python 3.13 will start with 3550

Expand All @@ -436,7 +437,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 = (3510).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3511).to_bytes(2, 'little') + b'\r\n'

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

Expand Down
2 changes: 2 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def pseudo_op(name, op, real_ops):
def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61)

def_op('STOPITERATION_ERROR', 63)

def_op('GET_ITER', 68)
def_op('GET_YIELD_FROM_ITER', 69)
def_op('PRINT_EXPR', 70)
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,10 @@ async def _asyncwith(c):
>> COPY 3
POP_EXCEPT
RERAISE 1
>> STOPITERATION_ERROR
RERAISE 1
ExceptionTable:
6 rows
12 rows
""" % (_asyncwith.__code__.co_firstlineno,
_asyncwith.__code__.co_firstlineno + 1,
_asyncwith.__code__.co_firstlineno + 2,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ def bar(cls):
check(bar, size('PP'))
# generator
def get_gen(): yield 1
check(get_gen(), size('P2P4P4c7P2ic??P'))
check(get_gen(), size('P2P4P4c7P2ic??2P'))
# iterator
check(iter('abc'), size('lP'))
# callable-iterator
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def make_tracer():
return Tracer()

def compare_events(self, line_offset, events, expected_events):
events = [(l - line_offset, e) for (l, e) in events]
events = [(l - line_offset if l is not None else None, e) for (l, e) in events]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced for debugging this PR. It is no longer strictly necessary, but it gives nicer output when tests fail.

if events != expected_events:
self.fail(
"events did not match expectation:\n" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Handle StopIteration and StopAsyncIteration raised in generator or
coroutines in the bytecode, rather than in wrapping C code.
22 changes: 3 additions & 19 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,25 +247,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
}
}
else {
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
const char *msg = "generator raised StopIteration";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine raised StopIteration";
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "async generator raised StopIteration";
}
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
}
else if (PyAsyncGen_CheckExact(gen) &&
PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
{
/* code in `gen` raised a StopAsyncIteration error:
raise a RuntimeError.
*/
const char *msg = "async generator raised StopAsyncIteration";
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
}
assert(!PyErr_ExceptionMatches(PyExc_StopIteration));
assert(!PyAsyncGen_CheckExact(gen) ||
!PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
}

/* generator can't be rerun, so release the frame */
Expand Down
42 changes: 42 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,48 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
goto exception_unwind;
}

TARGET(STOPITERATION_ERROR) {
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
PyObject *exc = TOP();
assert(PyExceptionInstance_Check(exc));
const char *msg = NULL;
if (PyErr_GivenExceptionMatches(exc, PyExc_StopIteration)) {
msg = "generator raised StopIteration";
if (frame->f_code->co_flags & CO_ASYNC_GENERATOR) {
msg = "async generator raised StopIteration";
}
else if (frame->f_code->co_flags & CO_COROUTINE) {
msg = "coroutine raised StopIteration";
}
}
else if ((frame->f_code->co_flags & CO_ASYNC_GENERATOR) &&
PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration))
{
/* code in `gen` raised a StopAsyncIteration error:
raise a RuntimeError.
*/
msg = "async generator raised StopAsyncIteration";
}
if (msg != NULL) {
PyObject *message = _PyUnicode_FromASCII(msg, strlen(msg));
if (message == NULL) {
goto error;
}
PyObject *error = PyObject_CallOneArg(PyExc_RuntimeError, message);
if (error == NULL) {
Py_DECREF(message);
goto error;
}
assert(PyExceptionInstance_Check(error));
SET_TOP(error);
PyException_SetCause(error, exc);
Py_INCREF(exc);
PyException_SetContext(error, exc);
Py_DECREF(message);
}
DISPATCH();
}

TARGET(LOAD_ASSERTION_ERROR) {
PyObject *value = PyExc_AssertionError;
Py_INCREF(value);
Expand Down
Loading