From 7acde40d4af1808ec14952cd953f4a7cf6b9e29a Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Mon, 24 Oct 2022 10:31:14 +0200 Subject: [PATCH 01/10] expose more of the PEP-590 vector call API --- Doc/data/stable_abi.dat | 4 ++++ Include/abstract.h | 24 +++++++++++++++++++ Include/cpython/abstract.h | 21 ---------------- Lib/test/test_stable_abi_ctypes.py | 3 +++ ...2-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst | 7 ++++++ Misc/stable_abi.toml | 8 +++++++ PC/python3dll.c | 3 +++ 7 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index fde62eacd00a7c..1ed4131c5644f6 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,9 @@ 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_VectorcallDict,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 784ff7e928676f..121a789426dfb7 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -238,6 +238,30 @@ 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); + +/* Same as PyObject_Vectorcall except that keyword arguments are passed as + dict, which may be NULL if there are no keyword arguments. */ +PyAPI_FUNC(PyObject *) PyObject_VectorcallDict( + PyObject *callable, + PyObject *const *args, + size_t nargsf, + PyObject *kwargs); + +/* 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 6da29cde9f6092..f96caabdd0ecbb 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 @@ -80,14 +71,6 @@ PyAPI_FUNC(PyObject *) PyObject_Vectorcall( #define _PyObject_CallMethodNoArgs PyObject_CallMethodNoArgs #define _PyObject_CallMethodOneArg PyObject_CallMethodOneArg -/* Same as PyObject_Vectorcall except that keyword arguments are passed as - dict, which may be NULL if there are no keyword arguments. */ -PyAPI_FUNC(PyObject *) PyObject_VectorcallDict( - PyObject *callable, - PyObject *const *args, - size_t nargsf, - PyObject *kwargs); - // Same as PyObject_Vectorcall(), except without keyword arguments PyAPI_FUNC(PyObject *) _PyObject_FastCall( PyObject *func, @@ -96,10 +79,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_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index a803e3a5025985..ab0225a04f94ed 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -547,6 +547,9 @@ def test_windows_feature_macros(self): "PyObject_Size", "PyObject_Str", "PyObject_Type", + "PyObject_Vectorcall", + "PyObject_VectorcallDict", + "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 00000000000000..97c252c64f2466 --- /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 ``PyObject_Vectorcall``, ``PyObject_VectorcallDict`` and +``PyObject_VectorcallMethod`` to the limited API along with the auxiliary +macro constant ``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 e78646fdea59cf..83c64d08bc56a7 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2293,3 +2293,11 @@ added = '3.12' [typedef.vectorcallfunc] added = '3.12' +[function.PyObject_Vectorcall] + added = '3.12' +[function.PyObject_VectorcallDict] + added = '3.12' +[function.PyObject_VectorcallMethod] + added = '3.12' +[macro.PY_VECTORCALL_ARGUMENTS_OFFSET] + added = '3.12' diff --git a/PC/python3dll.c b/PC/python3dll.c index c1b88c66903b10..c7ad0aa40f7d23 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -485,6 +485,9 @@ EXPORT_FUNC(PyObject_SetItem) EXPORT_FUNC(PyObject_Size) EXPORT_FUNC(PyObject_Str) EXPORT_FUNC(PyObject_Type) +EXPORT_FUNC(PyObject_Vectorcall) +EXPORT_FUNC(PyObject_VectorcallDict) +EXPORT_FUNC(PyObject_VectorcallMethod) EXPORT_FUNC(PyOS_CheckStack) EXPORT_FUNC(PyOS_double_to_string) EXPORT_FUNC(PyOS_FSPath) From 68e17a9982915e046759c55809d2c4da5c1b903f Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Mon, 24 Oct 2022 15:31:41 +0200 Subject: [PATCH 02/10] add testcases to test_capi --- Lib/test/test_call.py | 50 ++++++++++++- Modules/_testcapi/vectorcall_limited.c | 98 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 1f3307f822a5fc..491d9ec851ab79 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -812,11 +812,59 @@ def get_a(x): 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 test_vectorcall + + args_captured = [] + kwargs_captured = [] + + def f(*args, **kwargs): + args_captured.append(args) + kwargs_captured.append(kwargs) + return "success" + + self.assertEqual(test_vectorcall(f), "success") + self.assertEqual(args_captured, [("foo",)]) + self.assertEqual(kwargs_captured, [{"baz": "bar"}]) + + @requires_limited_api + def test_vectorcall_limited_outgoing_dict(self): + from _testcapi import test_vectorcall_dict + + args_captured = [] + kwargs_captured = [] + + def f(*args, **kwargs): + args_captured.append(args) + kwargs_captured.append(kwargs) + return "success" + + self.assertEqual(test_vectorcall_dict(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 test_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(test_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/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index ee57af84b1bb5f..c5c27243fc5aee 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -32,6 +32,101 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) return self; } +static PyObject *test_vectorcall(PyObject* self, PyObject *callable) { + PyObject *args[3], *kwname, *kwnames; + + args[0] = NULL; + args[1] = PyUnicode_FromString("foo"); + args[2] = PyUnicode_FromString("bar"); + kwname = PyUnicode_InternFromString("baz"); + kwnames = PyTuple_New(1); + + if (!args[1] || !args[2] || !kwname || !kwnames || + PyTuple_SetItem(kwnames, 0, kwname)) { + Py_XDECREF(args[1]); + Py_XDECREF(args[2]); + Py_XDECREF(kwnames); + return NULL; + } + + PyObject *result = PyObject_Vectorcall( + callable, + args + 1, + 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, + kwnames + ); + + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(kwnames); + + return result; +} + +static PyObject *test_vectorcall_dict(PyObject* self, PyObject *callable) { + PyObject *args[2], *kwargs, *kwarg; + + args[0] = NULL; + args[1] = PyUnicode_FromString("foo"); + kwarg = PyUnicode_FromString("bar"); + kwargs = PyDict_New(); + + if (!args[1] || !kwarg || !kwargs || + PyDict_SetItemString(kwargs, "baz", kwarg)) { + Py_XDECREF(args[1]); + Py_XDECREF(kwarg); + Py_XDECREF(kwargs); + return NULL; + } + + PyObject *result = PyObject_VectorcallDict( + callable, + args + 1, + 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, + kwargs + ); + + Py_DECREF(args[1]); + Py_DECREF(kwarg); + Py_DECREF(kwargs); + + return result; +} + +static PyObject *test_vectorcall_method(PyObject* self, PyObject *callable) { + PyObject *name, *args[3], *kwname, *kwnames; + + name = PyUnicode_FromString("f"); + args[0] = callable; + args[1] = PyUnicode_FromString("foo"); + args[2] = PyUnicode_FromString("bar"); + kwname = PyUnicode_InternFromString("baz"); + kwnames = PyTuple_New(1); + + if (!name || !args[1] || !args[2] || !kwname || !kwnames || + PyTuple_SetItem(kwnames, 0, kwname)) { + Py_XDECREF(name); + Py_XDECREF(args[1]); + Py_XDECREF(args[2]); + Py_XDECREF(kwnames); + return NULL; + } + + PyObject *result = PyObject_VectorcallMethod( + name, + args, + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, + kwnames + ); + + Py_DECREF(name); + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(kwnames); + + return result; +} + static PyMemberDef LimitedVectorCallClass_members[] = { {"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY}, {NULL} @@ -58,6 +153,9 @@ static PyMethodDef TestMethods[] = { * (Empty list left here as template/example, since using * PyModule_AddFunctions isn't very common.) */ + {"test_vectorcall", test_vectorcall, METH_O}, + {"test_vectorcall_method", test_vectorcall_method, METH_O}, + {"test_vectorcall_dict", test_vectorcall_dict, METH_O}, {NULL}, }; From 9934529e101aeb71b688365dda5ca5ef6332c174 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Mon, 24 Oct 2022 15:33:51 +0200 Subject: [PATCH 03/10] adjusted NEWS entry to use Sphinx formatting directives --- .../2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 index 97c252c64f2466..6c03cf8050d740 100644 --- 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 @@ -1,7 +1,8 @@ -Added the methods ``PyObject_Vectorcall``, ``PyObject_VectorcallDict`` and -``PyObject_VectorcallMethod`` to the limited API along with the auxiliary -macro constant ``PY_VECTORCALL_ARGUMENTS_OFFSET``. +Added the methods :c:func:`PyObject_Vectorcall`, +:c:func:`PyObject_VectorcallDict` and :c:func:`PyObject_VectorcallMethod` to +the limited API along with the auxiliary macro constant +:c:macro:`PY_VECTORCALL_ARGUMENTS_OFFSET`. -The availability of these functions enables more efficient PEP-590 vector +The availability of these functions enables more efficient :PEP:`590` vector calls from binary extension modules that avoid argument boxing/unboxing overheads. From 35685b0fc737b89a9a750115d06c7396072c22d7 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Mon, 24 Oct 2022 16:12:45 +0200 Subject: [PATCH 04/10] rename internal test methods to avoid them being picked up as independent tests --- Lib/test/test_call.py | 12 ++++++------ Modules/_testcapi/vectorcall_limited.c | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 491d9ec851ab79..96d3b93cfce0da 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -819,7 +819,7 @@ def test_vectorcall_limited_incoming(self): @requires_limited_api def test_vectorcall_limited_outgoing(self): - from _testcapi import test_vectorcall + from _testcapi import call_vectorcall args_captured = [] kwargs_captured = [] @@ -829,13 +829,13 @@ def f(*args, **kwargs): kwargs_captured.append(kwargs) return "success" - self.assertEqual(test_vectorcall(f), "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_dict(self): - from _testcapi import test_vectorcall_dict + from _testcapi import call_vectorcall_dict args_captured = [] kwargs_captured = [] @@ -845,13 +845,13 @@ def f(*args, **kwargs): kwargs_captured.append(kwargs) return "success" - self.assertEqual(test_vectorcall_dict(f), "success") + self.assertEqual(call_vectorcall_dict(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 test_vectorcall_method + from _testcapi import call_vectorcall_method args_captured = [] kwargs_captured = [] @@ -862,7 +862,7 @@ def f(self, *args, **kwargs): kwargs_captured.append(kwargs) return "success" - self.assertEqual(test_vectorcall_method(TestInstance()), "success") + self.assertEqual(call_vectorcall_method(TestInstance()), "success") self.assertEqual(args_captured, [("foo",)]) self.assertEqual(kwargs_captured, [{"baz": "bar"}]) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index c5c27243fc5aee..44dde5f2c453f8 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -32,7 +32,7 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) return self; } -static PyObject *test_vectorcall(PyObject* self, PyObject *callable) { +static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { PyObject *args[3], *kwname, *kwnames; args[0] = NULL; @@ -63,7 +63,7 @@ static PyObject *test_vectorcall(PyObject* self, PyObject *callable) { return result; } -static PyObject *test_vectorcall_dict(PyObject* self, PyObject *callable) { +static PyObject *call_vectorcall_dict(PyObject* self, PyObject *callable) { PyObject *args[2], *kwargs, *kwarg; args[0] = NULL; @@ -93,7 +93,7 @@ static PyObject *test_vectorcall_dict(PyObject* self, PyObject *callable) { return result; } -static PyObject *test_vectorcall_method(PyObject* self, PyObject *callable) { +static PyObject *call_vectorcall_method(PyObject* self, PyObject *callable) { PyObject *name, *args[3], *kwname, *kwnames; name = PyUnicode_FromString("f"); @@ -153,9 +153,9 @@ static PyMethodDef TestMethods[] = { * (Empty list left here as template/example, since using * PyModule_AddFunctions isn't very common.) */ - {"test_vectorcall", test_vectorcall, METH_O}, - {"test_vectorcall_method", test_vectorcall_method, METH_O}, - {"test_vectorcall_dict", test_vectorcall_dict, METH_O}, + {"call_vectorcall", call_vectorcall, METH_O}, + {"call_vectorcall_method", call_vectorcall_method, METH_O}, + {"call_vectorcall_dict", call_vectorcall_dict, METH_O}, {NULL}, }; From 550df596162c59a8b095b1d781ff549ba8261565 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 25 Oct 2022 15:23:07 +0200 Subject: [PATCH 05/10] incorporate feedback --- Lib/test/test_call.py | 16 -------- ...2-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst | 2 +- Modules/_testcapi/vectorcall_limited.c | 37 ++----------------- 3 files changed, 5 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 96d3b93cfce0da..037bc63eefe458 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -833,22 +833,6 @@ def f(*args, **kwargs): self.assertEqual(args_captured, [("foo",)]) self.assertEqual(kwargs_captured, [{"baz": "bar"}]) - @requires_limited_api - def test_vectorcall_limited_outgoing_dict(self): - from _testcapi import call_vectorcall_dict - - args_captured = [] - kwargs_captured = [] - - def f(*args, **kwargs): - args_captured.append(args) - kwargs_captured.append(kwargs) - return "success" - - self.assertEqual(call_vectorcall_dict(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 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 index 6c03cf8050d740..dc3dab1a093524 100644 --- 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 @@ -1,6 +1,6 @@ Added the methods :c:func:`PyObject_Vectorcall`, :c:func:`PyObject_VectorcallDict` and :c:func:`PyObject_VectorcallMethod` to -the limited API along with the auxiliary macro constant +the :ref:`Limited API ` along with the auxiliary macro constant :c:macro:`PY_VECTORCALL_ARGUMENTS_OFFSET`. The availability of these functions enables more efficient :PEP:`590` vector diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index 44dde5f2c453f8..bb6a1e07cb2107 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -42,7 +42,8 @@ static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { kwnames = PyTuple_New(1); if (!args[1] || !args[2] || !kwname || !kwnames || - PyTuple_SetItem(kwnames, 0, kwname)) { + PyTuple_SetItem(kwnames, 0, kwname)) + { Py_XDECREF(args[1]); Py_XDECREF(args[2]); Py_XDECREF(kwnames); @@ -63,36 +64,6 @@ static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { return result; } -static PyObject *call_vectorcall_dict(PyObject* self, PyObject *callable) { - PyObject *args[2], *kwargs, *kwarg; - - args[0] = NULL; - args[1] = PyUnicode_FromString("foo"); - kwarg = PyUnicode_FromString("bar"); - kwargs = PyDict_New(); - - if (!args[1] || !kwarg || !kwargs || - PyDict_SetItemString(kwargs, "baz", kwarg)) { - Py_XDECREF(args[1]); - Py_XDECREF(kwarg); - Py_XDECREF(kwargs); - return NULL; - } - - PyObject *result = PyObject_VectorcallDict( - callable, - args + 1, - 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, - kwargs - ); - - Py_DECREF(args[1]); - Py_DECREF(kwarg); - Py_DECREF(kwargs); - - return result; -} - static PyObject *call_vectorcall_method(PyObject* self, PyObject *callable) { PyObject *name, *args[3], *kwname, *kwnames; @@ -104,7 +75,8 @@ static PyObject *call_vectorcall_method(PyObject* self, PyObject *callable) { kwnames = PyTuple_New(1); if (!name || !args[1] || !args[2] || !kwname || !kwnames || - PyTuple_SetItem(kwnames, 0, kwname)) { + PyTuple_SetItem(kwnames, 0, kwname)) + { Py_XDECREF(name); Py_XDECREF(args[1]); Py_XDECREF(args[2]); @@ -155,7 +127,6 @@ static PyMethodDef TestMethods[] = { */ {"call_vectorcall", call_vectorcall, METH_O}, {"call_vectorcall_method", call_vectorcall_method, METH_O}, - {"call_vectorcall_dict", call_vectorcall_dict, METH_O}, {NULL}, }; From ba4192999e2a48b17975488a5fb1b03341989bc7 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 25 Oct 2022 16:17:09 +0200 Subject: [PATCH 06/10] remove PyObject_VectorcallDict from Py_LIMITED_API --- Doc/data/stable_abi.dat | 1 - Include/abstract.h | 8 -------- Include/cpython/abstract.h | 8 ++++++++ Lib/test/test_stable_abi_ctypes.py | 1 - .../2022-10-24-10-30-30.gh-issue-98586.Tha5Iy.rst | 7 +++---- Misc/stable_abi.toml | 2 -- PC/python3dll.c | 1 - 7 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 1ed4131c5644f6..133658491c5a4f 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -538,7 +538,6 @@ function,PyObject_Size,3.2,, function,PyObject_Str,3.2,, function,PyObject_Type,3.2,, function,PyObject_Vectorcall,3.12,, -function,PyObject_VectorcallDict,3.12,, function,PyObject_VectorcallMethod,3.12,, var,PyProperty_Type,3.2,, var,PyRangeIter_Type,3.2,, diff --git a/Include/abstract.h b/Include/abstract.h index 121a789426dfb7..064b0300b51ea2 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -249,14 +249,6 @@ PyAPI_FUNC(PyObject *) PyObject_Vectorcall( size_t nargsf, PyObject *kwnames); -/* Same as PyObject_Vectorcall except that keyword arguments are passed as - dict, which may be NULL if there are no keyword arguments. */ -PyAPI_FUNC(PyObject *) PyObject_VectorcallDict( - PyObject *callable, - PyObject *const *args, - size_t nargsf, - PyObject *kwargs); - /* Call the method 'name' on args[0] with arguments in args[1..nargsf-1]. */ PyAPI_FUNC(PyObject *) PyObject_VectorcallMethod( PyObject *name, PyObject *const *args, diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index f96caabdd0ecbb..3b27aab2fc4798 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -71,6 +71,14 @@ PyAPI_FUNC(vectorcallfunc) PyVectorcall_Function(PyObject *callable); #define _PyObject_CallMethodNoArgs PyObject_CallMethodNoArgs #define _PyObject_CallMethodOneArg PyObject_CallMethodOneArg +/* Same as PyObject_Vectorcall except that keyword arguments are passed as + dict, which may be NULL if there are no keyword arguments. */ +PyAPI_FUNC(PyObject *) PyObject_VectorcallDict( + PyObject *callable, + PyObject *const *args, + size_t nargsf, + PyObject *kwargs); + // Same as PyObject_Vectorcall(), except without keyword arguments PyAPI_FUNC(PyObject *) _PyObject_FastCall( PyObject *func, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index ab0225a04f94ed..67c653428a6dee 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -548,7 +548,6 @@ def test_windows_feature_macros(self): "PyObject_Str", "PyObject_Type", "PyObject_Vectorcall", - "PyObject_VectorcallDict", "PyObject_VectorcallMethod", "PyProperty_Type", "PyRangeIter_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 index dc3dab1a093524..5d7b0c89249657 100644 --- 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 @@ -1,7 +1,6 @@ -Added the methods :c:func:`PyObject_Vectorcall`, -:c:func:`PyObject_VectorcallDict` and :c:func:`PyObject_VectorcallMethod` to -the :ref:`Limited API ` along with the auxiliary macro constant -:c:macro:`PY_VECTORCALL_ARGUMENTS_OFFSET`. +Added the methods :c:func:`PyObject_Vectorcall` and +:c:func:`PyObject_VectorcallMethod` to the :ref:`Limited API ` 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 diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 83c64d08bc56a7..e18a6e8f6a9c2c 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2295,8 +2295,6 @@ added = '3.12' [function.PyObject_Vectorcall] added = '3.12' -[function.PyObject_VectorcallDict] - added = '3.12' [function.PyObject_VectorcallMethod] added = '3.12' [macro.PY_VECTORCALL_ARGUMENTS_OFFSET] diff --git a/PC/python3dll.c b/PC/python3dll.c index c7ad0aa40f7d23..931f316bb99843 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -486,7 +486,6 @@ EXPORT_FUNC(PyObject_Size) EXPORT_FUNC(PyObject_Str) EXPORT_FUNC(PyObject_Type) EXPORT_FUNC(PyObject_Vectorcall) -EXPORT_FUNC(PyObject_VectorcallDict) EXPORT_FUNC(PyObject_VectorcallMethod) EXPORT_FUNC(PyOS_CheckStack) EXPORT_FUNC(PyOS_double_to_string) From 8d05d912f91d5a6a783159220e5331e71b49eadb Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Wed, 26 Oct 2022 14:52:54 +0200 Subject: [PATCH 07/10] fix potential leaks in test suite --- Modules/_testcapi/vectorcall_limited.c | 81 +++++++++++++++++--------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index bb6a1e07cb2107..c320c890e270b1 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -33,68 +33,97 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) } static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { - PyObject *args[3], *kwname, *kwnames; + PyObject *args[3] = { NULL, NULL, NULL }; + PyObject *kwname = NULL, *kwnames = NULL, *result = NULL; args[0] = 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 (!args[1] || !args[2] || !kwname || !kwnames || - PyTuple_SetItem(kwnames, 0, kwname)) - { - Py_XDECREF(args[1]); - Py_XDECREF(args[2]); - Py_XDECREF(kwnames); - return NULL; + if (PyTuple_SetItem(kwnames, 0, kwname)) { + goto leave; } - PyObject *result = PyObject_Vectorcall( + result = PyObject_Vectorcall( callable, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames ); - Py_DECREF(args[1]); - Py_DECREF(args[2]); - Py_DECREF(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 *name, *args[3], *kwname, *kwnames; + 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 (!name || !args[1] || !args[2] || !kwname || !kwnames || - PyTuple_SetItem(kwnames, 0, kwname)) - { - Py_XDECREF(name); - Py_XDECREF(args[1]); - Py_XDECREF(args[2]); - Py_XDECREF(kwnames); - return NULL; + if (PyTuple_SetItem(kwnames, 0, kwname)) { + goto leave; } - PyObject *result = PyObject_VectorcallMethod( + + result = PyObject_VectorcallMethod( name, args, 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames ); - Py_DECREF(name); - Py_DECREF(args[1]); - Py_DECREF(args[2]); - Py_DECREF(kwnames); +leave: + Py_XDECREF(name); + Py_XDECREF(args[1]); + Py_XDECREF(args[2]); + Py_XDECREF(kwnames); return result; } From 2c893e00055d577a29f91b2ea758fa596fdb3d7b Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Wed, 26 Oct 2022 15:10:08 +0200 Subject: [PATCH 08/10] remove superflous assignment --- Modules/_testcapi/vectorcall_limited.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index c320c890e270b1..122e4809223f68 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -36,7 +36,6 @@ static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { PyObject *args[3] = { NULL, NULL, NULL }; PyObject *kwname = NULL, *kwnames = NULL, *result = NULL; - args[0] = NULL; args[1] = PyUnicode_FromString("foo"); if (!args[1]) { goto leave; From 7968a83b6fa6b18ca63d701dbcdf56f1416e7fa1 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Thu, 27 Oct 2022 09:58:09 +0200 Subject: [PATCH 09/10] formatting tweaks --- Modules/_testcapi/vectorcall_limited.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index 122e4809223f68..d290e343b6d40b 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -32,7 +32,9 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) return self; } -static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { +static PyObject * +call_vectorcall(PyObject* self, PyObject *callable) +{ PyObject *args[3] = { NULL, NULL, NULL }; PyObject *kwname = NULL, *kwnames = NULL, *result = NULL; @@ -75,7 +77,9 @@ static PyObject *call_vectorcall(PyObject* self, PyObject *callable) { return result; } -static PyObject *call_vectorcall_method(PyObject* self, PyObject *callable) { +static PyObject * +call_vectorcall_method(PyObject* self, PyObject *callable) +{ PyObject *args[3] = { NULL, NULL, NULL }; PyObject *name = NULL, *kwname = NULL, *kwnames = NULL, *result = NULL; From 72169aaaf9e63faeb8f816e44ec9c597ef5acd37 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 27 Oct 2022 10:58:18 +0200 Subject: [PATCH 10/10] Remove outdated commit --- Modules/_testcapi/vectorcall_limited.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index d290e343b6d40b..a69f1d3f2a79b5 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -153,10 +153,6 @@ 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},