8000 bpo-44590: Lazily allocate frame objects by markshannon · Pull Request #27077 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
79aeaf6
Turn specials array into struct and add 'lasti' to it.
markshannon Jun 21, 2021
3b6a4e8
Move stack-depth from frame object to specials struct.
markshannon Jun 22, 2021
6f0ba40
Rename 'specials' to 'frame' as it now includes most of the data for …
markshannon Jun 22, 2021
8543c27
Refactor, pushing PyFrame upward toward _PyEval_Vector.
markshannon Jun 22, 2021
423d403
Add pointer from stack frame to frame object and rename tstate.frame …
markshannon Jun 22, 2021
7bf6c30
More refactoring. Add tstate.frame for the top stack frame.
markshannon Jun 23, 2021
861a8d9
Convert use of PyFrameObject to _PyFrame.
markshannon Jun 23, 2021
35e793c
Replace most remaining uses frame object in the interpreter with stac…
markshannon Jun 24, 2021
192094e
Convert more uses of frameobject to frame.
markshannon Jun 24, 2021
3f601a7
Move f_state from frame object to frame.
markshannon Jun 29, 2021
bd95c32
Compute f_back when on thread stack, only filling in value when frame…
markshannon Jun 30, 2021
9961abc
Add NULL check
markshannon Jun 30, 2021
32af707
Get lazy f_back working (it still leaks).
markshannon Jul 2, 2021
ac7dbe8
Use frames not frameobjects in sys._getframe()
markshannon Jul 2, 2021
f33d291
NULL out frame->previous when leaving frame.
markshannon Jul 2, 2021
5c23a36
Frames now include nlocalspuls, so they have valid layout after code …
markshannon Jul 5, 2021
910e991
Move ownership of frame in generator from frame object ot generator o…
markshannon Jul 5, 2021
22e1c9b
Remove localsptr field from frame object.
markshannon Jul 6, 2021
f84a3f0
Add new _PyEval_EvalNoFrame function for evaluating frames directly.
markshannon Jul 6, 2021
1180a44
Allow for lazily created frames.
markshannon Jul 6, 2021
c76de89
Do not create frame objects for Python calls.
markshannon Jul 6, 2021
15aeef1
Don't create frame objects for generators.
markshannon Jul 6, 2021
1d2e1ce
Fix memory leak
markshannon Jul 7, 2021
1b19f8b
Merge branch 'main' into lazy-frame-updated
markshannon Jul 7, 2021
d619bae
Restore support for PEP 523.
markshannon Jul 8, 2021
d147d03
Streamline pushing and popping stack frames a bit.
markshannon Jul 9, 2021
25c6a71
Merge branch 'main' into lazy-frame
markshannon Jul 9, 2021
618b094
Add f_ prefix back to several frame fields to ease porting C code tha…
markshannon Jul 9, 2021
e5da338
Add NEWS
markshannon Jul 9, 2021
dda0b0c
Remove debugging artifact.
markshannon Jul 9, 2021
5ecc067
Make symbol private
markshannon Jul 9, 2021
3b65a0f
Fix use-after-free error.
markshannon Jul 9, 2021
596213d
Add some explanatory comments.
markshannon Jul 9, 2021
386275e
Remove debugging artifact.
markshannon Jul 9, 2021
596c041
Merge branch 'main' into lazy-frame
markshannon Jul 15, 2021
039bca7
Rename _PyFrame to InterpreterFrame.
markshannon Jul 15, 2021
2cad33b
Remove use-after-free in assert.
markshannon Jul 19, 2021
77cf187
Merge branch 'main' into lazy-frame
markshannon Jul 19, 2021
decf209
Make name of frame argument consistent across _PyEval_Vector, _PyEval…
markshannon Jul 19, 2021
666b618
Allow for old gdbs still using Python 2.
markshannon Jul 19, 2021
90ed5b6
Various small clarifications as suggested by Pablo.
markshannon Jul 21, 2021
593a348
Refactor interpreter frame code into its own file. Improve a few names.
markshannon Jul 21, 2021
b775f13
Tidy up assert.
markshannon Jul 21, 2021
e8476b2
Fix warning on Windows.
markshannon Jul 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Compute f_back when on thread stack, only filling in value when frame…
… object 
8000
outlives stack invocation.
  • Loading branch information
