Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit bf8e82f

Browse files
jdemeyerambv
authored andcommitted
[3.8] bpo-36974: separate vectorcall functions for each calling convention (pythonGH-13781) (python#14782)
1 parent 5dab5e7 commit bf8e82f

File tree

10 files changed

+372
-101
lines changed

10 files changed

+372
-101
lines changed

Include/descrobject.h

-3
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

-5
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ PyAPI_FUNC(PyObject *) _PyCFunction_FastCallDict(PyObject *func,
4646
PyObject *const *args,
4747
Py_ssize_t nargs,
4848
PyObject *kwargs);
49-
50-
PyAPI_FUNC(PyObject *) _PyCFunction_Vectorcall(PyObject *func,
51-
PyObject *const *stack,
52-
size_t nargsf,
53-
PyObject *kwnames);
5449
#endif
5550

5651
struct PyMethodDef {

Lib/test/test_call.py

+2
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

+4-4
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)
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

+1-20
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
206206
Py_DECREF(kwnames);
207207
}
208208

209-
return result;
209+
return _Py_CheckFunctionResult(callable, result, NULL);
210210
}
211211

212212

@@ -723,25 +723,6 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self,
723723
}
724724

725725

726-
PyObject *
727-
_PyCFunction_Vectorcall(PyObject *func,
728-
PyObject *const *args, size_t nargsf,
729-
PyObject *kwnames)
730-
{
731-
PyObject *result;
732-
733-
assert(func != NULL);
734-
assert(PyCFunction_Check(func));
735-
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
736-
737-
result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml,
738-
PyCFunction_GET_SELF(func),
739-
args, nargs, kwnames);
740-
result = _Py_CheckFunctionResult(func, result, NULL);
741-
return result;
742-
}
743-
744-
745726
static PyObject *
746727
cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs)
747728
{

Objects/descrobject.c

+196-50
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+
}
274339

340+
static PyObject *
341+
method_vectorcall_FASTCALL(
342+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
343+
{
275344
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);
345+
if (method_check_args(func, args, nargs, kwnames)) {
283346
return NULL;
284347
}
285-
self = args[0];
286-
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
287-
(PyObject *)PyDescr_TYPE(descr))) {
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+
}
357+
358+
static PyObject *
359+
method_vectorcall_FASTCALL_KEYWORDS(
360+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
361+
{
362+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
363+
if (method_check_args(func, args, nargs, NULL)) {
364+
return NULL;
365+
}
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);
294387
return NULL;
295388
}
389+
PyCFunction meth = (PyCFunction)method_enter_call(func);
390+
if (meth == NULL) {
391+
return NULL;
392+
}
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
static PyObject *
304423
classmethoddescr_call(PyMethodDescrObject *descr, PyObject *args,
305424
PyObject *kwds)
@@ -552,7 +671,7 @@ PyTypeObject PyMethodDescr_Type = {
552671
0, /* tp_as_sequence */
553672
0, /* tp_as_mapping */
554673
0, /* tp_hash */
555-
(ternaryfunc)methoddescr_call, /* tp_call */
674+
PyVectorcall_Call, /* tp_call */
556675
0, /* tp_str */
557676
PyObject_GenericGetAttr, /* tp_getattro */
558677
0, /* tp_setattro */
@@ -750,13 +869,40 @@ descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
750869
PyObject *
751870
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
752871
{
872+
/* Figure out correct vectorcall function to use */
873+
vectorcallfunc vectorcall;
874+
switch (method->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
875+
{
876+
case METH_VARARGS:
877+
vectorcall = method_vectorcall_VARARGS;
878+
break;
879+
case METH_VARARGS | METH_KEYWORDS:
880+
vectorcall = method_vectorcall_VARARGS_KEYWORDS;
881+
break;
882+
case METH_FASTCALL:
883+
vectorcall = method_vectorcall_FASTCALL;
884+
break;
885+
case METH_FASTCALL | METH_KEYWORDS:
886+
vectorcall = method_vectorcall_FASTCALL_KEYWORDS;
887+
break;
888+
case METH_NOARGS:
889+
vectorcall = method_vectorcall_NOARGS;
890+
break;
891+
case METH_O:
892+
vectorcall = method_vectorcall_O;
893+
break;
894+
default:
895+
PyErr_SetString(PyExc_SystemError, "bad call flags");
896+
return NULL;
897+
}
898+
753899
PyMethodDescrObject *descr;
754900

755901
descr = (PyMethodDescrObject *)descr_new(&PyMethodDescr_Type,
756902
type, method->ml_name);
757903
if (descr != NULL) {
758904
descr->d_method = method;
759-
descr->vectorcall = _PyMethodDescr_Vectorcall;
905+
descr->vectorcall = vectorcall;
760906
}
761907
return (PyObject *)descr;
762908
}

0 commit comments

Comments
 (0)