bpo-43693: Add the MAKE_CELL opcode and interleave fast locals offsets. (gh-26396)

This moves logic out of the frame initialization code and into the compiler and eval loop.  Doing so simplifies the runtime code and allows us to optimize it better.

https://bugs.python.org/issue43693
This commit is contained in:
Eric Snow 2021-06-07 16:52:00 -06:00 committed by GitHub
parent e915db3e9e
commit 631f9938b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 4469 additions and 4234 deletions

View file

@ -918,6 +918,19 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
return f;
}
int
_PyFrame_OpAlreadyRan(PyFrameObject *f, int opcode, int oparg)
{
const _Py_CODEUNIT *code =
(const _Py_CODEUNIT *)PyBytes_AS_STRING(f->f_code->co_code);
for (int i = 0; i < f->f_lasti; i++) {
if (_Py_OPCODE(code[i]) == opcode && _Py_OPARG(code[i]) == oparg) {
return 1;
}
}
return 0;
}
int
PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
@ -961,15 +974,52 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
former here and will later use the cell for the variable.
*/
if (kind & CO_FAST_LOCAL && kind & CO_FAST_CELL) {
assert(fast[i] == NULL);
continue;
}
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
PyObject *value = fast[i];
if (kind & (CO_FAST_CELL | CO_FAST_FREE) && value != NULL) {
assert(PyCell_Check(value));
value = PyCell_GET(value);
if (f->f_state != FRAME_CLEARED) {
int cellargoffset = CO_CELL_NOT_AN_ARG;
if (co->co_cell2arg != NULL) {
cellargoffset = co->co_cell2arg[i - co->co_nlocals];
}
if (kind & CO_FAST_FREE) {
// The cell was set by _PyEval_MakeFrameVector() from
// the function's closure.
assert(value != NULL && PyCell_Check(value));
value = PyCell_GET(value);
}
else if (kind & CO_FAST_CELL) {
// Note that no *_DEREF ops can happen before MAKE_CELL
// executes. So there's no need to duplicate the work
// that MAKE_CELL would otherwise do later, if it hasn't
// run yet.
if (value != NULL) {
if (PyCell_Check(value) &&
_PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
// (likely) MAKE_CELL must have executed already.
value = PyCell_GET(value);
}
// (unlikely) Otherwise it must be an initial value set
// by an earlier call to PyFrame_FastToLocals().
}
else {
// (unlikely) MAKE_CELL hasn't executed yet.
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
// It is an arg that escapes into an inner
// function so we use the initial value that
// was already set by _PyEval_MakeFrameVector().
// Normally the arg value would always be set.
// However, it can be NULL if it was deleted via
// PyFrame_LocalsToFast().
value = fast[cellargoffset];
}
}
}
}
else {
assert(value == NULL);
}
if (value == NULL) {
if (PyObject_DelItem(locals, name) != 0) {
@ -1010,8 +1060,9 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
PyObject **fast;
PyObject *error_type, *error_value, *error_traceback;
PyCodeObject *co;
if (f == NULL)
if (f == NULL || f->f_state == FRAME_CLEARED) {
return;
}
locals = _PyFrame_Specials(f)[FRAME_SPECIALS_LOCALS_OFFSET];
if (locals == NULL)
return;
@ -1039,16 +1090,67 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
continue;
}
}
if (kind & (CO_FAST_CELL | CO_FAST_FREE)) {
assert(PyCell_Check(fast[i]));
if (PyCell_GET(fast[i]) != value) {
if (PyCell_Set(fast[i], value) < 0) {
PyErr_Clear();
}
PyObject *oldvalue = fast[i];
int cellargoffset = CO_CELL_NOT_AN_ARG;
if (co->co_cell2arg != NULL) {
cellargoffset = co->co_cell2arg[i - co->co_nlocals];
}
PyObject *cell = NULL;
if (kind == CO_FAST_FREE) {
// The cell was cell by _PyEval_MakeFrameVector() from
// the function's closure.
assert(oldvalue != NULL && PyCell_Check(oldvalue));
cell = oldvalue;
}
else if (kind & CO_FAST_CELL && oldvalue != NULL) {
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
// (likely) MAKE_CELL must have executed already.
// It's the cell for an arg.
assert(PyCell_Check(oldvalue));
cell = oldvalue;
}
else {
if (PyCell_Check(oldvalue) &&
_PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
// (likely) MAKE_CELL must have executed already.
cell = oldvalue;
}
// (unlikely) Otherwise, it must have been set to some
// initial value by an earlier call to PyFrame_LocalsToFast().
}
}
if (cell != NULL) {
oldvalue = PyCell_GET(cell);
if (value != oldvalue) {
Py_XDECREF(oldvalue);
Py_XINCREF(value);
PyCell_SET(cell, value);
}
}
else {
int offset = i;
if (kind & CO_FAST_CELL) {
// (unlikely) MAKE_CELL hasn't executed yet.
// Note that there is no need to create the cell that
// MAKE_CELL would otherwise create later, since no
// *_DEREF ops can happen before MAKE_CELL has run.
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
// It's the cell for an arg.
// Replace the initial value that was set by
// _PyEval_MakeFrameVector().
// Normally the arg value would always be set.
// However, it can be NULL if it was deleted
// via an earlier PyFrame_LocalsToFast() call.
offset = cellargoffset;
oldvalue = fast[offset];
}
// Otherwise set an initial value for MAKE_CELL to use
// when it runs later.
}
if (value != oldvalue) {
Py_XINCREF(value);
Py_XSETREF(fast[offset], value);
}
} else if (fast[i] != value) {
Py_XINCREF(value);
Py_XSETREF(fast[i], value);
}
Py_XDECREF(value);
}

View file

@ -11,6 +11,8 @@
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_unionobject.h" // _Py_Union(), _Py_union_type_or
#include "frameobject.h"
#include "pycore_frame.h" // _PyFrame_OpAlreadyRan
#include "opcode.h" // MAKE_CELL
#include "structmember.h" // PyMemberDef
#include <ctype.h>
@ -8877,14 +8879,17 @@ super_init_without_args(PyFrameObject *f, PyCodeObject *co,
}
PyObject *obj = f->f_localsptr[0];
Py_ssize_t i;
int i;
if (obj == NULL && co->co_cell2arg) {
/* The first argument might be a cell. */
for (i = 0; i < co->co_ncellvars; i++) {
if (co->co_cell2arg[i] == 0) {
PyObject *cell = f->f_localsptr[co->co_nlocals + i];
assert(PyCell_Check(cell));
obj = PyCell_GET(cell);
int celloffset = co->co_nlocals + i;
PyObject *cell = f->f_localsptr[celloffset];
if (PyCell_Check(cell) &&
_PyFrame_OpAlreadyRan(f, MAKE_CELL, celloffset)) {
obj = PyCell_GET(cell);
}
break;
}
}