Skip to content

Commit 0d722f3

Browse files
jdemeyerencukou
authored andcommitted
bpo-36974: separate vectorcall functions for each calling convention (GH-13781)
1 parent 6e43d07 commit 0d722f3

File tree

10 files changed

+368
-99
lines changed

10 files changed

+368
-99
lines changed

Include/descrobject.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ PyAPI_FUNC(PyObject *) PyDescr_NewMember(PyTypeObject *,
9191
PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *,
9292
struct PyGetSetDef *);
9393
#ifndef Py_LIMITED_API
94-
95-
PyAPI_FUNC(PyObject *) _PyMethodDescr_Vectorcall(
96-
PyObject *descrobj, PyObject *const *args, size_t nargsf, PyObject *kwnames);
9794
PyAPI_FUNC(PyObject *) PyDescr_NewWrapper(PyTypeObject *,
9895
struct wrapperbase *, void *);
9996
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)

Include/methodobject.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
4141
#endif
4242
PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *);
4343

44-
#ifndef Py_LIMITED_API
45-
PyAPI_FUNC(PyObject *) _PyCFunction_Vectorcall(PyObject *func,
46-
PyObject *const *stack,
47-
size_t nargsf,
48-
PyObject *kwnames);
49-
#endif
50-
5144
struct PyMethodDef {
5245
const char *ml_name; /* The name of the built-in function/method */
5346
PyCFunction ml_meth; /* The C function that implements it */

Lib/test/test_call.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,8 @@ def __call__(self, *args):
586586
return super().__call__(*args)
587587

588588
calls += [
589+
(dict.update, ({},), {"key":True}, None),
590+
({}.update, ({},), {"key":True}, None),
589591
(MethodDescriptorHeap(), (0,), {}, True),
590592
(MethodDescriptorOverridden(), (0,), {}, 'new'),
591593
(MethodDescriptorSuper(), (0,), {}, True),

Lib/test/test_gdb.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -850,10 +850,10 @@ def test_pycfunction(self):
850850
# called, so test a variety of calling conventions.
851851
for py_name, py_args, c_name, expected_frame_number in (
852852
('gmtime', '', 'time_gmtime', 1), # METH_VARARGS
853-
('len', '[]', 'builtin_len', 2), # METH_O
854-
('locals', '', 'builtin_locals', 2), # METH_NOARGS
855-
('iter', '[]', 'builtin_iter', 2), # METH_FASTCALL
856-
('sorted', '[]', 'builtin_sorted', 2), # METH_FASTCALL|METH_KEYWORDS
853+
('len', '[]', 'builtin_len', 1), # METH_O
854+
('locals', '', 'builtin_locals', 1), # METH_NOARGS
855+
('iter', '[]', 'builtin_iter', 1), # METH_FASTCALL
856+
('sorted', '[]', 'builtin_sorted', 1), # METH_FASTCALL|METH_KEYWORDS
857857
):
858858
with self.subTest(c_name):
859859
cmd = ('from time import gmtime\n' # (not always needed)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implemented separate vectorcall functions for every calling convention of
2+
builtin functions and methods. This improves performance for calls.

Objects/call.c

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
216216
PyObject *result = func(callable, args,
217217
nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
218218
_PyStack_UnpackDict_Free(args, nargs, kwnames);
219-
return result;
219+
return _Py_CheckFunctionResult(callable, result, NULL);
220220
}
221221

222222

@@ -625,26 +625,6 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self,
625625
return result;
626626
}
627627

628-
629-
PyObject *
630-
_PyCFunction_Vectorcall(PyObject *func,
631-
PyObject *const *args, size_t nargsf,
632-
PyObject *kwnames)
633-
{
634-
PyObject *result;
635-
636-
assert(func != NULL);
637-
assert(PyCFunction_Check(func));
638-
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
639-
640-
result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml,
641-
PyCFunction_GET_SELF(func),
642-
args, nargs, kwnames);
643-
result = _Py_CheckFunctionResult(func, result, NULL);
644-
return result;
645-
}
646-
647-
648628
static PyObject *
649629
cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs)
650630
{

Objects/descrobject.c

Lines changed: 196 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -226,80 +226,199 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value)
226226
return -1;
227227
}
228228

229-
static PyObject *
230-
methoddescr_call(PyMethodDescrObject *descr, PyObject *args, PyObject *kwargs)
231-
{
232-
Py_ssize_t nargs;
233-
PyObject *self, *result;
234229

235-
/* Make sure that the first argument is acceptable as 'self' */
236-
assert(PyTuple_Check(args));
237-
nargs = PyTuple_GET_SIZE(args);
230+
/* Vectorcall functions for each of the PyMethodDescr calling conventions.
231+
*
232+
* First, common helpers
233+
*/
234+
static const char *
235+
get_name(PyObject *func) {
236+
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
237+
return ((PyMethodDescrObject *)func)->d_method->ml_name;
238+
}
239+
240+
typedef void (*funcptr)(void);
241+
242+
static inline int
243+
method_check_args(PyObject *func, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
244+
{
245+
assert(!PyErr_Occurred());
246+
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
238247
if (nargs < 1) {
239248
PyErr_Format(PyExc_TypeError,
240-
"descriptor '%V' of '%.100s' "
249+
"descriptor '%.200s' of '%.100s' "
241250
"object needs an argument",
242-
descr_name((PyDescrObject *)descr), "?",
243-
PyDescr_TYPE(descr)->tp_name);
244-
return NULL;
251+
get_name(func), PyDescr_TYPE(func)->tp_name);
252+
return -1;
245253
}
246-
self = PyTuple_GET_ITEM(args, 0);
254+
PyObject *self = args[0];
247255
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
248-
(PyObject *)PyDescr_TYPE(descr))) {
256+
(PyObject *)PyDescr_TYPE(func)))
257+
{
249258
PyErr_Format(PyExc_TypeError,
250-
"descriptor '%V' for '%.100s' objects "
259+
"descriptor '%.200s' for '%.100s' objects "
251260
"doesn't apply to a '%.100s' object",
252-
descr_name((PyDescrObject *)descr), "?",
253-
PyDescr_TYPE(descr)->tp_name,
254-
self->ob_type->tp_name);
261+
get_name(func), PyDescr_TYPE(func)->tp_name,
262+
Py_TYPE(self)->tp_name);
263+
return -1;
264+
}
265+
if (kwnames && PyTuple_GET_SIZE(kwnames)) {
266+
PyErr_Format(PyExc_TypeError,
267+
"%.200s() takes no keyword arguments", get_name(func));
268+
return -1;
269+
}
270+
return 0;
271+
}
272+
273+
static inline funcptr
274+
method_enter_call(PyObject *func)
275+
{
276+
if (Py_EnterRecursiveCall(" while calling a Python object")) {
255277
return NULL;
256278
}
279+
return (funcptr)((PyMethodDescrObject *)func)->d_method->ml_meth;
280+
}
257281

258-
result = _PyMethodDef_RawFastCallDict(descr->d_method, self,
259-
&_PyTuple_ITEMS(args)[1], nargs - 1,
260-
kwargs);
261-
result = _Py_CheckFunctionResult((PyObject *)descr, result, NULL);
282+
/* Now the actual vectorcall functions */
283+
static PyObject *
284+
method_vectorcall_VARARGS(
285+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
286+
{
287+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
288+
if (method_check_args(func, args, nargs, kwnames)) {
289+
return NULL;
290+
}
291+
PyObject *argstuple = _PyTuple_FromArray(args+1, nargs-1);
292+
if (argstuple == NULL) {
293+
return NULL;
294+
}
295+
PyCFunction meth = (PyCFunction)method_enter_call(func);
296+
if (meth == NULL) {
297+
Py_DECREF(argstuple);
298+
return NULL;
299+
}
300+
PyObject *result = meth(args[0], argstuple);
301+
Py_DECREF(argstuple);
302+
Py_LeaveRecursiveCall();
262303
return result;
263304
}
264305

265-
// same to methoddescr_call(), but use FASTCALL convention.
266-
PyObject *
267-
_PyMethodDescr_Vectorcall(PyObject *descrobj,
268-
PyObject *const *args, size_t nargsf,
269-
PyObject *kwnames)
306+
static PyObject *
307+
method_vectorcall_VARARGS_KEYWORDS(
308+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
270309
{
271-
assert(Py_TYPE(descrobj) == &PyMethodDescr_Type);
272-
PyMethodDescrObject *descr = (PyMethodDescrObject *)descrobj;
273-
PyObject *self, *result;
310+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
311+
if (method_check_args(func, args, nargs, NULL)) {
312+
return NULL;
313+
}
314+
PyObject *argstuple = _PyTuple_FromArray(args+1, nargs-1);
315+
if (argstuple == NULL) {
316+
return NULL;
317+
}
318+
PyObject *result = NULL;
319+
/* Create a temporary dict for keyword arguments */
320+
PyObject *kwdict = NULL;
321+
if (kwnames != NULL && PyTuple_GET_SIZE(kwnames) > 0) {
322+
kwdict = _PyStack_AsDict(args + nargs, kwnames);
323+
if (kwdict == NULL) {
324+
goto exit;
325+
}
326+
}
327+
PyCFunctionWithKeywords meth = (PyCFunctionWithKeywords)
328+
method_enter_call(func);
329+
if (meth == NULL) {
330+
goto exit;
331+
}
332+
result = meth(args[0], argstuple, kwdict);
333+
Py_LeaveRecursiveCall();
334+
exit:
335+
Py_DECREF(argstuple);
336+
Py_XDECREF(kwdict);
337+
return result;
338+
}
339+
340+
static PyObject *
341+
method_vectorcall_FASTCALL(
342+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
343+
{
344+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
345+
if (method_check_args(func, args, nargs, kwnames)) {
346+
return NULL;
347+
}
348+
_PyCFunctionFast meth = (_PyCFunctionFast)
349+
method_enter_call(func);
350+
if (meth == NULL) {
351+
return NULL;
352+
}
353+
PyObject *result = meth(args[0], args+1, nargs-1);
354+
Py_LeaveRecursiveCall();
355+
return result;
356+
}
274357

358+
static PyObject *
359+
method_vectorcall_FASTCALL_KEYWORDS(
360+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
361+
{
275362
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
276-
/* Make sure that the first argument is acceptable as 'self' */
277-
if (nargs < 1) {
278-
PyErr_Format(PyExc_TypeError,
279-
"descriptor '%V' of '%.100s' "
280-
"object needs an argument",
281-
descr_name((PyDescrObject *)descr), "?",
282-
PyDescr_TYPE(descr)->tp_name);
363+
if (method_check_args(func, args, nargs, NULL)) {
283364
return NULL;
284365
}
285-
self = args[0];
286-
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
287-
(PyObject *)PyDescr_TYPE(descr))) {
366+
_PyCFunctionFastWithKeywords meth = (_PyCFunctionFastWithKeywords)
367+
method_enter_call(func);
368+
if (meth == NULL) {
369+
return NULL;
370+
}
371+
PyObject *result = meth(args[0], args+1, nargs-1, kwnames);
372+
Py_LeaveRecursiveCall();
373+
return result;
374+
}
375+
376+
static PyObject *
377+
method_vectorcall_NOARGS(
378+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
379+
{
380+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
381+
if (method_check_args(func, args, nargs, kwnames)) {
382+
return NULL;
383+
}
384+
if (nargs != 1) {
288385
PyErr_Format(PyExc_TypeError,
289-
"descriptor '%V' for '%.100s' objects "
290-
"doesn't apply to a '%.100s' object",
291-
descr_name((PyDescrObject *)descr), "?",
292-
PyDescr_TYPE(descr)->tp_name,
293-
self->ob_type->tp_name);
386+
"%.200s() takes no arguments (%zd given)", get_name(func), nargs-1);
387+
return NULL;
388+
}
389+
PyCFunction meth = (PyCFunction)method_enter_call(func);
390+
if (meth == NULL) {
294391
return NULL;
295392
}
393+
PyObject *result = meth(args[0], NULL);
394+
Py_LeaveRecursiveCall();
395+
return result;
396+
}
296397

297-
result = _PyMethodDef_RawFastCallKeywords(descr->d_method, self,
298-
args+1, nargs-1, kwnames);
299-
result = _Py_CheckFunctionResult((PyObject *)descr, result, NULL);
398+
static PyObject *
399+
method_vectorcall_O(
400+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
401+
{
402+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
403+
if (method_check_args(func, args, nargs, kwnames)) {
404+
return NULL;
405+
}
406+
if (nargs != 2) {
407+
PyErr_Format(PyExc_TypeError,
408+
"%.200s() takes exactly one argument (%zd given)",
409+
get_name(func), nargs-1);
410+
return NULL;
411+
}
412+
PyCFunction meth = (PyCFunction)method_enter_call(func);
413+
if (meth == NULL) {
414+
return NULL;
415+
}
416+
PyObject *result = meth(args[0], args[1]);
417+
Py_LeaveRecursiveCall();
300418
return result;
301419
}
302420

421+
303422
/* Instances of classmethod_descriptor are unlikely to be called directly.
304423
For one, the analogous class "classmethod" (for Python classes) is not
305424
callable. Second, users are not likely to access a classmethod_descriptor
@@ -540,7 +659,7 @@ PyTypeObject PyMethodDescr_Type = {
540659
0, /* tp_as_sequence */
541660
0, /* tp_as_mapping */
542661
0, /* tp_hash */
543-
(ternaryfunc)methoddescr_call, /* tp_call */
662+
PyVectorcall_Call, /* tp_call */
544663
0, /* tp_str */
545664
PyObject_GenericGetAttr, /* tp_getattro */
546665
0, /* tp_setattro */
@@ -738,13 +857,40 @@ descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
738857
PyObject *
739858
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
740859
{
860+
/* Figure out correct vectorcall function to use */
861+
vectorcallfunc vectorcall;
862+
switch (method->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
863+
{
864+
case METH_VARARGS:
865+
vectorcall = method_vectorcall_VARARGS;
866+
break;
867+
case METH_VARARGS | METH_KEYWORDS:
868+
vectorcall = method_vectorcall_VARARGS_KEYWORDS;
869+
break;
870+
case METH_FASTCALL:
871+
vectorcall = method_vectorcall_FASTCALL;
872+
break;
873+
case METH_FASTCALL | METH_KEYWORDS:
874+
vectorcall = method_vectorcall_FASTCALL_KEYWORDS;
875+
break;
876+
case METH_NOARGS:
877+
vectorcall = method_vectorcall_NOARGS;
878+
break;
879+
case METH_O:
880+
vectorcall = method_vectorcall_O;
881+
break;
882+
default:
883+
PyErr_SetString(PyExc_SystemError, "bad call flags");
884+
return NULL;
885+
}
886+
741887
PyMethodDescrObject *descr;
742888

743889
descr = (PyMethodDescrObject *)descr_new(&PyMethodDescr_Type,
744890
type, method->ml_name);
745891
if (descr != NULL) {
746892
descr->d_method = method;
747-
descr->vectorcall = _PyMethodDescr_Vectorcall;
893+
descr->vectorcall = vectorcall;
748894
}
749895
return (PyObject *)descr;
750896
}

0 commit comments

Comments
 (0)