Skip to content

Commit 0d4d7e4

Browse files
committed
Merge gen and frame state variables into one.
1 parent 4f28f75 commit 0d4d7e4

File tree

9 files changed

+106
-54
lines changed

9 files changed

+106
-54
lines changed

Include/cpython/frameobject.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
# error "this header file must not be included directly"
55
#endif
66

7+
/* These values are chosen so that all tests involve comparing to zero. */
8+
typedef enum _framestate {
9+
FRAME_CREATED = -2,
10+
FRAME_SUSPENDED = -1,
11+
FRAME_EXECUTING = 0,
12+
FRAME_RETURNED = 1,
13+
FRAME_RAISED = 2,
14+
FRAME_CLEARED = 3
15+
} PyFrameState;
16+
717
typedef struct {
818
int b_type; /* what kind of block this is */
919
int b_handler; /* where to jump to find handler */
@@ -37,11 +47,22 @@ struct _frame {
3747
bytecode index. */
3848
int f_lineno; /* Current line number */
3949
int f_iblock; /* index in f_blockstack */
40-
char f_executing; /* whether the frame is still executing */
50+
PyFrameState f_state; /* What state the frame is in */
4151
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
4252
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
4353
};
4454

55+
static inline int _PyFrameIsRunnable(struct _frame *f) {
56+
return f->f_state < FRAME_EXECUTING;
57+
}
58+
59+
static inline int _PyFrameIsExecuting(struct _frame *f) {
60+
return f->f_state == FRAME_EXECUTING;
61+
}
62+
63+
static inline int _PyFrameHasCompleted(struct _frame *f) {
64+
return f->f_state > FRAME_EXECUTING;
65+
}
4566

4667
/* Standard object interface */
4768

Include/genobject.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ extern "C" {
1616
PyObject_HEAD \
1717
/* Note: gi_frame can be NULL if the generator is "finished" */ \
1818
PyFrameObject *prefix##_frame; \
19-
/* True if generator is being executed. */ \
20-
char prefix##_running; \
2119
/* The code object backing the generator */ \
2220
PyObject *prefix##_code; \
2321
/* List of weak reference. */ \

Lib/test/test_generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ def b():
881881
>>> i.gi_running = 42
882882
Traceback (most recent call last):
883883
...
884-
AttributeError: readonly attribute
884+
AttributeError: attribute 'gi_running' of 'generator' objects is not writable
885885
>>> def g():
886886
... yield me.gi_running
887887
>>> me = g()

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@ def bar(cls):
12531253
check(bar, size('PP'))
12541254
# generator
12551255
def get_gen(): yield 1
1256-
check(get_gen(), size('Pb2PPP4P'))
1256+
check(get_gen(), size('P2PPP4P'))
12571257
# iterator
12581258
check(iter('abc'), size('lP'))
12591259
# callable-iterator

Lib/test/test_yield_from.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,9 @@ def two():
938938
res.append(g1.throw(MyErr))
939939
except StopIteration:
940940
pass
941+
except:
942+
self.assertEqual(res, [0, 1, 2, 3])
943+
raise
941944
# Check with close
942945
class MyIt(object):
943946
def __iter__(self):

Modules/_xxsubinterpretersmodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,7 @@ _is_running(PyInterpreterState *interp)
18471847
return 0;
18481848
}
18491849

1850-
int executing = (int)(frame->f_executing);
1850+
int executing = _PyFrameIsExecuting(frame);
18511851
Py_DECREF(frame);
18521852

18531853
return executing;

Objects/frameobject.c

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -352,33 +352,32 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
352352
return -1;
353353
}
354354

355-
/* Upon the 'call' trace event of a new frame, f->f_lasti is -1 and
356-
* f->f_trace is NULL, check first on the first condition.
357-
* Forbidding jumps from the 'call' event of a new frame is a side effect
358-
* of allowing to set f_lineno only from trace functions. */
359-
if (f->f_lasti == -1) {
360-
PyErr_Format(PyExc_ValueError,
361-
"can't jump from the 'call' trace event of a new frame");
362-
return -1;
363-
}
364-
365-
/* You can only do this from within a trace function, not via
366-
* _getframe or similar hackery. */
367-
if (!f->f_trace) {
368-
PyErr_Format(PyExc_ValueError,
369-
"f_lineno can only be set by a trace function");
370-
return -1;
371-
}
372-
373355
/* Forbid jumps upon a 'return' trace event (except after executing a
374-
* YIELD_VALUE or YIELD_FROM opcode, f_stacktop is not NULL in that case)
356+
* YIELD_VALUE or YIELD_FROM opcode)
375357
* and upon an 'exception' trace event.
376358
* Jumps from 'call' trace events have already been forbidden above for new
377359
* frames, so this check does not change anything for 'call' events. */
378-
if (f->f_stacktop == NULL) {
379-
PyErr_SetString(PyExc_ValueError,
360+
switch(f->f_state) {
361+
case FRAME_CREATED:
362+
PyErr_Format(PyExc_ValueError,
363+
"can't jump from the 'call' trace event of a new frame");
364+
return -1;
365+
case FRAME_RETURNED:
366+
case FRAME_RAISED:
367+
case FRAME_CLEARED:
368+
PyErr_SetString(PyExc_ValueError,
380369
"can only jump from a 'line' trace event");
381-
return -1;
370+
return -1;
371+
case FRAME_EXECUTING:
372+
case FRAME_SUSPENDED:
373+
/* You can only do this from within a trace function, not via
374+
* _getframe or similar hackery. */
375+
if (!f->f_trace) {
376+
PyErr_Format(PyExc_ValueError,
377+
"f_lineno can only be set by a trace function");
378+
return -1;
379+
}
380+
break;
382381
}
383382

384383
int new_lineno;
@@ -665,7 +664,7 @@ frame_tp_clear(PyFrameObject *f)
665664
*/
666665
PyObject **oldtop = f->f_stacktop;
667666
f->f_stacktop = NULL;
668-
f->f_executing = 0;
667+
f->f_state = FRAME_CLEARED;
669668

670669
Py_CLEAR(f->f_trace);
671670

@@ -687,7 +686,7 @@ frame_tp_clear(PyFrameObject *f)
687686
static PyObject *
688687
frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
689688
{
690-
if (f->f_executing) {
689+
if (_PyFrameIsExecuting(f)) {
691690
PyErr_SetString(PyExc_RuntimeError,
692691
"cannot clear an executing frame");
693692
return NULL;
@@ -927,7 +926,7 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
927926
f->f_lasti = -1;
928927
f->f_lineno = code->co_firstlineno;
929928
f->f_iblock = 0;
930-
f->f_executing = 0;
929+
f->f_state = FRAME_CREATED;
931930
f->f_gen = NULL;
932931
f->f_trace_opcodes = 0;
933932
f->f_trace_lines = 1;

Objects/genobject.c

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ _PyGen_Finalize(PyObject *self)
4747
PyObject *res = NULL;
4848
PyObject *error_type, *error_value, *error_traceback;
4949

50-
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL) {
50+
if (gen->gi_frame == NULL || _PyFrameHasCompleted(gen->gi_frame)) {
5151
/* Generator isn't paused, so no need to close */
5252
return;
5353
}
@@ -143,7 +143,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
143143
PyFrameObject *f = gen->gi_frame;
144144
PyObject *result;
145145

146-
if (gen->gi_running) {
146+
if (f != NULL && _PyFrameIsExecuting(f)) {
147147
const char *msg = "generator already executing";
148148
if (PyCoro_CheckExact(gen)) {
149149
msg = "coroutine already executing";
@@ -154,7 +154,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
154154
PyErr_SetString(PyExc_ValueError, msg);
155155
return NULL;
156156
}
157-
if (f == NULL || f->f_stacktop == NULL) {
157+
if (f == NULL || _PyFrameHasCompleted(f)) {
158158
if (PyCoro_CheckExact(gen) && !closing) {
159159
/* `gen` is an exhausted coroutine: raise an error,
160160
except when called from gen_close(), which should
@@ -176,6 +176,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
176176
return NULL;
177177
}
178178

179+
assert(_PyFrameIsRunnable(f));
179180
if (f->f_lasti == -1) {
180181
if (arg && arg != Py_None) {
181182
const char *msg = "can't send non-None value to a "
@@ -203,7 +204,6 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
203204
assert(f->f_back == NULL);
204205
f->f_back = tstate->frame;
205206

206-
gen->gi_running = 1;
207207
gen->gi_exc_state.previous_item = tstate->exc_info;
208208
tstate->exc_info = &gen->gi_exc_state;
209209

@@ -215,7 +215,6 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
215215
result = _PyEval_EvalFrame(tstate, f, exc);
216216
tstate->exc_info = gen->gi_exc_state.previous_item;
217217
gen->gi_exc_state.previous_item = NULL;
218-
gen->gi_running = 0;
219218

220219
/* Don't keep the reference to f_back any longer than necessary. It
221220
* may keep a chain of frames alive or it could create a reference
@@ -225,7 +224,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
225224

226225
/* If the generator just returned (as opposed to yielding), signal
227226
* that the generator is exhausted. */
228-
if (result && f->f_stacktop == NULL) {
227+
if (result && _PyFrameHasCompleted(f)) {
229228
if (result == Py_None) {
230229
/* Delay exception instantiation if we can */
231230
if (PyAsyncGen_CheckExact(gen)) {
@@ -264,7 +263,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
264263
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
265264
}
266265

267-
if (!result || f->f_stacktop == NULL) {
266+
if (!result || _PyFrameHasCompleted(f)) {
268267
/* generator can't be rerun, so release the frame */
269268
/* first clean reference cycle through stored exception traceback */
270269
_PyErr_ClearExcState(&gen->gi_exc_state);
@@ -356,9 +355,10 @@ gen_close(PyGenObject *gen, PyObject *args)
356355
int err = 0;
357356

358357
if (yf) {
359-
gen->gi_running = 1;
358+
PyFrameState state = gen->gi_frame->f_state;
359+
gen->gi_frame->f_state = FRAME_EXECUTING;
360360
err = gen_close_iter(yf);
361-
gen->gi_running = 0;
361+
gen->gi_frame->f_state = state;
362362
Py_DECREF(yf);
363363
}
364364
if (err == 0)
@@ -405,9 +405,10 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
405405
We have to allow some awaits to work it through, hence the
406406
`close_on_genexit` parameter here.
407407
*/
408-
gen->gi_running = 1;
408+
PyFrameState state = gen->gi_frame->f_state;
409+
gen->gi_frame->f_state = FRAME_EXECUTING;
409410
err = gen_close_iter(yf);
410-
gen->gi_running = 0;
411+
gen->gi_frame->f_state = state;
411412
Py_DECREF(yf);
412413
if (err < 0)
413414
return gen_send_ex(gen, Py_None, 1, 0);
@@ -418,7 +419,6 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
418419
PyThreadState *tstate = _PyThreadState_GET();
419420
PyFrameObject *f = tstate->frame;
420421

421-
gen->gi_running = 1;
422422
/* Since we are fast-tracking things by skipping the eval loop,
423423
we need to update the current frame so the stack trace
424424
will be reported correctly to the user. */
@@ -427,10 +427,12 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
427427
tstate->frame = gen->gi_frame;
428428
/* Close the generator that we are currently iterating with
429429
'yield from' or awaiting on with 'await'. */
430+
PyFrameState state = gen->gi_frame->f_state;
431+
gen->gi_frame->f_state = FRAME_EXECUTING;
430432
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
431433
typ, val, tb);
432434
tstate->frame = f;
433-
gen->gi_running = 0;
435+
gen->gi_frame->f_state = state;
434436
} else {
435437
/* `yf` is an iterator or a coroutine-like object. */
436438
PyObject *meth;
@@ -442,9 +444,10 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
442444
Py_DECREF(yf);
443445
goto throw_here;
444446
}
445-
gen->gi_running = 1;
447+
PyFrameState state = gen->gi_frame->f_state;
448+
gen->gi_frame->f_state = FRAME_EXECUTING;
446449
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
447-
gen->gi_running = 0;
450+
gen->gi_frame->f_state = state;
448451
Py_DECREF(meth);
449452
}
450453
Py_DECREF(yf);
@@ -701,19 +704,30 @@ gen_getyieldfrom(PyGenObject *gen, void *Py_UNUSED(ignored))
701704
return yf;
702705
}
703706

707+
708+
static PyObject *
709+
gen_getrunning(PyGenObject *gen, void *Py_UNUSED(ignored))
710+
{
711+
if (gen->gi_frame == NULL) {
712+
Py_INCREF(Py_False);
713+
return Py_False;
714+
}
715+
return PyBool_FromLong(_PyFrameIsExecuting(gen->gi_frame));
716+
}
717+
704718
static PyGetSetDef gen_getsetlist[] = {
705719
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
706720
PyDoc_STR("name of the generator")},
707721
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
708722
PyDoc_STR("qualified name of the generator")},
709723
{"gi_yieldfrom", (getter)gen_getyieldfrom, NULL,
710724
PyDoc_STR("object being iterated by yield from, or None")},
725+
{"gi_running", (getter)gen_getrunning, NULL, NULL},
711726
{NULL} /* Sentinel */
712727
};
713728

714729
static PyMemberDef gen_memberlist[] = {
715730
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
716-
{"gi_running", T_BOOL, offsetof(PyGenObject, gi_running), READONLY},
717731
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY},
718732
{NULL} /* Sentinel */
719733
};
@@ -791,7 +805,6 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
791805
f->f_gen = (PyObject *) gen;
792806
Py_INCREF(f->f_code);
793807
gen->gi_code = (PyObject *)(f->f_code);
794-
gen->gi_running = 0;
795808
gen->gi_weakreflist = NULL;
796809
gen->gi_exc_state.exc_type = NULL;
797810
gen->gi_exc_state.exc_value = NULL;
@@ -921,19 +934,29 @@ coro_get_cr_await(PyCoroObject *coro, void *Py_UNUSED(ignored))
921934
return yf;
922935
}
923936

937+
static PyObject *
938+
cr_getrunning(PyCoroObject *coro, void *Py_UNUSED(ignored))
939+
{
940+
if (coro->cr_frame == NULL) {
941+
Py_INCREF(Py_False);
942+
return Py_False;
943+
}
944+
return PyBool_FromLong(_PyFrameIsExecuting(coro->cr_frame));
945+
}
946+
924947
static PyGetSetDef coro_getsetlist[] = {
925948
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
926949
PyDoc_STR("name of the coroutine")},
927950
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
928951
PyDoc_STR("qualified name of the coroutine")},
929952
{"cr_await", (getter)coro_get_cr_await, NULL,
930953
PyDoc_STR("object being awaited on, or None")},
954+
{"cr_running", (getter)cr_getrunning, NULL, NULL},
931955
{NULL} /* Sentinel */
932956
};
933957

934958
static PyMemberDef coro_memberlist[] = {
935959
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY},
936-
{"cr_running", T_BOOL, offsetof(PyCoroObject, cr_running), READONLY},
937960
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY},
938961
{"cr_origin", T_OBJECT, offsetof(PyCoroObject, cr_origin), READONLY},
939962
{NULL} /* Sentinel */
@@ -1828,7 +1851,7 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
18281851
return NULL;
18291852
}
18301853

1831-
if (f == NULL || f->f_stacktop == NULL) {
1854+
if (f == NULL || _PyFrameHasCompleted(f)) {
18321855
o->agt_state = AWAITABLE_STATE_CLOSED;
18331856
PyErr_SetNone(PyExc_StopIteration);
18341857
return NULL;

0 commit comments

Comments
 (0)