Skip to content

Commit b1263d5

Browse files
jdemeyermethane
authored andcommitted
bpo-37337: Add _PyObject_VectorcallMethod() (GH-14228)
1 parent b4bee03 commit b1263d5

File tree

7 files changed

+105
-33
lines changed

7 files changed

+105
-33
lines changed

Doc/c-api/object.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ Object Protocol
395395
argument 1 (not 0) in the allocated vector.
396396
The callee must restore the value of ``args[-1]`` before returning.
397397
398+
For :c:func:`_PyObject_VectorcallMethod`, this flag means instead that
399+
``args[0]`` may be changed.
400+
398401
Whenever they can do so cheaply (without additional allocation), callers
399402
are encouraged to use :const:`PY_VECTORCALL_ARGUMENTS_OFFSET`.
400403
Doing so will allow callables such as bound methods to make their onward
@@ -430,6 +433,25 @@ Object Protocol
430433
431434
.. versionadded:: 3.8
432435
436+
.. c:function:: PyObject* _PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames)
437+
438+
Call a method using the vectorcall calling convention. The name of the method
439+
is given as Python string *name*. The object whose method is called is
440+
*args[0]* and the *args* array starting at *args[1]* represents the arguments
441+
of the call. There must be at least one positional argument.
442+
*nargsf* is the number of positional arguments including *args[0]*,
443+
plus :const:`PY_VECTORCALL_ARGUMENTS_OFFSET` if the value of ``args[0]`` may
444+
temporarily be changed. Keyword arguments can be passed just like in
445+
:c:func:`_PyObject_Vectorcall`.
446+
447+
If the object has the :const:`Py_TPFLAGS_METHOD_DESCRIPTOR` feature,
448+
this will actually call the unbound method object with the full
449+
*args* vector as arguments.
450+
451+
Return the result of the call on success, or raise an exception and return
452+
*NULL* on failure.
453+
454+
.. versionadded:: 3.9
433455
434456
.. c:function:: Py_hash_t PyObject_Hash(PyObject *o)
435457

Include/cpython/abstract.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend(
158158
PyObject *args,
159159
PyObject *kwargs);
160160

161+
PyAPI_FUNC(PyObject *) _PyObject_VectorcallMethod(
162+
PyObject *name, PyObject *const *args,
163+
size_t nargsf, PyObject *kwnames);
164+
161165
/* Like PyObject_CallMethod(), but expect a _Py_Identifier*
162166
as the method name. */
163167
PyAPI_FUNC(PyObject *) _PyObject_CallMethodId(PyObject *obj,
@@ -174,6 +178,18 @@ PyAPI_FUNC(PyObject *) _PyObject_CallMethodIdObjArgs(
174178
struct _Py_Identifier *name,
175179
...);
176180

181+
static inline PyObject *
182+
_PyObject_VectorcallMethodId(
183+
_Py_Identifier *name, PyObject *const *args,
184+
size_t nargsf, PyObject *kwnames)
185+
{
186+
PyObject *oname = _PyUnicode_FromId(name); /* borrowed */
187+
if (!oname) {
188+
return NULL;
189+
}
190+
return _PyObject_VectorcallMethod(oname, args, nargsf, kwnames);
191+
}
192+
177193
PyAPI_FUNC(int) _PyObject_HasLen(PyObject *o);
178194

179195
/* Guess the size of object 'o' using len(o) or o.__length_hint__().
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add :c:func:`_PyObject_VectorcallMethod` for fast calling of methods.

Modules/_abc.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
480480
/*[clinic end generated code: output=b8b5148f63b6b56f input=a4f4525679261084]*/
481481
{
482482
PyObject *subtype, *result = NULL, *subclass = NULL;
483+
PyObject *margs[2];
483484
_abc_data *impl = _get_impl(self);
484485
if (impl == NULL) {
485486
return NULL;
@@ -514,12 +515,16 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
514515
}
515516
}
516517
/* Fall back to the subclass check. */
517-
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
518-
subclass, NULL);
518+
margs[0] = self;
519+
margs[1] = subclass;
520+
result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs,
521+
2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
519522
goto end;
520523
}
521-
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
522-
subclass, NULL);
524+
margs[0] = self;
525+
margs[1] = subclass;
526+
result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs,
527+
2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
523528
if (result == NULL) {
524529
goto end;
525530
}
@@ -531,8 +536,10 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
531536
break;
532537
case 0:
533538
Py_DECREF(result);
534-
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
535-
subtype, NULL);
539+
margs[0] = self;
540+
margs[1] = subtype;
541+
result = _PyObject_VectorcallMethodId(&PyId___subclasscheck__, margs,
542+
2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
536543
break;
537544
case 1: // Nothing to do.
538545
break;

Objects/call.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,38 @@ object_vacall(PyObject *base, PyObject *callable, va_list vargs)
10771077
}
10781078

10791079

1080+
PyObject *
1081+
_PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
1082+
size_t nargsf, PyObject *kwnames)
1083+
{
1084+
assert(name != NULL);
1085+
assert(args != NULL);
1086+
assert(PyVectorcall_NARGS(nargsf) >= 1);
1087+
1088+
PyObject *callable = NULL;
1089+
/* Use args[0] as "self" argument */
1090+
int unbound = _PyObject_GetMethod(args[0], name, &callable);
1091+
if (callable == NULL) {
1092+
return NULL;
1093+
}
1094+
1095+
if (unbound) {
1096+
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
1097+
* that would be interpreted as allowing to change args[-1] */
1098+
nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET;
1099+
}
1100+
else {
1101+
/* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since
1102+
* args[-1] in the onward call is args[0] here. */
1103+
args++;
1104+
nargsf--;
1105+
}
1106+
PyObject *result = _PyObject_Vectorcall(callable, args, nargsf, kwnames);
1107+
Py_DECREF(callable);
1108+
return result;
1109+
}
1110+
1111+
10801112
PyObject *
10811113
PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
10821114
{

Objects/descrobject.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -851,15 +851,22 @@ static PySequenceMethods mappingproxy_as_sequence = {
851851
};
852852

853853
static PyObject *
854-
mappingproxy_get(mappingproxyobject *pp, PyObject *args)
854+
mappingproxy_get(mappingproxyobject *pp, PyObject *const *args, Py_ssize_t nargs)
855855
{
856-
PyObject *key, *def = Py_None;
857-
_Py_IDENTIFIER(get);
856+
/* newargs: mapping, key, default=None */
857+
PyObject *newargs[3];
858+
newargs[0] = pp->mapping;
859+
newargs[2] = Py_None;
858860

859-
if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &def))
861+
if (!_PyArg_UnpackStack(args, nargs, "get", 1, 2,
862+
&newargs[1], &newargs[2]))
863+
{
860864
return NULL;
861-
return _PyObject_CallMethodIdObjArgs(pp->mapping, &PyId_get,
862-
key, def, NULL);
865+
}
866+
_Py_IDENTIFIER(get);
867+
return _PyObject_VectorcallMethodId(&PyId_get, newargs,
868+
3 | PY_VECTORCALL_ARGUMENTS_OFFSET,
869+
NULL);
863870
}
864871

865872
static PyObject *
@@ -894,7 +901,7 @@ mappingproxy_copy(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored))
894901
to the underlying mapping */
895902

896903
static PyMethodDef mappingproxy_methods[] = {
897-
{"get", (PyCFunction)mappingproxy_get, METH_VARARGS,
904+
{"get", (PyCFunction)mappingproxy_get, METH_FASTCALL,
898905
PyDoc_STR("D.get(k[,d]) -> D[k] if k in D, else d."
899906
" d defaults to None.")},
900907
{"keys", (PyCFunction)mappingproxy_keys, METH_NOARGS,

Python/sysmodule.c

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3110,30 +3110,17 @@ PySys_SetArgv(int argc, wchar_t **argv)
31103110
static int
31113111
sys_pyfile_write_unicode(PyObject *unicode, PyObject *file)
31123112
{
3113-
PyObject *writer = NULL, *result = NULL;
3114-
int err;
3115-
31163113
if (file == NULL)
31173114
return -1;
3118-
3119-
writer = _PyObject_GetAttrId(file, &PyId_write);
3120-
if (writer == NULL)
3121-
goto error;
3122-
3123-
result = PyObject_CallFunctionObjArgs(writer, unicode, NULL);
3115+
assert(unicode != NULL);
3116+
PyObject *margs[2] = {file, unicode};
3117+
PyObject *result = _PyObject_VectorcallMethodId(&PyId_write, margs,
3118+
2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
31243119
if (result == NULL) {
3125-
goto error;
3126-
} else {
3127-
err = 0;
3128-
goto finally;
3120+
return -1;
31293121
}
3130-
3131-
error:
3132-
err = -1;
3133-
finally:
3134-
Py_XDECREF(writer);
3135-
Py_XDECREF(result);
3136-
return err;
3122+
Py_DECREF(result);
3123+
return 0;
31373124
}
31383125

31393126
static int

0 commit comments

Comments
 (0)