markshannon committed Jun 30, 2021
commit bd95c3297b5017ef3ee2d7072a0588af05de8a16
1 change: 0 additions & 1 deletion Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ struct _ts {
PyInterpreterState *interp;

/* Borrowed reference to the current frame (it can be NULL) */
PyFrameObject *pyframe;
struct _py_frame *frame;
int recursion_depth;
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
Expand Down
1 change: 1 addition & 0 deletions
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ static inline void _Py_LeaveRecursiveCall_inline(void) {

#define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_inline()

struct _py_frame *_PyEval_GetFrame(void);

#ifdef __cplusplus
}
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ _PyFrame_GetFrameObject(_PyFrame *frame)
}

int
_PyFrame_FastToLocalsWithError(_PyFrame *frame, int cleared);
_PyFrame_FastToLocalsWithError(_PyFrame *frame);

void
_PyFrame_LocalsToFast(_PyFrame *frame, int clear);
Expand Down
11 changes: 4 additions & 7 deletions Modules/signalmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "pycore_atomic.h" // _Py_atomic_int
#include "pycore_call.h" // _PyObject_Call()
#include "pycore_ceval.h" // _PyEval_SignalReceived()
#include "pycore_frame.h"
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_pyerrors.h" // _PyErr_SetString()
#include "pycore_pylifecycle.h" // NSIG
Expand Down Expand Up @@ -1786,11 +1787,7 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
*/
_Py_atomic_store(&is_tripped, 0);

PyObject *frame = (PyObject *)tstate->pyframe;
if (!frame) {
frame = Py_None;
}

