mirror of https://github.com/python/cpython
gh-98586: Add vector call APIs to the Limited API (GH-98587)
Expose the facilities for making vector calls through Python's limited API.
This commit is contained in:
parent
d578aaea62
commit
e60892f9db
|
@ -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,,
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -547,6 +547,8 @@ SYMBOL_NAMES = (
|
|||
"PyObject_Size",
|
||||
"PyObject_Str",
|
||||
"PyObject_Type",
|
||||
"PyObject_Vectorcall",
|
||||
"PyObject_VectorcallMethod",
|
||||
"PyProperty_Type",
|
||||
"PyRangeIter_Type",
|
||||
"PyRange_Type",
|
||||
|
|
|
@ -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.
|
|
@ -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'
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue