Commit 0d722f3c authored by Jeroen Demeyer's avatar Jeroen Demeyer Committed by Petr Viktorin

bpo-36974: separate vectorcall functions for each calling convention (GH-13781)

parent 6e43d073
...@@ -91,9 +91,6 @@ PyAPI_FUNC(PyObject *) PyDescr_NewMember(PyTypeObject *, ...@@ -91,9 +91,6 @@ PyAPI_FUNC(PyObject *) PyDescr_NewMember(PyTypeObject *,
PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *, PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *,
struct PyGetSetDef *); struct PyGetSetDef *);
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
PyAPI_FUNC(PyObject *) _PyMethodDescr_Vectorcall(
PyObject *descrobj, PyObject *const *args, size_t nargsf, PyObject *kwnames);
PyAPI_FUNC(PyObject *) PyDescr_NewWrapper(PyTypeObject *, PyAPI_FUNC(PyObject *) PyDescr_NewWrapper(PyTypeObject *,
struct wrapperbase *, void *); struct wrapperbase *, void *);
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL) #define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)
......
...@@ -41,13 +41,6 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *); ...@@ -41,13 +41,6 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
#endif #endif
PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *);
#ifndef Py_LIMITED_API
PyAPI_FUNC(PyObject *) _PyCFunction_Vectorcall(PyObject *func,
PyObject *const *stack,
size_t nargsf,
PyObject *kwnames);
#endif
struct PyMethodDef { struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */ const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */ PyCFunction ml_meth; /* The C function that implements it */
......
...@@ -586,6 +586,8 @@ class TestPEP590(unittest.TestCase): ...@@ -586,6 +586,8 @@ class TestPEP590(unittest.TestCase):
return super().__call__(*args) return super().__call__(*args)
calls += [ calls += [
(dict.update, ({},), {"key":True}, None),
({}.update, ({},), {"key":True}, None),
(MethodDescriptorHeap(), (0,), {}, True), (MethodDescriptorHeap(), (0,), {}, True),
(MethodDescriptorOverridden(), (0,), {}, 'new'), (MethodDescriptorOverridden(), (0,), {}, 'new'),
(MethodDescriptorSuper(), (0,), {}, True), (MethodDescriptorSuper(), (0,), {}, True),
......
...@@ -850,10 +850,10 @@ id(42) ...@@ -850,10 +850,10 @@ id(42)
# called, so test a variety of calling conventions. # called, so test a variety of calling conventions.
for py_name, py_args, c_name, expected_frame_number in ( for py_name, py_args, c_name, expected_frame_number in (
('gmtime', '', 'time_gmtime', 1), # METH_VARARGS ('gmtime', '', 'time_gmtime', 1), # METH_VARARGS
('len', '[]', 'builtin_len', 2), # METH_O ('len', '[]', 'builtin_len', 1), # METH_O
('locals', '', 'builtin_locals', 2), # METH_NOARGS ('locals', '', 'builtin_locals', 1), # METH_NOARGS
('iter', '[]', 'builtin_iter', 2), # METH_FASTCALL ('iter', '[]', 'builtin_iter', 1), # METH_FASTCALL
('sorted', '[]', 'builtin_sorted', 2), # METH_FASTCALL|METH_KEYWORDS ('sorted', '[]', 'builtin_sorted', 1), # METH_FASTCALL|METH_KEYWORDS
): ):
with self.subTest(c_name): with self.subTest(c_name):
cmd = ('from time import gmtime\n' # (not always needed) cmd = ('from time import gmtime\n' # (not always needed)
......
Implemented separate vectorcall functions for every calling convention of
builtin functions and methods. This improves performance for calls.
...@@ -216,7 +216,7 @@ PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs) ...@@ -216,7 +216,7 @@ PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
PyObject *result = func(callable, args, PyObject *result = func(callable, args,
nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
_PyStack_UnpackDict_Free(args, nargs, kwnames); _PyStack_UnpackDict_Free(args, nargs, kwnames);
return result; return _Py_CheckFunctionResult(callable, result, NULL);
} }
...@@ -625,26 +625,6 @@ exit: ...@@ -625,26 +625,6 @@ exit:
return result; return result;
} }
PyObject *
_PyCFunction_Vectorcall(PyObject *func,
PyObject *const *args, size_t nargsf,
PyObject *kwnames)
{
PyObject *result;
assert(func != NULL);
assert(PyCFunction_Check(func));
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml,
PyCFunction_GET_SELF(func),
args, nargs, kwnames);
result = _Py_CheckFunctionResult(func, result, NULL);
return result;
}
static PyObject * static PyObject *
cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs) cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs)
{ {
......
This diff is collapsed.
...@@ -19,6 +19,17 @@ static int numfree = 0; ...@@ -19,6 +19,17 @@ static int numfree = 0;
/* undefine macro trampoline to PyCFunction_NewEx */ /* undefine macro trampoline to PyCFunction_NewEx */
#undef PyCFunction_New #undef PyCFunction_New
/* Forward declarations */
static PyObject * cfunction_vectorcall_FASTCALL(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_FASTCALL_KEYWORDS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_NOARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_O(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
PyObject * PyObject *
PyCFunction_New(PyMethodDef *ml, PyObject *self) PyCFunction_New(PyMethodDef *ml, PyObject *self)
{ {
...@@ -28,6 +39,33 @@ PyCFunction_New(PyMethodDef *ml, PyObject *self) ...@@ -28,6 +39,33 @@ PyCFunction_New(PyMethodDef *ml, PyObject *self)
PyObject * PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module) PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{ {
/* Figure out correct vectorcall function to use */
vectorcallfunc vectorcall;
switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
{
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
/* For METH_VARARGS functions, it's more efficient to use tp_call
* instead of vectorcall. */
vectorcall = NULL;
break;
case METH_FASTCALL:
vectorcall = cfunction_vectorcall_FASTCALL;
break;
case METH_FASTCALL | METH_KEYWORDS:
vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS;
break;
case METH_NOARGS:
vectorcall = cfunction_vectorcall_NOARGS;
break;
case METH_O:
vectorcall = cfunction_vectorcall_O;
break;
default:
PyErr_SetString(PyExc_SystemError, "bad call flags");
return NULL;
}
PyCFunctionObject *op; PyCFunctionObject *op;
op = free_list; op = free_list;
if (op != NULL) { if (op != NULL) {
...@@ -46,14 +84,7 @@ PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module) ...@@ -46,14 +84,7 @@ PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
op->m_self = self; op->m_self = self;
Py_XINCREF(module); Py_XINCREF(module);
op->m_module = module; op->m_module = module;
if (ml->ml_flags & METH_VARARGS) { op->vectorcall = vectorcall;
/* For METH_VARARGS functions, it's more efficient to use tp_call
* instead of vectorcall. */
op->vectorcall = NULL;
}
else {
op->vectorcall = _PyCFunction_Vectorcall;
}
_PyObject_GC_TRACK(op); _PyObject_GC_TRACK(op);
return (PyObject *)op; return (PyObject *)op;
} }
...@@ -333,3 +364,121 @@ _PyCFunction_DebugMallocStats(FILE *out) ...@@ -333,3 +364,121 @@ _PyCFunction_DebugMallocStats(FILE *out)
"free PyCFunctionObject", "free PyCFunctionObject",
numfree, sizeof(PyCFunctionObject)); numfree, sizeof(PyCFunctionObject));
} }
/* Vectorcall functions for each of the PyCFunction calling conventions,
* except for METH_VARARGS (possibly combined with METH_KEYWORDS) which
* doesn't use vectorcall.
*
* First, common helpers
*/
static const char *
get_name(PyObject *func)
{
assert(PyCFunction_Check(func));
PyMethodDef *method = ((PyCFunctionObject *)func)->m_ml;
return method->ml_name;
}
typedef void (*funcptr)(void);
static inline int
cfunction_check_kwargs(PyObject *func, PyObject *kwnames)
{
assert(!PyErr_Occurred());
assert(PyCFunction_Check(func));
if (kwnames && PyTuple_GET_SIZE(kwnames)) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes no keyword arguments", get_name(func));
return -1;
}
return 0;
}
static inline funcptr
cfunction_enter_call(PyObject *func)
{
if (Py_EnterRecursiveCall(" while calling a Python object")) {
return NULL;
}
return (funcptr)PyCFunction_GET_FUNCTION(func);
}
/* Now the actual vectorcall functions */
static PyObject *
cfunction_vectorcall_FASTCALL(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
if (cfunction_check_kwargs(func, kwnames)) {
return NULL;
}
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
_PyCFunctionFast meth = (_PyCFunctionFast)
cfunction_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(PyCFunction_GET_SELF(func), args, nargs);
Py_LeaveRecursiveCall();
return result;
}
static PyObject *
cfunction_vectorcall_FASTCALL_KEYWORDS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
_PyCFunctionFastWithKeywords meth = (_PyCFunctionFastWithKeywords)
cfunction_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(PyCFunction_GET_SELF(func), args, nargs, kwnames);
Py_LeaveRecursiveCall();
return result;
}
static PyObject *
cfunction_vectorcall_NOARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
if (cfunction_check_kwargs(func, kwnames)) {
return NULL;
}
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs != 0) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes no arguments (%zd given)", get_name(func), nargs);
return NULL;
}
PyCFunction meth = (PyCFunction)cfunction_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(PyCFunction_GET_SELF(func), NULL);
Py_LeaveRecursiveCall();
return result;
}
static PyObject *
cfunction_vectorcall_O(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
if (cfunction_check_kwargs(func, kwnames)) {
return NULL;
}
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs != 1) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)",
get_name(func), nargs);
return NULL;
}
PyCFunction meth = (PyCFunction)cfunction_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(PyCFunction_GET_SELF(func), args[0]);
Py_LeaveRecursiveCall();
return result;
}
...@@ -4936,7 +4936,7 @@ trace_call_function(PyThreadState *tstate, ...@@ -4936,7 +4936,7 @@ trace_call_function(PyThreadState *tstate,
{ {
PyObject *x; PyObject *x;
if (PyCFunction_Check(func)) { if (PyCFunction_Check(func)) {
C_TRACE(x, _PyCFunction_Vectorcall(func, args, nargs, kwnames)); C_TRACE(x, _PyObject_Vectorcall(func, args, nargs, kwnames));
return x; return x;
} }
else if (Py_TYPE(func) == &PyMethodDescr_Type && nargs > 0) { else if (Py_TYPE(func) == &PyMethodDescr_Type && nargs > 0) {
...@@ -4952,9 +4952,9 @@ trace_call_function(PyThreadState *tstate, ...@@ -4952,9 +4952,9 @@ trace_call_function(PyThreadState *tstate,
if (func == NULL) { if (func == NULL) {
return NULL; return NULL;
} }
C_TRACE(x, _PyCFunction_Vectorcall(func, C_TRACE(x, _PyObject_Vectorcall(func,
args+1, nargs-1, args+1, nargs-1,
kwnames)); kwnames));
Py_DECREF(func); Py_DECREF(func);
return x; return x;
} }
......
...@@ -1563,8 +1563,8 @@ class Frame(object): ...@@ -1563,8 +1563,8 @@ class Frame(object):
if not caller: if not caller:
return False return False
if caller in ('_PyCFunction_Vectorcall', if (caller.startswith('cfunction_vectorcall_') or
'cfunction_call_varargs'): caller == 'cfunction_call_varargs'):
arg_name = 'func' arg_name = 'func'
# Within that frame: # Within that frame:
# "func" is the local containing the PyObject* of the # "func" is the local containing the PyObject* of the
......
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