8000 bpo-40941: Unify implicit and explicit state in the frame and generat… · python/cpython@cb9879b · GitHub
[go: up one dir, main page]

Skip to content

Commit cb9879b

Browse files
authored
bpo-40941: Unify implicit and explicit state in the frame and generator objects into a single value. (GH-20803)
* Merge gen and frame state variables into one. * Replace stack pointer with depth in PyFrameObject. Makes code easier to read and saves a word of memory.
1 parent 8e836bb commit cb9879b

File tree

9 files changed

+155
-95
lines changed

9 files changed

+155
-95
lines changed

Include/cpython/frameobject.h

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44
# error "this header file must not be included directly"
55
#endif
66

7+
/* These values are chosen so that the inline functions below all
8+
* compare f_state to zero.
9+
*/
10+
enum _framestate {
11+
FRAME_CREATED = -2,
12+
FRAME_SUSPENDED = -1,
13+
FRAME_EXECUTING = 0,
14+
FRAME_RETURNED = 1,
15+
FRAME_UNWINDING = 2,
16+
FRAME_RAISED = 3,
17+
FRAME_CLEARED = 4
18+
};
19+
20+
typedef signed char PyFrameState;
21+
722
typedef struct {
823
int b_type; /* what kind of block this is */
924
int b_handler; /* where to jump to find handler */
@@ -18,11 +33,8 @@ struct _frame {
1833
PyObject *f_globals; /* global symbol table (PyDictObject) */
1934
PyObject *f_locals; /* local symbol table (any mapping) */
2035
PyObject **f_valuestack; /* points after the last local */
21-
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.
22-
Frame evaluation usually NULLs it, but a frame that yields sets it
23-
to the current stack top. */
24-
PyObject **f_stacktop;
2536
PyObject *f_trace; /* Trace function */
37+
int f_stackdepth; /* Depth of value stack */
2638
char f_trace_lines; /* Emit per-line trace events? */
2739
char f_trace_opcodes; /* Emit per-opcode trace events? */
2840

@@ -37,11 +49,22 @@ struct _frame {
3749
bytecode index. */
3850
int f_lineno; /* Current line number */
3951
int f_iblock; /* index in f_blockstack */
40-
char f_executing; /* whether the frame is still executing */
52+
PyFrameState f_state; /* What state the frame is in */
4153
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
4254
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
4355
};
4456

57+
static inline int _PyFrame_IsRunnable(struct _frame *f) {
58+
return f->f_state < FRAME_EXECUTING;
59+
}
60+
61+
static inline int _PyFrame_IsExecuting(struct _frame *f) {
62+
return f->f_state == FRAME_EXECUTING;
63+
}
64+
65+
static inline int _PyFrameHasCompleted(struct _frame *f) {
66+
return f->f_state > FRAME_EXECUTING;
67+
}
4568

4669
/* Standard object interface */
4770

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ class C(object): pass
12361236
nfrees = len(x.f_code.co_freevars)
12371237
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
12381238
ncells + nfrees - 1
1239-
check(x, vsize('5P2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
1239+
check(x, vsize('4Pi2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
12401240
# function
12411241
def func(): pass
12421242
check(func, size('13P'))
@@ -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 = _PyFrame_IsExecuting(frame);
18511851
Py_DECREF(frame);
18521852

18531853
return executing;

Objects/frameobject.c

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -300,17 +300,20 @@ first_line_not_before(int *lines, int len, int line)
300300
static void
301301
frame_stack_pop(PyFrameObject *f)
302302
{
303-
PyObject *v = (*--f->f_stacktop);
303+
assert(f->f_stackdepth >= 0);
304+
f->f_stackdepth--;
305+
PyObject *v = f->f_valuestack[f->f_stackdepth];
304306
Py_DECREF(v);
305307
}
306308

307309
static void
308310
frame_block_unwind(PyFrameObject *f)
309311
{
312+
assert(f->f_stackdepth >= 0);
310313
assert(f->f_iblock > 0);
311314
f->f_iblock--;
312315
PyTryBlock *b = &f->f_blockstack[f->f_iblock];
313-
intptr_t delta = (f->f_stacktop - f->f_valuestack) - b->b_level;
316+
intptr_t delta = f->f_stackdepth - b->b_level;
314317
while (delta > 0) {
315318
frame_stack_pop(f);
316319
delta--;
@@ -352,33 +355,36 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
352355
return -1;
353356
}
354357

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,
358+
/*
359+
* This code preserves the historical restrictions on
360+
* setting the line number of a frame.
361+
* Jumps are forbidden on a 'return' trace event (except after a yield).
362+
* Jumps from 'call' trace events are also forbidden.
363+
* In addition, jumps are forbidden when not tracing,
364+
* as this is a debugging feature.
365+
*/
366+
switch(f->f_state) {
367+
case FRAME_CREATED:
368+
PyErr_Format(PyExc_ValueError,
361369
"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-
373-
/* 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)
375-
* and upon an 'exception' trace event.
376-
* Jumps from 'call' trace events have already been forbidden above for new
377-
* frames, so this check does not change anything for 'call' events. */
378-
if (f->f_stacktop == NULL) {
379-
PyErr_SetString(PyExc_ValueError,
370+
return -1;
371+
case FRAME_RETURNED:
372+
case FRAME_UNWINDING:
373+
case FRAME_RAISED:
374+
case FRAME_CLEARED:
375+
PyErr_SetString(PyExc_ValueError,
380376
"can only jump from a 'line' trace event");
381-
return -1;
377+
return -1;
378+
case FRAME_EXECUTING:
379+
case FRAME_SUSPENDED:
380+
/* You can only do this from within a trace function, not via
381+
* _getframe or similar hackery. */
382+
if (!f->f_trace) {
383+
PyErr_Format(PyExc_ValueError,
384+
"f_lineno can only be set by a trace function");
385+
return -1;
386+
}
387+
break;
382388
}
383389

384390
int new_lineno;
@@ -585,11 +591,10 @@ frame_dealloc(PyFrameObject *f)
585591
}
586592

587593
/* Free stack */
588-
if (f->f_stacktop != NULL) {
589-
for (PyObject **p = valuestack; p < f->f_stacktop; p++) {
590-
Py_XDECREF(*p);
591-
}
594+
for (int i = 0; i < f->f_stackdepth; i++) {
595+
Py_XDECREF(f->f_valuestack[i]);
592596
}
597+
f->f_stackdepth = 0;
593598

594599
Py_XDECREF(f->f_back);
595600
Py_DECREF(f->f_builtins);
@@ -647,10 +652,8 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
647652
}
648653

649654
/* stack */
650-
if (f->f_stacktop != NULL) {
651-
for (PyObject **p = f->f_valuestack; p < f->f_stacktop; p++) {
652-
Py_VISIT(*p);
653-
}
655+
for (int i = 0; i < f->f_stackdepth; i++) {
656+
Py_VISIT(f->f_valuestack[i]);
654657
}
655658
return 0;
656659
}
@@ -663,9 +666,7 @@ frame_tp_clear(PyFrameObject *f)
663666
* frame may also point to this frame, believe itself to still be
664667
* active, and try cleaning up this frame again.
665668
*/
666-
PyObject **oldtop = f->f_stacktop;
667-
f->f_stacktop = NULL;
668-
f->f_executing = 0;
669+
f->f_state = FRAME_CLEARED;
669670

670671
Py_CLEAR(f->f_trace);
671672

@@ -676,18 +677,17 @@ frame_tp_clear(PyFrameObject *f)
676677
}
677678

678679
/* stack */
679-
if (oldtop != NULL) {
680-
for (PyObject **p = f->f_valuestack; p < oldtop; p++) {
681-
Py_CLEAR(*p);
682-
}
680+
for (int i = 0; i < f->f_stackdepth; i++) {
681+
Py_CLEAR(f->f_valuestack[i]);
683682
}
683+
f->f_stackdepth = 0;
684684
return 0;
685685
}
686686

687687
static PyObject *
688688
frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
689689
{
690-
if (f->f_executing) {
690+
if (_PyFrame_IsExecuting(f)) {
691691
PyErr_SetString(PyExc_RuntimeError,
692692
"cannot clear an executing frame");
693693
return NULL;
@@ -898,7 +898,7 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
898898
return NULL;
899899
}
900900

901-
f->f_stacktop = f->f_valuestack;
901+
f->f_stackdepth = 0;
902902
f->f_builtins = builtins;
903903
Py_XINCREF(back);
904904
f->f_back = back;
@@ -927,7 +927,7 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
927927
f->f_lasti = -1;
928928
f->f_lineno = code->co_firstlineno;
929929
f->f_iblock = 0;
930-
f->f_executing = 0;
930+
f->f_state = FRAME_CREATED;
931931
f->f_gen = NULL;
932932
f->f_trace_opcodes = 0;
933933
f->f_trace_lines = 1;

0 commit comments

Comments
 (0)
0