]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-98586: Add vector call APIs to the Limited API (GH-98587)
authorWenzel Jakob <wenzel.jakob@epfl.ch>
Thu, 27 Oct 2022 09:45:42 +0000 (11:45 +0200)
committerGitHub <noreply@github.com>
Thu, 27 Oct 2022 09:45:42 +0000 (11:45 +0200)
Expose the facilities for making vector calls through Python's limited API.

Doc/data/stable_abi.dat
Include/abstract.h
Include/cpython/abstract.h
Lib/test/test_call.py
Lib/test/test_stable_abi_ctypes.py
Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst [new file with mode: 0644]
Misc/stable_abi.toml
Modules/_testcapi/vectorcall_limited.c
PC/python3dll.c

index fde62eacd00a7c87230a5775aa94aa910c40508e..133658491c5a4fb218fea126feeadf57b50bdea9 100644 (file)
@@ -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,,
index 784ff7e928676f243283d094dd09b1b9f67cc6b8..064b0300b51ea2917f877503a93ccf46e58705bf 100644 (file)
@@ -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:
 
index 6da29cde9f60922567f5d799f3cfb95305fd8dac..3b27aab2fc4798de9d00ba7eb72e4ec5a2067c5e 100644 (file)
@@ -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)
 {
index 0b37116cd682396c6c77b93457ea6014d44e7532..d4ddb7922ef37d071a97c7d86e4da7c731d97a13 100644 (file)
@@ -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):
index a803e3a50259859bf78242c0b09e2bcc1e8c9f74..67c653428a6dee8ccddaa273e34387fb6384a950 100644 (file)
@@ -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 (file)
index 0000000..5d7b0c8
--- /dev/null
@@ -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.
index e78646fdea59cfbe62a5d16ec7c2f8c7dffcbb8d..e18a6e8f6a9c2c79b1a85832e5da245ff3e32cdd 100644 (file)
     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'
index ee57af84b1bb5fb33e2988a72c70d2a9def50338..a69f1d3f2a79b5abaa09ca0c47305992aa48f66a 100644 (file)
@@ -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},
 };
 
index c1b88c66903b101abbafa334f9d8553f6ef71f9d..931f316bb998439f73375d517a590808df49a9b2 100755 (executable)
@@ -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)