Skip to content

bpo-36974: separate vectorcall functions for each calling convention #13781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Include/descrobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,6 @@ PyAPI_FUNC(PyObject *) PyDescr_NewMember(PyTypeObject *,
PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *,
struct PyGetSetDef *);
#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 *,
struct wrapperbase *, void *);
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)
Expand Down
7 changes: 0 additions & 7 deletions Include/methodobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
#endif
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 {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,8 @@ def __call__(self, *args):
return super().__call__(*args)

calls += [
(dict.update, ({},), {"key":True}, None),
({}.update, ({},), {"key":True}, None),
(MethodDescriptorHeap(), (0,), {}, True),
(MethodDescriptorOverridden(), (0,), {}, 'new'),
(MethodDescriptorSuper(), (0,), {}, True),
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,10 +850,10 @@ def test_pycfunction(self):
# called, so test a variety of calling conventions.
for py_name, py_args, c_name, expected_frame_number in (
('gmtime', '', 'time_gmtime', 1), # METH_VARARGS
('len', '[]', 'builtin_len', 2), # METH_O
('locals', '', 'builtin_locals', 2), # METH_NOARGS
('iter', '[]', 'builtin_iter', 2), # METH_FASTCALL
('sorted', '[]', 'builtin_sorted', 2), # METH_FASTCALL|METH_KEYWORDS
('len', '[]', 'builtin_len', 1), # METH_O
('locals', '', 'builtin_locals', 1), # METH_NOARGS
('iter', '[]', 'builtin_iter', 1), # METH_FASTCALL
('sorted', '[]', 'builtin_sorted', 1), # METH_FASTCALL|METH_KEYWORDS
):
with self.subTest(c_name):
cmd = ('from time import gmtime\n' # (not always needed)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implemented separate vectorcall functions for every calling convention of
builtin functions and methods. This improves performance for calls.
22 changes: 1 addition & 21 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
PyObject *result = func(callable, args,
nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
_PyStack_UnpackDict_Free(args, nargs, kwnames);
return result;
return _Py_CheckFunctionResult(callable, result, NULL);
}


Expand Down Expand Up @@ -625,26 +625,6 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self,
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 *
cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs)
{
Expand Down
246 changes: 196 additions & 50 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,80 +226,199 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value)
return -1;
}

static PyObject *
methoddescr_call(PyMethodDescrObject *descr, PyObject *args, PyObject *kwargs)
{
Py_ssize_t nargs;
PyObject *self, *result;

/* Make sure that the first argument is acceptable as 'self' */
assert(PyTuple_Check(args));
nargs = PyTuple_GET_SIZE(args);
/* Vectorcall functions for each of the PyMethodDescr calling conventions.
*
* First, common helpers
*/
static const char *
get_name(PyObject *func) {
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
return ((PyMethodDescrObject *)func)->d_method->ml_name;
}

typedef void (*funcptr)(void);

static inline int
method_check_args(PyObject *func, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
assert(!PyErr_Occurred());
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
if (nargs < 1) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' of '%.100s' "
"descriptor '%.200s' of '%.100s' "
"object needs an argument",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name);
return NULL;
get_name(func), PyDescr_TYPE(func)->tp_name);
return -1;
}
self = PyTuple_GET_ITEM(args, 0);
PyObject *self = args[0];
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
(PyObject *)PyDescr_TYPE(descr))) {
(PyObject *)PyDescr_TYPE(func)))
{
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%.100s' objects "
"descriptor '%.200s' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
self->ob_type->tp_name);
get_name(func), PyDescr_TYPE(func)->tp_name,
Py_TYPE(self)->tp_name);
return -1;
}
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
method_enter_call(PyObject *func)
{
if (Py_EnterRecursiveCall(" while calling a Python object")) {
return NULL;
}
return (funcptr)((PyMethodDescrObject *)func)->d_method->ml_meth;
}

result = _PyMethodDef_RawFastCallDict(descr->d_method, self,
&_PyTuple_ITEMS(args)[1], nargs - 1,
kwargs);
result = _Py_CheckFunctionResult((PyObject *)descr, result, NULL);
/* Now the actual vectorcall functions */
static PyObject *
method_vectorcall_VARARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, kwnames)) {
return NULL;
}
PyObject *argstuple = _PyTuple_FromArray(args+1, nargs-1);
if (argstuple == NULL) {
return NULL;
}
PyCFunction meth = (PyCFunction)method_enter_call(func);
if (meth == NULL) {
Py_DECREF(argstuple);
return NULL;
}
PyObject *result = meth(args[0], argstuple);
Py_DECREF(argstuple);
Py_LeaveRecursiveCall();
return result;
}

// same to methoddescr_call(), but use FASTCALL convention.
PyObject *
_PyMethodDescr_Vectorcall(PyObject *descrobj,
PyObject *const *args, size_t nargsf,
PyObject *kwnames)
static PyObject *
method_vectorcall_VARARGS_KEYWORDS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
assert(Py_TYPE(descrobj) == &PyMethodDescr_Type);
PyMethodDescrObject *descr = (PyMethodDescrObject *)descrobj;
PyObject *self, *result;
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, NULL)) {
return NULL;
}
PyObject *argstuple = _PyTuple_FromArray(args+1, nargs-1);
if (argstuple == NULL) {
return NULL;
}
PyObject *result = NULL;
/* Create a temporary dict for keyword arguments */
PyObject *kwdict = NULL;
if (kwnames != NULL && PyTuple_GET_SIZE(kwnames) > 0) {
kwdict = _PyStack_AsDict(args + nargs, kwnames);
if (kwdict == NULL) {
goto exit;
}
}
PyCFunctionWithKeywords meth = (PyCFunctionWithKeywords)
method_enter_call(func);
if (meth == NULL) {
goto exit;
}
result = meth(args[0], argstuple, kwdict);
Py_LeaveRecursiveCall();
exit:
Py_DECREF(argstuple);
Py_XDECREF(kwdict);
return result;
}

static PyObject *
method_vectorcall_FASTCALL(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, kwnames)) {
return NULL;
}
_PyCFunctionFast meth = (_PyCFunctionFast)
method_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(args[0], args+1, nargs-1);
Py_LeaveRecursiveCall();
return result;
}

static PyObject *
method_vectorcall_FASTCALL_KEYWORDS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
/* Make sure that the first argument is acceptable as 'self' */
if (nargs < 1) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' of '%.100s' "
"object needs an argument",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name);
if (method_check_args(func, args, nargs, NULL)) {
return NULL;
}
self = args[0];
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
(PyObject *)PyDescr_TYPE(descr))) {
_PyCFunctionFastWithKeywords meth = (_PyCFunctionFastWithKeywords)
method_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(args[0], args+1, nargs-1, kwnames);
Py_LeaveRecursiveCall();
return result;
}

static PyObject *
method_vectorcall_NOARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, kwnames)) {
return NULL;
}
if (nargs != 1) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
self->ob_type->tp_name);
"%.200s() takes no arguments (%zd given)", get_name(func), nargs-1);
return NULL;
}
PyCFunction meth = (PyCFunction)method_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(args[0], NULL);
Py_LeaveRecursiveCall();
return result;
}

result = _PyMethodDef_RawFastCallKeywords(descr->d_method, self,
args+1, nargs-1, kwnames);
result = _Py_CheckFunctionResult((PyObject *)descr, result, NULL);
static PyObject *
method_vectorcall_O(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, kwnames)) {
return NULL;
}
if (nargs != 2) {
PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)",
get_name(func), nargs-1);
return NULL;
}
PyCFunction meth = (PyCFunction)method_enter_call(func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(args[0], args[1]);
Py_LeaveRecursiveCall();
return result;
}


/* Instances of classmethod_descriptor are unlikely to be called directly.
For one, the analogous class "classmethod" (for Python classes) is not
callable. Second, users are not likely to access a classmethod_descriptor
Expand Down Expand Up @@ -540,7 +659,7 @@ PyTypeObject PyMethodDescr_Type = {
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)methoddescr_call, /* tp_call */
PyVectorcall_Call, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
Expand Down Expand Up @@ -738,13 +857,40 @@ descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
PyObject *
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
{
/* Figure out correct vectorcall function to use */
vectorcallfunc vectorcall;
switch (method->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
{
case METH_VARARGS:
vectorcall = method_vectorcall_VARARGS;
break;
case METH_VARARGS | METH_KEYWORDS:
vectorcall = method_vectorcall_VARARGS_KEYWORDS;
break;
case METH_FASTCALL:
vectorcall = method_vectorcall_FASTCALL;
break;
case METH_FASTCALL | METH_KEYWORDS:
vectorcall = method_vectorcall_FASTCALL_KEYWORDS;
break;
case METH_NOARGS:
vectorcall = method_vectorcall_NOARGS;
break;
case METH_O:
vectorcall = method_vectorcall_O;
break;
default:
PyErr_SetString(PyExc_SystemError, "bad call flags");
return NULL;
}

PyMethodDescrObject *descr;

descr = (PyMethodDescrObject *)descr_new(&PyMethodDescr_Type,
type, method->ml_name);
if (descr != NULL) {
descr->d_method = method;
descr->vectorcall = _PyMethodDescr_Vectorcall;
descr->vectorcall = vectorcall;
}
return (PyObject *)descr;
}
Expand Down
Loading