8000 bpo-42990: Introduce 'frame constructor' struct to simplify API for PyEval_CodeEval and friends by markshannon · Pull Request #24298 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-42990: Introduce 'frame constructor' struct to simplify API for PyEval_CodeEval and friends #24298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
4 changes: 3 additions & 1 deletion Include/cpython/frameobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,

/* only internal use */
PyFrameObject* _PyFrame_New_NoTrack(PyThreadState *, PyCodeObject *,
PyObject *, PyObject *);
PyObject *, PyObject *, PyObject *);


/* The rest of the interface is specific for frame objects */
Expand All @@ -92,3 +92,5 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
PyAPI_FUNC(void) _PyFrame_DebugMallocStats(FILE *out);

PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);

PyObject *_PyEval_BuiltinsFromGlobals(PyObject *globals);
30 changes: 18 additions & 12 deletions Include/funcobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
extern "C" {
#endif


typedef struct {
PyObject *globals;
PyObject *builtins;
PyObject *name;
PyObject *qualname;
PyObject *code; /* A code object, the __code__ attribute */
PyObject *defaults; /* NULL or a tuple */
PyObject *kwdefaults; /* NULL or a dict */
PyObject *closure; /* NULL or a tuple of cell objects */
} PyFrameConstructor;

/* Function objects and code objects should not be confused with each other:
*
* Function objects are created by the execution of the 'def' statement.
Expand All @@ -20,18 +32,12 @@ extern "C" {

typedef struct {
PyObject_HEAD
PyObject *func_code; /* A code object, the __code__ attribute */
PyObject *func_globals; /* A dictionary (other mappings won't do) */
PyObject *func_defaults; /* NULL or a tuple */
PyObject *func_kwdefaults; /* NULL or a dict */
PyObject *func_closure; /* NULL or a tuple of cell objects */
PyFrameConstructor func_descr; /* Frame descriptor fields for this function */
PyObject *func_doc; /* The __doc__ attribute, can be anything */
PyObject *func_name; /* The __name__ attribute, a string object */
PyObject *func_dict; /* The __dict__ attribute, a dict or NULL */
PyObject *func_weakreflist; /* List of weak references */
PyObject *func_module; /* The __module__ attribute, can be anything */
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
vectorcallfunc vectorcall;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to my reading of PEP 387, this change should go through the incompatible changes process.

Copy link
Member Author
@markshannon markshannon Jan 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not according to my reading. The internal layout of the PyFunctionObject is not part of the API.


/* Invariant:
Expand Down Expand Up @@ -70,17 +76,17 @@ PyAPI_FUNC(PyObject *) _PyFunction_Vectorcall(
/* Macros for direct access to these values. Type checks are *not*
done, so use with care. */
#define PyFunction_GET_CODE(func) \
(((PyFunctionObject *)func) -> func_code)
(((PyFunctionObject *)func) -> func_descr.code)
#define PyFunction_GET_GLOBALS(func) \
(((PyFunctionObject *)func) -> func_globals)
(((PyFunctionObject *)func) -> func_descr.globals)
#define PyFunction_GET_MODULE(func) \
(((PyFunctionObject *)func) -> func_module)
#define PyFunction_GET_DEFAULTS(func) \
(((PyFunctionObject *)func) -> func_defaults)
(((PyFunctionObject *)func) -> func_descr.defaults)
#define PyFunction_GET_KW_DEFAULTS(func) \
(((PyFunctionObject *)func) -> func_kwdefaults)
(((PyFunctionObject *)func) -> func_descr.kwdefaults)
#define PyFunction_GET_CLOSURE(func) \
(((PyFunctionObject *)func) -> func_closure)
(((PyFunctionObject *)func) -> func_descr.closure)
#define PyFunction_GET_ANNOTATIONS(func) \
(((PyFunctionObject *)func) -> func_annotations)

Expand Down
7 changes: 2 additions & 5 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,10 @@ _PyEval_EvalFrame(PyThreadState *tstate, PyFrameObject *f, int throwflag)

extern PyObject *_PyEval_EvalCode(
PyThreadState *tstate,
PyObject *_co, PyObject *globals, PyObject *locals,
PyFrameConstructor *desc, PyObject *locals,
PyObject *const *args, Py_ssize_t argcount,
PyObject *const *kwnames, PyObject *const *kwargs,
Py_ssize_t kwcount, int kwstep,
PyObject *const *defs, Py_ssize_t defcount,
PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname);
Py_ssize_t kwcount, int kwstep);

#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
extern int _PyEval_ThreadsInitialized(PyInterpreterState *interp);
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ class C(object): pass
check(x, vsize('4Pi2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
# function
def func(): pass
check(func, size('13P'))
check(func, size('14P'))
class c():
@staticmethod
def foo():
Expand Down
33 changes: 7 additions & 26 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,16 @@ PyCFunction_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
static PyObject* _Py_HOT_FUNCTION
function_code_fastcall(PyThreadState *tstate, PyCodeObject *co,
PyObject *const *args, Py_ssize_t nargs,
PyObject *globals)
PyFunctionObject *func)
{
assert(tstate != NULL);
assert(globals != NULL);
assert(func != NULL);

/* XXX Perhaps we should create a specialized
_PyFrame_New_NoTrack() that doesn't take locals, but does
take builtins without sanity checking them.
*/
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, co, globals, NULL);
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, co, func->func_descr.globals, func->func_descr.builtins, NULL);
if (f == NULL) {
return NULL;
}
Expand Down Expand Up @@ -357,14 +357,13 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,

PyThreadState *tstate = _PyThreadState_GET();
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func);
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);

if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
{
if (argdefs == NULL && co->co_argcount == nargs) {
return function_code_fastcall(tstate, co, stack, nargs, globals);
return function_code_fastcall(tstate, co, stack, nargs, (PyFunctionObject *)func);
}
else if (nargs == 0 && argdefs != NU 2364 LL
&& co->co_argcount == PyTuple_GET_SIZE(argdefs)) {
Expand All @@ -373,34 +372,16 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
stack = _PyTuple_ITEMS(argdefs);
return function_code_fastcall(tstate, co,
stack, PyTuple_GET_SIZE(argdefs),
globals);
(PyFunctionObject *)func);
}
}

PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS(func);
PyObject *closure = PyFunction_GET_CLOSURE(func);
PyObject *name = ((PyFunctionObject *)func) -> func_name;
PyObject *qualname = ((PyFunctionObject *)func) -> func_qualname;

PyObject **d;
Py_ssize_t nd;
if (argdefs != NULL) {
d = _PyTuple_ITEMS(argdefs);
nd = PyTuple_GET_SIZE(argdefs);
assert(nd <= INT_MAX);
}
else {
d = NULL;
nd = 0;
}
return _PyEval_EvalCode(tstate,
(PyObject*)co, globals, (PyObject *)NULL,
&((PyFunctionObject *)func)->func_descr, (PyObject *)NULL,
stack, nargs,
nkwargs ? _PyTuple_ITEMS(kwnames) : NULL,
stack + nargs,
nkwargs, 1,
d, (int)nd, kwdefs,
closure, name, qualname);
nkwargs, 1);
}


Expand Down
85 changes: 33 additions & 52 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ static PyMemberDef frame_memberlist[] = {
{NULL} /* Sentinel */
};


static struct _Py_frame_state *
get_frame_state(void)
{
Expand Down Expand Up @@ -816,73 +815,27 @@ frame_alloc(PyCodeObject *code)
}


static inline PyObject *
frame_get_builtins(PyFrameObject *back, PyObject *globals)
{
PyObject *builtins;

if (back != NULL && back->f_globals == globals) {
/* If we share the globals, we share the builtins.
Save a lookup and a call. */
builtins = back->f_builtins;
assert(builtins != NULL);
Py_INCREF(builtins);
return builtins;
}

builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
if (builtins != NULL && PyModule_Check(builtins)) {
builtins = PyModule_GetDict(builtins);
assert(builtins != NULL);
}
if (builtins != NULL) {
Py_INCREF(builtins);
return builtins;
}

if (PyErr_Occurred()) {
return NULL;
}

/* No builtins! Make up a minimal one.
Give them 'None', at least. */
builtins = PyDict_New();
if (builtins == NULL) {
return NULL;
}
if (PyDict_SetItemString(builtins, "None", Py_None) < 0) {
Py_DECREF(builtins);
return NULL;
}
return builtins;
}


PyFrameObject* _Py_HOT_FUNCTION
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
PyObject *globals, PyObject *locals)
PyObject *globals, PyObject *builtins, PyObject *locals)
{
#ifdef Py_DEBUG
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
if (code == NULL || globals == NULL || builtins == NULL ||
(locals != NULL && !PyMapping_Check(locals))) {
PyErr_BadInternalCall();
return NULL;
}
#endif

PyFrameObject *back = tstate->frame;
PyObject *builtins = frame_get_builtins(back, globals);
if (builtins == NULL) {
return NULL;
}

PyFrameObject *f = frame_alloc(code);
if (f == NULL) {
Py_DECREF(builtins);
return NULL;
}

f->f_stackdepth = 0;
Py_INCREF(builtins);
f->f_builtins = builtins;
Py_XINCREF(back);
f->f_back = back;
Expand All @@ -902,8 +855,9 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
f->f_locals = locals;
}
else {
if (locals == NULL)
if (locals == NULL) {
locals = globals;
}
Py_INCREF(locals);
f->f_locals = locals;
}
Expand All @@ -925,7 +879,9 @@ PyFrameObject*
PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
PyObject *globals, PyObject *locals)
{
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, code, globals, locals);
PyObject *builtins = _PyEval_BuiltinsFromGlobals(globals);
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, code, globals, builtins, locals);
Py_DECREF(builtins);
if (f)
_PyObject_GC_TRACK(f);
return f;
Expand Down Expand Up @@ -1223,3 +1179,28 @@ PyFrame_GetBack(PyFrameObject *frame)
Py_XINCREF(back);
return back;
}

PyObject *_PyEval_BuiltinsFromGlobals(PyObject *globals) {
PyObject *builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
if (builtins) {
if (PyModule_Check(builtins)) {
builtins = PyModule_GetDict(builtins);
assert(builtins != NULL);
}
}
if (builtins == NULL) {
if (PyErr_Occurred()) {
return NULL;
}
/* No builtins! Make up a minimal one
Give them 'None', at least. */
builtins = PyDict_New();
if (builtins == NULL ||
PyDict_SetItemString(
builtins, "None", Py_None) < 0)
return NULL;
}
else
Py_INCREF(builtins);
return builtins;
}
Loading
0