Skip to content

Commit ae0a2b7

Browse files
authored
bpo-44590: Lazily allocate frame objects (GH-27077)
* Convert "specials" array to InterpreterFrame struct, adding f_lasti, f_state and other non-debug FrameObject fields to it. * Refactor, calls pushing the call to the interpreter upward toward _PyEval_Vector. * Compute f_back when on thread stack, only filling in value when frame object outlives stack invocation. * Move ownership of InterpreterFrame in generator from frame object to generator object. * Do not create frame objects for Python calls. * Do not create frame objects for generators.
1 parent 0363a40 commit ae0a2b7

27 files changed

+1036
-618
lines changed

Include/cpython/ceval.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ PyAPI_FUNC(PyObject *) _PyEval_GetBuiltinId(_Py_Identifier *);
1919
flag was set, else return 0. */
2020
PyAPI_FUNC(int) PyEval_MergeCompilerFlags(PyCompilerFlags *cf);
2121

22-
PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int exc);
22+
PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _interpreter_frame *f, int exc);
2323

2424
PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
2525
PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);

Include/cpython/frameobject.h

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,17 @@
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-
227
struct _frame {
238
PyObject_HEAD
249
struct _frame *f_back; /* previous frame, or NULL */
25-
PyObject **f_valuestack; /* points after the last local */
10+
struct _interpreter_frame *f_frame; /* points to the frame data */
2611
PyObject *f_trace; /* Trace function */
27-
/* Borrowed reference to a generator, or NULL */
28-
PyObject *f_gen;
29-
int f_stackdepth; /* Depth of value stack */
30-
int f_lasti; /* Last instruction if called */
3112
int f_lineno; /* Current line number. Only valid if non-zero */
32-
PyFrameState f_state; /* What state the frame is in */
3313
char f_trace_lines; /* Emit per-line trace events? */
3414
char f_trace_opcodes; /* Emit per-opcode trace events? */
3515
char f_own_locals_memory; /* This frame owns the memory for the locals */
36-
PyObject **f_localsptr; /* Pointer to locals, cells, free */
3716
};
3817

39-
static inline int _PyFrame_IsRunnable(struct _frame *f) {
40-
return f->f_state < FRAME_EXECUTING;
41-
}
42-
43-
static inline int _PyFrame_IsExecuting(struct _frame *f) {
44-
return f->f_state == FRAME_EXECUTING;
45-
}
46-
47-
static inline int _PyFrameHasCompleted(struct _frame *f) {
48-
return f->f_state > FRAME_EXECUTING;
49-
}
50-
5118
/* Standard object interface */
5219

5320
PyAPI_DATA(PyTypeObject) PyFrame_Type;
@@ -59,7 +26,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,
5926

6027
/* only internal use */
6128
PyFrameObject*
62-
_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *, PyObject **);
29+
_PyFrame_New_NoTrack(struct _interpreter_frame *, int);
6330

6431

6532
/* The rest of the interface is specific for frame objects */

Include/cpython/pystate.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct _ts {
7878
PyInterpreterState *interp;
7979

8080
/* Borrowed reference to the current frame (it can be NULL) */
81-
PyFrameObject *frame;
81+
struct _interpreter_frame *frame;
8282
int recursion_depth;
8383
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
8484
int stackcheck_counter;
@@ -223,7 +223,7 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
223223

224224
/* Frame evaluation API */
225225

226-
typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, PyFrameObject *, int);
226+
typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _interpreter_frame *, int);
227227

228228
PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
229229
PyInterpreterState *interp);

