Skip to content

Add PyObject_Vectorcall() #62

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 2 commits into from
Jul 10, 2023
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
12 changes: 12 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ PyObject

See `PyObject_CallOneArg() documentation <https://docs.python.org/dev/c-api/call.html#c.PyObject_CallOneArg>`__.

.. c:function:: PyObject* PyObject_Vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)

See `PyObject_Vectorcall() documentation <https://docs.python.org/dev/c-api/call.html#c.PyObject_Vectorcall>`__.

.. c:function:: Py_ssize_t PyVectorcall_NARGS(size_t nargsf)

See `PyVectorcall_NARGS() documentation <https://docs.python.org/dev/c-api/call.html#c.PyVectorcall_NARGS>`__.

.. c:macro:: PY_VECTORCALL_ARGUMENTS_OFFSET

See `PY_VECTORCALL_ARGUMENTS_OFFSET documentation <https://docs.python.org/dev/c-api/call.html#PY_VECTORCALL_ARGUMENTS_OFFSET>`__.


PyFrameObject
^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Changelog
=========

* 2023-07-05: Add ``PyObject_Vectorcall()`` function.
* 2023-06-21: Add ``PyWeakref_GetRef()`` function.
* 2023-06-20: Add ``PyImport_AddModuleRef()`` function.
* 2022-11-15: Add experimental operations to the ``upgrade_pythoncapi.py``
Expand Down
90 changes: 90 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,96 @@ PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
#endif


// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1
#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET
# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
#endif

// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1
#if PY_VERSION_HEX < 0x030800B1
static inline Py_ssize_t
PyVectorcall_NARGS(size_t n)
{
return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;
}
#endif


// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
{
#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION)
// bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1
return _PyObject_Vectorcall(callable, args, nargsf, kwnames);
#else
PyObject *posargs = NULL, *kwargs = NULL;
PyObject *res;
Py_ssize_t nposargs, nkwargs, i;

if (nargsf != 0 && args == NULL) {
PyErr_BadInternalCall();
goto error;
}
if (kwnames != NULL && !PyTuple_Check(kwnames)) {
PyErr_BadInternalCall();
goto error;
}

nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf);
if (kwnames) {
nkwargs = PyTuple_GET_SIZE(kwnames);
}
else {
nkwargs = 0;
}

posargs = PyTuple_New(nposargs);
if (posargs == NULL) {
goto error;
}
if (nposargs) {
for (i=0; i < nposargs; i++) {
PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args));
args++;
}
}

if (nkwargs) {
kwargs = PyDict_New();
if (kwargs == NULL) {
goto error;
}

for (i = 0; i < nkwargs; i++) {
PyObject *key = PyTuple_GET_ITEM(kwnames, i);
PyObject *value = *args;
args++;
if (PyDict_SetItem(kwargs, key, value) < 0) {
goto error;
}
}
}
else {
kwargs = NULL;
}

res = PyObject_Call(callable, posargs, kwargs);
Py_DECREF(posargs);
Py_XDECREF(kwargs);
return res;

error:
Py_DECREF(posargs);
Py_XDECREF(kwargs);
return NULL;
#endif
}
#endif


#ifdef __cplusplus
}
#endif
Expand Down
184 changes: 184 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,31 @@ gc_collect(void)
}


static PyObject *
func_varargs(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs)
{
if (kwargs != NULL) {
return PyTuple_Pack(2, args, kwargs);
}
else {
return PyTuple_Pack(1, args);
}
}


static void
check_int(PyObject *obj, int value)
{
#ifdef PYTHON3
assert(PyLong_Check(obj));
assert(PyLong_AsLong(obj) == value);
#else
assert(PyInt_Check(obj));
assert(PyInt_AsLong(obj) == value);
#endif
}


static PyObject *
test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
Expand Down Expand Up @@ -743,7 +768,164 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
#endif

Py_DECREF(weakref);
Py_RETURN_NONE;
}


static void
test_vectorcall_noargs(PyObject *func_varargs)
{
PyObject *res = PyObject_Vectorcall(func_varargs, NULL, 0, NULL);
assert(res != NULL);

assert(PyTuple_Check(res));
assert(PyTuple_GET_SIZE(res) == 1);
PyObject *posargs = PyTuple_GET_ITEM(res, 0);

assert(PyTuple_Check(posargs));
assert(PyTuple_GET_SIZE(posargs) == 0);

Py_DECREF(res);
}


static void
test_vectorcall_args(PyObject *func_varargs)
{
PyObject *args_tuple = Py_BuildValue("ii", 1, 2);
assert(args_tuple != NULL);
size_t nargs = (size_t)PyTuple_GET_SIZE(args_tuple);
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 0);

PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, NULL);
Py_DECREF(args_tuple);
assert(res != NULL);

