Skip to content

Commit 2023050

Browse files
markshannonpull[bot]
authored andcommitted
GH-108035: Remove the _PyCFrame struct as it is no longer needed for performance. (GH-108036)
1 parent edace7c commit 2023050

21 files changed

+66
-103
lines changed

Include/cpython/pystate.h

+2-23
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,6 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
2929
#define PyTrace_C_RETURN 6
3030
#define PyTrace_OPCODE 7
3131

32-
// Internal structure: you should not use it directly, but use public functions
33-
// like PyThreadState_EnterTracing() and PyThreadState_LeaveTracing().
34-
typedef struct _PyCFrame {
35-
/* This struct will be threaded through the C stack
36-
* allowing fast access to per-thread state that needs
37-
* to be accessed quickly by the interpreter, but can
38-
* be modified outside of the interpreter.
39-
*
40-
* WARNING: This makes data on the C stack accessible from
41-
* heap objects. Care must be taken to maintain stack
42-
* discipline and make sure that instances of this struct cannot
43-
* accessed outside of their lifetime.
44-
*/
45-
/* Pointer to the currently executing frame (it can be NULL) */
46-
struct _PyInterpreterFrame *current_frame;
47-
struct _PyCFrame *previous;
48-
} _PyCFrame;
49-
5032
typedef struct _err_stackitem {
5133
/* This struct represents a single execution context where we might
5234
* be currently handling an exception. It is a per-coroutine state
@@ -123,9 +105,8 @@ struct _ts {
123105
int tracing;
124106
int what_event; /* The event currently being monitored, if any. */
125107

126-
/* Pointer to current _PyCFrame in the C stack frame of the currently,
127-
* or most recently, executing _PyEval_EvalFrameDefault. */
128-
_PyCFrame *cframe;
108+
/* Pointer to currently executing frame. */
109+
struct _PyInterpreterFrame *current_frame;
129110

130111
Py_tracefunc c_profilefunc;
131112
Py_tracefunc c_tracefunc;
@@ -211,8 +192,6 @@ struct _ts {
211192
/* The thread's exception stack entry. (Always the last entry.) */
212193
_PyErr_StackItem exc_state;
213194

214-
/* The bottom-most frame on the stack. */
215-
_PyCFrame root_cframe;
216195
};
217196

218197
/* WASI has limited call stack. Python's recursion limit depends on code

Include/internal/pycore_frame.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ _PyFrame_GetFirstComplete(_PyInterpreterFrame *frame)
196196
static inline _PyInterpreterFrame *
197197
_PyThreadState_GetFrame(PyThreadState *tstate)
198198
{
199-
return _PyFrame_GetFirstComplete(tstate->cframe->current_frame);
199+
return _PyFrame_GetFirstComplete(tstate->current_frame);
200200
}
201201

202202
/* For use by _PyFrame_GetFrameObject

Include/internal/pycore_runtime.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef struct _Py_DebugOffsets {
8080
off_t prev;
8181
off_t next;
8282
off_t interp;
83-
off_t cframe;
83+
off_t current_frame;
8484
off_t thread_id;
8585
off_t native_thread_id;
8686
} thread_state;

Include/internal/pycore_runtime_init.h

+1-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extern PyTypeObject _PyExc_MemoryError;
4545
.prev = offsetof(PyThreadState, prev), \
4646
.next = offsetof(PyThreadState, next), \
4747
.interp = offsetof(PyThreadState, interp), \
48-
.cframe = offsetof(PyThreadState, cframe), \
48+
.current_frame = offsetof(PyThreadState, current_frame), \
4949
.thread_id = offsetof(PyThreadState, thread_id), \
5050
.native_thread_id = offsetof(PyThreadState, native_thread_id), \
5151
}, \
@@ -56,10 +56,6 @@ extern PyTypeObject _PyExc_MemoryError;
5656
.localsplus = offsetof(_PyInterpreterFrame, localsplus), \
5757
.owner = offsetof(_PyInterpreterFrame, owner), \
5858
}, \
59-
.cframe = { \
60-
.current_frame = offsetof(_PyCFrame, current_frame), \
61-
.previous = offsetof(_PyCFrame, previous), \
62-
}, \
6359
.code_object = { \
6460
.filename = offsetof(PyCodeObject, co_filename), \
6561
.name = offsetof(PyCodeObject, co_name), \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Remove the ``_PyCFrame`` struct, moving the pointer to the current intepreter frame
2+
back to the threadstate, as it was for 3.10 and earlier. The ``_PyCFrame``
3+
existed as a performance optimization for tracing. Since PEP 669 has been
4+
implemented, this optimization no longer applies.

Modules/_xxsubinterpretersmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ _is_running(PyInterpreterState *interp)
369369
}
370370

371371
assert(!PyErr_Occurred());
372-
struct _PyInterpreterFrame *frame = tstate->cframe->current_frame;
372+
struct _PyInterpreterFrame *frame = tstate->current_frame;
373373
if (frame == NULL) {
374374
return 0;
375375
}

Objects/genobject.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -476,17 +476,17 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
476476
will be reported correctly to the user. */
477477
/* XXX We should probably be updating the current frame
478478
somewhere in ceval.c. */
479-
_PyInterpreterFrame *prev = tstate->cframe->current_frame;
479+
_PyInterpreterFrame *prev = tstate->current_frame;
480480
frame->previous = prev;
481-
tstate->cframe->current_frame = frame;
481+
tstate->current_frame = frame;
482482
/* Close the generator that we are currently iterating with
483483
'yield from' or awaiting on with 'await'. */
484484
PyFrameState state = gen->gi_frame_state;
485485
gen->gi_frame_state = FRAME_EXECUTING;
486486
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
487487
typ, val, tb);
488488
gen->gi_frame_state = state;
489-
tstate->cframe->current_frame = prev;
489+
tstate->current_frame = prev;
490490
frame->previous = NULL;
491491
} else {
492492
/* `yf` is an iterator or a coroutine-like object. */
@@ -938,7 +938,7 @@ _Py_MakeCoro(PyFunctionObject *func)
938938
if (origin_depth == 0) {
939939
((PyCoroObject *)coro)->cr_origin_or_finalizer = NULL;
940940
} else {
941-
_PyInterpreterFrame *frame = tstate->cframe->current_frame;
941+
_PyInterpreterFrame *frame = tstate->current_frame;
942942
assert(frame);
943943
assert(_PyFrame_IsIncomplete(frame));
944944
frame = _PyFrame_GetFirstComplete(frame->previous);

Objects/typevarobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ make_union(PyObject *self, PyObject *other)
107107
static PyObject *
108108
caller(void)
109109
{
110-
_PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame;
110+
_PyInterpreterFrame *f = _PyThreadState_GET()->current_frame;
111111
if (f == NULL) {
112112
Py_RETURN_NONE;
113113
}

Python/bytecodes.c

+15-19
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ dummy_func(
7272
_PyInterpreterFrame *frame,
7373
unsigned char opcode,
7474
unsigned int oparg,
75-
_PyCFrame cframe,
7675
_Py_CODEUNIT *next_instr,
7776
PyObject **stack_pointer,
7877
PyObject *kwnames,
@@ -134,8 +133,7 @@ dummy_func(
134133
}
135134

136135
inst(RESUME, (--)) {
137-
assert(tstate->cframe == &cframe);
138-
assert(frame == cframe.current_frame);
136+
assert(frame == tstate->current_frame);
139137
/* Possibly combine this with eval breaker */
140138
if (_PyFrame_GetCode(frame)->_co_instrumentation_version != tstate->interp->monitoring_version) {
141139
int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp);
@@ -752,9 +750,8 @@ dummy_func(
752750
inst(INTERPRETER_EXIT, (retval --)) {
753751
assert(frame == &entry_frame);
754752
assert(_PyFrame_IsIncomplete(frame));
755-
/* Restore previous cframe and return. */
756-
tstate->cframe = cframe.previous;
757-
assert(tstate->cframe->current_frame == frame->previous);
753+
/* Restore previous frame and return. */
754+
tstate->current_frame = frame->previous;
758755
assert(!_PyErr_Occurred(tstate));
759756
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
760757
return retval;
@@ -768,7 +765,7 @@ dummy_func(
768765
assert(frame != &entry_frame);
769766
// GH-99729: We need to unlink the frame *before* clearing it:
770767
_PyInterpreterFrame *dying = frame;
771-
frame = cframe.current_frame = dying->previous;
768+
frame = tstate->current_frame = dying->previous;
772769
_PyEvalFrameClearAndPop(tstate, dying);
773770
frame->prev_instr += frame->return_offset;
774771
_PyFrame_StackPush(frame, retval);
@@ -787,7 +784,7 @@ dummy_func(
787784
assert(frame != &entry_frame);
788785
// GH-99729: We need to unlink the frame *before* clearing it:
789786
_PyInterpreterFrame *dying = frame;
790-
frame = cframe.current_frame = dying->previous;
787+
frame = tstate->current_frame = dying->previous;
791788
_PyEvalFrameClearAndPop(tstate, dying);
792789
frame->prev_instr += frame->return_offset;
793790
_PyFrame_StackPush(frame, retval);
@@ -803,7 +800,7 @@ dummy_func(
803800
assert(frame != &entry_frame);
804801
// GH-99729: We need to unlink the frame *before* clearing it:
805802
_PyInterpreterFrame *dying = frame;
806-
frame = cframe.current_frame = dying->previous;
803+
frame = tstate->current_frame = dying->previous;
807804
_PyEvalFrameClearAndPop(tstate, dying);
808805
frame->prev_instr += frame->return_offset;
809806
_PyFrame_StackPush(frame, retval);
@@ -823,7 +820,7 @@ dummy_func(
823820
assert(frame != &entry_frame);
824821
// GH-99729: We need to unlink the frame *before* clearing it:
825822
_PyInterpreterFrame *dying = frame;
826-
frame = cframe.current_frame = dying->previous;
823+
frame = tstate->current_frame = dying->previous;
827824
_PyEvalFrameClearAndPop(tstate, dying);
828825
frame->prev_instr += frame->return_offset;
829826
_PyFrame_StackPush(frame, retval);
@@ -1019,7 +1016,7 @@ dummy_func(
10191016
gen->gi_exc_state.previous_item = NULL;
10201017
_Py_LeaveRecursiveCallPy(tstate);
10211018
_PyInterpreterFrame *gen_frame = frame;
1022-
frame = cframe.current_frame = frame->previous;
1019+
frame = tstate->current_frame = frame->previous;
10231020
gen_frame->previous = NULL;
10241021
_PyFrame_StackPush(frame, retval);
10251022
goto resume_frame;
@@ -1038,7 +1035,7 @@ dummy_func(
10381035
gen->gi_exc_state.previous_item = NULL;
10391036
_Py_LeaveRecursiveCallPy(tstate);
10401037
_PyInterpreterFrame *gen_frame = frame;
1041-
frame = cframe.current_frame = frame->previous;
1038+
frame = tstate->current_frame = frame->previous;
10421039
gen_frame->previous = NULL;
10431040
_PyFrame_StackPush(frame, retval);
10441041
goto resume_frame;
@@ -2207,10 +2204,10 @@ dummy_func(
22072204
OBJECT_STAT_INC(optimization_attempts);
22082205
frame = _PyOptimizer_BackEdge(frame, here, next_instr, stack_pointer);
22092206
if (frame == NULL) {
2210-
frame = cframe.current_frame;
2207+
frame = tstate->current_frame;
22112208
goto resume_with_error;
22122209
}
2213-
assert(frame == cframe.current_frame);
2210+
assert(frame == tstate->current_frame);
22142211
here[1].cache &= ((1 << OPTIMIZER_BITS_IN_COUNTER) -1);
22152212
goto resume_frame;
22162213
}
@@ -2238,7 +2235,7 @@ dummy_func(
22382235
Py_INCREF(executor);
22392236
frame = executor->execute(executor, frame, stack_pointer);
22402237
if (frame == NULL) {
2241-
frame = cframe.current_frame;
2238+
frame = tstate->current_frame;
22422239
goto resume_with_error;
22432240
}
22442241
goto resume_frame;
@@ -2993,12 +2990,11 @@ dummy_func(
29932990
_PyFrame_SetStackPointer(frame, stack_pointer);
29942991
new_frame->previous = frame;
29952992
CALL_STAT_INC(inlined_py_calls);
2993+
frame = tstate->current_frame = new_frame;
29962994
#if TIER_ONE
2997-
frame = cframe.current_frame = new_frame;
29982995
goto start_frame;
29992996
#endif
30002997
#if TIER_TWO
3001-
frame = tstate->cframe->current_frame = new_frame;
30022998
ERROR_IF(_Py_EnterRecursivePy(tstate), exit_unwind);
30032999
stack_pointer = _PyFrame_GetStackPointer(frame);
30043000
ip_offset = (_Py_CODEUNIT *)_PyFrame_GetCode(frame)->co_code_adaptive;
@@ -3135,7 +3131,7 @@ dummy_func(
31353131
/* Link frames */
31363132
init_frame->previous = shim;
31373133
shim->previous = frame;
3138-
frame = cframe.current_frame = init_frame;
3134+
frame = tstate->current_frame = init_frame;
31393135
CALL_STAT_INC(inlined_py_calls);
31403136
/* Account for pushing the extra frame.
31413137
* We don't check recursion depth here,
@@ -3598,7 +3594,7 @@ dummy_func(
35983594
assert(frame != &entry_frame);
35993595
_PyInterpreterFrame *prev = frame->previous;
36003596
_PyThreadState_PopFrame(tstate, frame);
3601-
frame = cframe.current_frame = prev;
3597+
frame = tstate->current_frame = prev;
36023598
_PyFrame_StackPush(frame, (PyObject *)gen);
36033599
goto resume_frame;
36043600
}

Python/ceval.c

+7-15
Original file line numberDiff line numberDiff line change
@@ -656,17 +656,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
656656
int lltrace = 0;
657657
#endif
658658

659-
_PyCFrame cframe;
660659
_PyInterpreterFrame entry_frame;
661660
PyObject *kwnames = NULL; // Borrowed reference. Reset by CALL instructions.
662661

663-
/* WARNING: Because the _PyCFrame lives on the C stack,
664-
* but can be accessed from a heap allocated object (tstate)
665-
* strict stack discipline must be maintained.
666-
*/
667-
_PyCFrame *prev_cframe = tstate->cframe;
668-
cframe.previous = prev_cframe;
669-
tstate->cframe = &cframe;
662+
670663

671664
#ifdef Py_DEBUG
672665
/* Set these to invalid but identifiable values for debugging. */
@@ -682,9 +675,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
682675
entry_frame.owner = FRAME_OWNED_BY_CSTACK;
683676
entry_frame.return_offset = 0;
684677
/* Push frame */
685-
entry_frame.previous = prev_cframe->current_frame;
678+
entry_frame.previous = tstate->current_frame;
686679
frame->previous = &entry_frame;
687-
cframe.current_frame = frame;
680+
tstate->current_frame = frame;
688681

689682
tstate->c_recursion_remaining -= (PY_EVAL_C_STACK_UNITS - 1);
690683
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
@@ -924,13 +917,12 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
924917
assert(frame != &entry_frame);
925918
// GH-99729: We need to unlink the frame *before* clearing it:
926919
_PyInterpreterFrame *dying = frame;
927-
frame = cframe.current_frame = dying->previous;
920+
frame = tstate->current_frame = dying->previous;
928921
_PyEvalFrameClearAndPop(tstate, dying);
929922
frame->return_offset = 0;
930923
if (frame == &entry_frame) {
931-
/* Restore previous cframe and exit */
932-
tstate->cframe = cframe.previous;
933-
assert(tstate->cframe->current_frame == frame->previous);
924+
/* Restore previous frame and exit */
925+
tstate->current_frame = frame->previous;
934926
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
935927
return NULL;
936928
}
@@ -2297,7 +2289,7 @@ int
22972289
PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
22982290
{
22992291
PyThreadState *tstate = _PyThreadState_GET();
2300-
_PyInterpreterFrame *current_frame = tstate->cframe->current_frame;
2292+
_PyInterpreterFrame *current_frame = tstate->current_frame;
23012293
int result = cf->cf_flags != 0;
23022294

23032295
if (current_frame != NULL) {

Python/ceval_macros.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
_PyFrame_SetStackPointer(frame, stack_pointer); \
110110
frame->prev_instr = next_instr - 1; \
111111
(NEW_FRAME)->previous = frame; \
112-
frame = cframe.current_frame = (NEW_FRAME); \
112+
frame = tstate->current_frame = (NEW_FRAME); \
113113
CALL_STAT_INC(inlined_py_calls); \
114114
goto start_frame; \
115115
} while (0)

Python/executor.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject
111111
STACK_SHRINK(1);
112112
error:
113113
// On ERROR_IF we return NULL as the frame.
114-
// The caller recovers the frame from cframe.current_frame.
114+
// The caller recovers the frame from tstate->current_frame.
115115
DPRINTF(2, "Error: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand);
116116
_PyFrame_SetStackPointer(frame, stack_pointer);
117117
Py_DECREF(self);

Python/executor_cases.c.h

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

Python/frame.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame)
124124
_PyFrame_GetGenerator(frame)->gi_frame_state == FRAME_CLEARED);
125125
// GH-99729: Clearing this frame can expose the stack (via finalizers). It's
126126
// crucial that this frame has been unlinked, and is no longer visible:
127-
assert(_PyThreadState_GET()->cframe->current_frame != frame);
127+
assert(_PyThreadState_GET()->current_frame != frame);
128128
if (frame->frame_obj) {
129129
PyFrameObject *f = frame->frame_obj;
130130
frame->frame_obj = NULL;

0 commit comments

Comments
 (0)