Include/genobject.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ extern "C" {
1616
#define _PyGenObject_HEAD(prefix) \
1717
PyObject_HEAD \
1818
/* Note: gi_frame can be NULL if the generator is "finished" */ \
19-
PyFrameObject *prefix##_frame; \
19+
struct _interpreter_frame *prefix##_xframe; \
2020
/* The code object backing the generator */ \
21-
PyCodeObject *prefix##_code; \
21+
PyCodeObject *prefix##_code; \
2222
/* List of weak reference. */ \
2323
PyObject *prefix##_weakreflist; \
2424
/* Name of the generator. */ \

Include/internal/pycore_ceval.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ extern PyObject *_PyEval_BuiltinsFromGlobals(
4141

4242

4343
static inline PyObject*
44-
_PyEval_EvalFrame(PyThreadState *tstate, PyFrameObject *f, int throwflag)
44+
_PyEval_EvalFrame(PyThreadState *tstate, struct _interpreter_frame *frame, int throwflag)
4545
{
46-
return tstate->interp->eval_frame(tstate, f, throwflag);
46+
return tstate->interp->eval_frame(tstate, frame, throwflag);
4747
}
4848

4949
extern PyObject *
@@ -107,6 +107,9 @@ static inline void _Py_LeaveRecursiveCall_inline(void) {
107107

108108
#define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_inline()
109109

110+
struct _interpreter_frame *_PyEval_GetFrame(void);
111+
112+
PyObject *_Py_MakeCoro(PyFrameConstructor *, struct _interpreter_frame *);
110113

111114
#ifdef __cplusplus
112115
}

Include/internal/pycore_frame.h

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,123 @@
44
extern "C" {
55
#endif
66

7-
enum {
8-
FRAME_SPECIALS_GLOBALS_OFFSET = 0,
9-
FRAME_SPECIALS_BUILTINS_OFFSET = 1,
10-
FRAME_SPECIALS_LOCALS_OFFSET = 2,
11-
FRAME_SPECIALS_CODE_OFFSET = 3,
12-
FRAME_SPECIALS_SIZE = 4
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
1318
};
1419

15-
static inline PyObject **
16-
_PyFrame_Specials(PyFrameObject *f) {
17-
return &f->f_valuestack[-FRAME_SPECIALS_SIZE];
20+
typedef signed char PyFrameState;
21+
22+
typedef struct _interpreter_frame {
23+
PyObject *f_globals;
24+
PyObject *f_builtins;
25+
PyObject *f_locals;
26+
PyCodeObject *f_code;
27+
PyFrameObject *frame_obj;
28+
/* Borrowed reference to a generator, or NULL */
29+
PyObject *generator;
30+
struct _interpreter_frame *previous;
31+
int f_lasti; /* Last instruction if called */
32+
int stackdepth; /* Depth of value stack */
33+
int nlocalsplus;
34+
PyFrameState f_state; /* What state the frame is in */
35+
PyObject *stack[1];
36+
} InterpreterFrame;
37+
38+
static inline int _PyFrame_IsRunnable(InterpreterFrame *f) {
39+
return f->f_state < FRAME_EXECUTING;
40+
}
41+
42+
static inline int _PyFrame_IsExecuting(InterpreterFrame *f) {
43+
return f->f_state == FRAME_EXECUTING;
1844
}
1945

20-
/* Returns a *borrowed* reference. */
21-
static inline PyObject *
22-
_PyFrame_GetGlobals(PyFrameObject *f)
46+
static inline int _PyFrameHasCompleted(InterpreterFrame *f) {
47+
return f->f_state > FRAME_EXECUTING;
48+
}
49+
50+
#define FRAME_SPECIALS_SIZE ((sizeof(InterpreterFrame)-1)/sizeof(PyObject *))
51+
52+
InterpreterFrame *
53+
_PyInterpreterFrame_HeapAlloc(PyFrameConstructor *con, PyObject *locals);
54+
55+
static inline void
56+
_PyFrame_InitializeSpecials(
57+
InterpreterFrame *frame, PyFrameConstructor *con,
58+
PyObject *locals, int nlocalsplus)
2359
{
24-
return _PyFrame_Specials(f)[FRAME_SPECIALS_GLOBALS_OFFSET];
60+
frame->f_code = (PyCodeObject *)Py_NewRef(con->fc_code);
61+
frame->f_builtins = Py_NewRef(con->fc_builtins);
62+
frame->f_globals = Py_NewRef(con->fc_globals);
63+
frame->f_locals = Py_XNewRef(locals);
64+
frame->nlocalsplus = nlocalsplus;
65+
frame->stackdepth = 0;
66+
frame->frame_obj = NULL;
67+
frame->generator = NULL;
68+
frame->f_lasti = -1;
69+
frame->f_state = FRAME_CREATED;
2570
}
2671

27-
/* Returns a *borrowed* reference. */
28-
static inline PyObject *
29-
_PyFrame_GetBuiltins(PyFrameObject *f)
72+
/* Gets the pointer to the locals array
73+
* that precedes this frame.
74+
*/
75+
static inline PyObject**
76+
_PyFrame_GetLocalsArray(InterpreterFrame *frame)
3077
{
31-
return _PyFrame_Specials(f)[FRAME_SPECIALS_BUILTINS_OFFSET];
78+
return ((PyObject **)frame) - frame->nlocalsplus;
3279
}
3380

34-
/* Returns a *borrowed* reference. */
35-
static inline PyCodeObject *
36-
_PyFrame_GetCode(PyFrameObject *f)
81+
/* For use by _PyFrame_GetFrameObject
82+
Do not call directly. */
83+
PyFrameObject *
84+
_PyFrame_MakeAndSetFrameObject(InterpreterFrame *frame);
85+
86+
/* Gets the PyFrameObject for this frame, lazily
87+
* creating it if necessary.
88+
* Returns a borrowed referennce */
89+
static inline PyFrameObject *
90+
_PyFrame_GetFrameObject(InterpreterFrame *frame)
3791
{
38-
return (PyCodeObject *)_PyFrame_Specials(f)[FRAME_SPECIALS_CODE_OFFSET];
92+
PyFrameObject *res = frame->frame_obj;
93+
if (res != NULL) {
94+
return res;
95+
}
96+
return _PyFrame_MakeAndSetFrameObject(frame);
3997
}
4098

41-
int _PyFrame_TakeLocals(PyFrameObject *f);
99+
/* Clears all references in the frame.
100+
* If take is non-zero, then the InterpreterFrame frame
101+
* may be transfered to the frame object it references
102+
* instead of being cleared. Either way
103+
* the caller no longer owns the references
104+
* in the frame.
105+
* take should be set to 1 for heap allocated
106+
* frames like the ones in generators and coroutines.
107+
*/
108+
int
109+
_PyFrame_Clear(InterpreterFrame * frame, int take);
110+
111+
int
112+
_PyFrame_Traverse(InterpreterFrame *frame, visitproc visit, void *arg);
113+
114+
int
115+
_PyFrame_FastToLocalsWithError(InterpreterFrame *frame);
116+
117+
void
118+
_PyFrame_LocalsToFast(InterpreterFrame *frame, int clear);
119+
120+
InterpreterFrame *_PyThreadState_PushFrame(
121+
PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals);
122+
123+
void _PyThreadState_PopFrame(PyThreadState *tstate, InterpreterFrame *frame);
42124

43125
#ifdef __cplusplus
44126
}

Include/internal/pycore_pystate.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,6 @@ PyAPI_FUNC(int) _PyState_AddModule(
147147

148148
PyAPI_FUNC(int) _PyOS_InterruptOccurred(PyThreadState *tstate);
149149

150-
PyObject **_PyThreadState_PushLocals(PyThreadState *, int size);
151-
void _PyThreadState_PopLocals(PyThreadState *, PyObject **);
152-
153150
#ifdef __cplusplus
154151
}
155152
#endif

Lib/test/test_faulthandler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def temporary_filename():
5656
os_helper.unlink(filename)
5757

5858
class FaultHandlerTests(unittest.TestCase):
59+
5960
def get_output(self, code, filename=None, fd=None):
6061
"""
6162
Run the specified code in Python (in a new child process) and read the

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ class C(object): pass
12751275
# frame
12761276
import inspect
12771277
x = inspect.currentframe()
1278-
check(x, size('4P3i4cP'))
1278+
check(x, size('3Pi3c'))
12791279
# function
12801280
def func(): pass
12811281
check(func, size('14Pi'))

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ PYTHON_OBJS= \
350350
Python/context.o \
351351
Python/dynamic_annotations.o \
352352
Python/errors.o \
353+
Python/frame.o \
353354
Python/frozenmain.o \
354355
Python/future.o \
355356
Python/getargs.o \

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy