Skip to content

Commit 416732e

Browse files
authored
Some fixes to returning from generators (mypyc/mypyc#688)
* Don't set a traceback frame when raising StopIteration. This seems to match CPython and also speeds up generators a *lot*. * Use `_PyGen_SetStopIterationValue` to set the iteration value since `PyErr_SetObject` doesn't work right if the value is a tuple.
1 parent 4c97097 commit 416732e

File tree

4 files changed

+61
-5
lines changed

4 files changed

+61
-5
lines changed

mypyc/genops.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def f(x: int) -> int:
9292
from mypyc.ops_exc import (
9393
raise_exception_op, raise_exception_with_tb_op, reraise_exception_op,
9494
error_catch_op, restore_exc_info_op, exc_matches_op, get_exc_value_op,
95-
get_exc_info_op, keep_propagating_op,
95+
get_exc_info_op, keep_propagating_op, set_stop_iteration_value,
9696
)
9797
from mypyc.genops_for import ForGenerator, ForRange, ForList, ForIterable, ForEnumerate, ForZip
9898
from mypyc.rt_subtype import is_runtime_subtype
@@ -901,8 +901,13 @@ def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
901901
# StopIteration isn't caught by except blocks inside of the generator function.
902902
builder.error_handlers.append(None)
903903
builder.goto_new_block()
904-
builder.add(RaiseStandardError(RaiseStandardError.STOP_ITERATION, value,
905-
line))
904+
# Skip creating a traceback frame when we raise here, because
905+
# we don't care about the traceback frame and it is kind of
906+
# expensive since raising StopIteration is an extremely common case.
907+
# Also we call a special internal function to set StopIteration instead of
908+
# using RaiseStandardError because the obvious thing doesn't work if the
909+
# value is a tuple (???).
910+
builder.primitive_op(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO)
906911
builder.add(Unreachable())
907912
builder.error_handlers.pop()
908913

mypyc/lib-rt/pythonsupport.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,44 @@ _PyDict_GetItemStringWithError(PyObject *v, const char *key)
328328
#define CPyUnicode_EqualToASCIIString(x, y) _PyUnicode_EqualToASCIIString(x, y)
329329
#endif
330330

331+
// Adapted from genobject.c in Python 3.7.2
332+
// Copied because it wasn't in 3.5.2 and it is undocumented anyways.
333+
/*
334+
* Set StopIteration with specified value. Value can be arbitrary object
335+
* or NULL.
336+
*
337+
* Returns 0 if StopIteration is set and -1 if any other exception is set.
338+
*/
339+
static int
340+
CPyGen_SetStopIterationValue(PyObject *value)
341+
{
342+
PyObject *e;
343+
344+
if (value == NULL ||
345+
(!PyTuple_Check(value) && !PyExceptionInstance_Check(value)))
346+
{
347+
/* Delay exception instantiation if we can */
348+
PyErr_SetObject(PyExc_StopIteration, value);
349+
return 0;
350+
}
351+
/* Construct an exception instance manually with
352+
* PyObject_CallFunctionObjArgs and pass it to PyErr_SetObject.
353+
*
354+
* We do this to handle a situation when "value" is a tuple, in which
355+
* case PyErr_SetObject would set the value of StopIteration to
356+
* the first element of the tuple.
357+
*
358+
* (See PyErr_SetObject/_PyErr_CreateException code for details.)
359+
*/
360+
e = PyObject_CallFunctionObjArgs(PyExc_StopIteration, value, NULL);
361+
if (e == NULL) {
362+
return -1;
363+
}
364+
PyErr_SetObject(PyExc_StopIteration, e);
365+
Py_DECREF(e);
366+
return 0;
367+
}
368+
331369
#ifdef __cplusplus
332370
}
333371
#endif

mypyc/ops_exc.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
format_str = 'raise_exception({args[0]}); {dest} = 0',
1818
emit=simple_emit('CPy_Raise({args[0]}); {dest} = 0;'))
1919

20+
set_stop_iteration_value = custom_op(
21+
arg_types=[object_rprimitive],
22+
result_type=bool_rprimitive,
23+
error_kind=ERR_FALSE,
24+
format_str = 'set_stop_iteration_value({args[0]}); {dest} = 0',
25+
emit=simple_emit('CPyGen_SetStopIterationValue({args[0]}); {dest} = 0;'))
26+
2027
raise_exception_with_tb_op = custom_op(
2128
arg_types=[object_rprimitive, object_rprimitive, object_rprimitive],
2229
result_type=bool_rprimitive,

test-data/run.test

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3497,7 +3497,8 @@ else:
34973497
assert False
34983498

34993499
[case testYield]
3500-
from typing import Generator, Iterable, Union
3500+
from typing import Generator, Iterable, Union, Tuple
3501+
35013502
def yield_three_times() -> Iterable[int]:
35023503
yield 1
35033504
yield 2
@@ -3567,8 +3568,12 @@ class A(object):
35673568
def generator(self) -> Iterable[int]:
35683569
yield self.x
35693570

3571+
def return_tuple() -> Generator[int, None, Tuple[int, int]]:
3572+
yield 0
3573+
return 1, 2
3574+
35703575
[file driver.py]
3571-
from native import yield_three_times, yield_twice_and_return, yield_while_loop, yield_for_loop, yield_with_except, complex_yield, yield_with_default, A
3576+
from native import yield_three_times, yield_twice_and_return, yield_while_loop, yield_for_loop, yield_with_except, complex_yield, yield_with_default, A, return_tuple
35723577
from testutil import run_generator
35733578

35743579
assert run_generator(yield_three_times()) == ((1, 2, 3), None)
@@ -3579,6 +3584,7 @@ assert run_generator(yield_with_except()) == ((10,), None)
35793584
assert run_generator(complex_yield(5, 'foo', 1.0)) == (('2 foo', 3, '4 foo'), 1.0)
35803585
assert run_generator(yield_with_default()) == ((), None)
35813586
assert run_generator(A(0).generator()) == ((0,), None)
3587+
assert run_generator(return_tuple()) == ((0,), (1, 2))
35823588

35833589
for i in yield_twice_and_return():
35843590
print(i)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy