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:
Wenzel Jakob 2022-10-27 11:45:42 +02:00 committed by GitHub
parent d578aaea62
commit e60892f9db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 18 deletions

View File

@ -1,4 +1,5 @@
role,name,added,ifdef_note,struct_abi_kind role,name,added,ifdef_note,struct_abi_kind
macro,PY_VECTORCALL_ARGUMENTS_OFFSET,3.12,,
function,PyAIter_Check,3.10,, function,PyAIter_Check,3.10,,
function,PyArg_Parse,3.2,, function,PyArg_Parse,3.2,,
function,PyArg_ParseTuple,3.2,, function,PyArg_ParseTuple,3.2,,
@ -536,6 +537,8 @@ function,PyObject_SetItem,3.2,,
function,PyObject_Size,3.2,, function,PyObject_Size,3.2,,
function,PyObject_Str,3.2,, function,PyObject_Str,3.2,,
function,PyObject_Type,3.2,, function,PyObject_Type,3.2,,
function,PyObject_Vectorcall,3.12,,
function,PyObject_VectorcallMethod,3.12,,
var,PyProperty_Type,3.2,, var,PyProperty_Type,3.2,,
var,PyRangeIter_Type,3.2,, var,PyRangeIter_Type,3.2,,
var,PyRange_Type,3.2,, var,PyRange_Type,3.2,,

View 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 */ "tuple" and keyword arguments "dict". "dict" may also be NULL */
PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict); 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: /* Implemented elsewhere:

View File

@ -50,9 +50,6 @@ PyAPI_FUNC(PyObject *) _PyObject_MakeTpCall(
PyObject *const *args, Py_ssize_t nargs, PyObject *const *args, Py_ssize_t nargs,
PyObject *keywords); 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. // 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 // Here (when we are not using the stable ABI), the name is overridden to
// call a static inline function for best performance. // 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(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 // Backwards compatibility aliases for API that was provisional in Python 3.8
#define _PyObject_Vectorcall PyObject_Vectorcall #define _PyObject_Vectorcall PyObject_Vectorcall
#define _PyObject_VectorcallMethod PyObject_VectorcallMethod #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_CallOneArg(PyObject *func, PyObject *arg);
PyAPI_FUNC(PyObject *) PyObject_VectorcallMethod(
PyObject *name, PyObject *const *args,
size_t nargsf, PyObject *kwnames);
static inline PyObject * static inline PyObject *
PyObject_CallMethodNoArgs(PyObject *self, PyObject *name) PyObject_CallMethodNoArgs(PyObject *self, PyObject *name)
{ {

View File

@ -812,11 +812,43 @@ class TestPEP590(unittest.TestCase):
assert_equal("overridden", get_a(x)) assert_equal("overridden", get_a(x))
@requires_limited_api @requires_limited_api
def test_vectorcall_limited(self): def test_vectorcall_limited_incoming(self):
from _testcapi import pyobject_vectorcall from _testcapi import pyobject_vectorcall
obj = _testcapi.LimitedVectorCallClass() obj = _testcapi.LimitedVectorCallClass()
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called") 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: class A:
def method_two_args(self, x, y): def method_two_args(self, x, y):

View File

@ -547,6 +547,8 @@ SYMBOL_NAMES = (
"PyObject_Size", "PyObject_Size",
"PyObject_Str", "PyObject_Str",
"PyObject_Type", "PyObject_Type",
"PyObject_Vectorcall",
"PyObject_VectorcallMethod",
"PyProperty_Type", "PyProperty_Type",
"PyRangeIter_Type", "PyRangeIter_Type",
"PyRange_Type", "PyRange_Type",

View File

@ -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.

View File

@ -2293,3 +2293,9 @@
added = '3.12' added = '3.12'
[typedef.vectorcallfunc] [typedef.vectorcallfunc]
added = '3.12' added = '3.12'
[function.PyObject_Vectorcall]
added = '3.12'
[function.PyObject_VectorcallMethod]
added = '3.12'
[macro.PY_VECTORCALL_ARGUMENTS_OFFSET]
added = '3.12'

View File

@ -32,6 +32,105 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw)
return self; 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[] = { static PyMemberDef LimitedVectorCallClass_members[] = {
{"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY}, {"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY},
{NULL} {NULL}
@ -54,10 +153,8 @@ static PyType_Spec LimitedVectorCallClass_spec = {
}; };
static PyMethodDef TestMethods[] = { static PyMethodDef TestMethods[] = {
/* Add module methods here. {"call_vectorcall", call_vectorcall, METH_O},
* (Empty list left here as template/example, since using {"call_vectorcall_method", call_vectorcall_method, METH_O},
* PyModule_AddFunctions isn't very common.)
*/
{NULL}, {NULL},
}; };

2
PC/python3dll.c generated
View File

@ -485,6 +485,8 @@ EXPORT_FUNC(PyObject_SetItem)
EXPORT_FUNC(PyObject_Size) EXPORT_FUNC(PyObject_Size)
EXPORT_FUNC(PyObject_Str) EXPORT_FUNC(PyObject_Str)
EXPORT_FUNC(PyObject_Type) EXPORT_FUNC(PyObject_Type)
EXPORT_FUNC(PyObject_Vectorcall)
EXPORT_FUNC(PyObject_VectorcallMethod)
EXPORT_FUNC(PyOS_CheckStack) EXPORT_FUNC(PyOS_CheckStack)
EXPORT_FUNC(PyOS_double_to_string) EXPORT_FUNC(PyOS_double_to_string)
EXPORT_FUNC(PyOS_FSPath) EXPORT_FUNC(PyOS_FSPath)