[Python-checkins] bpo-37337: Add _PyObject_VectorcallMethod() (GH-14228)

Inada Naoki webhook-mailer at python.org
Fri Jun 28 05:49:06 EDT 2019


https://github.com/python/cpython/commit/b1263d5a60d3f7ab02dd28409fff59b3815a3f67
commit: b1263d5a60d3f7ab02dd28409fff59b3815a3f67
branch: master
author: Jeroen Demeyer <J.Demeyer at UGent.be>
committer: Inada Naoki <songofacandy at gmail.com>
date: 2019-06-28T18:49:00+09:00
summary:

bpo-37337: Add _PyObject_VectorcallMethod() (GH-14228)

files:
A Misc/NEWS.d/next/C API/2019-06-19-12-06-31.bpo-37337.gXIGyU.rst
M Doc/c-api/object.rst
M Include/cpython/abstract.h
M Modules/_abc.c
M Objects/call.c
M Objects/descrobject.c
M Python/sysmodule.c

diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst
index 13f13b3489b8..a84235b7e32d 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 415f3e15f974..b4288e714c70 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 000000000000..96ba2d0ec085
--- /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 1fbf3a831007..7233690a57dc 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 bde5513fb4d4..2b52bdf6abd4 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 806c0af97e24..a0eb5057af04 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 b200318c75f2..1c5756141250 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



More information about the Python-checkins mailing list