Skip to content

Commit 49daf6d

Browse files
authored
bpo-47045: Remove f_state field (GH-31963)
* Remove the f_state field from _PyInterpreterFrame * Make ownership of the frame explicit, replacing the is_generator field with an owner field.
1 parent 88872a2 commit 49daf6d

File tree

9 files changed

+260
-220
lines changed

9 files changed

+260
-220
lines changed

Include/cpython/genobject.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extern "C" {
2727
char prefix##_closed; \
2828
char prefix##_running_async; \
2929
/* The frame */ \
30-
char prefix##_frame_valid; \
30+
int8_t prefix##_frame_state; \
3131
PyObject *prefix##_iframe[1];
3232

3333
typedef struct {

Include/cpython/pystate.h

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ struct _ts {
103103
This is to prevent the actual trace/profile code from being recorded in
104104
the trace/profile. */
105105
int tracing;
106+
int tracing_what; /* The event currently being traced, if any. */
106107

107108
/* Pointer to current _PyCFrame in the C stack frame of the currently,
108109
* or most recently, executing _PyEval_EvalFrameDefault. */

Include/internal/pycore_frame.h

+20-26
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern "C" {
55
#endif
66

77
#include <stdbool.h>
8+
#include <stddef.h>
89

910
struct _frame {
1011
PyObject_HEAD
@@ -14,7 +15,6 @@ struct _frame {
1415
int f_lineno; /* Current line number. Only valid if non-zero */
1516
char f_trace_lines; /* Emit per-line trace events? */
1617
char f_trace_opcodes; /* Emit per-opcode trace events? */
17-
char f_owns_frame; /* This frame owns the frame */
1818
/* The frame data, if this frame object owns the frame */
1919
PyObject *_f_frame_data[1];
2020
};
@@ -24,20 +24,19 @@ extern PyFrameObject* _PyFrame_New_NoTrack(PyCodeObject *code);
2424

2525
/* other API */
2626

27-
/* These values are chosen so that the inline functions below all
28-
* compare f_state to zero.
29-
*/
30-
enum _framestate {
27+
typedef enum _framestate {
3128
FRAME_CREATED = -2,
3229
FRAME_SUSPENDED = -1,
3330
FRAME_EXECUTING = 0,
34-
FRAME_RETURNED = 1,
35-
FRAME_UNWINDING = 2,
36-
FRAME_RAISED = 3,
31+
FRAME_COMPLETED = 1,
3732
FRAME_CLEARED = 4
38-
};
33+
} PyFrameState;
3934

40-
typedef signed char PyFrameState;
35+
enum _frameowner {
36+
FRAME_OWNED_BY_THREAD = 0,
37+
FRAME_OWNED_BY_GENERATOR = 1,
38+
FRAME_OWNED_BY_FRAME_OBJECT = 2
39+
};
4140

4241
/*
4342
frame->f_lasti refers to the index of the last instruction,
@@ -54,24 +53,11 @@ typedef struct _PyInterpreterFrame {
5453
struct _PyInterpreterFrame *previous;
5554
int f_lasti; /* Last instruction if called */
5655
int stacktop; /* Offset of TOS from localsplus */
57-
PyFrameState f_state; /* What state the frame is in */
5856
bool is_entry; // Whether this is the "root" frame for the current _PyCFrame.
59-
bool is_generator;
57+
char owner;
6058
PyObject *localsplus[1];
6159
} _PyInterpreterFrame;
6260

63-
static inline int _PyFrame_IsRunnable(_PyInterpreterFrame *f) {
64-
return f->f_state < FRAME_EXECUTING;
65-
}
66-
67-
static inline int _PyFrame_IsExecuting(_PyInterpreterFrame *f) {
68-
return f->f_state == FRAME_EXECUTING;
69-
}
70-
71-
static inline int _PyFrameHasCompleted(_PyInterpreterFrame *f) {
72-
return f->f_state > FRAME_EXECUTING;
73-
}
74-
7561
static inline PyObject **_PyFrame_Stackbase(_PyInterpreterFrame *f) {
7662
return f->localsplus + f->f_code->co_nlocalsplus;
7763
}
@@ -111,9 +97,8 @@ _PyFrame_InitializeSpecials(
11197
frame->stacktop = nlocalsplus;
11298
frame->frame_obj = NULL;
11399
frame->f_lasti = -1;
114-
frame->f_state = FRAME_CREATED;
115100
frame->is_entry = false;
116-
frame->is_generator = false;
101+
frame->owner = FRAME_OWNED_BY_THREAD;
117102
}
118103

119104
/* Gets the pointer to the locals array
@@ -200,6 +185,15 @@ void _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame *frame);
200185
_PyInterpreterFrame *
201186
_PyFrame_Push(PyThreadState *tstate, PyFunctionObject *func);
202187

188+
189+
static inline
190+
PyGenObject *_PyFrame_GetGenerator(_PyInterpreterFrame *frame)
191+
{
192+
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
193+
size_t offset_in_gen = offsetof(PyGenObject, gi_iframe);
194+
return (PyGenObject *)(((char *)frame) - offset_in_gen);
195+
}
196+
203197
#ifdef __cplusplus
204198
}
205199
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Remove the ``f_state`` field from the _PyInterpreterFrame struct. Add the
2+
``owner`` field to the _PyInterpreterFrame struct to make ownership explicit
3+
to simplify clearing and deallocing frames and generators.

Modules/_xxsubinterpretersmodule.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -1843,10 +1843,7 @@ _is_running(PyInterpreterState *interp)
18431843
if (frame == NULL) {
18441844
return 0;
18451845
}
1846-
1847-
int executing = _PyFrame_IsExecuting(frame);
1848-
1849-
return executing;
1846+
return 1;
18501847
}
18511848

18521849
static int

Objects/frameobject.c

+83-42
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,42 @@ frame_stack_pop(PyFrameObject *f)
412412
Py_DECREF(v);
413413
}
414414

415+
static PyFrameState
416+
_PyFrame_GetState(PyFrameObject *frame)
417+
{
418+
if (frame->f_frame->stacktop == 0) {
419+
return FRAME_CLEARED;
420+
}
421+
switch(frame->f_frame->owner) {
422+
case FRAME_OWNED_BY_GENERATOR:
423+
{
424+
PyGenObject *gen = _PyFrame_GetGenerator(frame->f_frame);
425+
return gen->gi_frame_state;
426+
}
427+
case FRAME_OWNED_BY_THREAD:
428+
{
429+
if (frame->f_frame->f_lasti < 0) {
430+
return FRAME_CREATED;
431+
}
432+
uint8_t *code = (uint8_t *)frame->f_frame->f_code->co_code_adaptive;
433+
int opcode = code[frame->f_frame->f_lasti*sizeof(_Py_CODEUNIT)];
434+
switch(_PyOpcode_Deopt[opcode]) {
435+
case COPY_FREE_VARS:
436+
case MAKE_CELL:
437+
case RETURN_GENERATOR:
438+
/* Frame not fully initialized */
439+
return FRAME_CREATED;
440+
default:
441+
return FRAME_EXECUTING;
442+
}
443+
}
444+
case FRAME_OWNED_BY_FRAME_OBJECT:
445+
return FRAME_COMPLETED;
446+
}
447+
Py_UNREACHABLE();
448+
}
449+
450+
415451
/* Setter for f_lineno - you can set f_lineno from within a trace function in
416452
* order to jump to a given line of code, subject to some restrictions. Most
417453
* lines are OK to jump to because they don't make any assumptions about the
@@ -440,6 +476,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
440476
return -1;
441477
}
442478

479+
PyFrameState state = _PyFrame_GetState(f);
443480
/*
444481
* This code preserves the historical restrictions on
445482
* setting the line number of a frame.
@@ -448,28 +485,31 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
448485
* In addition, jumps are forbidden when not tracing,
449486
* as this is a debugging feature.
450487
*/
451-
switch(f->f_frame->f_state) {
452-
case FRAME_CREATED:
488+
switch(PyThreadState_GET()->tracing_what) {
489+
case PyTrace_EXCEPTION:
490+
PyErr_SetString(PyExc_ValueError,
491+
"can only jump from a 'line' trace event");
492+
return -1;
493+
case PyTrace_CALL:
453494
PyErr_Format(PyExc_ValueError,
454495
"can't jump from the 'call' trace event of a new frame");
455496
return -1;
456-
case FRAME_RETURNED:
457-
case FRAME_UNWINDING:
458-
case FRAME_RAISED:
459-
case FRAME_CLEARED:
497+
case PyTrace_LINE:
498+
break;
499+
case PyTrace_RETURN:
500+
if (state == FRAME_SUSPENDED) {
501+
break;
502+
}
503+
/* fall through */
504+
default:
460505
PyErr_SetString(PyExc_ValueError,
461506
"can only jump from a 'line' trace event");
462507
return -1;
463-
case FRAME_EXECUTING:
464-
case FRAME_SUSPENDED:
465-
/* You can only do this from within a trace function, not via
466-
* _getframe or similar hackery. */
467-
if (!f->f_trace) {
468-
PyErr_Format(PyExc_ValueError,
469-
"f_lineno can only be set by a trace function");
470-
return -1;
471-
}
472-
break;
508+
}
509+
if (!f->f_trace) {
510+
PyErr_Format(PyExc_ValueError,
511+
"f_lineno can only be set by a trace function");
512+
return -1;
473513
}
474514

475515
int new_lineno;
@@ -555,8 +595,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
555595
PyErr_SetString(PyExc_ValueError, msg);
556596
return -1;
557597
}
558-
/* Unwind block stack. */
559-
if (f->f_frame->f_state == FRAME_SUSPENDED) {
598+
if (state == FRAME_SUSPENDED) {
560599
/* Account for value popped by yield */
561600
start_stack = pop_value(start_stack);
562601
}
@@ -623,7 +662,9 @@ frame_dealloc(PyFrameObject *f)
623662
{
624663
/* It is the responsibility of the owning generator/coroutine
625664
* to have cleared the generator pointer */
626-
assert(!f->f_frame->is_generator);
665+
666+
assert(f->f_frame->owner != FRAME_OWNED_BY_GENERATOR ||
667+
_PyFrame_GetGenerator(f->f_frame)->gi_frame_state == FRAME_CLEARED);
627668

628669
if (_PyObject_GC_IS_TRACKED(f)) {
629670
_PyObject_GC_UNTRACK(f);
@@ -633,8 +674,7 @@ frame_dealloc(PyFrameObject *f)
633674
PyCodeObject *co = NULL;
634675

635676
/* Kill all local variables including specials, if we own them */
636-
if (f->f_owns_frame) {
637-
f->f_owns_frame = 0;
677+
if (f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
638678
assert(f->f_frame == (_PyInterpreterFrame *)f->_f_frame_data);
639679
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)f->_f_frame_data;
640680
/* Don't clear code object until the end */
@@ -659,7 +699,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
659699
{
660700
Py_VISIT(f->f_back);
661701
Py_VISIT(f->f_trace);
662-
if (f->f_owns_frame == 0) {
702+
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
663703
return 0;
664704
}
665705
assert(f->f_frame->frame_obj == NULL);
@@ -669,13 +709,6 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
669709
static int
670710
frame_tp_clear(PyFrameObject *f)
671711
{
672-
/* Before anything else, make sure that this frame is clearly marked
673-
* as being defunct! Else, e.g., a generator reachable from this
674-
* frame may also point to this frame, believe itself to still be
675-
* active, and try cleaning up this frame again.
676-
*/
677-
f->f_frame->f_state = FRAME_CLEARED;
678-
679712
Py_CLEAR(f->f_trace);
680713

681714
/* locals and stack */
@@ -691,19 +724,25 @@ frame_tp_clear(PyFrameObject *f)
691724
static PyObject *
692725
frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
693726
{
694-
if (_PyFrame_IsExecuting(f->f_frame)) {
695-
PyErr_SetString(PyExc_RuntimeError,
696-
"cannot clear an executing frame");
697-
return NULL;
727+
if (f->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
728+
PyGenObject *gen = _PyFrame_GetGenerator(f->f_frame);
729+
if (gen->gi_frame_state == FRAME_EXECUTING) {
730+
goto running;
731+
}
732+
_PyGen_Finalize((PyObject *)gen);
698733
}
699-
if (f->f_frame->is_generator) {
700-
assert(!f->f_owns_frame);
701-
size_t offset_in_gen = offsetof(PyGenObject, gi_iframe);
702-
PyObject *gen = (PyObject *)(((char *)f->f_frame) - offset_in_gen);
703-
_PyGen_Finalize(gen);
734+
else if (f->f_frame->owner == FRAME_OWNED_BY_THREAD) {
735+
goto running;
736+
}
737+
else {
738+
assert(f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT);
739+
(void)frame_tp_clear(f);
704740
}
705-
(void)frame_tp_clear(f);
706741
Py_RETURN_NONE;
742+
running:
743+
PyErr_SetString(PyExc_RuntimeError,
744+
"cannot clear an executing frame");
745+
return NULL;
707746
}
708747

709748
PyDoc_STRVAR(clear__doc__,
@@ -835,7 +874,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
835874
}
836875
init_frame((_PyInterpreterFrame *)f->_f_frame_data, func, locals);
837876
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
838-
f->f_owns_frame = 1;
877+
f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
839878
Py_DECREF(func);
840879
_PyObject_GC_TRACK(f);
841880
return f;
@@ -912,7 +951,7 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) {
912951

913952
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
914953
PyObject *value = fast[i];
915-
if (frame->f_state != FRAME_CLEARED) {
954+
if (frame->stacktop) {
916955
if (kind & CO_FAST_FREE) {
917956
// The cell was set by COPY_FREE_VARS.
918957
assert(value != NULL && PyCell_Check(value));
@@ -1049,7 +1088,7 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
10491088
void
10501089
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
10511090
{
1052-
if (f == NULL || f->f_frame->f_state == FRAME_CLEARED) {
1091+
if (f == NULL || _PyFrame_GetState(f) == FRAME_CLEARED) {
10531092
return;
10541093
}
10551094
_PyFrame_LocalsToFast(f->f_frame, clear);
@@ -1096,3 +1135,5 @@ _PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
10961135

10971136
return _PyEval_GetBuiltins(tstate);
10981137
}
1138+
1139+

0 commit comments

Comments
 (0)