summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWenzel Jakob <wenzel.jakob@epfl.ch>2022-10-27 09:45:42 (GMT)
committerGitHub <noreply@github.com>2022-10-27 09:45:42 (GMT)
commite60892f9db1316dbabf7a652d7648e4f968b745d (patch)
treef3e6c05f67907d2778241c17425ce88a88c407cc
parentd578aaea6257458c199328100cbb5af64c6a043e (diff)
downloadcpython-e60892f9db1316dbabf7a652d7648e4f968b745d.zip
cpython-e60892f9db1316dbabf7a652d7648e4f968b745d.tar.gz
cpython-e60892f9db1316dbabf7a652d7648e4f968b745d.tar.bz2
gh-98586: Add vector call APIs to the Limited API (GH-98587)
Expose the facilities for making vector calls through Python's limited API.
-rw-r--r--Doc/data/stable_abi.dat3
-rw-r--r--Include/abstract.h16
-rw-r--r--Include/cpython/abstract.h13
-rw-r--r--Lib/test/test_call.py34
-rw-r--r--Lib/test/test_stable_abi_ctypes.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst7
-rw-r--r--Misc/stable_abi.toml6
-rw-r--r--Modules/_testcapi/vectorcall_limited.c105
-rwxr-xr-xPC/python3dll.c2
9 files changed, 170 insertions, 18 deletions
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index fde62ea..1336584 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -1,4 +1,5 @@
role,name,added,ifdef_note,struct_abi_kind
+macro,PY_VECTORCALL_ARGUMENTS_OFFSET,3.12,,
function,PyAIter_Check,3.10,,
function,PyArg_Parse,3.2,,
function,PyArg_ParseTuple,3.2,,
@@ -536,6 +537,8 @@ function,PyObject_SetItem,3.2,,
function,PyObject_Size,3.2,,
function,PyObject_Str,3.2,,
function,PyObject_Type,3.2,,
+function,PyObject_Vectorcall,3.12,,
+function,PyObject_VectorcallMethod,3.12,,
var,PyProperty_Type,3.2,,
var,PyRangeIter_Type,3.2,,
var,PyRange_Type,3.2,,
diff --git a/Include/abstract.h b/Include/abstract.h
index 784ff7e..064b030 100644
--- a/Include/abstract.h
+++ b/Include/abstract.h
@@ -238,6 +238,22 @@ PyAPI_FUNC(Py_ssize_t) PyVectorcall_NARGS(size_t nargsf);
"tuple" and keyword arguments "dict". "dict" may also be NULL */
PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict);
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
+#define PY_VECTORCALL_ARGUMENTS_OFFSET \
+ (_Py_STATIC_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
+
+/* Perform a PEP 590-style vector call on 'callable' */
+PyAPI_FUNC(PyObject *) PyObject_Vectorcall(
+ PyObject *callable,
+ PyObject *const *args,
+ size_t nargsf,
+ PyObject *kwnames);
+
+/* Call the method 'name' on args[0] with arguments in args[1..nargsf-1]. */
+PyAPI_FUNC(PyObject *) PyObject_VectorcallMethod(
+ PyObject *name, PyObject *const *args,
+ size_t nargsf, PyObject *kwnames);
+#endif
/* Implemented elsewhere:
diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h
index 6da29cde..3b27aab 100644
--- a/Include/cpython/abstract.h
+++ b/Include/cpython/abstract.h
@@ -50,9 +50,6 @@ PyAPI_FUNC(PyObject *) _PyObject_MakeTpCall(
PyObject *const *args, Py_ssize_t nargs,
PyObject *keywords);
-#define PY_VECTORCALL_ARGUMENTS_OFFSET \
- (_Py_STATIC_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
-
// PyVectorcall_NARGS() is exported as a function for the stable ABI.
// Here (when we are not using the stable ABI), the name is overridden to
// call a static inline function for best performance.
@@ -65,12 +62,6 @@ _PyVectorcall_NARGS(size_t n)
PyAPI_FUNC(vectorcallfunc) PyVectorcall_Function(PyObject *callable);
-PyAPI_FUNC(PyObject *) PyObject_Vectorcall(
- PyObject *callable,
- PyObject *const *args,
- size_t nargsf,
- PyObject *kwnames);
-
// Backwards compatibility aliases for API that was provisional in Python 3.8
#define _PyObject_Vectorcall PyObject_Vectorcall
#define _PyObject_VectorcallMethod PyObject_VectorcallMethod
@@ -96,10 +87,6 @@ PyAPI_FUNC(PyObject *) _PyObject_FastCall(
PyAPI_FUNC(PyObject *) PyObject_CallOneArg(PyObject *func, PyObject *arg);
-PyAPI_FUNC(PyObject *) PyObject_VectorcallMethod(
- PyObject *name, PyObject *const *args,
- size_t nargsf, PyObject *kwnames);
-
static inline PyObject *
PyObject_CallMethodNoArgs(PyObject *self, PyObject *name)
{
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 0b37116..d4ddb79 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -812,11 +812,43 @@ class TestPEP590(unittest.TestCase):
assert_equal("overridden", get_a(x))
@requires_limited_api
- def test_vectorcall_limited(self):
+ def test_vectorcall_limited_incoming(self):
from _testcapi import pyobject_vectorcall
obj = _testcapi.LimitedVectorCallClass()
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")
+ @requires_limited_api
+ def test_vectorcall_limited_outgoing(self):
+ from _testcapi import call_vectorcall
+
+ args_captured = []
+ kwargs_captured = []
+
+ def f(*args, **kwargs):
+ args_captured.append(args)
+ kwargs_captured.append(kwargs)
+ return "success"
+
+ self.assertEqual(call_vectorcall(f), "success")
+ self.assertEqual(args_captured, [("foo",)])
+ self.assertEqual(kwargs_captured, [{"baz": "bar"}])
+
+ @requires_limited_api
+ def test_vectorcall_limited_outgoing_method(self):
+ from _testcapi import call_vectorcall_method
+
+ args_captured = []
+ kwargs_captured = []
+
+ class TestInstance:
+ def f(self, *args, **kwargs):
+ args_captured.append(args)
+ kwargs_captured.append(kwargs)
+ return "success"
+
+ self.assertEqual(call_vectorcall_method(TestInstance()), "success")
+ self.assertEqual(args_captured, [("foo",)])
+ self.assertEqual(kwargs_captured, [{"baz": "bar"}])
class A:
def method_two_args(self, x, y):
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index a803e3a..67c6534 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -547,6 +547,8 @@ SYMBOL_NAMES = (
"PyObject_Size",
"PyObject_Str",
"PyObject_Type",
+ "PyObject_Vectorcall",
+ "PyObject_VectorcallMethod",
"PyProperty_Type",
"PyRangeIter_Type",
"PyRange_Type",
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst b/Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst
new file mode 100644
index 0000000..5d7b0c8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst
@@ -0,0 +1,7 @@
+Added the methods :c:func:`PyObject_Vectorcall` and
+:c:func:`PyObject_VectorcallMethod` to the :ref:`Limited API <stable>` along
+with the auxiliary macro constant :c:macro:`PY_VECTORCALL_ARGUMENTS_OFFSET`.
+
+The availability of these functions enables more efficient :PEP:`590` vector
+calls from binary extension modules that avoid argument boxing/unboxing
+overheads.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index e78646f..e18a6e8 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2293,3 +2293,9 @@
added = '3.12'
[typedef.vectorcallfunc]
added = '3.12'
+[function.PyObject_Vectorcall]
+ added = '3.12'
+[function.PyObject_VectorcallMethod]
+ added = '3.12'
+[macro.PY_VECTORCALL_ARGUMENTS_OFFSET]
+ added = '3.12'
diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c
index ee57af8..a69f1d3 100644
--- a/Modules/_testcapi/vectorcall_limited.c
+++ b/Modules/_testcapi/vectorcall_limited.c
@@ -32,6 +32,105 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw)
return self;
}
+static PyObject *
+call_vectorcall(PyObject* self, PyObject *callable)
+{
+ PyObject *args[3] = { NULL, NULL, NULL };
+ PyObject *kwname = NULL, *kwnames = NULL, *result = NULL;
+
+ args[1] = PyUnicode_FromString("foo");
+ if (!args[1]) {
+ goto leave;
+ }
+
+ args[2] = PyUnicode_FromString("bar");
+ if (!args[2]) {
+ goto leave;
+ }
+
+ kwname = PyUnicode_InternFromString("baz");
+ if (!kwname) {
+ goto leave;
+ }
+
+ kwnames = PyTuple_New(1);
+ if (!kwnames) {
+ goto leave;
+ }
+
+ if (PyTuple_SetItem(kwnames, 0, kwname)) {
+ goto leave;
+ }
+
+ result = PyObject_Vectorcall(
+ callable,
+ args + 1,
+ 1 | PY_VECTORCALL_ARGUMENTS_OFFSET,
+ kwnames
+ );
+
+leave:
+ Py_XDECREF(args[1]);
+ Py_XDECREF(args[2]);
+ Py_XDECREF(kwnames);
+
+ return result;
+}
+
+static PyObject *
+call_vectorcall_method(PyObject* self, PyObject *callable)
+{
+ PyObject *args[3] = { NULL, NULL, NULL };
+ PyObject *name = NULL, *kwname = NULL,
+ *kwnames = NULL, *result = NULL;
+
+ name = PyUnicode_FromString("f");
+ if (!name) {
+ goto leave;
+ }
+
+ args[0] = callable;
+ args[1] = PyUnicode_FromString("foo");
+ if (!args[1]) {
+ goto leave;
+ }
+
+ args[2] = PyUnicode_FromString("bar");
+ if (!args[2]) {
+ goto leave;
+ }
+
+ kwname = PyUnicode_InternFromString("baz");
+ if (!kwname) {
+ goto leave;
+ }
+
+ kwnames = PyTuple_New(1);
+ if (!kwnames) {
+ goto leave;
+ }
+
+ if (PyTuple_SetItem(kwnames, 0, kwname)) {
+ goto leave;
+ }
+
+
+ result = PyObject_VectorcallMethod(
+ name,
+ args,
+ 2 | PY_VECTORCALL_ARGUMENTS_OFFSET,
+ kwnames
+ );
+
+leave:
+ Py_XDECREF(name);
+ Py_XDECREF(args[1]);
+ Py_XDECREF(args[2]);
+ Py_XDECREF(kwnames);
+
+ return result;
+}
+
static PyMemberDef LimitedVectorCallClass_members[] = {
{"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY},
{NULL}
@@ -54,10 +153,8 @@ static PyType_Spec LimitedVectorCallClass_spec = {
};
static PyMethodDef TestMethods[] = {
- /* Add module methods here.
- * (Empty list left here as template/example, since using
- * PyModule_AddFunctions isn't very common.)
- */
+ {"call_vectorcall", call_vectorcall, METH_O},
+ {"call_vectorcall_method", call_vectorcall_method, METH_O},
{NULL},
};
diff --git a/PC/python3dll.c b/PC/python3dll.c
index c1b88c6..931f316 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -485,6 +485,8 @@ EXPORT_FUNC(PyObject_SetItem)
EXPORT_FUNC(PyObject_Size)
EXPORT_FUNC(PyObject_Str)
EXPORT_FUNC(PyObject_Type)
+EXPORT_FUNC(PyObject_Vectorcall)
+EXPORT_FUNC(PyObject_VectorcallMethod)
EXPORT_FUNC(PyOS_CheckStack)
EXPORT_FUNC(PyOS_double_to_string)
EXPORT_FUNC(PyOS_FSPath)