Skip to content

Commit 1911dd4

Browse files
authored
Add PyObject_Vectorcall() (#62)
1 parent 4dbb310 commit 1911dd4

File tree

4 files changed

+287
-0
lines changed

4 files changed

+287
-0
lines changed

docs/api.rst

+12
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ PyObject
211211
212212
See `PyObject_CallOneArg() documentation <https://docs.python.org/dev/c-api/call.html#c.PyObject_CallOneArg>`__.
213213
214+
.. c:function:: PyObject* PyObject_Vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
215+
216+
See `PyObject_Vectorcall() documentation <https://docs.python.org/dev/c-api/call.html#c.PyObject_Vectorcall>`__.
217+
218+
.. c:function:: Py_ssize_t PyVectorcall_NARGS(size_t nargsf)
219+
220+
See `PyVectorcall_NARGS() documentation <https://docs.python.org/dev/c-api/call.html#c.PyVectorcall_NARGS>`__.
221+
222+
.. c:macro:: PY_VECTORCALL_ARGUMENTS_OFFSET
223+
224+
See `PY_VECTORCALL_ARGUMENTS_OFFSET documentation <https://docs.python.org/dev/c-api/call.html#PY_VECTORCALL_ARGUMENTS_OFFSET>`__.
225+
214226
215227
PyFrameObject
216228
^^^^^^^^^^^^^

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Changelog
22
=========
33

4+
* 2023-07-05: Add ``PyObject_Vectorcall()`` function.
45
* 2023-06-21: Add ``PyWeakref_GetRef()`` function.
56
* 2023-06-20: Add ``PyImport_AddModuleRef()`` function.
67
* 2022-11-15: Add experimental operations to the ``upgrade_pythoncapi.py``

pythoncapi_compat.h

+90
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,96 @@ PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
608608
#endif
609609

610610

611+
// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1
612+
#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET
613+
# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
614+
#endif
615+
616+
// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1
617+
#if PY_VERSION_HEX < 0x030800B1
618+
static inline Py_ssize_t
619+
PyVectorcall_NARGS(size_t n)
620+
{
621+
return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;
622+
}
623+
#endif
624+
625+
626+
// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4
627+
#if PY_VERSION_HEX < 0x030900A4
628+
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
629+
PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
630+
size_t nargsf, PyObject *kwnames)
631+
{
632+
#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION)
633+
// bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1
634+
return _PyObject_Vectorcall(callable, args, nargsf, kwnames);
635+
#else
636+
PyObject *posargs = NULL, *kwargs = NULL;
637+
PyObject *res;
638+
Py_ssize_t nposargs, nkwargs, i;
639+
640+
if (nargsf != 0 && args == NULL) {
641+
PyErr_BadInternalCall();
642+
goto error;
643+
}
644+
if (kwnames != NULL && !PyTuple_Check(kwnames)) {
645+
PyErr_BadInternalCall();
646+
goto error;
647+
}
648+
649+
nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf);
650+
if (kwnames) {
651+
nkwargs = PyTuple_GET_SIZE(kwnames);
652+
}
653+
else {
654+
nkwargs = 0;
655+
}
656+
657+
posargs = PyTuple_New(nposargs);
658+
if (posargs == NULL) {
659+
goto error;
660+
}
661+
if (nposargs) {
662+
for (i=0; i < nposargs; i++) {
663+
PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args));
664+
args++;
665+
}
666+
}
667+
668+
if (nkwargs) {
669+
kwargs = PyDict_New();
670+
if (kwargs == NULL) {
671+
goto error;
672+
}
673+
674+
for (i = 0; i < nkwargs; i++) {
675+
PyObject *key = PyTuple_GET_ITEM(kwnames, i);
676+
PyObject *value = *args;
677+
args++;
678+
if (PyDict_SetItem(kwargs, key, value) < 0) {
679+
goto error;
680+
}
681+
}
682+
}
683+
else {
684+
kwargs = NULL;
685+
}
686+
687+
res = PyObject_Call(callable, posargs, kwargs);
688+
Py_DECREF(posargs);
689+
Py_XDECREF(kwargs);
690+
return res;
691+
692+
error:
693+
Py_DECREF(posargs);
694+
Py_XDECREF(kwargs);
695+
return NULL;
696+
#endif
697+
}
698+
#endif
699+
700+
611701
#ifdef __cplusplus
612702
}
613703
#endif

tests/test_pythoncapi_compat_cext.c

+184
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,31 @@ gc_collect(void)
685685
}
686686

687687

688+
static PyObject *
689+
func_varargs(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs)
690+
{
691+
if (kwargs != NULL) {
692+
return PyTuple_Pack(2, args, kwargs);
693+
}
694+
else {
695+
return PyTuple_Pack(1, args);
696+
}
697+
}
698+
699+
700+
static void
701+
check_int(PyObject *obj, int value)
702+
{
703+
#ifdef PYTHON3
704+
assert(PyLong_Check(obj));
705+
assert(PyLong_AsLong(obj) == value);
706+
#else
707+
assert(PyInt_Check(obj));
708+
assert(PyInt_AsLong(obj) == value);
709+
#endif
710+
}
711+
712+
688713
static PyObject *
689714
test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
690715
{
@@ -743,7 +768,164 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
743768
#endif
744769

745770
Py_DECREF(weakref);
771+
Py_RETURN_NONE;
772+
}
773+
774+
775+
static void
776+
test_vectorcall_noargs(PyObject *func_varargs)
777+
{
778+
PyObject *res = PyObject_Vectorcall(func_varargs, NULL, 0, NULL);
779+
assert(res != NULL);
780+
781+
assert(PyTuple_Check(res));
782+
assert(PyTuple_GET_SIZE(res) == 1);
783+
PyObject *posargs = PyTuple_GET_ITEM(res, 0);
784+
785+
assert(PyTuple_Check(posargs));
786+
assert(PyTuple_GET_SIZE(posargs) == 0);
787+
788+
Py_DECREF(res);
789+
}
790+
791+
792+
static void
793+
test_vectorcall_args(PyObject *func_varargs)
794+
{
795+
PyObject *args_tuple = Py_BuildValue("ii", 1, 2);
796+
assert(args_tuple != NULL);
797+
size_t nargs = (size_t)PyTuple_GET_SIZE(args_tuple);
798+
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 0);
799+
800+
PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, NULL);
801+
Py_DECREF(args_tuple);
802+
assert(res != NULL);
803+
804+
assert(PyTuple_Check(res));
805+
assert(PyTuple_GET_SIZE(res) == 1);
806+
PyObject *posargs = PyTuple_GET_ITEM(res, 0);
807+
808+
assert(PyTuple_Check(posargs));
809+
assert(PyTuple_GET_SIZE(posargs) == 2);
810+
check_int(PyTuple_GET_ITEM(posargs, 0), 1);
811+
check_int(PyTuple_GET_ITEM(posargs, 1), 2);
812+
813+
Py_DECREF(res);
814+
}
815+
816+
817+
static void
818+
test_vectorcall_args_offset(PyObject *func_varargs)
819+
{
820+
// args contains 3 values, but only pass 2 last values
821+
PyObject *args_tuple = Py_BuildValue("iii", 1, 2, 3);
822+
assert(args_tuple != NULL);
823+
size_t nargs = 2 | PY_VECTORCALL_ARGUMENTS_OFFSET;
824+
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 1);
825+
PyObject *arg0 = PyTuple_GET_ITEM(args_tuple, 0);
826+
827+
PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, NULL);
828+
assert(PyTuple_GET_ITEM(args_tuple, 0) == arg0);
829+
Py_DECREF(args_tuple);
830+
assert(res != NULL);
831+
832+
assert(PyTuple_Check(res));
833+
assert(PyTuple_GET_SIZE(res) == 1);
834+
PyObject *posargs = PyTuple_GET_ITEM(res, 0);
835+
836+
assert(PyTuple_Check(posargs));
837+
assert(PyTuple_GET_SIZE(posargs) == 2);
838+
check_int(PyTuple_GET_ITEM(posargs, 0), 2);
839+
check_int(PyTuple_GET_ITEM(posargs, 1), 3);
840+
841+
Py_DECREF(res);
842+
}
843+
844+
845+
static void
846+
test_vectorcall_args_kwnames(PyObject *func_varargs)
847+
{
848+
PyObject *args_tuple = Py_BuildValue("iiiii", 1, 2, 3, 4, 5);
849+
assert(args_tuple != NULL);
850+
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 0);
851+
852+
#ifdef PYTHON3
853+
PyObject *key1 = PyUnicode_FromString("key1");
854+
PyObject *key2 = PyUnicode_FromString("key2");
855+
#else
856+
PyObject *key1 = PyString_FromString("key1");
857+
PyObject *key2 = PyString_FromString("key2");
858+
#endif
859+
assert(key1 != NULL);
860+
assert(key2 != NULL);
861+
PyObject *kwnames = PyTuple_Pack(2, key1, key2);
862+
assert(kwnames != NULL);
863+
size_t nargs = (size_t)(PyTuple_GET_SIZE(args_tuple) - PyTuple_GET_SIZE(kwnames));
864+
865+
PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, kwnames);
866+
Py_DECREF(args_tuple);
867+
Py_DECREF(kwnames);
868+
assert(res != NULL);
869+
870+
assert(PyTuple_Check(res));
871+
assert(PyTuple_GET_SIZE(res) == 2);
872+
PyObject *posargs = PyTuple_GET_ITEM(res, 0);
873+
PyObject *kwargs = PyTuple_GET_ITEM(res, 1);
874+
875+
assert(PyTuple_Check(posargs));
876+
assert(PyTuple_GET_SIZE(posargs) == 3);
877+
check_int(PyTuple_GET_ITEM(posargs, 0), 1);
878+
check_int(PyTuple_GET_ITEM(posargs, 1), 2);
879+
check_int(PyTuple_GET_ITEM(posargs, 2), 3);
880+
881+
assert(PyDict_Check(kwargs));
882+
assert(PyDict_Size(kwargs) == 2);
883+
884+
Py_ssize_t pos = 0;
885+
PyObject *key, *value;
886+
while (PyDict_Next(kwargs, &pos, &key, &value)) {
887+
#ifdef PYTHON3
888+
assert(PyUnicode_Check(key));
889+
#else
890+
assert(PyString_Check(key));
891+
#endif
892+
if (PyObject_RichCompareBool(key, key1, Py_EQ)) {
893+
check_int(value, 4);
894+
}
895+
else {
896+
assert(PyObject_RichCompareBool(key, key2, Py_EQ));
897+
check_int(value, 5);
898+
}
899+
}
900+
901+
Py_DECREF(res);
902+
Py_DECREF(key1);
903+
Py_DECREF(key2);
904+
}
905+
906+
907+
static PyObject *
908+
test_vectorcall(PyObject *module, PyObject *Py_UNUSED(args))
909+
{
910+
#ifndef PYTHON3
911+
module = PyImport_ImportModule(MODULE_NAME_STR);
912+
assert(module != NULL);
913+
#endif
914+
PyObject *func_varargs = PyObject_GetAttrString(module, "func_varargs");
915+
#ifndef PYTHON3
916+
Py_DECREF(module);
917+
#endif
918+
if (func_varargs == NULL) {
919+
return NULL;
920+
}
921+
922+
// test PyObject_Vectorcall()
923+
test_vectorcall_noargs(func_varargs);
924+
test_vectorcall_args(func_varargs);
925+
test_vectorcall_args_offset(func_varargs);
926+
test_vectorcall_args_kwnames(func_varargs);
746927

928+
Py_DECREF(func_varargs);
747929
Py_RETURN_NONE;
748930
}
749931

@@ -768,6 +950,8 @@ static struct PyMethodDef methods[] = {
768950
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
769951
{"test_import", test_import, METH_NOARGS, _Py_NULL},
770952
{"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
953+
{"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL},
954+
{"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
771955
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
772956
};
773957

0 commit comments

Comments
 (0)