assert(PyTuple_Check(res));
assert(PyTuple_GET_SIZE(res) == 1);
PyObject *posargs = PyTuple_GET_ITEM(res, 0);

assert(PyTuple_Check(posargs));
assert(PyTuple_GET_SIZE(posargs) == 2);
check_int(PyTuple_GET_ITEM(posargs, 0), 1);
check_int(PyTuple_GET_ITEM(posargs, 1), 2);

Py_DECREF(res);
}


static void
test_vectorcall_args_offset(PyObject *func_varargs)
{
// args contains 3 values, but only pass 2 last values
PyObject *args_tuple = Py_BuildValue("iii", 1, 2, 3);
assert(args_tuple != NULL);
size_t nargs = 2 | PY_VECTORCALL_ARGUMENTS_OFFSET;
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 1);
PyObject *arg0 = PyTuple_GET_ITEM(args_tuple, 0);

PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, NULL);
assert(PyTuple_GET_ITEM(args_tuple, 0) == arg0);
Py_DECREF(args_tuple);
assert(res != NULL);

assert(PyTuple_Check(res));
assert(PyTuple_GET_SIZE(res) == 1);
PyObject *posargs = PyTuple_GET_ITEM(res, 0);

assert(PyTuple_Check(posargs));
assert(PyTuple_GET_SIZE(posargs) == 2);
check_int(PyTuple_GET_ITEM(posargs, 0), 2);
check_int(PyTuple_GET_ITEM(posargs, 1), 3);

Py_DECREF(res);
}


static void
test_vectorcall_args_kwnames(PyObject *func_varargs)
{
PyObject *args_tuple = Py_BuildValue("iiiii", 1, 2, 3, 4, 5);
assert(args_tuple != NULL);
PyObject **args = &PyTuple_GET_ITEM(args_tuple, 0);

#ifdef PYTHON3
PyObject *key1 = PyUnicode_FromString("key1");
PyObject *key2 = PyUnicode_FromString("key2");
#else
PyObject *key1 = PyString_FromString("key1");
PyObject *key2 = PyString_FromString("key2");
#endif
assert(key1 != NULL);
assert(key2 != NULL);
PyObject *kwnames = PyTuple_Pack(2, key1, key2);
assert(kwnames != NULL);
size_t nargs = (size_t)(PyTuple_GET_SIZE(args_tuple) - PyTuple_GET_SIZE(kwnames));

PyObject *res = PyObject_Vectorcall(func_varargs, args, nargs, kwnames);
Py_DECREF(args_tuple);
Py_DECREF(kwnames);
assert(res != NULL);

assert(PyTuple_Check(res));
assert(PyTuple_GET_SIZE(res) == 2);
PyObject *posargs = PyTuple_GET_ITEM(res, 0);
PyObject *kwargs = PyTuple_GET_ITEM(res, 1);

assert(PyTuple_Check(posargs));
assert(PyTuple_GET_SIZE(posargs) == 3);
check_int(PyTuple_GET_ITEM(posargs, 0), 1);
check_int(PyTuple_GET_ITEM(posargs, 1), 2);
check_int(PyTuple_GET_ITEM(posargs, 2), 3);

assert(PyDict_Check(kwargs));
assert(PyDict_Size(kwargs) == 2);

Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(kwargs, &pos, &key, &value)) {
#ifdef PYTHON3
assert(PyUnicode_Check(key));
#else
assert(PyString_Check(key));
#endif
if (PyObject_RichCompareBool(key, key1, Py_EQ)) {
check_int(value, 4);
}
else {
assert(PyObject_RichCompareBool(key, key2, Py_EQ));
check_int(value, 5);
}
}

Py_DECREF(res);
Py_DECREF(key1);
Py_DECREF(key2);
}


static PyObject *
test_vectorcall(PyObject *module, PyObject *Py_UNUSED(args))
{
#ifndef PYTHON3
module = PyImport_ImportModule(MODULE_NAME_STR);
assert(module != NULL);
#endif
PyObject *func_varargs = PyObject_GetAttrString(module, "func_varargs");
#ifndef PYTHON3
Py_DECREF(module);
#endif
if (func_varargs == NULL) {
return NULL;
}

// test PyObject_Vectorcall()
test_vectorcall_noargs(func_varargs);
test_vectorcall_args(func_varargs);
test_vectorcall_args_offset(func_varargs);
test_vectorcall_args_kwnames(func_varargs);

Py_DECREF(func_varargs);
Py_RETURN_NONE;
}

Expand All @@ -768,6 +950,8 @@ static struct PyMethodDef methods[] = {
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
{"test_import", test_import, METH_NOARGS, _Py_NULL},
{"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
{"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL},
{"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
};

Expand Down