Skip to content

bpo-37337: Add _PyObject_VectorcallMethod() #14228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
16 changes: 16 additions & 0 deletions Include/cpython/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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__().
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :c:func:`_PyObject_VectorcallMethod` for fast calling of methods.
19 changes: 13 additions & 6 deletions Modules/_abc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,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, ...)
{
Expand Down
21 changes: 14 additions & 7 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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,
Expand Down
27 changes: 7 additions & 20 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3201,30 +3201,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
Expand Down