_PyFrame *frame = tstate->frame;
signal_state_t *state = &signal_global_state;
for (int i = 1; i < NSIG; i++) {
if (!_Py_atomic_load_relaxed(&Handlers[i].tripped)) {
Expand Down Expand Up @@ -1821,8 +1818,8 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
PyErr_WriteUnraisable(Py_None);
continue;
}

PyObject *arglist = Py_BuildValue("(iO)", i, frame);
PyObject * f = frame == NULL ? Py_None : (PyObject *)_PyFrame_GetFrameObject(frame);
PyObject *arglist = Py_BuildValue("(iO)", i, f);
PyObject *result;
if (arglist) {
result = _PyObject_Call(tstate, func, arglist, NULL);
Expand Down
25 changes: 20 additions & 5 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#define OFF(x) offsetof(PyFrameObject, x)

static PyMemberDef frame_memberlist[] = {
{"f_back", T_OBJECT, OFF(f_back), READONLY},
{"f_trace_lines", T_BOOL, OFF(f_trace_lines), 0},
{"f_trace_opcodes", T_BOOL, OFF(f_trace_opcodes), 0},
{NULL} /* Sentinel */
Expand Down Expand Up @@ -102,6 +101,15 @@ frame_getcode(PyFrameObject *f, void *closure)
return (PyObject *)PyFrame_GetCode(f);
}

static PyObject *
frame_getback(PyFrameObject *f, void *closure)
{
PyObject *res = (PyObject *)PyFrame_GetBack(f);
if (res == NULL) {
Py_RETURN_NONE;
}
}

/* Given the index of the effective opcode,
scan back to construct the oparg with EXTENDED_ARG */
static unsigned int
Expand Down Expand Up @@ -579,6 +587,7 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)


static PyGetSetDef frame_getsetlist[] = {
{"f_back", (getter)frame_getback, NULL, NULL},
{"f_locals", (getter)frame_getlocals, NULL, NULL},
{"f_lineno", (getter)frame_getlineno,
(setter)frame_setlineno, NULL},
Expand Down Expand Up @@ -847,6 +856,9 @@ _PyFrame_TakeLocals(PyFrameObject *f)
assert(f->f_frame->stackdepth == 0);
Py_ssize_t size = ((char*)f->f_frame->stack)-((char *)f->f_localsptr);
PyObject **copy = PyMem_Malloc(size);
if (f->f_frame->previous != NULL) {
f->f_back = (PyFrameObject *)Py_NewRef(f->f_frame->previous->frame_obj);
}
if (copy == NULL) {
for (int i = 0; i < f->f_frame->code->co_nlocalsplus; i++) {
PyObject *o = f->f_localsptr[i];
Expand All @@ -873,7 +885,7 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, _PyFrame *frame, int owns)
if (f == NULL) {
return NULL;
}
f->f_back = (PyFrameObject*)Py_XNewRef(tstate->pyframe);
f->f_back = NULL;
f->f_trace = NULL;
f->f_frame->stackdepth = 0;
f->f_trace_lines = 1;
Expand Down Expand Up @@ -928,7 +940,7 @@ _PyFrame_OpAlreadyRan(_PyFrame *frame, int opcode, int oparg)
}

int
_PyFrame_FastToLocalsWithError(_PyFrame *frame, int cleared) {
_PyFrame_FastToLocalsWithError(_PyFrame *frame) {
/* Merge fast locals into f->f_locals */
PyObject *locals;
PyObject **fast;
Expand Down Expand Up @@ -958,7 +970,7 @@ _PyFrame_FastToLocalsWithError(_PyFrame *frame, int cleared) {

PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
PyObject *value = fast[i];
if (!cleared) {
if (frame->f_state != FRAME_CLEARED) {
if (kind & CO_FAST_FREE) {
// The cell was set when the frame was created from
// the function's closure.
Expand Down Expand Up @@ -1012,7 +1024,7 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
PyErr_BadInternalCall();
return -1;
}
return _PyFrame_FastToLocalsWithError(f->f_frame, f->f_frame->f_state == FRAME_CLEARED);
return _PyFrame_FastToLocalsWithError(f->f_frame);
}

void
Expand Down Expand Up @@ -1153,6 +1165,9 @@ PyFrame_GetBack(PyFrameObject *frame)
{
assert(frame != NULL);
PyFrameObject *back = frame->f_back;
if (back == NULL && frame->f_frame->previous != NULL) {
back = frame->f_frame->previous->frame_obj;
}
Py_XINCREF(back);
return back;
}
Expand Down
26 changes: 13 additions & 13 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,8 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,

/* Generators always return to their most recent caller, not
* necessarily their creator. */
Py_XINCREF(tstate->pyframe);
assert(f->f_back == NULL);
f->f_back = tstate->pyframe;
f->f_frame->previous = tstate->frame;

gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
Expand All @@ -202,11 +201,12 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;

tstate->frame = f->f_frame->previous;
/* Don't keep the reference to f_back any longer than necessary. It
* may keep a chain of frames alive or it could create a reference
* cycle. */
assert(f->f_back == tstate->pyframe);
Py_CLEAR(f->f_back);
f->f_frame->previous = NULL;
assert(f->f_back == NULL);

/* If the generator just returned (as opposed to yielding), signal
* that the generator is exhausted. */
Expand Down Expand Up @@ -423,22 +423,22 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
/* `yf` is a generator or a coroutine. */
PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *f = tstate->pyframe;
_PyFrame *f = tstate->frame;

/* Since we are fast-tracking things by skipping the eval loop,
we need to update the current frame so the stack trace
will be reported correctly to the user. */
/* XXX We should probably be updating the current frame
somewhere in ceval.c. */
tstate->pyframe = gen->gi_frame;
tstate->frame = gen->gi_frame->f_frame;
/* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */
PyFrameState state = gen->gi_frame->f_frame->f_state;
gen->gi_frame->f_frame->f_state = FRAME_EXECUTING;
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb);
gen->gi_frame->f_frame->f_state = state;
tstate->pyframe = f;
tstate->frame = f;
} else {
/* `yf` is an iterator or a coroutine-like object. */
PyObject *meth;
Expand Down Expand Up @@ -1153,32 +1153,32 @@ PyTypeObject _PyCoroWrapper_Type = {
static PyObject *
compute_cr_origin(int origin_depth)
{
PyFrameObject *frame = PyEval_GetFrame();
_PyFrame *frame = _PyEval_GetFrame();
/* First count how many frames we have */
int frame_count = 0;
for (; frame && frame_count < origin_depth; ++frame_count) {
frame = frame->f_back;
frame = frame->previous;
}

/* Now collect them */
PyObject *cr_origin = PyTuple_New(frame_count);
if (cr_origin == NULL) {
return NULL;
}
frame = PyEval_GetFrame();
frame = _PyEval_GetFrame();
for (int i = 0; i < frame_count; ++i) {
PyCodeObject *code = PyFrame_GetCode(frame);
PyCodeObject *code = frame->code;
PyObject *frameinfo = Py_BuildValue("OiO",
code->co_filename,
PyFrame_GetLineNumber(frame),
PyCode_Addr2Line(frame->code, frame->lasti*2),
code->co_name);
Py_DECREF(code);
if (!frameinfo) {
Py_DECREF(cr_origin);
return NULL;
}
PyTuple_SET_ITEM(cr_origin, i, frameinfo);
frame = frame->f_back;
frame = frame->previous;
}

return cr_origin;
Expand Down
40 changes: 24 additions & 16 deletions Python/ceval.c
< 6D38 /tr>
Original file line number Diff line number Diff line change
Expand Up @@ -1450,8 +1450,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *fo, int throwflag
tstate->cframe = &cframe;

/* push frame */
tstate->pyframe = fo;
frame = fo->f_frame;
tstate->frame = frame;
co = frame->code;

if (cframe.use_tracing) {
Expand Down Expand Up @@ -3507,7 +3507,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *fo, int throwflag
case TARGET(IMPORT_STAR): {
PyObject *from = POP(), *locals;
int err;
if (_PyFrame_FastToLocalsWithError(frame, 0) < 0) {
if (_PyFrame_FastToLocalsWithError(frame) < 0) {
Py_DECREF(from);
goto error;
}
Expand Down Expand Up @@ -4404,7 +4404,7 @@ MISS_WITH_CACHE(LOAD_GLOBAL)
if (PyDTrace_FUNCTION_RETURN_ENABLED())
dtrace_function_return(frame);
_Py_LeaveRecursiveCall(tstate);
tstate->pyframe = fo->f_back;
tstate->frame = frame->previous;
return _Py_CheckFunctionResult(tstate, NULL, retval, __func__);
}

Expand Down Expand Up @@ -4967,9 +4967,7 @@ make_coro(PyThreadState *tstate, PyFrameConstructor *con,
PyObject *gen;
int is_coro = ((PyCodeObject *)con->fc_code)->co_flags & CO_COROUTINE;

/* Don't need to keep the reference to f_back, it will be set
* when the generator is resumed. */
Py_CLEAR(f->f_back);
assert(f->f_back == NULL);

/* Create a new generator that owns the ready to run frame
* and return that as the value. */
Expand Down Expand Up @@ -5676,19 +5674,29 @@ _PyEval_GetAsyncGenFinalizer(void)
return tstate->async_gen_finalizer;
}

_PyFrame *
_PyEval_GetFrame(void)
{
PyThreadState *tstate = _PyThreadState_GET();
return tstate->frame;
}

PyFrameObject *
PyEval_GetFrame(void)
{
PyThreadState *tstate = _PyThreadState_GET();
return tstate->pyframe;
if (tstate->frame == NULL) {
return NULL;
}
return _PyFrame_GetFrameObject(tstate->frame);
}

PyObject *
_PyEval_GetBuiltins(PyThreadState *tstate)
{
PyFrameObject *frame = tstate->pyframe;
_PyFrame *frame = tstate->frame;
if (frame != NULL) {
return frame->f_frame->builtins;
return frame->builtins;
}
return tstate->interp->builtins;
}
Expand Down Expand Up @@ -5719,17 +5727,17 @@ PyObject *
PyEval_GetLocals(void)
{
PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *current_frame = tstate->pyframe;
_PyFrame *current_frame = tstate->frame;
if (current_frame == NULL) {
_PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
return NULL;
}

if (PyFrame_FastToLocalsWithError(current_frame) < 0) {
if (_PyFrame_FastToLocalsWithError(current_frame) < 0) {
return NULL;
}

PyObject *locals = current_frame->f_frame->locals;
PyObject *locals = current_frame->locals;
assert(locals != NULL);
return locals;
}
Expand All @@ -5738,22 +5746,22 @@ PyObject *
PyEval_GetGlobals(void)
{
PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *current_frame = tstate->pyframe;
_PyFrame *current_frame = tstate->frame;
if (current_frame == NULL) {
return NULL;
}
return current_frame->f_frame->globals;
return current_frame->globals;
}

int
PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
{
PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *current_frame = tstate->pyframe;
_PyFrame *current_frame = tstate->frame;
int result = cf->cf_flags != 0;

if (current_frame != NULL) {
const int codeflags = current_frame->f_frame->code->co_flags;
const int codeflags = current_frame->code->co_flags;
const int compilerflags = codeflags & PyCF_MASK;
if (compilerflags) {
result = 1;
Expand Down
2 changes: 1 addition & 1 deletion Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,7 @@ _PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj)
}

if (exc_tb == NULL) {
PyFrameObject *frame = tstate->pyframe;
PyFrameObject *frame = PyThreadState_GetFrame(tstate);
if (frame != NULL) {
exc_tb = _PyTraceBack_FromFrame(NULL, frame);
if (exc_tb == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2003,7 +2003,7 @@ Py_EndInterpreter(PyThreadState *tstate)
if (tstate != _PyThreadState_GET()) {
Py_FatalError("thread is not current");
}
if (tstate->pyframe != NULL) {
if (tstate->frame != NULL) {
Py_FatalError("thread still has a frame");
}
interp->finalizing = 1;
Expand Down
Loading
0