summaryrefslogtreecommitdiffstats
path: root/Python/ceval.c
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2021-10-18 08:57:24 (GMT)
committerGitHub <noreply@github.com>2021-10-18 08:57:24 (GMT)
commit70945d57e775b335eb58b734d82e68484063e835 (patch)
treea8ce452431e8db3c44f3cc6ebb7845a420b60b1a /Python/ceval.c
parentfd03917786a9036ee87b7df604dfb260cc2420c9 (diff)
downloadcpython-70945d57e775b335eb58b734d82e68484063e835.zip
cpython-70945d57e775b335eb58b734d82e68484063e835.tar.gz
cpython-70945d57e775b335eb58b734d82e68484063e835.tar.bz2
bpo-45256: Avoid C calls for most Python to Python calls. (GH-28937)
* Avoid making C calls for most calls to Python functions. * Change initialize_locals(steal=true) and _PyTuple_FromArraySteal to consume the argument references regardless of whether they succeed or fail.
Diffstat (limited to 'Python/ceval.c')
-rw-r--r--Python/ceval.c250
1 files changed, 97 insertions, 153 deletions
diff --git a/Python/ceval.c b/Python/ceval.c
index 8986872..60fdf01 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -50,9 +50,9 @@
_Py_IDENTIFIER(__name__);
/* Forward declarations */
-Py_LOCAL_INLINE(PyObject *) call_function(
- PyThreadState *tstate, PyObject ***pp_stack,
- Py_ssize_t oparg, PyObject *kwnames, int use_tracing);
+static PyObject *trace_call_function(
+ PyThreadState *tstate, PyObject *callable, PyObject **stack,
+ Py_ssize_t oparg, PyObject *kwnames);
static PyObject * do_call_core(
PyThreadState *tstate, PyObject *func,
PyObject *callargs, PyObject *kwdict, int use_tracing);
@@ -1702,6 +1702,11 @@ check_eval_breaker:
switch (opcode) {
#endif
+ /* Variables used for making calls */
+ PyObject *kwnames;
+ int nargs;
+ int postcall_shrink;
+
/* BEWARE!
It is essential that any operation that fails must goto error
and that all operation that succeed call DISPATCH() ! */
@@ -4599,10 +4604,6 @@ check_eval_breaker:
TARGET(CALL_METHOD) {
/* Designed to work in tamdem with LOAD_METHOD. */
- PyObject **sp, *res;
- int meth_found;
-
- sp = stack_pointer;
/* `meth` is NULL when LOAD_METHOD thinks that it's not
a method call.
@@ -4628,114 +4629,84 @@ check_eval_breaker:
We'll be passing `oparg + 1` to call_function, to
make it accept the `self` as a first argument.
*/
- meth_found = (PEEK(oparg + 2) != NULL);
- res = call_function(tstate, &sp, oparg + meth_found, NULL, cframe.use_tracing);
- stack_pointer = sp;
-
- STACK_SHRINK(1 - meth_found);
- PUSH(res);
- if (res == NULL) {
- goto error;
- }
- CHECK_EVAL_BREAKER();
- DISPATCH();
+ int is_method = (PEEK(oparg + 2) != NULL);
+ oparg += is_method;
+ nargs = oparg;
+ kwnames = NULL;
+ postcall_shrink = 2-is_method;
+ goto call_function;
}
TARGET(CALL_METHOD_KW) {
/* Designed to work in tandem with LOAD_METHOD. Same as CALL_METHOD
but pops TOS to get a tuple of keyword names. */
- PyObject **sp, *res;
- PyObject *names = NULL;
- int meth_found;
-
- names = POP();
-
- sp = stack_pointer;
- meth_found = (PEEK(oparg + 2) != NULL);
- res = call_function(tstate, &sp, oparg + meth_found, names, cframe.use_tracing);
- stack_pointer = sp;
+ kwnames = POP();
+ int is_method = (PEEK(oparg + 2) != NULL);
+ oparg += is_method;
+ nargs = oparg - (int)PyTuple_GET_SIZE(kwnames);
+ postcall_shrink = 2-is_method;
+ goto call_function;
+ }
- STACK_SHRINK(1 - meth_found);
- PUSH(res);
- Py_DECREF(names);
- if (res == NULL) {
- goto error;
- }
- CHECK_EVAL_BREAKER();
- DISPATCH();
+ TARGET(CALL_FUNCTION_KW) {
+ kwnames = POP();
+ nargs = oparg - (int)PyTuple_GET_SIZE(kwnames);
+ postcall_shrink = 1;
+ goto call_function;
}
TARGET(CALL_FUNCTION) {
PREDICTED(CALL_FUNCTION);
- PyObject *res;
-
+ PyObject *function;
+ nargs = oparg;
+ kwnames = NULL;
+ postcall_shrink = 1;
+ call_function:
// Check if the call can be inlined or not
- PyObject *function = PEEK(oparg + 1);
+ function = PEEK(oparg + 1);
if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL) {
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags;
- PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
int is_generator = code_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR);
if (!is_generator) {
+ PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
+ STACK_SHRINK(oparg);
InterpreterFrame *new_frame = _PyEvalFramePushAndInit(
tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function), locals,
- stack_pointer-oparg,
- oparg, NULL, 1);
- if (new_frame == NULL) {
- // When we exit here, we own all variables in the stack
- // (the frame creation has not stolen any variable) so
- // we need to clean the whole stack (done in the
- // "error" label).
- goto error;
- }
-
- STACK_SHRINK(oparg + 1);
+ stack_pointer,
+ nargs, kwnames, 1);
+ STACK_SHRINK(postcall_shrink);
// The frame has stolen all the arguments from the stack,
// so there is no need to clean them up.
+ Py_XDECREF(kwnames);
Py_DECREF(function);
+ if (new_frame == NULL) {
+ goto error;
+ }
_PyFrame_SetStackPointer(frame, stack_pointer);
new_frame->depth = frame->depth + 1;
tstate->frame = frame = new_frame;
goto start_frame;
}
- else {
- /* Callable is a generator or coroutine function: create
- * coroutine or generator. */
- res = make_coro(tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function),
- locals, stack_pointer-oparg, oparg, NULL);
- STACK_SHRINK(oparg + 1);
- for (int i = 0; i < oparg + 1; i++) {
- Py_DECREF(stack_pointer[i]);
- }
- }
+ }
+ /* Callable is not a normal Python function */
+ PyObject *res;
+ if (cframe.use_tracing) {
+ res = trace_call_function(tstate, function, stack_pointer-oparg, nargs, kwnames);
}
else {
- /* Callable is not a Python function */
- PyObject **sp = stack_pointer;
- res = call_function(tstate, &sp, oparg, NULL, cframe.use_tracing);
- stack_pointer = sp;
+ res = PyObject_Vectorcall(function, stack_pointer-oparg,
+ nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
}
-
- PUSH(res);
- if (res == NULL) {
- goto error;
+ assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
+ Py_DECREF(function);
+ Py_XDECREF(kwnames);
+ /* Clear the stack */
+ STACK_SHRINK(oparg);
+ for (int i = 0; i < oparg; i++) {
+ Py_DECREF(stack_pointer[i]);
}
- CHECK_EVAL_BREAKER();
- DISPATCH();
- }
-
- TARGET(CALL_FUNCTION_KW) {
- PyObject **sp, *res, *names;
-
- names = POP();
- assert(PyTuple_Check(names));
- assert(PyTuple_GET_SIZE(names) <= oparg);
- /* We assume without checking that names contains only strings */
- sp = stack_pointer;
- res = call_function(tstate, &sp, oparg, names, cframe.use_tracing);
- stack_pointer = sp;
+ STACK_SHRINK(postcall_shrink);
PUSH(res);
- Py_DECREF(names);
-
if (res == NULL) {
goto error;
}
@@ -5482,7 +5453,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if (co->co_flags & CO_VARKEYWORDS) {
kwdict = PyDict_New();
if (kwdict == NULL) {
- goto fail;
+ goto fail_pre_positional;
}
i = total_args;
if (co->co_flags & CO_VARARGS) {
@@ -5521,11 +5492,19 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
u = _PyTuple_FromArray(args + n, argcount - n);
}
if (u == NULL) {
- goto fail;
+ goto fail_post_positional;
}
assert(localsplus[total_args] == NULL);
localsplus[total_args] = u;
}
+ else if (argcount > n) {
+ /* Too many postional args. Error is reported later */
+ if (steal_args) {
+ for (j = n; j < argcount; j++) {
+ Py_DECREF(args[j]);
+ }
+ }
+ }
/* Handle keyword arguments */
if (kwnames != NULL) {
@@ -5540,7 +5519,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
_PyErr_Format(tstate, PyExc_TypeError,
"%U() keywords must be strings",
con->fc_qualname);
- goto fail;
+ goto kw_fail;
}
/* Speed hack: do raw pointer compares. As names are
@@ -5561,7 +5540,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
goto kw_found;
}
else if (cmp < 0) {
- goto fail;
+ goto kw_fail;
}
}
@@ -5573,29 +5552,38 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
kwcount, kwnames,
con->fc_qualname))
{
- goto fail;
+ goto kw_fail;
}
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got an unexpected keyword argument '%S'",
con->fc_qualname, keyword);
- goto fail;
+ goto kw_fail;
}
if (PyDict_SetItem(kwdict, keyword, value) == -1) {
- goto fail;
+ goto kw_fail;
}
if (steal_args) {
Py_DECREF(value);
}
continue;
+ kw_fail:
+ if (steal_args) {
+ for (;i < kwcount; i++) {
+ PyObject *value = args[i+argcount];
+ Py_DECREF(value);
+ }
+ }
+ goto fail_noclean;
+
kw_found:
if (localsplus[j] != NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got multiple values for argument '%S'",
con->fc_qualname, keyword);
- goto fail;
+ goto kw_fail;
}
if (!steal_args) {
Py_INCREF(value);
@@ -5608,7 +5596,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
too_many_positional(tstate, co, argcount, con->fc_defaults, localsplus,
con->fc_qualname);
- goto fail;
+ goto fail_noclean;
}
/* Add missing positional arguments (copy default values from defs) */
@@ -5624,7 +5612,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if (missing) {
missing_arguments(tstate, co, missing, defcount, localsplus,
con->fc_qualname);
- goto fail;
+ goto fail_noclean;
}
if (n > m)
i = n - m;
@@ -5657,7 +5645,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
continue;
}
else if (_PyErr_Occurred(tstate)) {
- goto fail;
+ goto fail_noclean;
}
}
missing++;
@@ -5665,7 +5653,7 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if (missing) {
missing_arguments(tstate, co, missing, -1, localsplus,
con->fc_qualname);
- goto fail;
+ goto fail_noclean;
}
}
@@ -5678,33 +5666,23 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
return 0;
-fail: /* Jump here from prelude on failure */
+fail_pre_positional:
if (steal_args) {
- // If we failed to initialize locals, make sure the caller still own all the
- // arguments that were on the stack. We need to increment the reference count
- // of everything we copied (everything in localsplus) that came from the stack
- // (everything that is present in the "args" array).
- Py_ssize_t kwcount = kwnames != NULL ? PyTuple_GET_SIZE(kwnames) : 0;
- for (Py_ssize_t k=0; k < total_args; k++) {
- PyObject* arg = localsplus[k];
- for (Py_ssize_t j=0; j < argcount + kwcount; j++) {
- if (args[j] == arg) {
- Py_XINCREF(arg);
- break;
- }
- }
+ for (j = 0; j < argcount; j++) {
+ Py_DECREF(args[j]);
}
- // Restore all the **kwargs we placed into the kwargs dictionary
- if (kwdict) {
- PyObject *key, *value;
- Py_ssize_t pos = 0;
- while (PyDict_Next(kwdict, &pos, &key, &value)) {
- Py_INCREF(value);
- }
+ }
+ /* fall through */
+fail_post_positional:
+ if (steal_args) {
+ Py_ssize_t kwcount = kwnames != NULL ? PyTuple_GET_SIZE(kwnames) : 0;
+ for (j = argcount; j < argcount+kwcount; j++) {
+ Py_DECREF(args[j]);
}
}
+ /* fall through */
+fail_noclean:
return -1;
-
}
static InterpreterFrame *
@@ -6593,40 +6571,6 @@ trace_call_function(PyThreadState *tstate,
return PyObject_Vectorcall(func, args, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
}
-/* Issue #29227: Inline call_function() into _PyEval_EvalFrameDefault()
- to reduce the stack consumption. */
-Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
-call_function(PyThreadState *tstate,
- PyObject ***pp_stack,
- Py_ssize_t oparg,
- PyObject *kwnames,
- int use_tracing)
-{
- PyObject **pfunc = (*pp_stack) - oparg - 1;
- PyObject *func = *pfunc;
- PyObject *x, *w;
- Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
- Py_ssize_t nargs = oparg - nkwargs;
- PyObject **stack = (*pp_stack) - nargs - nkwargs;
-
- if (use_tracing) {
- x = trace_call_function(tstate, func, stack, nargs, kwnames);
- }
- else {
- x = PyObject_Vectorcall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
- }
-
- assert((x != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
-
- /* Clear the stack of the function object. */
- while ((*pp_stack) > pfunc) {
- w = EXT_POP(*pp_stack);
- Py_DECREF(w);
- }
-
- return x;
-}
-
static PyObject *
do_call_core(PyThreadState *tstate,
PyObject *func,