Add _PyObject_FastCall()

Issue #27128: Add _PyObject_FastCall(), a new calling convention avoiding a
temporary tuple to pass positional parameters in most cases, but create a
temporary tuple if needed (ex: for the tp_call slot).

The API is prepared to support keyword parameters, but the full implementation
will come later (_PyFunction_FastCall() doesn't support keyword parameters
yet).

Add also:

* _PyStack_AsTuple() helper function: convert a "stack" of parameters to
  a tuple.
* _PyCFunction_FastCall(): fast call implementation for C functions
* _PyFunction_FastCall(): fast call implementation for Python functions
This commit is contained in:
Victor Stinner 2016-08-19 16:11:43 +02:00
parent fa46aa7899
commit 9be7e7b52f
6 changed files with 314 additions and 49 deletions

View file

@ -2193,6 +2193,82 @@ PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
return _Py_CheckFunctionResult(func, result, NULL);
}
PyObject*
_PyStack_AsTuple(PyObject **stack, Py_ssize_t nargs)
{
PyObject *args;
Py_ssize_t i;
args = PyTuple_New(nargs);
if (args == NULL) {
return NULL;
}
for (i=0; i < nargs; i++) {
PyObject *item = stack[i];
Py_INCREF(item);
PyTuple_SET_ITEM(args, i, item);
}
return args;
}
PyObject *
_PyObject_FastCall(PyObject *func, PyObject **args, int nargs, PyObject *kwargs)
{
ternaryfunc call;
PyObject *result = NULL;
/* _PyObject_FastCall() must not be called with an exception set,
because it may clear it (directly or indirectly) and so the
caller loses its exception */
assert(!PyErr_Occurred());
assert(func != NULL);
assert(nargs >= 0);
assert(nargs == 0 || args != NULL);
/* issue #27128: support for keywords will come later:
_PyFunction_FastCall() doesn't support keyword arguments yet */
assert(kwargs == NULL);
if (Py_EnterRecursiveCall(" while calling a Python object")) {
return NULL;
}
if (PyFunction_Check(func)) {
result = _PyFunction_FastCall(func, args, nargs, kwargs);
}
else if (PyCFunction_Check(func)) {
result = _PyCFunction_FastCall(func, args, nargs, kwargs);
}
else {
PyObject *tuple;
/* Slow-path: build a temporary tuple */
call = func->ob_type->tp_call;
if (call == NULL) {
PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
func->ob_type->tp_name);
goto exit;
}
tuple = _PyStack_AsTuple(args, nargs);
if (tuple == NULL) {
goto exit;
}
result = (*call)(func, tuple, kwargs);
Py_DECREF(tuple);
}
result = _Py_CheckFunctionResult(func, result, NULL);
exit:
Py_LeaveRecursiveCall();
return result;
}
static PyObject*
call_function_tail(PyObject *callable, PyObject *args)
{

View file

@ -145,6 +145,99 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
return _Py_CheckFunctionResult(func, res, NULL);
}
PyObject *
_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, int nargs,
PyObject *kwargs)
{
PyCFunctionObject* func = (PyCFunctionObject*)func_obj;
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
PyObject *self = PyCFunction_GET_SELF(func);
PyObject *result;
int flags;
/* _PyCFunction_FastCall() must not be called with an exception set,
because it may clear it (directly or indirectly) and so the
caller loses its exception */
assert(!PyErr_Occurred());
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
switch (flags)
{
case METH_NOARGS:
if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
func->m_ml->ml_name);
return NULL;
}
if (nargs != 0) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes no arguments (%zd given)",
func->m_ml->ml_name, nargs);
return NULL;
}
result = (*meth) (self, NULL);
break;
case METH_O:
if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
func->m_ml->ml_name);
return NULL;
}
if (nargs != 1) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)",
func->m_ml->ml_name, nargs);
return NULL;
}
result = (*meth) (self, args[0]);
break;
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
{
/* Slow-path: create a temporary tuple */
PyObject *tuple;
if (!(flags & METH_KEYWORDS) && kwargs != NULL && PyDict_Size(kwargs) != 0) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes no keyword arguments",
func->m_ml->ml_name);
return NULL;
}
tuple = _PyStack_AsTuple(args, nargs);
if (tuple == NULL) {
return NULL;
}
if (flags & METH_KEYWORDS) {
result = (*(PyCFunctionWithKeywords)meth) (self, tuple, kwargs);
}
else {
result = (*meth) (self, tuple);
}
Py_DECREF(tuple);
break;
}
default:
PyErr_SetString(PyExc_SystemError,
"Bad call flags in PyCFunction_Call. "
"METH_OLDARGS is no longer supported!");
return NULL;
}
result = _Py_CheckFunctionResult(func_obj, result, NULL);
return result;
}
/* Methods (the standard built-in methods, that is) */
static void