Skip to content

Commit 0f3206a

Browse files
committed
bpo-36974: separate vectorcall functions for each calling convention
1 parent a195212 commit 0f3206a

File tree

9 files changed

+281
-99
lines changed

9 files changed

+281
-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: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,15 @@ 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);
44+
#ifdef Py_BUILD_CORE
45+
extern PyObject * _PyCFunction_Vectorcall_FASTCALL(
46+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
47+
extern PyObject * _PyCFunction_Vectorcall_FASTCALL_KEYWORDS(
48+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
49+
extern PyObject * _PyCFunction_Vectorcall_NOARGS(
50+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
51+
extern PyObject * _PyCFunction_Vectorcall_O(
52+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
4953
#endif
5054

5155
struct PyMethodDef {

Lib/test/test_gdb.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -871,10 +871,10 @@ def test_pycfunction(self):
871871
# called, so test a variety of calling conventions.
872872
for py_name, py_args, c_name, expected_frame_number in (
873873
('gmtime', '', 'time_gmtime', 1), # METH_VARARGS
874-
('len', '[]', 'builtin_len', 2), # METH_O
875-
('locals', '', 'builtin_locals', 2), # METH_NOARGS
876-
('iter', '[]', 'builtin_iter', 2), # METH_FASTCALL
877-
('sorted', '[]', 'builtin_sorted', 2), # METH_FASTCALL|METH_KEYWORDS
874+
('len', '[]', 'builtin_len', 1), # METH_O
875+
('locals', '', 'builtin_locals', 1), # METH_NOARGS
876+
('iter', '[]', 'builtin_iter', 1), # METH_FASTCALL
877+
('sorted', '[]', 'builtin_sorted', 1), # METH_FASTCALL|METH_KEYWORDS
878878
):
879879
with self.subTest(c_name):
880880
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: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -625,22 +625,83 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self,
625625
}
626626

627627

628+
/* Vectorcall functions for each of the PyCFunction calling conventions,
629+
* except for METH_VARARGS (possibly combined with METH_KEYWORDS) which
630+
* doesn't use vectorcall.
631+
*
632+
* First two macros to define common boilerplate
633+
*/
634+
#define PyCFunction_VECTORCALL_BEGIN(ALLOW_KWARGS) \
635+
assert(!PyErr_Occurred()); \
636+
assert(PyCFunction_Check(func)); \
637+
PyCFunctionObject *f = (PyCFunctionObject *)func; \
638+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); \
639+
PyObject *self = f->m_self; \
640+
PyMethodDef *method = f->m_ml; \
641+
const char *name = method->ml_name; \
642+
void(*meth)(void) = (void(*)(void))method->ml_meth; \
643+
PyObject *result = NULL; \
644+
\
645+
if (!ALLOW_KWARGS && kwnames && PyTuple_GET_SIZE(kwnames) > 0) { \
646+
PyErr_Format(PyExc_TypeError, \
647+
"%.200s() takes no keyword arguments", name); \
648+
return NULL; \
649+
} \
650+
if (Py_EnterRecursiveCall(" while calling a Python object")) { \
651+
return NULL; \
652+
}
653+
654+
#define PyCFunction_VECTORCALL_END \
655+
Py_LeaveRecursiveCall(); \
656+
return _Py_CheckFunctionResult(func, result, NULL);
657+
658+
/* Now the actual vectorcall functions */
628659
PyObject *
629-
_PyCFunction_Vectorcall(PyObject *func,
630-
PyObject *const *args, size_t nargsf,
631-
PyObject *kwnames)
660+
_PyCFunction_Vectorcall_FASTCALL(
661+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
632662
{
633-
PyObject *result;
663+
PyCFunction_VECTORCALL_BEGIN(0)
664+
result = ((_PyCFunctionFast)meth)(self, args, nargs);
665+
PyCFunction_VECTORCALL_END
666+
}
634667

635-
assert(func != NULL);
636-
assert(PyCFunction_Check(func));
637-
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
668+
PyObject *
669+
_PyCFunction_Vectorcall_FASTCALL_KEYWORDS(
670+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
671+
{
672+
PyCFunction_VECTORCALL_BEGIN(1)
673+
result = ((_PyCFunctionFastWithKeywords)meth)(self, args, nargs, kwnames);
674+
PyCFunction_VECTORCALL_END
675+
}
638676

639-
result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml,
640-
PyCFunction_GET_SELF(func),
641-
args, nargs, kwnames);
642-
result = _Py_CheckFunctionResult(func, result, NULL);
643-
return result;
677+
PyObject *
678+
_PyCFunction_Vectorcall_NOARGS(
679+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
680+
{
681+
PyCFunction_VECTORCALL_BEGIN(0)
682+
if (nargs != 0) {
683+
PyErr_Format(PyExc_TypeError,
684+
"%.200s() takes no arguments (%zd given)", name, nargs);
685+
}
686+
else {
687+
result = ((PyCFunction)meth)(self, NULL);
688+
}
689+
PyCFunction_VECTORCALL_END
690+
}
691+
692+
PyObject *
693+
_PyCFunction_Vectorcall_O(
694+
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
695+
{
696+
PyCFunction_VECTORCALL_BEGIN(0)
697+
if (nargs != 1) {
698+
PyErr_Format(PyExc_TypeError,
699+
"%.200s() takes exactly one argument (%zd given)", name, nargs);
700+
}
701+
else {
702+
result = ((PyCFunction)meth)(self, args[0]);
703+
}
704+
PyCFunction_VECTORCALL_END
644705
}
645706

646707

0 commit comments

Comments
 (0)