From 099c58cd01e865588aebd1639094f29f6cd7ca9e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sat, 6 Oct 2018 17:06:05 +0100 Subject: [PATCH 1/9] bpo-36974: minimal implementation of PEP 590 --- Include/classobject.h | 1 + Include/cpython/abstract.h | 18 ++++- Include/cpython/object.h | 7 +- Include/descrobject.h | 1 + Include/funcobject.h | 2 + Include/methodobject.h | 3 + Include/object.h | 3 + Include/vectorcall.h | 35 ++++++++++ Lib/test/test_sys.py | 8 +-- Modules/_asynciomodule.c | 4 +- Modules/_csv.c | 6 +- Modules/_testcapimodule.c | 2 +- Modules/pyexpat.c | 2 +- Objects/call.c | 137 ++++++++++++++++++++++++------------- Objects/classobject.c | 46 ++++++++++++- Objects/descrobject.c | 7 +- Objects/funcobject.c | 3 + Objects/methodobject.c | 5 +- Objects/typeobject.c | 5 ++ Python/bltinmodule.c | 8 +-- Python/ceval.c | 69 +++++++++---------- Python/context.c | 4 +- Python/sysmodule.c | 4 +- 23 files changed, 269 insertions(+), 111 deletions(-) create mode 100644 Include/vectorcall.h diff --git a/Include/classobject.h b/Include/classobject.h index 209f0f4a284301..d6c9363a571b01 100644 --- a/Include/classobject.h +++ b/Include/classobject.h @@ -14,6 +14,7 @@ typedef struct { PyObject *im_func; /* The callable object implementing the method */ PyObject *im_self; /* The instance it is bound to */ PyObject *im_weakreflist; /* List of weak references */ + vectorcall_func vector_call; } PyMethodObject; PyAPI_DATA(PyTypeObject) PyMethod_Type; diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index b8b2d449faf306..2c34b8e8f8c7c7 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -56,7 +56,7 @@ PyAPI_FUNC(int) _PyStack_UnpackDict( #define _PY_FASTCALL_SMALL_STACK 5 /* Return 1 if callable supports FASTCALL calling convention for positional - arguments: see _PyObject_FastCallDict() and _PyObject_FastCallKeywords() */ + arguments: see _PyObject_FastCallDict() and PyObject_VectorCallWithCallable() */ PyAPI_FUNC(int) _PyObject_HasFastCall(PyObject *callable); /* Call the callable object 'callable' with the "fast call" calling convention: @@ -89,12 +89,24 @@ PyAPI_FUNC(PyObject *) _PyObject_FastCallDict( Return the result on success. Raise an exception and return NULL on error. */ -PyAPI_FUNC(PyObject *) _PyObject_FastCallKeywords( +PyAPI_FUNC(PyObject *) PyObject_VectorCallWithCallable( PyObject *callable, - PyObject *const *args, + PyObject **args, Py_ssize_t nargs, PyObject *kwnames); +PyObject * +PyObject_VectorCallWithCallable( + PyObject *callable, + PyObject **stack, + Py_ssize_t nargs, + PyObject *kwnames); + +PyObject* PyCall_MakeVectorCall(PyObject* callable, PyObject* tuple, PyObject* dict); + +PyObject * +PyCall_MakeTpCall(PyObject *callable, PyObject *const *stack, Py_ssize_t nargs, PyObject *kwnames); + #define _PyObject_FastCall(func, args, nargs) \ _PyObject_FastCallDict((func), (args), (nargs), NULL) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index ba52a4835823b6..f35701f3a12cd3 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -55,6 +55,10 @@ typedef struct bufferinfo { typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); typedef void (*releasebufferproc)(PyObject *, Py_buffer *); +typedef PyObject *(*vectorcall_func) + (PyObject *callable, PyObject *const *stack, + Py_ssize_t nargs, PyObject *kwnames); + /* Maximum number of dimensions */ #define PyBUF_MAX_NDIM 64 @@ -182,7 +186,7 @@ typedef struct _typeobject { /* Methods to implement standard operations */ destructor tp_dealloc; - printfunc tp_print; + vectorcall_func tp_vectorcall; getattrfunc tp_getattr; setattrfunc tp_setattr; PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) @@ -254,6 +258,7 @@ typedef struct _typeobject { unsigned int tp_version_tag; destructor tp_finalize; + uint32_t tp_vectorcall_offset; #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ diff --git a/Include/descrobject.h b/Include/descrobject.h index 73bbb3fe54e5d0..5c3ac1df5ff6ff 100644 --- a/Include/descrobject.h +++ b/Include/descrobject.h @@ -53,6 +53,7 @@ typedef struct { typedef struct { PyDescr_COMMON; PyMethodDef *d_method; + vectorcall_func vector_call; } PyMethodDescrObject; typedef struct { diff --git a/Include/funcobject.h b/Include/funcobject.h index 86674ac90a08de..9764dcfa736b90 100644 --- a/Include/funcobject.h +++ b/Include/funcobject.h @@ -7,6 +7,7 @@ extern "C" { #endif + /* Function objects and code objects should not be confused with each other: * * Function objects are created by the execution of the 'def' statement. @@ -32,6 +33,7 @@ typedef struct { PyObject *func_module; /* The __module__ attribute, can be anything */ PyObject *func_annotations; /* Annotations, a dict or NULL */ PyObject *func_qualname; /* The qualified name */ + vectorcall_func vector_call; /* Invariant: * func_closure contains the bindings for func_code->co_freevars, so diff --git a/Include/methodobject.h b/Include/methodobject.h index ea35d86bcd171d..c3ebdbdb193bbd 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -7,6 +7,8 @@ extern "C" { #endif +#include "vectorcall.h" + /* This is about the type 'builtin_function_or_method', not Python methods in user-defined classes. See classobject.h for the latter. */ @@ -105,6 +107,7 @@ typedef struct { PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */ PyObject *m_module; /* The __module__ attribute, can be anything */ PyObject *m_weakreflist; /* List of weak references */ + vectorcall_func vectorcall; } PyCFunctionObject; PyAPI_FUNC(PyObject *) _PyMethodDef_RawFastCallDict( diff --git a/Include/object.h b/Include/object.h index d5d98d3bd885be..b9a35a8309354b 100644 --- a/Include/object.h +++ b/Include/object.h @@ -338,6 +338,9 @@ given type object has a specified feature. /* Type structure has tp_finalize member (3.4) */ #define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0) +/* Type structure has tp_vectorcall_offset member (3.8) */ +#define Py_TPFLAGS_HAVE_VECTORCALL (1UL << 1) + #ifdef Py_LIMITED_API # define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0) #endif diff --git a/Include/vectorcall.h b/Include/vectorcall.h new file mode 100644 index 00000000000000..8f43e778e2771e --- /dev/null +++ b/Include/vectorcall.h @@ -0,0 +1,35 @@ +#include "object.h" +#include "pyport.h" +#include "abstract.h" + +#ifndef Py_VECTORCALL_H +#define Py_VECTORCALL_H +#ifdef __cplusplus +extern "C" { +#endif + +#define PY_VECTORCALL_ARGUMENTS_OFFSET INTPTR_MIN + +#ifndef Py_LIMITED_API +static inline PyObject * +_Py_VectorCall( + PyObject *callable, PyObject **stack, + Py_ssize_t nargs, PyObject *kwnames +) { + PyTypeObject *tp = Py_TYPE(callable); + uintptr_t offset = tp->tp_vectorcall_offset; + if (offset != 0) { + assert(tp->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL); + vectorcall_func func = *(vectorcall_func *)(((char *)callable) + offset); + if (func) { + return (func)(callable, stack, nargs, kwnames); + } + } + return PyCall_MakeTpCall(callable, stack, nargs, kwnames); +} +#endif + +#ifdef __cplusplus +} +#endif +#endif /* !Py_VECTORCALL_H */ diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index dfe63b1aade2eb..d15d3756e5fdf9 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1064,7 +1064,7 @@ def test_objecttypes(self): # buffer # XXX # builtin_function_or_method - check(len, size('4P')) # XXX check layout + check(len, size('5P')) # XXX check layout # bytearray samples = [b'', b'u'*100000] for sample in samples: @@ -1095,7 +1095,7 @@ def inner(): # complex check(complex(0,1), size('2d')) # method_descriptor (descriptor object) - check(str.lower, size('3PP')) + check(str.lower, size('3PPP')) # classmethod_descriptor (descriptor object) # XXX # member_descriptor (descriptor object) @@ -1164,7 +1164,7 @@ class C(object): pass check(x, vsize('5P2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass - check(func, size('12P')) + check(func, size('13P')) class c(): @staticmethod def foo(): @@ -1259,7 +1259,7 @@ def delx(self): del self.__x check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - fmt = 'P2n15Pl4Pn9Pn11PIP' + fmt = 'P2n15Pl4Pn9Pn11PIPI' if hasattr(sys, 'getcounts'): fmt += '3n2P' s = vsize(fmt) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index ac15d0169b8158..4dbd334efcb75e 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -367,7 +367,7 @@ call_soon(PyObject *loop, PyObject *func, PyObject *arg, PyObject *ctx) } stack[nargs] = (PyObject *)ctx; - handle = _PyObject_FastCallKeywords( + handle = PyObject_VectorCallWithCallable( callable, stack, nargs, context_kwname); Py_DECREF(callable); } @@ -2805,7 +2805,7 @@ task_step_impl(TaskObj *task, PyObject *exc) PyObject *stack[2]; stack[0] = wrapper; stack[1] = (PyObject *)task->task_context; - res = _PyObject_FastCallKeywords( + res = PyObject_VectorCallWithCallable( add_cb, stack, 1, context_kwname); Py_DECREF(add_cb); Py_DECREF(wrapper); diff --git a/Modules/_csv.c b/Modules/_csv.c index e31b158c601e86..8101a86ae2584b 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -469,7 +469,7 @@ static PyTypeObject Dialect_Type = { 0, /* tp_itemsize */ /* methods */ (destructor)Dialect_dealloc, /* tp_dealloc */ - (printfunc)0, /* tp_print */ + 0, /* tp_print */ (getattrfunc)0, /* tp_getattr */ (setattrfunc)0, /* tp_setattr */ 0, /* tp_reserved */ @@ -902,7 +902,7 @@ static PyTypeObject Reader_Type = { 0, /*tp_itemsize*/ /* methods */ (destructor)Reader_dealloc, /*tp_dealloc*/ - (printfunc)0, /*tp_print*/ + 0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ 0, /*tp_reserved*/ @@ -1332,7 +1332,7 @@ static PyTypeObject Writer_Type = { 0, /*tp_itemsize*/ /* methods */ (destructor)Writer_dealloc, /*tp_dealloc*/ - (printfunc)0, /*tp_print*/ + 0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ 0, /*tp_reserved*/ diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 8ba927039c2705..25d4b94b059dc2 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4742,7 +4742,7 @@ test_pyobject_fastcallkeywords(PyObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "kwnames must be None or a tuple"); return NULL; } - return _PyObject_FastCallKeywords(func, stack, nargs, kwnames); + return PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); } diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 2e8be3706db914..29143d589a14da 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1468,7 +1468,7 @@ static PyTypeObject Xmlparsetype = { 0, /*tp_itemsize*/ /* methods */ (destructor)xmlparse_dealloc, /*tp_dealloc*/ - (printfunc)0, /*tp_print*/ + 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_reserved*/ diff --git a/Objects/call.c b/Objects/call.c index b608492dd6bef5..c1e0cd28a3ce04 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -3,6 +3,7 @@ #include "pycore_pystate.h" #include "pycore_tupleobject.h" #include "frameobject.h" +#include "vectorcall.h" int @@ -136,80 +137,115 @@ _PyObject_FastCallDict(PyObject *callable, PyObject *const *args, Py_ssize_t nar PyObject * -_PyObject_FastCallKeywords(PyObject *callable, PyObject *const *stack, Py_ssize_t nargs, +PyObject_VectorCallWithCallable(PyObject *callable, PyObject **stack, Py_ssize_t nargs, PyObject *kwnames) { - /* _PyObject_FastCallKeywords() must not be called with an exception set, + /* PyObject_VectorCallWithCallable() must not be called with an exception set, because it can clear it (directly or indirectly) and so the caller loses its exception */ assert(!PyErr_Occurred()); - - assert(nargs >= 0); assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); /* kwnames must only contains str strings, no subclass, and all keys must be unique: these checks are implemented in Python/ceval.c and _PyArg_ParseStackAndKeywords(). */ - if (PyFunction_Check(callable)) { - return _PyFunction_FastCallKeywords(callable, stack, nargs, kwnames); - } - if (PyCFunction_Check(callable)) { - return _PyCFunction_FastCallKeywords(callable, stack, nargs, kwnames); - } - else { - /* Slow-path: build a temporary tuple for positional arguments and a - temporary dictionary for keyword arguments (if any) */ + return _Py_VectorCall(callable, stack, nargs, kwnames); +} - ternaryfunc call; - PyObject *argstuple; - PyObject *kwdict, *result; - Py_ssize_t nkwargs; +PyObject * +PyCall_MakeTpCall(PyObject *callable, PyObject *const *stack, Py_ssize_t nargs, PyObject *kwnames) { + /* Slow-path: build a temporary tuple for positional arguments and a + temporary dictionary for keyword arguments (if any) */ - nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); - assert((nargs == 0 && nkwargs == 0) || stack != NULL); + ternaryfunc call; + PyObject *argstuple; + PyObject *kwdict, *result; + Py_ssize_t nkwargs; - call = callable->ob_type->tp_call; - if (call == NULL) { - PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", - callable->ob_type->tp_name); - return NULL; - } + nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); - argstuple = _PyTuple_FromArray(stack, nargs); - if (argstuple == NULL) { - return NULL; - } + nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + assert((nargs == 0 && nkwargs == 0) || stack != NULL); - if (nkwargs > 0) { - kwdict = _PyStack_AsDict(stack + nargs, kwnames); - if (kwdict == NULL) { - Py_DECREF(argstuple); - return NULL; - } - } - else { - kwdict = NULL; - } + call = callable->ob_type->tp_call; + if (call == NULL) { + PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", + callable->ob_type->tp_name); + return NULL; + } - if (Py_EnterRecursiveCall(" while calling a Python object")) { + argstuple = _PyTuple_FromArray(stack, nargs); + if (argstuple == NULL) { + return NULL; + } + + if (nkwargs > 0) { + kwdict = _PyStack_AsDict(stack + nargs, kwnames); + if (kwdict == NULL) { Py_DECREF(argstuple); - Py_XDECREF(kwdict); return NULL; } + } + else { + kwdict = NULL; + } - result = (*call)(callable, argstuple, kwdict); - - Py_LeaveRecursiveCall(); - + if (Py_EnterRecursiveCall(" while calling a Python object")) { Py_DECREF(argstuple); Py_XDECREF(kwdict); - - result = _Py_CheckFunctionResult(callable, result, NULL); - return result; + return NULL; } + + result = (*call)(callable, argstuple, kwdict); + + Py_LeaveRecursiveCall(); + + Py_DECREF(argstuple); + Py_XDECREF(kwdict); + + result = _Py_CheckFunctionResult(callable, result, NULL); + return result; +} + +PyObject * +PyObject_VectorCall(PyObject **stack, Py_ssize_t nargs, + PyObject *kwnames) +{ + return PyObject_VectorCallWithCallable(stack[0], &stack[1], (nargs-1)|PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); } +PyObject * +PyCall_MakeVectorCall(PyObject *callable, PyObject *tuple, PyObject *kwargs) { + PyTypeObject *tp = Py_TYPE(callable); + vectorcall_func func = NULL; + uintptr_t offset = tp->tp_vectorcall_offset; + if (offset != 0) { + assert(tp->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL); + func = *(vectorcall_func *)(((char *)callable) + offset); + } + if (func == NULL) { + PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", + callable->ob_type->tp_name); + return NULL; + } + PyObject * const*stack; + Py_ssize_t nargs = PyTuple_GET_SIZE(tuple); + PyObject *kwnames, *result; + if (_PyStack_UnpackDict(&PyTuple_GET_ITEM(tuple, 0), nargs, + kwargs, &stack, &kwnames) < 0) { + return NULL; + } + /* It's OK to discard the `const` qualifier as PY_VECTORCALL_ARGUMENTS_OFFSET is not + * set, so the stack will not be modified. */ + result = (*func) (callable, stack, nargs, kwnames); + if (stack != &PyTuple_GET_ITEM(tuple, 0)) { + PyMem_Free((PyObject **)stack); + } + + return result; + +} PyObject * PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs) @@ -385,7 +421,7 @@ _PyFunction_FastCallDict(PyObject *func, PyObject *const *args, Py_ssize_t nargs } PyObject * -_PyFunction_FastCallKeywords(PyObject *func, PyObject *const *stack, +_PyFunction_FastCallKeywords(PyObject *func, PyObject* const* stack, Py_ssize_t nargs, PyObject *kwnames) { PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func); @@ -397,6 +433,7 @@ _PyFunction_FastCallKeywords(PyObject *func, PyObject *const *stack, Py_ssize_t nd; assert(PyFunction_Check(func)); + nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; assert(nargs >= 0); assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); assert((nargs == 0 && nkwargs == 0) || stack != NULL); @@ -605,6 +642,7 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self, assert(method != NULL); assert(nargs >= 0); + nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); /* kwnames must only contains str strings, no subclass, and all keys must be unique */ @@ -732,6 +770,7 @@ _PyCFunction_FastCallKeywords(PyObject *func, assert(func != NULL); assert(PyCFunction_Check(func)); + nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml, PyCFunction_GET_SELF(func), diff --git a/Objects/classobject.c b/Objects/classobject.c index 1ee897847fb03e..74e786e856cead 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -40,6 +40,47 @@ PyMethod_Self(PyObject *im) return ((PyMethodObject *)im)->im_self; } +/* Vector call for bound-methods */ +static PyObject * +method_vector_call(PyObject *method, PyObject *const *stack, + Py_ssize_t nargs, PyObject *kwnames) +{ + assert(Py_TYPE(method) == &PyMethod_Type); + PyObject *self, *func, *result; + self = PyMethod_GET_SELF(method); + func = PyMethod_GET_FUNCTION(method); + + if (nargs & PY_VECTORCALL_ARGUMENTS_OFFSET) { + nargs = (nargs&~PY_VECTORCALL_ARGUMENTS_OFFSET)+1; + /* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */ + PyObject **args = (PyObject**)stack-1; + PyObject *tmp = args[0]; + args[0] = self; + result = _Py_VectorCall(func, args, nargs, kwnames); + args[0] = tmp; + } + else { + Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); + PyObject **newstack; + Py_ssize_t totalargs = nargs+nkwargs; + newstack = PyMem_Malloc((totalargs+1) * sizeof(PyObject *)); + if (newstack == NULL) { + PyErr_NoMemory(); + return NULL; + } + /* use borrowed references */ + newstack[0] = self; + if (totalargs > 0) { + memcpy(&newstack[1], stack, totalargs * sizeof(PyObject *)); + } + result = _Py_VectorCall(func, newstack, nargs+1, kwnames); + PyMem_Free(newstack); + } + return result; +} + + + /* Method objects are used for bound instance methods returned by instancename.methodname. ClassName.methodname returns an ordinary function. @@ -69,6 +110,7 @@ PyMethod_New(PyObject *func, PyObject *self) im->im_func = func; Py_XINCREF(self); im->im_self = self; + im->vector_call = method_vector_call; _PyObject_GC_TRACK(im); return (PyObject *)im; } @@ -323,7 +365,8 @@ PyTypeObject PyMethod_Type = { method_getattro, /* tp_getattro */ PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ method_doc, /* tp_doc */ (traverseproc)method_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -342,6 +385,7 @@ PyTypeObject PyMethod_Type = { 0, /* tp_init */ 0, /* tp_alloc */ method_new, /* tp_new */ + .tp_vectorcall_offset = offsetof(PyMethodObject, vector_call), }; /* Clear out the free list */ diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 6c99f9b211b93b..df7afdfff952cc 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -272,6 +272,7 @@ _PyMethodDescr_FastCallKeywords(PyObject *descrobj, PyMethodDescrObject *descr = (PyMethodDescrObject *)descrobj; PyObject *self, *result; + nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; /* Make sure that the first argument is acceptable as 'self' */ if (nargs < 1) { PyErr_Format(PyExc_TypeError, @@ -557,6 +558,7 @@ PyTypeObject PyMethodDescr_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ 0, /* tp_doc */ descr_traverse, /* tp_traverse */ @@ -572,6 +574,7 @@ PyTypeObject PyMethodDescr_Type = { 0, /* tp_dict */ (descrgetfunc)method_get, /* tp_descr_get */ 0, /* tp_descr_set */ + .tp_vectorcall_offset = offsetof(PyMethodDescrObject, vector_call), }; /* This is for METH_CLASS in C, not for "f = classmethod(f)" in Python! */ @@ -752,8 +755,10 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method) descr = (PyMethodDescrObject *)descr_new(&PyMethodDescr_Type, type, method->ml_name); - if (descr != NULL) + if (descr != NULL) { descr->d_method = method; + descr->vector_call = &_PyMethodDescr_FastCallKeywords; + } return (PyObject *)descr; } diff --git a/Objects/funcobject.c b/Objects/funcobject.c index fb7abfacb2e40b..d544c78ee6362c 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -36,6 +36,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname op->func_defaults = NULL; /* No default arguments */ op->func_kwdefaults = NULL; /* No keyword only defaults */ op->func_closure = NULL; + op->vector_call = _PyFunction_FastCallKeywords; consts = ((PyCodeObject *)code)->co_consts; if (PyTuple_Size(consts) >= 1) { @@ -664,6 +665,7 @@ PyTypeObject PyFunction_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ func_new__doc__, /* tp_doc */ (traverseproc)func_traverse, /* tp_traverse */ @@ -683,6 +685,7 @@ PyTypeObject PyFunction_Type = { 0, /* tp_init */ 0, /* tp_alloc */ func_new, /* tp_new */ + .tp_vectorcall_offset = offsetof(PyFunctionObject, vector_call), }; diff --git a/Objects/methodobject.c b/Objects/methodobject.c index 9fed3fca99be9f..f299d6390ccdbc 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -46,6 +46,7 @@ PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module) op->m_self = self; Py_XINCREF(module); op->m_module = module; + op->vectorcall = &_PyCFunction_FastCallKeywords; _PyObject_GC_TRACK(op); return (PyObject *)op; } @@ -278,7 +279,8 @@ PyTypeObject PyCFunction_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ 0, /* tp_doc */ (traverseproc)meth_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -291,6 +293,7 @@ PyTypeObject PyCFunction_Type = { meth_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ + .tp_vectorcall_offset = offsetof(PyCFunctionObject, vectorcall), }; /* Clear out the free list */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c14cbad875b443..1cc8e9eab87b6e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5221,6 +5221,11 @@ PyType_Ready(PyTypeObject *type) (type->tp_flags & Py_TPFLAGS_READYING) == 0); type->tp_flags |= Py_TPFLAGS_READYING; + + /* Make sure that Py_TPFLAGS_HAVE_VECTORCALL and tp_vectorcall_offset are consistent */ + if ((type->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) == 0) { + type->tp_vectorcall_offset = 0; + } #ifdef Py_TRACE_REFS /* PyType_Ready is the closest thing we have to a choke point diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 5d5808530e1197..ead7cb8a0379fd 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -474,7 +474,7 @@ builtin_callable(PyObject *module, PyObject *obj) } static PyObject * -builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) +builtin_breakpoint(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *keywords) { PyObject *hook = PySys_GetObject("breakpointhook"); @@ -483,7 +483,7 @@ builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyOb return NULL; } Py_INCREF(hook); - PyObject *retval = _PyObject_FastCallKeywords(hook, args, nargs, keywords); + PyObject *retval = PyObject_VectorCallWithCallable(hook, args, nargs, keywords); Py_DECREF(hook); return retval; } @@ -2260,7 +2260,7 @@ PyDoc_STRVAR(builtin_sorted__doc__, {"sorted", (PyCFunction)(void(*)(void))builtin_sorted, METH_FASTCALL | METH_KEYWORDS, builtin_sorted__doc__}, static PyObject * -builtin_sorted(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +builtin_sorted(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *newlist, *v, *seq, *callable; @@ -2280,7 +2280,7 @@ builtin_sorted(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject } assert(nargs >= 1); - v = _PyObject_FastCallKeywords(callable, args + 1, nargs - 1, kwnames); + v = PyObject_VectorCallWithCallable(callable, args + 1, nargs - 1, kwnames); Py_DECREF(callable); if (v == NULL) { Py_DECREF(newlist); diff --git a/Python/ceval.c b/Python/ceval.c index 9263df9b8fc490..20be650482bd8f 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4806,10 +4806,9 @@ if (tstate->use_tracing && tstate->c_profilefunc) { \ x = call; \ } -/* Issue #29227: Inline call_function() into _PyEval_EvalFrameDefault() - to reduce the stack consumption. */ -Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION -call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) + +static PyObject * +trace_call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) { PyObject **pfunc = (*pp_stack) - oparg - 1; PyObject *func = *pfunc; @@ -4817,15 +4816,14 @@ call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyO Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); Py_ssize_t nargs = oparg - nkwargs; PyObject **stack = (*pp_stack) - nargs - nkwargs; - - /* Always dispatch PyCFunction first, because these are - presumed to be the most frequent callable object. - */ - if (PyCFunction_Check(func)) { + if (!tstate->use_tracing) { + x = _Py_VectorCall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); + } + else if (PyCFunction_Check(func)) { C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames)); } else if (Py_TYPE(func) == &PyMethodDescr_Type) { - if (nargs > 0 && tstate->use_tracing) { + if (nargs > 0) { /* We need to create a temporary bound method as argument for profiling. @@ -4846,35 +4844,11 @@ call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyO } } else { - x = _PyMethodDescr_FastCallKeywords(func, stack, nargs, kwnames); + x = PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); } } else { - if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) { - /* Optimize access to bound methods. Reuse the Python stack - to pass 'self' as the first argument, replace 'func' - with 'self'. It avoids the creation of a new temporary tuple - for arguments (to replace func with self) when the method uses - FASTCALL. */ - PyObject *self = PyMethod_GET_SELF(func); - Py_INCREF(self); - func = PyMethod_GET_FUNCTION(func); - Py_INCREF(func); - Py_SETREF(*pfunc, self); - nargs++; - stack--; - } - else { - Py_INCREF(func); - } - - if (PyFunction_Check(func)) { - x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames); - } - else { - x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames); - } - Py_DECREF(func); + x = PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); } assert((x != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -4888,6 +4862,29 @@ call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyO return x; } +/* Issue #29227: Inline call_function() into _PyEval_EvalFrameDefault() + to reduce the stack consumption. */ +Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION +call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) +{ + PyObject **pfunc = (*pp_stack) - oparg - 1; + PyObject *func = *pfunc; + PyObject *x, *w; + Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); + Py_ssize_t nargs = oparg - nkwargs; + PyObject **stack = (*pp_stack) - nargs - nkwargs; + if (!tstate->use_tracing) { + x = _Py_VectorCall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); + /* Clear the stack of the function object. */ + while ((*pp_stack) > pfunc) { + w = EXT_POP(*pp_stack); + Py_DECREF(w); + } + return x; + } + return trace_call_function(tstate, pp_stack, oparg, kwnames); +} + static PyObject * do_call_core(PyThreadState *tstate, PyObject *func, PyObject *callargs, PyObject *kwdict) { diff --git a/Python/context.c b/Python/context.c index 9a50ea91a77ee3..4e2f7ba0840e66 100644 --- a/Python/context.c +++ b/Python/context.c @@ -618,7 +618,7 @@ _contextvars_Context_copy_impl(PyContext *self) static PyObject * -context_run(PyContext *self, PyObject *const *args, +context_run(PyContext *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames) { if (nargs < 1) { @@ -631,7 +631,7 @@ context_run(PyContext *self, PyObject *const *args, return NULL; } - PyObject *call_result = _PyObject_FastCallKeywords( + PyObject *call_result = PyObject_VectorCallWithCallable( args[0], args + 1, nargs - 1, kwnames); if (PyContext_Exit((PyObject *)self)) { diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 343601ec8596f1..486a290560eb33 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -416,7 +416,7 @@ sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc) static PyObject * -sys_breakpointhook(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) +sys_breakpointhook(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *keywords) { assert(!PyErr_Occurred()); char *envar = Py_GETENV("PYTHONBREAKPOINT"); @@ -481,7 +481,7 @@ sys_breakpointhook(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyOb return NULL; } PyMem_RawFree(envar); - PyObject *retval = _PyObject_FastCallKeywords(hook, args, nargs, keywords); + PyObject *retval = PyObject_VectorCallWithCallable(hook, args, nargs, keywords); Py_DECREF(hook); return retval; From 01f3709e4ee968b31527d025aea5664708b95e16 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 22 May 2019 10:36:15 +0200 Subject: [PATCH 2/9] bpo-36974: implement PEP 590 --- Include/classobject.h | 2 +- Include/cpython/abstract.h | 116 ++++++---- Include/cpython/object.h | 18 +- Include/descrobject.h | 4 +- Include/funcobject.h | 5 +- Include/methodobject.h | 6 +- Include/object.h | 8 +- Include/vectorcall.h | 35 --- Lib/test/test_call.py | 6 +- Lib/test/test_sys.py | 4 +- .../2019-05-22-15-24-08.bpo-36974.TkySRe.rst | 2 + Modules/_asynciomodule.c | 6 +- Modules/_csv.c | 6 +- Modules/_testcapimodule.c | 2 +- Modules/pyexpat.c | 2 +- Objects/call.c | 219 ++++++++---------- Objects/classobject.c | 49 ++-- Objects/descrobject.c | 11 +- Objects/funcobject.c | 7 +- Objects/methodobject.c | 14 +- Objects/typeobject.c | 5 - Python/bltinmodule.c | 8 +- Python/ceval.c | 93 ++++---- Python/context.c | 4 +- Python/sysmodule.c | 4 +- 25 files changed, 285 insertions(+), 351 deletions(-) delete mode 100644 Include/vectorcall.h create mode 100644 Misc/NEWS.d/next/C API/2019-05-22-15-24-08.bpo-36974.TkySRe.rst diff --git a/Include/classobject.h b/Include/classobject.h index d6c9363a571b01..c83303c3900553 100644 --- a/Include/classobject.h +++ b/Include/classobject.h @@ -14,7 +14,7 @@ typedef struct { PyObject *im_func; /* The callable object implementing the method */ PyObject *im_self; /* The instance it is bound to */ PyObject *im_weakreflist; /* List of weak references */ - vectorcall_func vector_call; + vectorcallfunc vectorcall; } PyMethodObject; PyAPI_DATA(PyTypeObject) PyMethod_Type; diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 2c34b8e8f8c7c7..65c5a9e4c43414 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -47,7 +47,7 @@ PyAPI_FUNC(int) _PyStack_UnpackDict( /* Suggested size (number of positional arguments) for arrays of PyObject* allocated on a C stack to avoid allocating memory on the heap memory. Such array is used to pass positional arguments to call functions of the - _PyObject_FastCall() family. + _PyObject_Vectorcall() family. The size is chosen to not abuse the C stack and so limit the risk of stack overflow. The size is also chosen to allow using the small stack for most @@ -56,62 +56,96 @@ PyAPI_FUNC(int) _PyStack_UnpackDict( #define _PY_FASTCALL_SMALL_STACK 5 /* Return 1 if callable supports FASTCALL calling convention for positional - arguments: see _PyObject_FastCallDict() and PyObject_VectorCallWithCallable() */ + arguments: see _PyObject_Vectorcall() and _PyObject_FastCallDict() */ PyAPI_FUNC(int) _PyObject_HasFastCall(PyObject *callable); -/* Call the callable object 'callable' with the "fast call" calling convention: - args is a C array for positional arguments (nargs is the number of - positional arguments), kwargs is a dictionary for keyword arguments. +PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *callable, + PyObject *result, + const char *where); - If nargs is equal to zero, args can be NULL. kwargs can be NULL. - nargs must be greater or equal to zero. +/* === Vectorcall protocol (PEP 590) ============================= */ - Return the result on success. Raise an exception and return NULL on - error. */ -PyAPI_FUNC(PyObject *) _PyObject_FastCallDict( +/* Call callable using tp_call. Arguments are like _PyObject_Vectorcall() + or _PyObject_FastCallDict() (both forms are supported), + except that nargs is plainly the number of arguments without flags. */ +PyAPI_FUNC(PyObject *) _PyObject_MakeTpCall( PyObject *callable, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwargs); + PyObject *const *args, Py_ssize_t nargs, + PyObject *keywords); -/* Call the callable object 'callable' with the "fast call" calling convention: - args is a C array for positional arguments followed by values of - keyword arguments. Keys of keyword arguments are stored as a tuple - of strings in kwnames. nargs is the number of positional parameters at - the beginning of stack. The size of kwnames gives the number of keyword - values in the stack after positional arguments. +#define PY_VECTORCALL_ARGUMENTS_OFFSET ((size_t)1 << (8 * sizeof(size_t) - 1)) - kwnames must only contains str strings, no subclass, and all keys must - be unique. +static inline Py_ssize_t +PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} + +static inline vectorcallfunc +_PyVectorcall_Function(PyObject *callable) +{ + PyTypeObject *tp = Py_TYPE(callable); + if (!PyType_HasFeature(tp, _Py_TPFLAGS_HAVE_VECTORCALL)) { + return NULL; + } + assert(PyCallable_Check(callable)); + Py_ssize_t offset = tp->tp_vectorcall_offset; + assert(offset > 0); + vectorcallfunc *ptr = (vectorcallfunc *)(((char *)callable) + offset); + return *ptr; +} + +/* Call the callable object 'callable' with the "vectorcall" calling + convention. + + args is a C array for positional arguments. + + nargsf is the number of positional arguments plus optionally the flag + PY_VECTORCALL_ARGUMENTS_OFFSET which means that the caller is allowed to + modify args[-1]. - If nargs is equal to zero and there is no keyword argument (kwnames is - NULL or its size is zero), args can be NULL. + kwnames is a tuple of keyword names. The values of the keyword arguments + are stored in "args" after the positional arguments (note that the number + of keyword arguments does not change nargsf). kwnames can also be NULL if + there are no keyword arguments. + + keywords must only contains str strings (no subclass), and all keys must + be unique. Return the result on success. Raise an exception and return NULL on error. */ -PyAPI_FUNC(PyObject *) PyObject_VectorCallWithCallable( - PyObject *callable, - PyObject **args, - Py_ssize_t nargs, - PyObject *kwnames); +static inline PyObject * +_PyObject_Vectorcall(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + assert(kwnames == NULL || PyTuple_Check(kwnames)); + assert(args != NULL || PyVectorcall_NARGS(nargsf) == 0); + vectorcallfunc func = _PyVectorcall_Function(callable); + if (func == NULL) { + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + return _PyObject_MakeTpCall(callable, args, nargs, kwnames); + } + PyObject *res = func(callable, args, nargsf, kwnames); + return _Py_CheckFunctionResult(callable, res, NULL); +} -PyObject * -PyObject_VectorCallWithCallable( +/* Same as _PyObject_Vectorcall except that keyword arguments are passed as + dict, which may be NULL if there are no keyword arguments. */ +PyAPI_FUNC(PyObject *) _PyObject_FastCallDict( PyObject *callable, - PyObject **stack, - Py_ssize_t nargs, - PyObject *kwnames); - -PyObject* PyCall_MakeVectorCall(PyObject* callable, PyObject* tuple, PyObject* dict); + PyObject *const *args, + size_t nargsf, + PyObject *kwargs); -PyObject * -PyCall_MakeTpCall(PyObject *callable, PyObject *const *stack, Py_ssize_t nargs, PyObject *kwnames); +/* Call "callable" (which must support vectorcall) with positional arguments + "tuple" and keyword arguments "dict". "dict" may also be NULL */ +PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict); #define _PyObject_FastCall(func, args, nargs) \ - _PyObject_FastCallDict((func), (args), (nargs), NULL) + _PyObject_Vectorcall((func), (args), (nargs), NULL) #define _PyObject_CallNoArg(func) \ - _PyObject_FastCallDict((func), NULL, 0, NULL) + _PyObject_Vectorcall((func), NULL, 0, NULL) PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend( PyObject *callable, @@ -125,10 +159,6 @@ PyAPI_FUNC(PyObject *) _PyObject_FastCall_Prepend( PyObject *const *args, Py_ssize_t nargs); -PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *callable, - PyObject *result, - const char *where); - /* Like PyObject_CallMethod(), but expect a _Py_Identifier* as the method name. */ PyAPI_FUNC(PyObject *) _PyObject_CallMethodId(PyObject *obj, diff --git a/Include/cpython/object.h b/Include/cpython/object.h index f35701f3a12cd3..a65aaf6482159c 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -55,9 +55,8 @@ typedef struct bufferinfo { typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); typedef void (*releasebufferproc)(PyObject *, Py_buffer *); -typedef PyObject *(*vectorcall_func) - (PyObject *callable, PyObject *const *stack, - Py_ssize_t nargs, PyObject *kwnames); +typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames); /* Maximum number of dimensions */ #define PyBUF_MAX_NDIM 64 @@ -171,12 +170,9 @@ typedef struct { releasebufferproc bf_releasebuffer; } PyBufferProcs; -/* We can't provide a full compile-time check that limited-API - users won't implement tp_print. However, not defining printfunc - and making tp_print of a different function pointer type - if Py_LIMITED_API is set should at least cause a warning - in most cases. */ -typedef int (*printfunc)(PyObject *, FILE *, int); +/* Allow printfunc in the tp_vectorcall_offset slot for + * backwards-compatibility */ +typedef Py_ssize_t printfunc; typedef struct _typeobject { PyObject_VAR_HEAD @@ -186,7 +182,7 @@ typedef struct _typeobject { /* Methods to implement standard operations */ destructor tp_dealloc; - vectorcall_func tp_vectorcall; + Py_ssize_t tp_vectorcall_offset; getattrfunc tp_getattr; setattrfunc tp_setattr; PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) @@ -258,7 +254,7 @@ typedef struct _typeobject { unsigned int tp_version_tag; destructor tp_finalize; - uint32_t tp_vectorcall_offset; + vectorcallfunc tp_vectorcall; #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ diff --git a/Include/descrobject.h b/Include/descrobject.h index 5c3ac1df5ff6ff..3db09635399cc2 100644 --- a/Include/descrobject.h +++ b/Include/descrobject.h @@ -53,7 +53,7 @@ typedef struct { typedef struct { PyDescr_COMMON; PyMethodDef *d_method; - vectorcall_func vector_call; + vectorcallfunc vectorcall; } PyMethodDescrObject; typedef struct { @@ -93,7 +93,7 @@ PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *, #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyMethodDescr_FastCallKeywords( - PyObject *descrobj, PyObject *const *stack, Py_ssize_t nargs, PyObject *kwnames); + 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) diff --git a/Include/funcobject.h b/Include/funcobject.h index 9764dcfa736b90..7ba000e1f13cc2 100644 --- a/Include/funcobject.h +++ b/Include/funcobject.h @@ -7,7 +7,6 @@ extern "C" { #endif - /* Function objects and code objects should not be confused with each other: * * Function objects are created by the execution of the 'def' statement. @@ -33,7 +32,7 @@ typedef struct { PyObject *func_module; /* The __module__ attribute, can be anything */ PyObject *func_annotations; /* Annotations, a dict or NULL */ PyObject *func_qualname; /* The qualified name */ - vectorcall_func vector_call; + vectorcallfunc vectorcall; /* Invariant: * func_closure contains the bindings for func_code->co_freevars, so @@ -70,7 +69,7 @@ PyAPI_FUNC(PyObject *) _PyFunction_FastCallDict( PyAPI_FUNC(PyObject *) _PyFunction_FastCallKeywords( PyObject *func, PyObject *const *stack, - Py_ssize_t nargs, + size_t nargsf, PyObject *kwnames); #endif diff --git a/Include/methodobject.h b/Include/methodobject.h index c3ebdbdb193bbd..5dbe2145dadc39 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -7,8 +7,6 @@ extern "C" { #endif -#include "vectorcall.h" - /* This is about the type 'builtin_function_or_method', not Python methods in user-defined classes. See classobject.h for the latter. */ @@ -51,7 +49,7 @@ PyAPI_FUNC(PyObject *) _PyCFunction_FastCallDict(PyObject *func, PyAPI_FUNC(PyObject *) _PyCFunction_FastCallKeywords(PyObject *func, PyObject *const *stack, - Py_ssize_t nargs, + size_t nargsf, PyObject *kwnames); #endif @@ -107,7 +105,7 @@ typedef struct { PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */ PyObject *m_module; /* The __module__ attribute, can be anything */ PyObject *m_weakreflist; /* List of weak references */ - vectorcall_func vectorcall; + vectorcallfunc vectorcall; } PyCFunctionObject; PyAPI_FUNC(PyObject *) _PyMethodDef_RawFastCallDict( diff --git a/Include/object.h b/Include/object.h index b9a35a8309354b..11ba2bb8e2388c 100644 --- a/Include/object.h +++ b/Include/object.h @@ -291,6 +291,11 @@ given type object has a specified feature. /* Set if the type allows subclassing */ #define Py_TPFLAGS_BASETYPE (1UL << 10) +/* Set if the type implements the vectorcall protocol (PEP 590) */ +#ifndef Py_LIMITED_API +#define _Py_TPFLAGS_HAVE_VECTORCALL (1UL << 11) +#endif + /* Set if the type is 'ready' -- fully initialized */ #define Py_TPFLAGS_READY (1UL << 12) @@ -338,9 +343,6 @@ given type object has a specified feature. /* Type structure has tp_finalize member (3.4) */ #define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0) -/* Type structure has tp_vectorcall_offset member (3.8) */ -#define Py_TPFLAGS_HAVE_VECTORCALL (1UL << 1) - #ifdef Py_LIMITED_API # define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0) #endif diff --git a/Include/vectorcall.h b/Include/vectorcall.h deleted file mode 100644 index 8f43e778e2771e..00000000000000 --- a/Include/vectorcall.h +++ /dev/null @@ -1,35 +0,0 @@ -#include "object.h" -#include "pyport.h" -#include "abstract.h" - -#ifndef Py_VECTORCALL_H -#define Py_VECTORCALL_H -#ifdef __cplusplus -extern "C" { -#endif - -#define PY_VECTORCALL_ARGUMENTS_OFFSET INTPTR_MIN - -#ifndef Py_LIMITED_API -static inline PyObject * -_Py_VectorCall( - PyObject *callable, PyObject **stack, - Py_ssize_t nargs, PyObject *kwnames -) { - PyTypeObject *tp = Py_TYPE(callable); - uintptr_t offset = tp->tp_vectorcall_offset; - if (offset != 0) { - assert(tp->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL); - vectorcall_func func = *(vectorcall_func *)(((char *)callable) + offset); - if (func) { - return (func)(callable, stack, nargs, kwnames); - } - } - return PyCall_MakeTpCall(callable, stack, nargs, kwnames); -} -#endif - -#ifdef __cplusplus -} -#endif -#endif /* !Py_VECTORCALL_H */ diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index e4ab33cbc16b58..a8874203e0adaf 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -402,7 +402,7 @@ def test_fastcall(self): result = _testcapi.pyobject_fastcall(func, None) self.check_result(result, expected) - def test_fastcall_dict(self): + def test_vectorcall_dict(self): # Test _PyObject_FastCallDict() for func, args, expected in self.CALLS_POSARGS: @@ -429,8 +429,8 @@ def test_fastcall_dict(self): result = _testcapi.pyobject_fastcalldict(func, args, kwargs) self.check_result(result, expected) - def test_fastcall_keywords(self): - # Test _PyObject_FastCallKeywords() + def test_vectorcall(self): + # Test _PyObject_Vectorcall() for func, args, expected in self.CALLS_POSARGS: with self.subTest(func=func, args=args): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index d15d3756e5fdf9..c558d116e6fe9d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1064,7 +1064,7 @@ def test_objecttypes(self): # buffer # XXX # builtin_function_or_method - check(len, size('5P')) # XXX check layout + check(len, size('5P')) # bytearray samples = [b'', b'u'*100000] for sample in samples: @@ -1259,7 +1259,7 @@ def delx(self): del self.__x check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - fmt = 'P2n15Pl4Pn9Pn11PIPI' + fmt = 'P2nPI13Pl4Pn9Pn11PIPP' if hasattr(sys, 'getcounts'): fmt += '3n2P' s = vsize(fmt) diff --git a/Misc/NEWS.d/next/C API/2019-05-22-15-24-08.bpo-36974.TkySRe.rst b/Misc/NEWS.d/next/C API/2019-05-22-15-24-08.bpo-36974.TkySRe.rst new file mode 100644 index 00000000000000..c51c2e2419d1fa --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-05-22-15-24-08.bpo-36974.TkySRe.rst @@ -0,0 +1,2 @@ +Implement :pep:`590`: Vectorcall: a fast calling protocol for CPython. +This is a new protocol to optimize calls of custom callable objects. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 4dbd334efcb75e..60136082a7eecf 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -367,8 +367,7 @@ call_soon(PyObject *loop, PyObject *func, PyObject *arg, PyObject *ctx) } stack[nargs] = (PyObject *)ctx; - handle = PyObject_VectorCallWithCallable( - callable, stack, nargs, context_kwname); + handle = _PyObject_Vectorcall(callable, stack, nargs, context_kwname); Py_DECREF(callable); } @@ -2805,8 +2804,7 @@ task_step_impl(TaskObj *task, PyObject *exc) PyObject *stack[2]; stack[0] = wrapper; stack[1] = (PyObject *)task->task_context; - res = PyObject_VectorCallWithCallable( - add_cb, stack, 1, context_kwname); + res = _PyObject_Vectorcall(add_cb, stack, 1, context_kwname); Py_DECREF(add_cb); Py_DECREF(wrapper); if (res == NULL) { diff --git a/Modules/_csv.c b/Modules/_csv.c index 8101a86ae2584b..e31b158c601e86 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -469,7 +469,7 @@ static PyTypeObject Dialect_Type = { 0, /* tp_itemsize */ /* methods */ (destructor)Dialect_dealloc, /* tp_dealloc */ - 0, /* tp_print */ + (printfunc)0, /* tp_print */ (getattrfunc)0, /* tp_getattr */ (setattrfunc)0, /* tp_setattr */ 0, /* tp_reserved */ @@ -902,7 +902,7 @@ static PyTypeObject Reader_Type = { 0, /*tp_itemsize*/ /* methods */ (destructor)Reader_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ + (printfunc)0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ 0, /*tp_reserved*/ @@ -1332,7 +1332,7 @@ static PyTypeObject Writer_Type = { 0, /*tp_itemsize*/ /* methods */ (destructor)Writer_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ + (printfunc)0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ 0, /*tp_reserved*/ diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 25d4b94b059dc2..0b8f477a6eaf0b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4742,7 +4742,7 @@ test_pyobject_fastcallkeywords(PyObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "kwnames must be None or a tuple"); return NULL; } - return PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); + return _PyObject_Vectorcall(func, stack, nargs, kwnames); } diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 29143d589a14da..2e8be3706db914 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1468,7 +1468,7 @@ static PyTypeObject Xmlparsetype = { 0, /*tp_itemsize*/ /* methods */ (destructor)xmlparse_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ + (printfunc)0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_reserved*/ diff --git a/Objects/call.c b/Objects/call.c index c1e0cd28a3ce04..183a5c2e5a24ff 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -3,7 +3,10 @@ #include "pycore_pystate.h" #include "pycore_tupleobject.h" #include "frameobject.h" -#include "vectorcall.h" + + +static PyObject * +cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs); int @@ -84,169 +87,135 @@ _Py_CheckFunctionResult(PyObject *callable, PyObject *result, const char *where) /* --- Core PyObject call functions ------------------------------- */ PyObject * -_PyObject_FastCallDict(PyObject *callable, PyObject *const *args, Py_ssize_t nargs, - PyObject *kwargs) +_PyObject_FastCallDict(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwargs) { /* _PyObject_FastCallDict() must not be called with an exception set, because it can clear it (directly or indirectly) and so the caller loses its exception */ assert(!PyErr_Occurred()); - assert(callable != NULL); + + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); assert(nargs >= 0); assert(nargs == 0 || args != NULL); assert(kwargs == NULL || PyDict_Check(kwargs)); - if (PyFunction_Check(callable)) { - return _PyFunction_FastCallDict(callable, args, nargs, kwargs); + vectorcallfunc func = _PyVectorcall_Function(callable); + if (func == NULL) { + /* Use tp_call instead */ + return _PyObject_MakeTpCall(callable, args, nargs, kwargs); } - else if (PyCFunction_Check(callable)) { - return _PyCFunction_FastCallDict(callable, args, nargs, kwargs); + + PyObject *res; + if (kwargs == NULL) { + res = func(callable, args, nargsf, NULL); } else { - PyObject *argstuple, *result; - ternaryfunc call; - - /* Slow-path: build a temporary tuple */ - call = callable->ob_type->tp_call; - if (call == NULL) { - PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", - callable->ob_type->tp_name); - return NULL; - } - - argstuple = _PyTuple_FromArray(args, nargs); - if (argstuple == NULL) { + PyObject *kwnames; + PyObject *const *newargs; + if (_PyStack_UnpackDict(args, nargs, kwargs, &newargs, &kwnames) < 0) { return NULL; } - - if (Py_EnterRecursiveCall(" while calling a Python object")) { - Py_DECREF(argstuple); - return NULL; + res = func(callable, newargs, nargs, kwnames); + if (kwnames != NULL) { + Py_ssize_t i, n = PyTuple_GET_SIZE(kwnames) + nargs; + for (i = 0; i < n; i++) { + Py_DECREF(newargs[i]); + } + PyMem_Free((PyObject **)newargs); + Py_DECREF(kwnames); } - - result = (*call)(callable, argstuple, kwargs); - - Py_LeaveRecursiveCall(); - Py_DECREF(argstuple); - - result = _Py_CheckFunctionResult(callable, result, NULL); - return result; } + return _Py_CheckFunctionResult(callable, res, NULL); } PyObject * -PyObject_VectorCallWithCallable(PyObject *callable, PyObject **stack, Py_ssize_t nargs, - PyObject *kwnames) +_PyObject_MakeTpCall(PyObject *callable, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) { - /* PyObject_VectorCallWithCallable() must not be called with an exception set, - because it can clear it (directly or indirectly) and so the - caller loses its exception */ - assert(!PyErr_Occurred()); - assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); - - /* kwnames must only contains str strings, no subclass, and all keys must - be unique: these checks are implemented in Python/ceval.c and - _PyArg_ParseStackAndKeywords(). */ - - return _Py_VectorCall(callable, stack, nargs, kwnames); -} - -PyObject * -PyCall_MakeTpCall(PyObject *callable, PyObject *const *stack, Py_ssize_t nargs, PyObject *kwnames) { - /* Slow-path: build a temporary tuple for positional arguments and a - temporary dictionary for keyword arguments (if any) */ - - ternaryfunc call; - PyObject *argstuple; - PyObject *kwdict, *result; - Py_ssize_t nkwargs; - - nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); - - nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; - assert((nargs == 0 && nkwargs == 0) || stack != NULL); - - call = callable->ob_type->tp_call; + /* Slow path: build a temporary tuple for positional arguments and a + * temporary dictionary for keyword arguments (if any) */ + ternaryfunc call = Py_TYPE(callable)->tp_call; if (call == NULL) { PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", - callable->ob_type->tp_name); + Py_TYPE(callable)->tp_name); return NULL; } - argstuple = _PyTuple_FromArray(stack, nargs); + assert(nargs >= 0); + assert(nargs == 0 || args != NULL); + assert(keywords == NULL || PyTuple_Check(keywords) || PyDict_Check(keywords)); + PyObject *argstuple = _PyTuple_FromArray(args, nargs); if (argstuple == NULL) { return NULL; } - if (nkwargs > 0) { - kwdict = _PyStack_AsDict(stack + nargs, kwnames); - if (kwdict == NULL) { - Py_DECREF(argstuple); - return NULL; - } + PyObject *kwdict; + if (keywords == NULL || PyDict_Check(keywords)) { + kwdict = keywords; } else { - kwdict = NULL; + if (PyTuple_GET_SIZE(keywords)) { + assert(args != NULL); + kwdict = _PyStack_AsDict(args + nargs, keywords); + if (kwdict == NULL) { + Py_DECREF(argstuple); + return NULL; + } + } + else { + keywords = kwdict = NULL; + } } - if (Py_EnterRecursiveCall(" while calling a Python object")) { - Py_DECREF(argstuple); - Py_XDECREF(kwdict); - return NULL; + PyObject *result = NULL; + if (Py_EnterRecursiveCall(" while calling a Python object") == 0) + { + result = call(callable, argstuple, kwdict); + Py_LeaveRecursiveCall(); } - result = (*call)(callable, argstuple, kwdict); - - Py_LeaveRecursiveCall(); - Py_DECREF(argstuple); - Py_XDECREF(kwdict); + if (kwdict != keywords) { + Py_DECREF(kwdict); + } result = _Py_CheckFunctionResult(callable, result, NULL); return result; } -PyObject * -PyObject_VectorCall(PyObject **stack, Py_ssize_t nargs, - PyObject *kwnames) -{ - return PyObject_VectorCallWithCallable(stack[0], &stack[1], (nargs-1)|PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); -} PyObject * -PyCall_MakeVectorCall(PyObject *callable, PyObject *tuple, PyObject *kwargs) { - PyTypeObject *tp = Py_TYPE(callable); - vectorcall_func func = NULL; - uintptr_t offset = tp->tp_vectorcall_offset; - if (offset != 0) { - assert(tp->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL); - func = *(vectorcall_func *)(((char *)callable) + offset); - } +PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs) +{ + vectorcallfunc func = _PyVectorcall_Function(callable); if (func == NULL) { - PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable", - callable->ob_type->tp_name); + PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall", + Py_TYPE(callable)->tp_name); return NULL; } - PyObject * const*stack; + PyObject *const *args; Py_ssize_t nargs = PyTuple_GET_SIZE(tuple); - PyObject *kwnames, *result; - if (_PyStack_UnpackDict(&PyTuple_GET_ITEM(tuple, 0), nargs, - kwargs, &stack, &kwnames) < 0) { + PyObject *kwnames; + if (_PyStack_UnpackDict(_PyTuple_ITEMS(tuple), nargs, + kwargs, &args, &kwnames) < 0) { return NULL; } - /* It's OK to discard the `const` qualifier as PY_VECTORCALL_ARGUMENTS_OFFSET is not - * set, so the stack will not be modified. */ - result = (*func) (callable, stack, nargs, kwnames); - if (stack != &PyTuple_GET_ITEM(tuple, 0)) { - PyMem_Free((PyObject **)stack); + PyObject *result = func(callable, args, nargs, kwnames); + if (kwnames != NULL) { + Py_ssize_t i, n = PyTuple_GET_SIZE(kwnames) + nargs; + for (i = 0; i < n; i++) { + Py_DECREF(args[i]); + } + PyMem_Free((PyObject **)args); + Py_DECREF(kwnames); } return result; - } + PyObject * PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs) { @@ -260,14 +229,13 @@ PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs) assert(PyTuple_Check(args)); assert(kwargs == NULL || PyDict_Check(kwargs)); - if (PyFunction_Check(callable)) { - return _PyFunction_FastCallDict(callable, - _PyTuple_ITEMS(args), - PyTuple_GET_SIZE(args), - kwargs); + if (_PyVectorcall_Function(callable) != NULL) { + return PyVectorcall_Call(callable, args, kwargs); } else if (PyCFunction_Check(callable)) { - return PyCFunction_Call(callable, args, kwargs); + /* This must be a METH_VARARGS function, otherwise we would be + * in the previous case */ + return cfunction_call_varargs(callable, args, kwargs); } else { call = callable->ob_type->tp_call; @@ -420,9 +388,10 @@ _PyFunction_FastCallDict(PyObject *func, PyObject *const *args, Py_ssize_t nargs return result; } + PyObject * _PyFunction_FastCallKeywords(PyObject *func, PyObject* const* stack, - Py_ssize_t nargs, PyObject *kwnames) + size_t nargsf, PyObject *kwnames) { PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func); PyObject *globals = PyFunction_GET_GLOBALS(func); @@ -433,7 +402,7 @@ _PyFunction_FastCallKeywords(PyObject *func, PyObject* const* stack, Py_ssize_t nd; assert(PyFunction_Check(func)); - nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); assert(nargs >= 0); assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); assert((nargs == 0 && nkwargs == 0) || stack != NULL); @@ -642,7 +611,6 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self, assert(method != NULL); assert(nargs >= 0); - nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); /* kwnames must only contains str strings, no subclass, and all keys must be unique */ @@ -763,14 +731,14 @@ _PyMethodDef_RawFastCallKeywords(PyMethodDef *method, PyObject *self, PyObject * _PyCFunction_FastCallKeywords(PyObject *func, - PyObject *const *args, Py_ssize_t nargs, + PyObject *const *args, size_t nargsf, PyObject *kwnames) { PyObject *result; assert(func != NULL); assert(PyCFunction_Check(func)); - nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); result = _PyMethodDef_RawFastCallKeywords(((PyCFunctionObject*)func)->m_ml, PyCFunction_GET_SELF(func), @@ -790,6 +758,7 @@ cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs) PyObject *self = PyCFunction_GET_SELF(func); PyObject *result; + assert(PyCFunction_GET_FLAGS(func) & METH_VARARGS); if (PyCFunction_GET_FLAGS(func) & METH_KEYWORDS) { if (Py_EnterRecursiveCall(" while calling a Python object")) { return NULL; @@ -822,18 +791,12 @@ cfunction_call_varargs(PyObject *func, PyObject *args, PyObject *kwargs) PyObject * PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwargs) { - /* first try METH_VARARGS to pass directly args tuple unchanged. - _PyMethodDef_RawFastCallDict() creates a new temporary tuple - for METH_VARARGS. */ + /* For METH_VARARGS, we cannot use vectorcall as the vectorcall pointer + * is NULL. This is intentional, since vectorcall would be slower. */ if (PyCFunction_GET_FLAGS(func) & METH_VARARGS) { return cfunction_call_varargs(func, args, kwargs); } - else { - return _PyCFunction_FastCallDict(func, - _PyTuple_ITEMS(args), - PyTuple_GET_SIZE(args), - kwargs); - } + return PyVectorcall_Call(func, args, kwargs); } diff --git a/Objects/classobject.c b/Objects/classobject.c index 74e786e856cead..cfc24460a747b0 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -40,47 +40,45 @@ PyMethod_Self(PyObject *im) return ((PyMethodObject *)im)->im_self; } -/* Vector call for bound-methods */ + static PyObject * -method_vector_call(PyObject *method, PyObject *const *stack, - Py_ssize_t nargs, PyObject *kwnames) +method_vectorcall(PyObject *method, PyObject *const *args, + size_t nargsf, PyObject *kwnames) { assert(Py_TYPE(method) == &PyMethod_Type); PyObject *self, *func, *result; self = PyMethod_GET_SELF(method); func = PyMethod_GET_FUNCTION(method); - - if (nargs & PY_VECTORCALL_ARGUMENTS_OFFSET) { - nargs = (nargs&~PY_VECTORCALL_ARGUMENTS_OFFSET)+1; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + + if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { /* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */ - PyObject **args = (PyObject**)stack-1; - PyObject *tmp = args[0]; - args[0] = self; - result = _Py_VectorCall(func, args, nargs, kwnames); - args[0] = tmp; + PyObject **newargs = (PyObject**)args - 1; + nargs += 1; + PyObject *tmp = newargs[0]; + newargs[0] = self; + result = _PyObject_Vectorcall(func, newargs, nargs, kwnames); + newargs[0] = tmp; } else { Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); - PyObject **newstack; - Py_ssize_t totalargs = nargs+nkwargs; - newstack = PyMem_Malloc((totalargs+1) * sizeof(PyObject *)); - if (newstack == NULL) { + PyObject **newargs; + Py_ssize_t totalargs = nargs + nkwargs; + newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *)); + if (newargs == NULL) { PyErr_NoMemory(); return NULL; } /* use borrowed references */ - newstack[0] = self; - if (totalargs > 0) { - memcpy(&newstack[1], stack, totalargs * sizeof(PyObject *)); - } - result = _Py_VectorCall(func, newstack, nargs+1, kwnames); - PyMem_Free(newstack); + newargs[0] = self; + memcpy(newargs + 1, args, totalargs * sizeof(PyObject *)); + result = _PyObject_Vectorcall(func, newargs, nargs+1, kwnames); + PyMem_Free(newargs); } return result; } - /* Method objects are used for bound instance methods returned by instancename.methodname. ClassName.methodname returns an ordinary function. @@ -110,7 +108,7 @@ PyMethod_New(PyObject *func, PyObject *self) im->im_func = func; Py_XINCREF(self); im->im_self = self; - im->vector_call = method_vector_call; + im->vectorcall = method_vectorcall; _PyObject_GC_TRACK(im); return (PyObject *)im; } @@ -351,7 +349,7 @@ PyTypeObject PyMethod_Type = { sizeof(PyMethodObject), 0, (destructor)method_dealloc, /* tp_dealloc */ - 0, /* tp_print */ + offsetof(PyMethodObject, vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ @@ -366,7 +364,7 @@ PyTypeObject PyMethod_Type = { PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ + _Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ method_doc, /* tp_doc */ (traverseproc)method_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -385,7 +383,6 @@ PyTypeObject PyMethod_Type = { 0, /* tp_init */ 0, /* tp_alloc */ method_new, /* tp_new */ - .tp_vectorcall_offset = offsetof(PyMethodObject, vector_call), }; /* Clear out the free list */ diff --git a/Objects/descrobject.c b/Objects/descrobject.c index df7afdfff952cc..759018503c65e0 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -265,14 +265,14 @@ methoddescr_call(PyMethodDescrObject *descr, PyObject *args, PyObject *kwargs) // same to methoddescr_call(), but use FASTCALL convention. PyObject * _PyMethodDescr_FastCallKeywords(PyObject *descrobj, - PyObject *const *args, Py_ssize_t nargs, + PyObject *const *args, size_t nargsf, PyObject *kwnames) { assert(Py_TYPE(descrobj) == &PyMethodDescr_Type); PyMethodDescrObject *descr = (PyMethodDescrObject *)descrobj; PyObject *self, *result; - nargs &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); /* Make sure that the first argument is acceptable as 'self' */ if (nargs < 1) { PyErr_Format(PyExc_TypeError, @@ -543,7 +543,7 @@ PyTypeObject PyMethodDescr_Type = { sizeof(PyMethodDescrObject), 0, (destructor)descr_dealloc, /* tp_dealloc */ - 0, /* tp_print */ + offsetof(PyMethodDescrObject, vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ @@ -558,7 +558,7 @@ PyTypeObject PyMethodDescr_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_HAVE_VECTORCALL | + _Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ 0, /* tp_doc */ descr_traverse, /* tp_traverse */ @@ -574,7 +574,6 @@ PyTypeObject PyMethodDescr_Type = { 0, /* tp_dict */ (descrgetfunc)method_get, /* tp_descr_get */ 0, /* tp_descr_set */ - .tp_vectorcall_offset = offsetof(PyMethodDescrObject, vector_call), }; /* This is for METH_CLASS in C, not for "f = classmethod(f)" in Python! */ @@ -757,7 +756,7 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method) type, method->ml_name); if (descr != NULL) { descr->d_method = method; - descr->vector_call = &_PyMethodDescr_FastCallKeywords; + descr->vectorcall = &_PyMethodDescr_FastCallKeywords; } return (PyObject *)descr; } diff --git a/Objects/funcobject.c b/Objects/funcobject.c index d544c78ee6362c..2b1f42db746dbc 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -36,7 +36,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname op->func_defaults = NULL; /* No default arguments */ op->func_kwdefaults = NULL; /* No keyword only defaults */ op->func_closure = NULL; - op->vector_call = _PyFunction_FastCallKeywords; + op->vectorcall = _PyFunction_FastCallKeywords; consts = ((PyCodeObject *)code)->co_consts; if (PyTuple_Size(consts) >= 1) { @@ -650,7 +650,7 @@ PyTypeObject PyFunction_Type = { sizeof(PyFunctionObject), 0, (destructor)func_dealloc, /* tp_dealloc */ - 0, /* tp_print */ + offsetof(PyFunctionObject, vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ @@ -665,7 +665,7 @@ PyTypeObject PyFunction_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_HAVE_VECTORCALL | + _Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ func_new__doc__, /* tp_doc */ (traverseproc)func_traverse, /* tp_traverse */ @@ -685,7 +685,6 @@ PyTypeObject PyFunction_Type = { 0, /* tp_init */ 0, /* tp_alloc */ func_new, /* tp_new */ - .tp_vectorcall_offset = offsetof(PyFunctionObject, vector_call), }; diff --git a/Objects/methodobject.c b/Objects/methodobject.c index f299d6390ccdbc..76497c93894a89 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -46,7 +46,14 @@ PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module) op->m_self = self; Py_XINCREF(module); op->m_module = module; - op->vectorcall = &_PyCFunction_FastCallKeywords; + if (ml->ml_flags & METH_VARARGS) { + /* For METH_VARARGS functions, it's more efficient to use tp_call + * instead of vectorcall. */ + op->vectorcall = NULL; + } + else { + op->vectorcall = &_PyCFunction_FastCallKeywords; + } _PyObject_GC_TRACK(op); return (PyObject *)op; } @@ -265,7 +272,7 @@ PyTypeObject PyCFunction_Type = { sizeof(PyCFunctionObject), 0, (destructor)meth_dealloc, /* tp_dealloc */ - 0, /* tp_print */ + offsetof(PyCFunctionObject, vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ @@ -280,7 +287,7 @@ PyTypeObject PyCFunction_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ + _Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ 0, /* tp_doc */ (traverseproc)meth_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -293,7 +300,6 @@ PyTypeObject PyCFunction_Type = { meth_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - .tp_vectorcall_offset = offsetof(PyCFunctionObject, vectorcall), }; /* Clear out the free list */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1cc8e9eab87b6e..c14cbad875b443 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5221,11 +5221,6 @@ PyType_Ready(PyTypeObject *type) (type->tp_flags & Py_TPFLAGS_READYING) == 0); type->tp_flags |= Py_TPFLAGS_READYING; - - /* Make sure that Py_TPFLAGS_HAVE_VECTORCALL and tp_vectorcall_offset are consistent */ - if ((type->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) == 0) { - type->tp_vectorcall_offset = 0; - } #ifdef Py_TRACE_REFS /* PyType_Ready is the closest thing we have to a choke point diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index ead7cb8a0379fd..b6b52592493799 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -474,7 +474,7 @@ builtin_callable(PyObject *module, PyObject *obj) } static PyObject * -builtin_breakpoint(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *keywords) +builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) { PyObject *hook = PySys_GetObject("breakpointhook"); @@ -483,7 +483,7 @@ builtin_breakpoint(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject * return NULL; } Py_INCREF(hook); - PyObject *retval = PyObject_VectorCallWithCallable(hook, args, nargs, keywords); + PyObject *retval = _PyObject_Vectorcall(hook, args, nargs, keywords); Py_DECREF(hook); return retval; } @@ -2260,7 +2260,7 @@ PyDoc_STRVAR(builtin_sorted__doc__, {"sorted", (PyCFunction)(void(*)(void))builtin_sorted, METH_FASTCALL | METH_KEYWORDS, builtin_sorted__doc__}, static PyObject * -builtin_sorted(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames) +builtin_sorted(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *newlist, *v, *seq, *callable; @@ -2280,7 +2280,7 @@ builtin_sorted(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwna } assert(nargs >= 1); - v = PyObject_VectorCallWithCallable(callable, args + 1, nargs - 1, kwnames); + v = _PyObject_Vectorcall(callable, args + 1, nargs - 1, kwnames); Py_DECREF(callable); if (v == NULL) { Py_DECREF(newlist); diff --git a/Python/ceval.c b/Python/ceval.c index 20be650482bd8f..47baa4d03ed9cc 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4808,7 +4808,42 @@ if (tstate->use_tracing && tstate->c_profilefunc) { \ static PyObject * -trace_call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) +trace_call_function(PyThreadState *tstate, + PyObject *func, + PyObject **args, Py_ssize_t nargs, + PyObject *kwnames) +{ + PyObject *x; + if (PyCFunction_Check(func)) { + C_TRACE(x, _PyCFunction_FastCallKeywords(func, args, nargs, kwnames)); + return x; + } + else if (Py_TYPE(func) == &PyMethodDescr_Type && nargs > 0) { + /* We need to create a temporary bound method as argument + for profiling. + + If nargs == 0, then this cannot work because we have no + "self". In any case, the call itself would raise + TypeError (foo needs an argument), so we just skip + profiling. */ + PyObject *self = args[0]; + func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self)); + if (func == NULL) { + return NULL; + } + C_TRACE(x, _PyCFunction_FastCallKeywords(func, + args+1, nargs-1, + kwnames)); + Py_DECREF(func); + return x; + } + return _PyObject_Vectorcall(func, args, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); +} + +/* Issue #29227: Inline call_function() into _PyEval_EvalFrameDefault() + to reduce the stack consumption. */ +Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION +call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) { PyObject **pfunc = (*pp_stack) - oparg - 1; PyObject *func = *pfunc; @@ -4816,39 +4851,12 @@ trace_call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t opar Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); Py_ssize_t nargs = oparg - nkwargs; PyObject **stack = (*pp_stack) - nargs - nkwargs; - if (!tstate->use_tracing) { - x = _Py_VectorCall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); - } - else if (PyCFunction_Check(func)) { - C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames)); - } - else if (Py_TYPE(func) == &PyMethodDescr_Type) { - if (nargs > 0) { - /* We need to create a temporary bound method as argument - for profiling. - If nargs == 0, then this cannot work because we have no - "self". In any case, the call itself would raise - TypeError (foo needs an argument), so we just skip - profiling. */ - PyObject *self = stack[0]; - func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self)); - if (func != NULL) { - C_TRACE(x, _PyCFunction_FastCallKeywords(func, - stack+1, nargs-1, - kwnames)); - Py_DECREF(func); - } - else { - x = NULL; - } - } - else { - x = PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); - } + if (tstate->use_tracing) { + x = trace_call_function(tstate, func, stack, nargs, kwnames); } else { - x = PyObject_VectorCallWithCallable(func, stack, nargs, kwnames); + x = _PyObject_Vectorcall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); } assert((x != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -4862,29 +4870,6 @@ trace_call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t opar return x; } -/* Issue #29227: Inline call_function() into _PyEval_EvalFrameDefault() - to reduce the stack consumption. */ -Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION -call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) -{ - PyObject **pfunc = (*pp_stack) - oparg - 1; - PyObject *func = *pfunc; - PyObject *x, *w; - Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); - Py_ssize_t nargs = oparg - nkwargs; - PyObject **stack = (*pp_stack) - nargs - nkwargs; - if (!tstate->use_tracing) { - x = _Py_VectorCall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); - /* Clear the stack of the function object. */ - while ((*pp_stack) > pfunc) { - w = EXT_POP(*pp_stack); - Py_DECREF(w); - } - return x; - } - return trace_call_function(tstate, pp_stack, oparg, kwnames); -} - static PyObject * do_call_core(PyThreadState *tstate, PyObject *func, PyObject *callargs, PyObject *kwdict) { diff --git a/Python/context.c b/Python/context.c index 4e2f7ba0840e66..f48c376b4ffaa9 100644 --- a/Python/context.c +++ b/Python/context.c @@ -618,7 +618,7 @@ _contextvars_Context_copy_impl(PyContext *self) static PyObject * -context_run(PyContext *self, PyObject **args, +context_run(PyContext *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { if (nargs < 1) { @@ -631,7 +631,7 @@ context_run(PyContext *self, PyObject **args, return NULL; } - PyObject *call_result = PyObject_VectorCallWithCallable( + PyObject *call_result = _PyObject_Vectorcall( args[0], args + 1, nargs - 1, kwnames); if (PyContext_Exit((PyObject *)self)) { diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 486a290560eb33..12b1bd7711d5b8 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -416,7 +416,7 @@ sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc) static PyObject * -sys_breakpointhook(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *keywords) +sys_breakpointhook(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) { assert(!PyErr_Occurred()); char *envar = Py_GETENV("PYTHONBREAKPOINT"); @@ -481,7 +481,7 @@ sys_breakpointhook(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject * return NULL; } PyMem_RawFree(envar); - PyObject *retval = PyObject_VectorCallWithCallable(hook, args, nargs, keywords); + PyObject *retval = _PyObject_Vectorcall(hook, args, nargs, keywords); Py_DECREF(hook); return retval; From f4b1224927548a3dc917486cbebf5ab722d3fab2 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 22 May 2019 12:19:02 +0200 Subject: [PATCH 3/9] bpo-36974: add PEP 590 tests --- Lib/test/test_capi.py | 37 +++++++++++++++++++++++++++++++++++++ Modules/_testcapimodule.c | 25 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index f3d41a20ab0560..c58b3f7a8a23dc 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -34,6 +34,11 @@ def testfunction(self): """some doc""" return self +def testfunction_kw(self, *, kw): + """some doc""" + return self + + class InstanceMethod: id = _testcapi.instancemethod(id) testfunction = _testcapi.instancemethod(testfunction) @@ -479,6 +484,38 @@ class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): pass self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + def test_vectorcall(self): + # Test a bunch of different ways to call objects: + # 1. normal call + # 2. vectorcall + # 3. call as bound method + # 4. call using functools.partial + + # A list of (function, args, kwargs, result) calls to test + calls = [(len, (range(42),), {}, 42), + (list.append, ([], 0), {}, None), + ([].append, (0,), {}, None), + (sum, ([36],), {"start":6}, 42), + (testfunction, (42,), {}, 42), + (testfunction_kw, (42,), {"kw":None}, 42)] + + from _testcapi import vectorcall + from types import MethodType + from functools import partial + for (func, args, kwargs, expected) in calls: + args1 = args[1:] + meth = MethodType(func, args[0]) + wrapped = partial(func) + if not kwargs: + self.assertEqual(expected, func(*args)) + self.assertEqual(expected, vectorcall(func, args)) + self.assertEqual(expected, meth(*args1)) + self.assertEqual(expected, wrapped(*args)) + self.assertEqual(expected, func(*args, **kwargs)) + self.assertEqual(expected, vectorcall(func, args, kwargs)) + self.assertEqual(expected, meth(*args1, **kwargs)) + self.assertEqual(expected, wrapped(*args, **kwargs)) + class SubinterpreterTest(unittest.TestCase): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 0b8f477a6eaf0b..2d69b56496fc4b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4746,6 +4746,30 @@ test_pyobject_fastcallkeywords(PyObject *self, PyObject *args) } +static PyObject * +vectorcall(PyObject *self, PyObject *args) +{ + PyObject *func; + PyObject *argstuple; + PyObject *kwargs = NULL; + + if (!PyArg_ParseTuple(args, "OO|O", &func, &argstuple, &kwargs)) { + return NULL; + } + + if (!PyTuple_Check(argstuple)) { + PyErr_SetString(PyExc_TypeError, "args must be a tuple"); + return NULL; + } + if (kwargs != NULL && !PyDict_Check(kwargs)) { + PyErr_SetString(PyExc_TypeError, "kwargs must be a dict"); + return NULL; + } + + return PyVectorcall_Call(func, argstuple, kwargs); +} + + static PyObject* stack_pointer(PyObject *self, PyObject *args) { @@ -5231,6 +5255,7 @@ static PyMethodDef TestMethods[] = { {"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS}, {"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS}, {"pyobject_fastcallkeywords", test_pyobject_fastcallkeywords, METH_VARARGS}, + {"vectorcall", vectorcall, METH_VARARGS}, {"stack_pointer", stack_pointer, METH_NOARGS}, #ifdef W_STOPCODE {"W_STOPCODE", py_w_stopcode, METH_VARARGS}, From 424b44f703cb951b8d220a0c69d01b78d9ba2b3d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 May 2019 13:23:46 +0200 Subject: [PATCH 4/9] Convert macros to static inline functions This adds some additional type safety, and (for _PyObject_FastCall) makes the exact argument types explicit. Also, add descriptions in comments --- Include/cpython/abstract.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 65c5a9e4c43414..7099178f82087d 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -141,11 +141,18 @@ PyAPI_FUNC(PyObject *) _PyObject_FastCallDict( "tuple" and keyword arguments "dict". "dict" may also be NULL */ PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict); -#define _PyObject_FastCall(func, args, nargs) \ - _PyObject_Vectorcall((func), (args), (nargs), NULL) +/* Same as _PyObject_Vectorcall except without keyword arguments */ +static inline PyObject * +_PyObject_FastCall(PyObject *func, PyObject *const *args, Py_ssize_t nargs) +{ + return _PyObject_Vectorcall(func, args, (size_t)nargs, NULL); +} -#define _PyObject_CallNoArg(func) \ - _PyObject_Vectorcall((func), NULL, 0, NULL) +/* Call a callable without any arguments */ +static inline PyObject * +_PyObject_CallNoArg(PyObject *func) { + return _PyObject_Vectorcall(func, NULL, 0, NULL); +} PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend( PyObject *callable, From c1f7a61992bd1a79a20a16853df1db874fd1a168 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 May 2019 13:40:55 +0200 Subject: [PATCH 5/9] test_capi: Use unittest.subTest to make potential failures clearer --- Lib/test/test_capi.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index c58b3f7a8a23dc..e6dc4696b23bc4 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -503,18 +503,19 @@ def test_vectorcall(self): from types import MethodType from functools import partial for (func, args, kwargs, expected) in calls: - args1 = args[1:] - meth = MethodType(func, args[0]) - wrapped = partial(func) - if not kwargs: - self.assertEqual(expected, func(*args)) - self.assertEqual(expected, vectorcall(func, args)) - self.assertEqual(expected, meth(*args1)) - self.assertEqual(expected, wrapped(*args)) - self.assertEqual(expected, func(*args, **kwargs)) - self.assertEqual(expected, vectorcall(func, args, kwargs)) - self.assertEqual(expected, meth(*args1, **kwargs)) - self.assertEqual(expected, wrapped(*args, **kwargs)) + with self.subTest(str(func)): + args1 = args[1:] + meth = MethodType(func, args[0]) + wrapped = partial(func) + if not kwargs: + self.assertEqual(expected, func(*args)) + self.assertEqual(expected, vectorcall(func, args)) + self.assertEqual(expected, meth(*args1)) + self.assertEqual(expected, wrapped(*args)) + self.assertEqual(expected, func(*args, **kwargs)) + self.assertEqual(expected, vectorcall(func, args, kwargs)) + self.assertEqual(expected, meth(*args1, **kwargs)) + self.assertEqual(expected, wrapped(*args, **kwargs)) class SubinterpreterTest(unittest.TestCase): From 78a0644b7e916af8c0b4ca3b9967fe9e4d337ae1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 May 2019 13:59:17 +0200 Subject: [PATCH 6/9] Test both PyVectorcall_Call and _PyObject_Vectorcall; rename test functions Test functions are renamed to reflect the C API they're testing. --- Lib/test/test_call.py | 10 +++++----- Lib/test/test_capi.py | 12 ++++++++++-- Modules/_testcapimodule.c | 8 ++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index a8874203e0adaf..9f0a75b84a03c3 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -435,27 +435,27 @@ def test_vectorcall(self): for func, args, expected in self.CALLS_POSARGS: with self.subTest(func=func, args=args): # kwnames=NULL - result = _testcapi.pyobject_fastcallkeywords(func, args, None) + result = _testcapi.pyobject_vectorcall(func, args, None) self.check_result(result, expected) # kwnames=() - result = _testcapi.pyobject_fastcallkeywords(func, args, ()) + result = _testcapi.pyobject_vectorcall(func, args, ()) self.check_result(result, expected) if not args: # kwnames=NULL - result = _testcapi.pyobject_fastcallkeywords(func, None, None) + result = _testcapi.pyobject_vectorcall(func, None, None) self.check_result(result, expected) # kwnames=() - result = _testcapi.pyobject_fastcallkeywords(func, None, ()) + result = _testcapi.pyobject_vectorcall(func, None, ()) self.check_result(result, expected) for func, args, kwargs, expected in self.CALLS_KWARGS: with self.subTest(func=func, args=args, kwargs=kwargs): kwnames = tuple(kwargs.keys()) args = args + tuple(kwargs.values()) - result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames) + result = _testcapi.pyobject_vectorcall(func, args, kwnames) self.check_result(result, expected) def test_fastcall_clearing_dict(self): diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index e6dc4696b23bc4..22351bc994d93a 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -499,9 +499,15 @@ def test_vectorcall(self): (testfunction, (42,), {}, 42), (testfunction_kw, (42,), {"kw":None}, 42)] - from _testcapi import vectorcall + from _testcapi import pyobject_vectorcall, pyvectorcall_call from types import MethodType from functools import partial + + def vectorcall(func, args, kwargs=None): + args = *args, *kwargs.values() + kwnames = tuple(kwargs) + return pyobject_vectorcall(func, args, kwnames) + for (func, args, kwargs, expected) in calls: with self.subTest(str(func)): args1 = args[1:] @@ -509,11 +515,13 @@ def test_vectorcall(self): wrapped = partial(func) if not kwargs: self.assertEqual(expected, func(*args)) - self.assertEqual(expected, vectorcall(func, args)) + self.assertEqual(expected, pyobject_vectorcall(func, args, None)) + self.assertEqual(expected, pyvectorcall_call(func, args)) self.assertEqual(expected, meth(*args1)) self.assertEqual(expected, wrapped(*args)) self.assertEqual(expected, func(*args, **kwargs)) self.assertEqual(expected, vectorcall(func, args, kwargs)) + self.assertEqual(expected, pyvectorcall_call(func, args, kwargs)) self.assertEqual(expected, meth(*args1, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs)) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 2d69b56496fc4b..f2f418c997ab59 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4713,7 +4713,7 @@ test_pyobject_fastcalldict(PyObject *self, PyObject *args) static PyObject * -test_pyobject_fastcallkeywords(PyObject *self, PyObject *args) +test_pyobject_vectorcall(PyObject *self, PyObject *args) { PyObject *func, *func_args, *kwnames = NULL; PyObject **stack; @@ -4747,7 +4747,7 @@ test_pyobject_fastcallkeywords(PyObject *self, PyObject *args) static PyObject * -vectorcall(PyObject *self, PyObject *args) +test_pyvectorcall_call(PyObject *self, PyObject *args) { PyObject *func; PyObject *argstuple; @@ -5254,8 +5254,8 @@ static PyMethodDef TestMethods[] = { {"raise_SIGINT_then_send_None", raise_SIGINT_then_send_None, METH_VARARGS}, {"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS}, {"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS}, - {"pyobject_fastcallkeywords", test_pyobject_fastcallkeywords, METH_VARARGS}, - {"vectorcall", vectorcall, METH_VARARGS}, + {"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS}, + {"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS}, {"stack_pointer", stack_pointer, METH_NOARGS}, #ifdef W_STOPCODE {"W_STOPCODE", py_w_stopcode, METH_VARARGS}, From 7536e69231fc94ab9c43b9ea84645c0745b45c86 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 May 2019 14:25:05 +0200 Subject: [PATCH 7/9] Return Py_EnterRecursiveCall to _PyObject_FastCallDict --- Objects/call.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Objects/call.c b/Objects/call.c index 183a5c2e5a24ff..5f7fe3eb1de36a 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -107,9 +107,12 @@ _PyObject_FastCallDict(PyObject *callable, PyObject *const *args, return _PyObject_MakeTpCall(callable, args, nargs, kwargs); } - PyObject *res; + PyObject *res = NULL; if (kwargs == NULL) { - res = func(callable, args, nargsf, NULL); + if (Py_EnterRecursiveCall(" while calling a Python object") == 0) { + res = func(callable, args, nargsf, NULL); + Py_LeaveRecursiveCall(); + } } else { PyObject *kwnames; @@ -117,7 +120,10 @@ _PyObject_FastCallDict(PyObject *callable, PyObject *const *args, if (_PyStack_UnpackDict(args, nargs, kwargs, &newargs, &kwnames) < 0) { return NULL; } - res = func(callable, newargs, nargs, kwnames); + if (Py_EnterRecursiveCall(" while calling a Python object") == 0) { + res = func(callable, newargs, nargs, kwnames); + Py_LeaveRecursiveCall(); + } if (kwnames != NULL) { Py_ssize_t i, n = PyTuple_GET_SIZE(kwnames) + nargs; for (i = 0; i < n; i++) { From 83dec385b6aadb7efca7acfec1eded064b1cd538 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 29 May 2019 17:38:50 +0200 Subject: [PATCH 8/9] bpo-36974: revert "Return Py_EnterRecursiveCall to _PyObject_FastCallDict" This reverts commit 7536e69231fc94ab9c43b9ea84645c0745b45c86. --- Objects/call.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Objects/call.c b/Objects/call.c index 5f7fe3eb1de36a..183a5c2e5a24ff 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -107,12 +107,9 @@ _PyObject_FastCallDict(PyObject *callable, PyObject *const *args, return _PyObject_MakeTpCall(callable, args, nargs, kwargs); } - PyObject *res = NULL; + PyObject *res; if (kwargs == NULL) { - if (Py_EnterRecursiveCall(" while calling a Python object") == 0) { - res = func(callable, args, nargsf, NULL); - Py_LeaveRecursiveCall(); - } + res = func(callable, args, nargsf, NULL); } else { PyObject *kwnames; @@ -120,10 +117,7 @@ _PyObject_FastCallDict(PyObject *callable, PyObject *const *args, if (_PyStack_UnpackDict(args, nargs, kwargs, &newargs, &kwnames) < 0) { return NULL; } - if (Py_EnterRecursiveCall(" while calling a Python object") == 0) { - res = func(callable, newargs, nargs, kwnames); - Py_LeaveRecursiveCall(); - } + res = func(callable, newargs, nargs, kwnames); if (kwnames != NULL) { Py_ssize_t i, n = PyTuple_GET_SIZE(kwnames) + nargs; for (i = 0; i < n; i++) { From 929318733f71cf875026c39688ef9934469d654a Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 29 May 2019 17:40:32 +0200 Subject: [PATCH 9/9] bpo-36974: minor fixes to test_vectorcall() --- Lib/test/test_capi.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 22351bc994d93a..0813abb9a69722 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -487,9 +487,10 @@ class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): def test_vectorcall(self): # Test a bunch of different ways to call objects: # 1. normal call - # 2. vectorcall - # 3. call as bound method - # 4. call using functools.partial + # 2. vectorcall using _PyObject_Vectorcall() + # 3. vectorcall using PyVectorcall_Call() + # 4. call as bound method + # 5. call using functools.partial # A list of (function, args, kwargs, result) calls to test calls = [(len, (range(42),), {}, 42), @@ -503,7 +504,7 @@ def test_vectorcall(self): from types import MethodType from functools import partial - def vectorcall(func, args, kwargs=None): + def vectorcall(func, args, kwargs): args = *args, *kwargs.values() kwnames = tuple(kwargs) return pyobject_vectorcall(func, args, kwnames)