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.
This commit is contained in:
Mark Shannon 2021-07-26 11:22:16 +01:00 committed by GitHub
parent 0363a4014d
commit ae0a2b7562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1037 additions and 619 deletions

View file

@ -6,6 +6,7 @@
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "frameobject.h"
#include "pycore_frame.h"
#include "structmember.h" // PyMemberDef
#include "opcode.h"
@ -31,10 +32,17 @@ exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg)
static int
gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
{
Py_VISIT((PyObject *)gen->gi_frame);
Py_VISIT(gen->gi_code);
Py_VISIT(gen->gi_name);
Py_VISIT(gen->gi_qualname);
InterpreterFrame *frame = gen->gi_xframe;
if (frame != NULL) {
assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
int err = _PyFrame_Traverse(frame, visit, arg);
if (err) {
return err;
}
}
/* No need to visit cr_origin, because it's just tuples/str/int, so can't
participate in a reference cycle. */
return exc_state_traverse(&gen->gi_exc_state, visit, arg);
@ -47,7 +55,7 @@ _PyGen_Finalize(PyObject *self)
PyObject *res = NULL;
PyObject *error_type, *error_value, *error_traceback;
if (gen->gi_frame == NULL || _PyFrameHasCompleted(gen->gi_frame)) {
if (gen->gi_xframe == NULL || _PyFrameHasCompleted(gen->gi_xframe)) {
/* Generator isn't paused, so no need to close */
return;
}
@ -79,7 +87,7 @@ _PyGen_Finalize(PyObject *self)
issue a RuntimeWarning. */
if (gen->gi_code != NULL &&
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
gen->gi_frame->f_lasti == -1)
gen->gi_xframe->f_lasti == -1)
{
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
}
@ -122,9 +130,11 @@ gen_dealloc(PyGenObject *gen)
and GC_Del. */
Py_CLEAR(((PyAsyncGenObject*)gen)->ag_finalizer);
}
if (gen->gi_frame != NULL) {
gen->gi_frame->f_gen = NULL;
Py_CLEAR(gen->gi_frame);
InterpreterFrame *frame = gen->gi_xframe;
if (frame != NULL) {
gen->gi_xframe = NULL;
frame->previous = NULL;
_PyFrame_Clear(frame, 1);
}
if (((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE) {
Py_CLEAR(((PyCoroObject *)gen)->cr_origin);
@ -141,11 +151,11 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
int exc, int closing)
{
PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *f = gen->gi_frame;
InterpreterFrame *frame = gen->gi_xframe;
PyObject *result;
*presult = NULL;
if (f != NULL && _PyFrame_IsExecuting(f)) {
if (frame != NULL && _PyFrame_IsExecuting(frame)) {
const char *msg = "generator already executing";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine already executing";
@ -156,7 +166,7 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
PyErr_SetString(PyExc_ValueError, msg);
return PYGEN_ERROR;
}
if (f == NULL || _PyFrameHasCompleted(f)) {
if (frame == NULL || _PyFrameHasCompleted(frame)) {
if (PyCoro_CheckExact(gen) && !closing) {
/* `gen` is an exhausted coroutine: raise an error,
except when called from gen_close(), which should
@ -175,19 +185,15 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
return PYGEN_ERROR;
}
assert(_PyFrame_IsRunnable(f));
assert(f->f_lasti >= 0 || ((unsigned char *)PyBytes_AS_STRING(gen->gi_code->co_code))[0] == GEN_START);
assert(_PyFrame_IsRunnable(frame));
assert(frame->f_lasti >= 0 || ((unsigned char *)PyBytes_AS_STRING(gen->gi_code->co_code))[0] == GEN_START);
/* Push arg onto the frame's value stack */
result = arg ? arg : Py_None;
Py_INCREF(result);
gen->gi_frame->f_valuestack[gen->gi_frame->f_stackdepth] = result;
gen->gi_frame->f_stackdepth++;
frame->stack[frame->stackdepth] = result;
frame->stackdepth++;
/* Generators always return to their most recent caller, not
* necessarily their creator. */
Py_XINCREF(tstate->frame);
assert(f->f_back == NULL);
f->f_back = tstate->frame;
frame->previous = tstate->frame;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
@ -197,20 +203,20 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
_PyErr_ChainStackItem(NULL);
}
result = _PyEval_EvalFrame(tstate, f, exc);
result = _PyEval_EvalFrame(tstate, frame, exc);
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
/* Don't keep the reference to f_back any longer than necessary. It
assert(tstate->frame == frame->previous);
/* Don't keep the reference to previous any longer than necessary. It
* may keep a chain of frames alive or it could create a reference
* cycle. */
assert(f->f_back == tstate->frame);
Py_CLEAR(f->f_back);
frame->previous = NULL;
/* If the generator just returned (as opposed to yielding), signal
* that the generator is exhausted. */
if (result) {
if (!_PyFrameHasCompleted(f)) {
if (!_PyFrameHasCompleted(frame)) {
*presult = result;
return PYGEN_NEXT;
}
@ -245,10 +251,10 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
/* generator can't be rerun, so release the frame */
/* first clean reference cycle through stored exception traceback */
_PyErr_ClearExcState(&gen->gi_exc_state);
gen->gi_frame->f_gen = NULL;
gen->gi_frame = NULL;
Py_DECREF(f);
frame->generator = NULL;
gen->gi_xframe = NULL;
_PyFrame_Clear(frame, 1);
*presult = result;
return result ? PYGEN_RETURN : PYGEN_ERROR;
}
@ -328,13 +334,13 @@ PyObject *
_PyGen_yf(PyGenObject *gen)
{
PyObject *yf = NULL;
PyFrameObject *f = gen->gi_frame;
if (f) {
if (gen->gi_xframe) {
InterpreterFrame *frame = gen->gi_xframe;
PyObject *bytecode = gen->gi_code->co_code;
unsigned char *code = (unsigned char *)PyBytes_AS_STRING(bytecode);
if (f->f_lasti < 0) {
if (frame->f_lasti < 0) {
/* Return immediately if the frame didn't start yet. YIELD_FROM
always come after LOAD_CONST: a code object should not start
with YIELD_FROM */
@ -342,10 +348,10 @@ _PyGen_yf(PyGenObject *gen)
return NULL;
}
if (code[(f->f_lasti+1)*sizeof(_Py_CODEUNIT)] != YIELD_FROM)
if (code[(frame->f_lasti+1)*sizeof(_Py_CODEUNIT)] != YIELD_FROM)
return NULL;
assert(f->f_stackdepth > 0);
yf = f->f_valuestack[f->f_stackdepth-1];
assert(frame->stackdepth > 0);
yf = frame->stack[frame->stackdepth-1];
Py_INCREF(yf);
}
@ -360,10 +366,10 @@ gen_close(PyGenObject *gen, PyObject *args)
int err = 0;
if (yf) {
PyFrameState state = gen->gi_frame->f_state;
gen->gi_frame->f_state = FRAME_EXECUTING;
PyFrameState state = gen->gi_xframe->f_state;
gen->gi_xframe->f_state = FRAME_EXECUTING;
err = gen_close_iter(yf);
gen->gi_frame->f_state = state;
gen->gi_xframe->f_state = state;
Py_DECREF(yf);
}
if (err == 0)
@ -410,10 +416,10 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
We have to allow some awaits to work it through, hence the
`close_on_genexit` parameter here.
*/
PyFrameState state = gen->gi_frame->f_state;
gen->gi_frame->f_state = FRAME_EXECUTING;
PyFrameState state = gen->gi_xframe->f_state;
gen->gi_xframe->f_state = FRAME_EXECUTING;
err = gen_close_iter(yf);
gen->gi_frame->f_state = state;
gen->gi_xframe->f_state = state;
Py_DECREF(yf);
if (err < 0)
return gen_send_ex(gen, Py_None, 1, 0);
@ -422,22 +428,26 @@ _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->frame;
InterpreterFrame *frame = gen->gi_xframe;
/* 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->frame = gen->gi_frame;
InterpreterFrame *prev = tstate->frame;
frame->previous = prev;
tstate->frame = frame;
/* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */
PyFrameState state = gen->gi_frame->f_state;
gen->gi_frame->f_state = FRAME_EXECUTING;
PyFrameState state = gen->gi_xframe->f_state;
gen->gi_xframe->f_state = FRAME_EXECUTING;
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb);
gen->gi_frame->f_state = state;
tstate->frame = f;
gen->gi_xframe->f_state = state;
tstate->frame = prev;
frame->previous = NULL;
} else {
/* `yf` is an iterator or a coroutine-like object. */
PyObject *meth;
@ -449,24 +459,24 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
Py_DECREF(yf);
goto throw_here;
}
PyFrameState state = gen->gi_frame->f_state;
gen->gi_frame->f_state = FRAME_EXECUTING;
PyFrameState state = gen->gi_xframe->f_state;
gen->gi_xframe->f_state = FRAME_EXECUTING;
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
gen->gi_frame->f_state = state;
gen->gi_xframe->f_state = state;
Py_DECREF(meth);
}
Py_DECREF(yf);
if (!ret) {
PyObject *val;
/* Pop subiterator from stack */
assert(gen->gi_frame->f_stackdepth > 0);
gen->gi_frame->f_stackdepth--;
ret = gen->gi_frame->f_valuestack[gen->gi_frame->f_stackdepth];
assert(gen->gi_xframe->stackdepth > 0);
gen->gi_xframe->stackdepth--;
ret = gen->gi_xframe->stack[gen->gi_xframe->stackdepth];
assert(ret == yf);
Py_DECREF(ret);
/* Termination repetition of YIELD_FROM */
assert(gen->gi_frame->f_lasti >= 0);
gen->gi_frame->f_lasti += 1;
assert(gen->gi_xframe->f_lasti >= 0);
gen->gi_xframe->f_lasti += 1;
if (_PyGen_FetchStopIterationValue(&val) == 0) {
ret = gen_send(gen, val);
Py_DECREF(val);
@ -723,10 +733,28 @@ gen_getyieldfrom(PyGenObject *gen, void *Py_UNUSED(ignored))
static PyObject *
gen_getrunning(PyGenObject *gen, void *Py_UNUSED(ignored))
{
if (gen->gi_frame == NULL) {
if (gen->gi_xframe == NULL) {
Py_RETURN_FALSE;
}
return PyBool_FromLong(_PyFrame_IsExecuting(gen->gi_frame));
return PyBool_FromLong(_PyFrame_IsExecuting(gen->gi_xframe));
}
static PyObject *
_gen_getframe(PyGenObject *gen, const char *const name)
{
if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) {
return NULL;
}
if (gen->gi_xframe == NULL) {
Py_RETURN_NONE;
}
return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject(gen->gi_xframe));
}
static PyObject *
gen_getframe(PyGenObject *gen, void *Py_UNUSED(ignored))
{
return _gen_getframe(gen, "gi_frame");
}
static PyGetSetDef gen_getsetlist[] = {
@ -737,11 +765,11 @@ static PyGetSetDef gen_getsetlist[] = {
{"gi_yieldfrom", (getter)gen_getyieldfrom, NULL,
PyDoc_STR("object being iterated by yield from, or None")},
{"gi_running", (getter)gen_getrunning, NULL, NULL},
{"gi_frame", (getter)gen_getframe, NULL, NULL},
{NULL} /* Sentinel */
};
static PyMemberDef gen_memberlist[] = {
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY|PY_AUDIT_READ},
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY|PY_AUDIT_READ},
{NULL} /* Sentinel */
};
@ -814,6 +842,84 @@ PyTypeObject PyGen_Type = {
_PyGen_Finalize, /* tp_finalize */
};
static PyObject *
make_gen(PyTypeObject *type, PyFrameConstructor *con, InterpreterFrame *frame)
{
PyGenObject *gen = PyObject_GC_New(PyGenObject, type);
if (gen == NULL) {
assert(frame->frame_obj == NULL);
_PyFrame_Clear(frame, 1);
return NULL;
}
gen->gi_xframe = frame;
frame->generator = (PyObject *)gen;
gen->gi_code = frame->f_code;
Py_INCREF(gen->gi_code);
gen->gi_weakreflist = NULL;
gen->gi_exc_state.exc_type = NULL;
gen->gi_exc_state.exc_value = NULL;
gen->gi_exc_state.exc_traceback = NULL;
gen->gi_exc_state.previous_item = NULL;
if (con->fc_name != NULL)
gen->gi_name = con->fc_name;
else
gen->gi_name = gen->gi_code->co_name;
Py_INCREF(gen->gi_name);
if (con->fc_qualname != NULL)
gen->gi_qualname = con->fc_qualname;
else
gen->gi_qualname = gen->gi_name;
Py_INCREF(gen->gi_qualname);
_PyObject_GC_TRACK(gen);
return (PyObject *)gen;
}
static PyObject *
compute_cr_origin(int origin_depth);
PyObject *
_Py_MakeCoro(PyFrameConstructor *con, InterpreterFrame *frame)
{
int coro_flags = ((PyCodeObject *)con->fc_code)->co_flags &
(CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR);
assert(coro_flags);
if (coro_flags == CO_GENERATOR) {
return make_gen(&PyGen_Type, con, frame);
}
if (coro_flags == CO_ASYNC_GENERATOR) {
PyAsyncGenObject *o;
o = (PyAsyncGenObject *)make_gen(&PyAsyncGen_Type, con, frame);
if (o == NULL) {
return NULL;
}
o->ag_finalizer = NULL;
o->ag_closed = 0;
o->ag_hooks_inited = 0;
o->ag_running_async = 0;
return (PyObject*)o;
}
assert (coro_flags == CO_COROUTINE);
PyObject *coro = make_gen(&PyCoro_Type, con, frame);
if (!coro) {
return NULL;
}
PyThreadState *tstate = _PyThreadState_GET();
int origin_depth = tstate->coroutine_origin_tracking_depth;
if (origin_depth == 0) {
((PyCoroObject *)coro)->cr_origin = NULL;
} else {
PyObject *cr_origin = compute_cr_origin(origin_depth);
((PyCoroObject *)coro)->cr_origin = cr_origin;
if (!cr_origin) {
Py_DECREF(coro);
return NULL;
}
}
return coro;
}
static PyObject *
gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
PyObject *name, PyObject *qualname)
@ -823,8 +929,16 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
Py_DECREF(f);
return NULL;
}
gen->gi_frame = f;
f->f_gen = (PyObject *) gen;
/* Take ownership of the frame */
assert(f->f_frame->frame_obj == NULL);
assert(f->f_own_locals_memory);
gen->gi_xframe = f->f_frame;
gen->gi_xframe->frame_obj = f;
f->f_own_locals_memory = 0;
gen->gi_xframe->generator = (PyObject *) gen;
assert(PyObject_GC_IsTracked((PyObject *)f));
gen->gi_code = PyFrame_GetCode(f);
gen->gi_weakreflist = NULL;
gen->gi_exc_state.exc_type = NULL;
@ -958,12 +1072,19 @@ coro_get_cr_await(PyCoroObject *coro, void *Py_UNUSED(ignored))
static PyObject *
cr_getrunning(PyCoroObject *coro, void *Py_UNUSED(ignored))
{
if (coro->cr_frame == NULL) {
if (coro->cr_xframe == NULL) {
Py_RETURN_FALSE;
}
return PyBool_FromLong(_PyFrame_IsExecuting(coro->cr_frame));
return PyBool_FromLong(_PyFrame_IsExecuting(coro->cr_xframe));
}
static PyObject *
cr_getframe(PyCoroObject *coro, void *Py_UNUSED(ignored))
{
return _gen_getframe((PyGenObject *)coro, "cr_frame");
}
static PyGetSetDef coro_getsetlist[] = {
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
PyDoc_STR("name of the coroutine")},
@ -972,11 +1093,11 @@ static PyGetSetDef coro_getsetlist[] = {
{"cr_await", (getter)coro_get_cr_await, NULL,
PyDoc_STR("object being awaited on, or None")},
{"cr_running", (getter)cr_getrunning, NULL, NULL},
{"cr_frame", (getter)cr_getframe, NULL, NULL},
{NULL} /* Sentinel */
};
static PyMemberDef coro_memberlist[] = {
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY|PY_AUDIT_READ},
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY|PY_AUDIT_READ},
{"cr_origin", T_OBJECT, offsetof(PyCoroObject, cr_origin), READONLY},
{NULL} /* Sentinel */
@ -1150,11 +1271,11 @@ PyTypeObject _PyCoroWrapper_Type = {
static PyObject *
compute_cr_origin(int origin_depth)
{
PyFrameObject *frame = PyEval_GetFrame();
InterpreterFrame *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 */
@ -1162,20 +1283,19 @@ compute_cr_origin(int origin_depth)
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->f_code;
PyObject *frameinfo = Py_BuildValue("OiO",
code->co_filename,
PyFrame_GetLineNumber(frame),
PyCode_Addr2Line(frame->f_code, frame->f_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;
@ -1346,6 +1466,11 @@ async_gen_athrow(PyAsyncGenObject *o, PyObject *args)
return async_gen_athrow_new(o, args);
}
static PyObject *
ag_getframe(PyAsyncGenObject *ag, void *Py_UNUSED(ignored))
{
return _gen_getframe((PyGenObject *)ag, "ag_frame");
}
static PyGetSetDef async_gen_getsetlist[] = {
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
@ -1354,11 +1479,11 @@ static PyGetSetDef async_gen_getsetlist[] = {
PyDoc_STR("qualified name of the async generator")},
{"ag_await", (getter)coro_get_cr_await, NULL,
PyDoc_STR("object being awaited on, or None")},
{"ag_frame", (getter)ag_getframe, NULL, NULL},
{NULL} /* Sentinel */
};
static PyMemberDef async_gen_memberlist[] = {
{"ag_frame", T_OBJECT, offsetof(PyAsyncGenObject, ag_frame), READONLY|PY_AUDIT_READ},
{"ag_running", T_BOOL, offsetof(PyAsyncGenObject, ag_running_async),
READONLY},
{"ag_code", T_OBJECT, offsetof(PyAsyncGenObject, ag_code), READONLY|PY_AUDIT_READ},
@ -1865,7 +1990,7 @@ static PyObject *
async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
{
PyGenObject *gen = (PyGenObject*)o->agt_gen;
PyFrameObject *f = gen->gi_frame;
InterpreterFrame *frame = gen->gi_xframe;
PyObject *retval;
if (o->agt_state == AWAITABLE_STATE_CLOSED) {
@ -1875,7 +2000,7 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
return NULL;
}
if (f == NULL || _PyFrameHasCompleted(f)) {
if (frame == NULL || _PyFrameHasCompleted(frame)) {
o->agt_state = AWAITABLE_STATE_CLOSED;
PyErr_SetNone(PyExc_StopIteration);
return NULL;