Commit 7fc252ad authored by Victor Stinner's avatar Victor Stinner

Optimize _PyCFunction_FastCallKeywords()

Issue #29259: Write fast path in _PyCFunction_FastCallKeywords() for
METH_FASTCALL, avoid the creation of a temporary dictionary for keyword
arguments.

Cleanup also _PyCFunction_FastCallDict():

* Don't dereference func before checking that it's not NULL
* Move code to raise the "no keyword argument" exception into a new
  no_keyword_error label.

Update python-gdb.py for the change.
parent 15f94596
...@@ -89,7 +89,7 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds) ...@@ -89,7 +89,7 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
assert(kwds == NULL || PyDict_Check(kwds)); assert(kwds == NULL || PyDict_Check(kwds));
/* PyCFunction_Call() must not be called with an exception set, /* PyCFunction_Call() must not be called with an exception set,
because it may clear it (directly or indirectly) and so the because it can clear it (directly or indirectly) and so the
caller loses its exception */ caller loses its exception */
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
...@@ -155,14 +155,14 @@ PyObject * ...@@ -155,14 +155,14 @@ PyObject *
_PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs, _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
PyObject *kwargs) PyObject *kwargs)
{ {
PyCFunctionObject *func = (PyCFunctionObject*)func_obj; PyCFunctionObject *func;
PyCFunction meth = PyCFunction_GET_FUNCTION(func); PyCFunction meth;
PyObject *self = PyCFunction_GET_SELF(func); PyObject *self;
PyObject *result; PyObject *result;
int flags; int flags;
assert(PyCFunction_Check(func)); assert(func_obj != NULL);
assert(func != NULL); assert(PyCFunction_Check(func_obj));
assert(nargs >= 0); assert(nargs >= 0);
assert(nargs == 0 || args != NULL); assert(nargs == 0 || args != NULL);
assert(kwargs == NULL || PyDict_Check(kwargs)); assert(kwargs == NULL || PyDict_Check(kwargs));
...@@ -172,34 +172,28 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs, ...@@ -172,34 +172,28 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
caller loses its exception */ caller loses its exception */
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
func = (PyCFunctionObject*)func_obj;
meth = PyCFunction_GET_FUNCTION(func);
self = PyCFunction_GET_SELF(func);
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST); flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
switch (flags) switch (flags)
{ {
case METH_NOARGS: case METH_NOARGS:
if (nargs != 0) {
goto no_keyword_error;
}
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) { if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments", PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
func->m_ml->ml_name); func->m_ml->ml_name);
return NULL; 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); result = (*meth) (self, NULL);
break; break;
case METH_O: case METH_O:
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
func->m_ml->ml_name);
return NULL;
}
if (nargs != 1) { if (nargs != 1) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)", "%.200s() takes exactly one argument (%zd given)",
...@@ -207,20 +201,22 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs, ...@@ -207,20 +201,22 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
return NULL; return NULL;
} }
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
goto no_keyword_error;
}
result = (*meth) (self, args[0]); result = (*meth) (self, args[0]);
break; break;
case METH_VARARGS: case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS: case METH_VARARGS | METH_KEYWORDS:
{ {
/* Slow-path: create a temporary tuple */ /* Slow-path: create a temporary tuple for positional arguments */
PyObject *tuple; PyObject *tuple;
if (!(flags & METH_KEYWORDS) && kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) { if (!(flags & METH_KEYWORDS)
PyErr_Format(PyExc_TypeError, && kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
"%.200s() takes no keyword arguments", goto no_keyword_error;
func->m_ml->ml_name);
return NULL;
} }
tuple = _PyStack_AsTuple(args, nargs); tuple = _PyStack_AsTuple(args, nargs);
...@@ -267,35 +263,134 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs, ...@@ -267,35 +263,134 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
result = _Py_CheckFunctionResult(func_obj, result, NULL); result = _Py_CheckFunctionResult(func_obj, result, NULL);
return result; return result;
no_keyword_error:
PyErr_Format(PyExc_TypeError,
"%.200s() takes no arguments (%zd given)",
func->m_ml->ml_name, nargs);
return NULL;
} }
PyObject * PyObject *
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack, _PyCFunction_FastCallKeywords(PyObject *func_obj, PyObject **args,
Py_ssize_t nargs, PyObject *kwnames) Py_ssize_t nargs, PyObject *kwnames)
{ {
PyObject *kwdict, *result; PyCFunctionObject *func;
PyCFunction meth;
PyObject *self, *result;
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
int flags;
assert(PyCFunction_Check(func)); assert(func_obj != NULL);
assert(PyCFunction_Check(func_obj));
assert(nargs >= 0); assert(nargs >= 0);
assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
assert((nargs == 0 && nkwargs == 0) || stack != NULL); assert((nargs == 0 && nkwargs == 0) || args != NULL);
/* kwnames must only contains str strings, no subclass, and all keys must /* kwnames must only contains str strings, no subclass, and all keys must
be unique */ be unique */
if (nkwargs > 0) { /* _PyCFunction_FastCallKeywords() must not be called with an exception
kwdict = _PyStack_AsDict(stack + nargs, kwnames); set, because it can clear it (directly or indirectly) and so the caller
if (kwdict == NULL) { loses its exception */
assert(!PyErr_Occurred());
func = (PyCFunctionObject*)func_obj;
meth = PyCFunction_GET_FUNCTION(func);
self = PyCFunction_GET_SELF(func);
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
switch (flags)
{
case METH_NOARGS:
if (nargs != 0) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes no arguments (%zd given)",
func->m_ml->ml_name, nargs);
return NULL;
}
if (nkwargs) {
goto no_keyword_error;
}
result = (*meth) (self, NULL);
break;
case METH_O:
if (nargs != 1) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)",
func->m_ml->ml_name, nargs);
return NULL; return NULL;
} }
if (nkwargs) {
goto no_keyword_error;
}
result = (*meth) (self, args[0]);
break;
case METH_FASTCALL:
/* Fast-path: avoid temporary dict to pass keyword arguments */
result = ((_PyCFunctionFast)meth) (self, args, nargs, kwnames);
break;
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
{
/* Slow-path: create a temporary tuple for positional arguments
and a temporary dict for keyword arguments */
PyObject *argtuple;
if (!(flags & METH_KEYWORDS) && nkwargs) {
goto no_keyword_error;
}
argtuple = _PyStack_AsTuple(args, nargs);
if (argtuple == NULL) {
return NULL;
}
if (flags & METH_KEYWORDS) {
PyObject *kwdict;
if (nkwargs > 0) {
kwdict = _PyStack_AsDict(args + nargs, kwnames);
if (kwdict == NULL) {
Py_DECREF(argtuple);
return NULL;
}
}
else {
kwdict = NULL;
}
result = (*(PyCFunctionWithKeywords)meth) (self, argtuple, kwdict);
Py_XDECREF(kwdict);
}
else {
result = (*meth) (self, argtuple);
}
Py_DECREF(argtuple);
break;
} }
else {
kwdict = NULL; default:
PyErr_SetString(PyExc_SystemError,
"Bad call flags in _PyCFunction_FastCallKeywords. "
"METH_OLDARGS is no longer supported!");
return NULL;
} }
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict); result = _Py_CheckFunctionResult(func_obj, result, NULL);
Py_XDECREF(kwdict);
return result; return result;
no_keyword_error:
PyErr_Format(PyExc_TypeError,
"%.200s() takes no keyword arguments",
func->m_ml->ml_name);
return NULL;
} }
/* Methods (the standard built-in methods, that is) */ /* Methods (the standard built-in methods, that is) */
......
...@@ -1518,7 +1518,8 @@ class Frame(object): ...@@ -1518,7 +1518,8 @@ class Frame(object):
except RuntimeError: except RuntimeError:
return 'PyCFunction invocation (unable to read "func")' return 'PyCFunction invocation (unable to read "func")'
elif caller == '_PyCFunction_FastCallDict': elif caller in {'_PyCFunction_FastCallDict',
'_PyCFunction_FastCallKeywords'}:
try: try:
func = older._gdbframe.read_var('func_obj') func = older._gdbframe.read_var('func_obj')
return str(func) return str(func)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment