diff options
author | Mark Shannon <mark@hotpy.org> | 2021-10-18 08:57:24 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-18 08:57:24 (GMT) |
commit | 70945d57e775b335eb58b734d82e68484063e835 (patch) | |
tree | a8ce452431e8db3c44f3cc6ebb7845a420b60b1a /Python/ceval.c | |
parent | fd03917786a9036ee87b7df604dfb260cc2420c9 (diff) | |
download | cpython-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.c | 250 |
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, |