Skip to content

Commit 4a1c58d

Browse files
authored
GH-96793: Specialize FOR_ITER for generators. (GH-98772)
1 parent 80c08d1 commit 4a1c58d

13 files changed

+207
-71
lines changed

Include/internal/pycore_code.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ extern void _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs,
230230
_Py_CODEUNIT *instr, int oparg);
231231
extern void _Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr,
232232
int oparg);
233-
extern void _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr);
233+
extern void _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg);
234234

235235
/* Finalizer function for static codeobjects used in deepfreeze.py */
236236
extern void _PyStaticCode_Fini(PyCodeObject *co);

Include/internal/pycore_frame.h

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ typedef struct _PyInterpreterFrame {
6161
// over, or (in the case of a newly-created frame) a totally invalid value:
6262
_Py_CODEUNIT *prev_instr;
6363
int stacktop; /* Offset of TOS from localsplus */
64+
uint16_t yield_offset;
6465
bool is_entry; // Whether this is the "root" frame for the current _PyCFrame.
6566
char owner;
6667
/* Locals and stack */
@@ -110,6 +111,7 @@ _PyFrame_InitializeSpecials(
110111
frame->frame_obj = NULL;
111112
frame->prev_instr = _PyCode_CODE(code) - 1;
112113
frame->is_entry = false;
114+
frame->yield_offset = 0;
113115
frame->owner = FRAME_OWNED_BY_THREAD;
114116
}
115117

Include/internal/pycore_opcode.h

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

Include/opcode.h

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

Lib/opcode.py

+1
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ def pseudo_op(name, op, real_ops):
328328
"FOR_ITER_ADAPTIVE",
329329
"FOR_ITER_LIST",
330330
"FOR_ITER_RANGE",
331+
"FOR_ITER_GEN",
331332
],
332333
"LOAD_ATTR": [
333334
"LOAD_ATTR_ADAPTIVE",

Lib/test/test_generators.py

+20
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,26 @@ def gen():
306306
self.assertEqual(next(g), "done")
307307
self.assertEqual(sys.exc_info(), (None, None, None))
308308

309+
def test_nested_gen_except_loop(self):
310+
def gen():
311+
for i in range(100):
312+
self.assertIsInstance(sys.exception(), TypeError)
313+
yield "doing"
314+
315+
def outer():
316+
try:
317+
raise TypeError
318+
except:
319+
for x in gen():
320+
yield x
321+
322+
try:
323+
raise ValueError
324+
except Exception:
325+
for x in outer():
326+
self.assertEqual(x, "doing")
327+
self.assertEqual(sys.exception(), None)
328+
309329
def test_except_throw_exception_context(self):
310330
def gen():
311331
try:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add specialization of :opcode:`FOR_ITER` for generators. Saves multiple
2+
layers of dispatch and checking to get from the :opcode:`FOR_ITER`
3+
instruction in the caller to the :opcode:`RESUME` in the generator.

Objects/genobject.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
209209

210210
frame->previous = tstate->cframe->current_frame;
211211

212-
gen->gi_exc_state.previous_item = tstate->exc_info;
212+
_PyErr_StackItem *prev_exc_info = tstate->exc_info;
213+
gen->gi_exc_state.previous_item = prev_exc_info;
213214
tstate->exc_info = &gen->gi_exc_state;
214215

215216
if (exc) {
@@ -220,12 +221,11 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
220221
gen->gi_frame_state = FRAME_EXECUTING;
221222
EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR);
222223
result = _PyEval_EvalFrame(tstate, frame, exc);
224+
assert(tstate->exc_info == prev_exc_info);
225+
assert(gen->gi_exc_state.previous_item == NULL);
223226
if (gen->gi_frame_state == FRAME_EXECUTING) {
224227
gen->gi_frame_state = FRAME_COMPLETED;
225228
}
226-
tstate->exc_info = gen->gi_exc_state.previous_item;
227-
gen->gi_exc_state.previous_item = NULL;
228-
229229
assert(tstate->cframe->current_frame == frame->previous);
230230
/* Don't keep the reference to previous any longer than necessary. It
231231
* may keep a chain of frames alive or it could create a reference

Python/bytecodes.c

+40-4
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,11 @@ dummy_func(
756756
goto resume_frame;
757757
}
758758
_Py_LeaveRecursiveCallTstate(tstate);
759+
if (frame->owner == FRAME_OWNED_BY_GENERATOR) {
760+
PyGenObject *gen = _PyFrame_GetGenerator(frame);
761+
tstate->exc_info = gen->gi_exc_state.previous_item;
762+
gen->gi_exc_state.previous_item = NULL;
763+
}
759764
/* Restore previous cframe and return. */
760765
tstate->cframe = cframe.previous;
761766
tstate->cframe->use_tracing = cframe.use_tracing;
@@ -895,7 +900,6 @@ dummy_func(
895900

896901
// error: SEND stack effect depends on jump flag
897902
inst(SEND) {
898-
assert(frame->is_entry);
899903
assert(STACK_LEVEL() >= 2);
900904
PyObject *v = POP();
901905
PyObject *receiver = TOP();
@@ -960,13 +964,21 @@ dummy_func(
960964
// The compiler treats any exception raised here as a failed close()
961965
// or throw() call.
962966
assert(oparg == STACK_LEVEL());
963-
assert(frame->is_entry);
964967
PyObject *retval = POP();
965-
_PyFrame_GetGenerator(frame)->gi_frame_state = FRAME_SUSPENDED;
968+
PyGenObject *gen = _PyFrame_GetGenerator(frame);
969+
gen->gi_frame_state = FRAME_SUSPENDED;
966970
_PyFrame_SetStackPointer(frame, stack_pointer);
967971
TRACE_FUNCTION_EXIT();
968972
DTRACE_FUNCTION_EXIT();
973+
tstate->exc_info = gen->gi_exc_state.previous_item;
974+
gen->gi_exc_state.previous_item = NULL;
969975
_Py_LeaveRecursiveCallPy(tstate);
976+
if (!frame->is_entry) {
977+
frame = cframe.current_frame = frame->previous;
978+
frame->prev_instr -= frame->yield_offset;
979+
_PyFrame_StackPush(frame, retval);
980+
goto resume_frame;
981+
}
970982
_Py_LeaveRecursiveCallTstate(tstate);
971983
/* Restore previous cframe and return. */
972984
tstate->cframe = cframe.previous;
@@ -2788,7 +2800,7 @@ dummy_func(
27882800
_PyForIterCache *cache = (_PyForIterCache *)next_instr;
27892801
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
27902802
next_instr--;
2791-
_Py_Specialize_ForIter(TOP(), next_instr);
2803+
_Py_Specialize_ForIter(TOP(), next_instr, oparg);
27922804
DISPATCH_SAME_OPARG();
27932805
}
27942806
else {
@@ -2844,6 +2856,30 @@ dummy_func(
28442856
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + 1);
28452857
}
28462858

2859+
inst(FOR_ITER_GEN) {
2860+
assert(cframe.use_tracing == 0);
2861+
PyGenObject *gen = (PyGenObject *)TOP();
2862+
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type, FOR_ITER);
2863+
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, FOR_ITER);
2864+
STAT_INC(FOR_ITER, hit);
2865+
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
2866+
_PyFrame_SetStackPointer(frame, stack_pointer);
2867+
frame->yield_offset = oparg;
2868+
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg);
2869+
assert(_Py_OPCODE(*next_instr) == END_FOR);
2870+
frame->prev_instr = next_instr - 1;
2871+
Py_INCREF(Py_None);
2872+
_PyFrame_StackPush(gen_frame, Py_None);
2873+
gen->gi_frame_state = FRAME_EXECUTING;
2874+
gen->gi_exc_state.previous_item = tstate->exc_info;
2875+
tstate->exc_info = &gen->gi_exc_state;
2876+
gen_frame->previous = frame;
2877+
gen_frame->is_entry = false;
2878+
frame = cframe.current_frame = gen_frame;
2879+
goto start_frame;
2880+
}
2881+
2882+
28472883
// stack effect: ( -- __0)
28482884
inst(BEFORE_ASYNC_WITH) {
28492885
PyObject *mgr = TOP();

0 commit comments

Comments
 (0)