From b1263d5a60d3f7ab02dd28409fff59b3815a3f67 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Fri, 28 Jun 2019 11:49:00 +0200 Subject: [PATCH] bpo-37337: Add _PyObject_VectorcallMethod() (GH-14228) --- Doc/c-api/object.rst | 22 +++++++++++++ Include/cpython/abstract.h | 16 ++++++++++ .../2019-06-19-12-06-31.bpo-37337.gXIGyU.rst | 1 + Modules/_abc.c | 19 +++++++---- Objects/call.c | 32 +++++++++++++++++++ Objects/descrobject.c | 21 ++++++++---- Python/sysmodule.c | 27 ++++------------ 7 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2019-06-19-12-06-31.bpo-37337.gXIGyU.rst diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 13f13b3489b..a84235b7e32 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -395,6 +395,9 @@ Object Protocol argument 1 (not 0) in the allocated vector. The callee must restore the value of ``args[-1]`` before returning. + For :c:func:`_PyObject_VectorcallMethod`, this flag means instead that + ``args[0]`` may be changed. + Whenever they can do so cheaply (without additional allocation), callers are encouraged to use :const:`PY_VECTORCALL_ARGUMENTS_OFFSET`. Doing so will allow callables such as bound methods to make their onward @@ -430,6 +433,25 @@ Object Protocol .. versionadded:: 3.8 +.. c:function:: PyObject* _PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames) + + Call a method using the vectorcall calling convention. The name of the method + is given as Python string *name*. The object whose method is called is + *args[0]* and the *args* array starting at *args[1]* represents the arguments + of the call. There must be at least one positional argument. + *nargsf* is the number of positional arguments including *args[0]*, + plus :const:`PY_VECTORCALL_ARGUMENTS_OFFSET` if the value of ``args[0]`` may + temporarily be changed. Keyword arguments can be passed just like in + :c:func:`_PyObject_Vectorcall`. + + If the object has the :const:`Py_TPFLAGS_METHOD_DESCRIPTOR` feature, + this will actually call the unbound method object with the full + *args* vector as arguments. + + Return the result of the call on success, or raise an exception and return + *NULL* on failure. + + .. versionadded:: 3.9 .. c:function:: Py_hash_t PyObject_Hash(PyObject *o) diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 415f3e15f97..b4288e714c7 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -158,6 +158,10 @@ PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend( PyObject *args, PyObject *kwargs); +PyAPI_FUNC(PyObject *) _PyObject_VectorcallMethod( + PyObject *name, PyObject *const *args, + size_t nargsf, PyObject *kwnames); + /* Like PyObject_CallMethod(), but expect a _Py_Identifier* as the method name. */ PyAPI_FUNC(PyObject *) _PyObject_CallMethodId(PyObject *obj, @@ -174,6 +178,18 @@ PyAPI_FUNC(PyObject *) _PyObject_CallMethodIdObjArgs( struct _Py_Identifier *name, ...); +static inline PyObject * +_PyObject_VectorcallMethodId( + _Py_Identifier *name, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *oname = _PyUnicode_FromId(name); /* borrowed */ + if (!oname) { + return NULL; + } + return _PyObject_VectorcallMethod(oname, args, nargsf, kwnames); +} + PyAPI_FUNC(int) _PyObject_HasLen(PyObject *o); /* Guess the size of object 'o' using len(o) or o.__length_hint__(). diff --git a/Misc/NEWS.d/next/C API/2019-06-19-12-06-31.bpo-37337.gXIGyU.rst b/Misc/NEWS.d/next/C API/2019-06-19-12-06-31.bpo-37337.gXIGyU.rst new file mode 100644 index 00000000000..96ba2d0ec08 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-06-19-12-06-31.bpo-37337.gXIGyU.rst @@ -0,0 +1 @@ +Add :c:func:`_PyObject_VectorcallMethod` for fast calling of methods. diff --git a/Modules/_abc.c b/Modules/_abc.c index 1fbf3a83100..7233690a57d 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -480,6 +480,7 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, /*[clinic end generated code: output=b8b5148f63b6b56f input=a4f4525679261084]*/ { PyObject *subtype, *result = NULL, *subclass = NULL; + PyObject *margs[2]; _abc_data *impl = _get_impl(self); if (impl == NULL) { return NULL; @@ -514,12 +515,16 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, } } /* Fall back to the subclass check. */ - result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__, - subclass, NULL); + margs[0] = self; + margs[1] = subclass; + result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs, + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); goto end; } - result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__, - subclass, NULL); + margs[0] = self; + margs[1] = subclass; + result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs, + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (result == NULL) { goto end; } @@ -531,8 +536,10 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, break; case 0: Py_DECREF(result); - result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__, - subtype, NULL); + margs[0] = self; + margs[1] = subtype; + result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs, + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); break; case 1: // Nothing to do. break; diff --git a/Objects/call.c b/Objects/call.c index bde5513fb4d..2b52bdf6abd 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -1077,6 +1077,38 @@ object_vacall(PyObject *base, PyObject *callable, va_list vargs) } +PyObject * +_PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + assert(name != NULL); + assert(args != NULL); + assert(PyVectorcall_NARGS(nargsf) >= 1); + + PyObject *callable = NULL; + /* Use args[0] as "self" argument */ + int unbound = _PyObject_GetMethod(args[0], name, &callable); + if (callable == NULL) { + return NULL; + } + + if (unbound) { + /* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since + * that would be interpreted as allowing to change args[-1] */ + nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + } + else { + /* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since + * args[-1] in the onward call is args[0] here. */ + args++; + nargsf--; + } + PyObject *result = _PyObject_Vectorcall(callable, args, nargsf, kwnames); + Py_DECREF(callable); + return result; +} + + PyObject * PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...) { diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 806c0af97e2..a0eb5057af0 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -851,15 +851,22 @@ static PySequenceMethods mappingproxy_as_sequence = { }; static PyObject * -mappingproxy_get(mappingproxyobject *pp, PyObject *args) +mappingproxy_get(mappingproxyobject *pp, PyObject *const *args, Py_ssize_t nargs) { - PyObject *key, *def = Py_None; - _Py_IDENTIFIER(get); + /* newargs: mapping, key, default=None */ + PyObject *newargs[3]; + newargs[0] = pp->mapping; + newargs[2] = Py_None; - if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &def)) + if (!_PyArg_UnpackStack(args, nargs, "get", 1, 2, + &newargs[1], &newargs[2])) + { return NULL; - return _PyObject_CallMethodIdObjArgs(pp->mapping, &PyId_get, - key, def, NULL); + } + _Py_IDENTIFIER(get); + return _PyObject_VectorcallMethodId(&PyId_get, newargs, + 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, + NULL); } static PyObject * @@ -894,7 +901,7 @@ mappingproxy_copy(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) to the underlying mapping */ static PyMethodDef mappingproxy_methods[] = { - {"get", (PyCFunction)mappingproxy_get, METH_VARARGS, + {"get", (PyCFunction)mappingproxy_get, METH_FASTCALL, PyDoc_STR("D.get(k[,d]) -> D[k] if k in D, else d." " d defaults to None.")}, {"keys", (PyCFunction)mappingproxy_keys, METH_NOARGS, diff --git a/Python/sysmodule.c b/Python/sysmodule.c index b200318c75f..1c575614125 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3110,30 +3110,17 @@ PySys_SetArgv(int argc, wchar_t **argv) static int sys_pyfile_write_unicode(PyObject *unicode, PyObject *file) { - PyObject *writer = NULL, *result = NULL; - int err; - if (file == NULL) return -1; - - writer = _PyObject_GetAttrId(file, &PyId_write); - if (writer == NULL) - goto error; - - result = PyObject_CallFunctionObjArgs(writer, unicode, NULL); + assert(unicode != NULL); + PyObject *margs[2] = {file, unicode}; + PyObject *result = _PyObject_VectorcallMethodId(&PyId_write, margs, + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (result == NULL) { - goto error; - } else { - err = 0; - goto finally; + return -1; } - -error: - err = -1; -finally: - Py_XDECREF(writer); - Py_XDECREF(result); - return err; + Py_DECREF(result); + return 0; } static int