From 656dad702d3b25bf678ee9bd7109d98876946258 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 8 Aug 2022 14:12:05 +0200 Subject: [PATCH] gh-93274: Expose receiving vectorcall in the Limited API (GH-95717) --- Doc/data/stable_abi.dat | 3 + Doc/whatsnew/3.12.rst | 14 +++- Include/abstract.h | 10 +++ Include/cpython/abstract.h | 10 +-- Include/cpython/object.h | 3 - Include/object.h | 9 ++- Lib/test/test_call.py | 5 ++ Lib/test/test_stable_abi_ctypes.py | 2 + ...2-08-01-16-21-39.gh-issue-93274.QoDHEu.rst | 3 + Misc/stable_abi.toml | 9 +++ Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/parts.h | 1 + Modules/_testcapi/vectorcall_limited.c | 77 +++++++++++++++++++ Modules/_testcapimodule.c | 3 + Objects/call.c | 8 ++ PC/python3dll.c | 2 + PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 + 18 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-08-01-16-21-39.gh-issue-93274.QoDHEu.rst create mode 100644 Modules/_testcapi/vectorcall_limited.c diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 82cd5796efd27d..fde62eacd00a7c 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -783,6 +783,8 @@ function,PyUnicode_WriteChar,3.7,, type,PyVarObject,3.2,,members member,PyVarObject.ob_base,3.2,, member,PyVarObject.ob_size,3.2,, +function,PyVectorcall_Call,3.12,, +function,PyVectorcall_NARGS,3.12,, type,PyWeakReference,3.2,,opaque function,PyWeakref_GetObject,3.2,, function,PyWeakref_NewProxy,3.2,, @@ -883,4 +885,5 @@ type,symtable,3.2,,opaque type,ternaryfunc,3.2,, type,traverseproc,3.2,, type,unaryfunc,3.2,, +type,vectorcallfunc,3.12,, type,visitproc,3.2,, diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index ddf9e1f6a59b47..f1696cc4584cd5 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -426,14 +426,22 @@ New Features an additional metaclass argument. (Contributed by Wenzel Jakob in :gh:`93012`.) -* (XXX: this should be combined with :gh:`93274` when that is done) +* API for creating objects that can be called using + :ref:`the vectorcall protocol ` was added to the + :ref:`Limited API `: + + * :const:`Py_TPFLAGS_HAVE_VECTORCALL` + * :c:func:`PyVectorcall_NARGS` + * :c:func:`PyVectorcall_Call` + * :c:type:`vectorcallfunc` + The :const:`Py_TPFLAGS_HAVE_VECTORCALL` flag is now removed from a class when the class's :py:meth:`~object.__call__` method is reassigned. This makes vectorcall safe to use with mutable types (i.e. heap types without the :const:`immutable ` flag). Mutable types that do not override :c:member:`~PyTypeObject.tp_call` now - inherit the :const:`Py_TPFLAGS_HAVE_VECTORCALL` flag. - (Contributed by Petr Viktorin in :gh:`93012`.) + inherit the ``Py_TPFLAGS_HAVE_VECTORCALL`` flag. + (Contributed by Petr Viktorin in :gh:`93274`.) Porting to Python 3.12 ---------------------- diff --git a/Include/abstract.h b/Include/abstract.h index 576024e09c4101..784ff7e928676f 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -228,6 +228,16 @@ PyAPI_FUNC(PyObject *) PyObject_CallMethodObjArgs( PyObject *name, ...); +/* Given a vectorcall nargsf argument, return the actual number of arguments. + * (For use outside the limited API, this is re-defined as a static inline + * function in cpython/abstract.h) + */ +PyAPI_FUNC(Py_ssize_t) PyVectorcall_NARGS(size_t nargsf); + +/* Call "callable" (which must support vectorcall) with positional arguments + "tuple" and keyword arguments "dict". "dict" may also be NULL */ +PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict); + /* Implemented elsewhere: diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 7038918f018880..6da29cde9f6092 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -53,8 +53,12 @@ PyAPI_FUNC(PyObject *) _PyObject_MakeTpCall( #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. +#define PyVectorcall_NARGS(n) _PyVectorcall_NARGS(n) static inline Py_ssize_t -PyVectorcall_NARGS(size_t n) +_PyVectorcall_NARGS(size_t n) { return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; } @@ -84,10 +88,6 @@ PyAPI_FUNC(PyObject *) PyObject_VectorcallDict( size_t nargsf, PyObject *kwargs); -/* Call "callable" (which must support vectorcall) with positional arguments - "tuple" and keyword arguments "dict". "dict" may also be NULL */ -PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict); - // Same as PyObject_Vectorcall(), except without keyword arguments PyAPI_FUNC(PyObject *) _PyObject_FastCall( PyObject *func, diff --git a/Include/cpython/object.h b/Include/cpython/object.h index a26fc7f6aadfb4..c80fc1df0e0ba4 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -54,9 +54,6 @@ typedef struct _Py_Identifier { typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); typedef void (*releasebufferproc)(PyObject *, Py_buffer *); -typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, - size_t nargsf, PyObject *kwnames); - typedef struct { /* Number implementations must check *both* diff --git a/Include/object.h b/Include/object.h index c00b9eb583420a..7d499d8b306e57 100644 --- a/Include/object.h +++ b/Include/object.h @@ -228,6 +228,11 @@ typedef int (*initproc)(PyObject *, PyObject *, PyObject *); typedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *); typedef PyObject *(*allocfunc)(PyTypeObject *, Py_ssize_t); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000 // 3.12 +typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames); +#endif + typedef struct{ int slot; /* slot id, see below */ void *pfunc; /* function pointer */ @@ -381,11 +386,13 @@ given type object has a specified feature. #define Py_TPFLAGS_BASETYPE (1UL << 10) /* Set if the type implements the vectorcall protocol (PEP 590) */ -#ifndef Py_LIMITED_API +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000 #define Py_TPFLAGS_HAVE_VECTORCALL (1UL << 11) +#ifndef Py_LIMITED_API // Backwards compatibility alias for API that was provisional in Python 3.8 #define _Py_TPFLAGS_HAVE_VECTORCALL Py_TPFLAGS_HAVE_VECTORCALL #endif +#endif /* Set if the type is 'ready' -- fully initialized */ #define Py_TPFLAGS_READY (1UL << 12) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 6c81a154f65f47..d3a254f15b6291 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -759,6 +759,11 @@ def __call__(self, *args): self.assertEqual(expected, meth(*args1, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs)) + def test_vectorcall_limited(self): + from _testcapi import pyobject_vectorcall + obj = _testcapi.LimitedVectorCallClass() + self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called") + 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 53e93ab6b9b4c9..a803e3a5025985 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -782,6 +782,8 @@ def test_windows_feature_macros(self): "PyUnicode_Translate", "PyUnicode_Type", "PyUnicode_WriteChar", + "PyVectorcall_Call", + "PyVectorcall_NARGS", "PyWeakref_GetObject", "PyWeakref_NewProxy", "PyWeakref_NewRef", diff --git a/Misc/NEWS.d/next/C API/2022-08-01-16-21-39.gh-issue-93274.QoDHEu.rst b/Misc/NEWS.d/next/C API/2022-08-01-16-21-39.gh-issue-93274.QoDHEu.rst new file mode 100644 index 00000000000000..da6cce4a7b283a --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-08-01-16-21-39.gh-issue-93274.QoDHEu.rst @@ -0,0 +1,3 @@ +API for implementing vectorcall (:c:data:`Py_TPFLAGS_HAVE_VECTORCALL`, +:c:func:`PyVectorcall_NARGS` and :c:func:`PyVectorcall_Call`) was added to +the limited API and stable ABI. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 84bec827096050..4da002a0586299 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2275,5 +2275,14 @@ added = '3.11' [function.PyErr_SetHandledException] added = '3.11' + [function.PyType_FromMetaclass] added = '3.12' +[const.Py_TPFLAGS_HAVE_VECTORCALL] + added = '3.12' +[function.PyVectorcall_NARGS] + added = '3.12' +[function.PyVectorcall_Call] + added = '3.12' +[typedef.vectorcallfunc] + added = '3.12' diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index c5dc1e8eb45377..908e6df9766731 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -169,7 +169,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c # Some testing modules MUST be built as shared libraries. *shared* diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index e6d2ed23cb18e7..4b672c9d05bddd 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -1,4 +1,5 @@ #include "Python.h" int _PyTestCapi_Init_Vectorcall(PyObject *module); +int _PyTestCapi_Init_VectorcallLimited(PyObject *module); int _PyTestCapi_Init_Heaptype(PyObject *module); diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c new file mode 100644 index 00000000000000..63ea3b3101b276 --- /dev/null +++ b/Modules/_testcapi/vectorcall_limited.c @@ -0,0 +1,77 @@ +#define Py_LIMITED_API 0x030c0000 // 3.12 +#include "parts.h" +#include "structmember.h" // PyMemberDef + +/* Test Vectorcall in the limited API */ + +static PyObject * +LimitedVectorCallClass_tpcall(PyObject *self, PyObject *args, PyObject *kwargs) { + return PyUnicode_FromString("tp_call called"); +} + +static PyObject * +LimitedVectorCallClass_vectorcall(PyObject *callable, + PyObject *const *args, + size_t nargsf, + PyObject *kwnames) { + return PyUnicode_FromString("vectorcall called"); +} + +static PyObject * +LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) +{ + PyObject *self = ((allocfunc)PyType_GetSlot(tp, Py_tp_alloc))(tp, 0); + if (!self) { + return NULL; + } + *(vectorcallfunc*)((char*)self + sizeof(PyObject)) = ( + LimitedVectorCallClass_vectorcall); + return self; +} + +static PyMemberDef LimitedVectorCallClass_members[] = { + {"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY}, + {NULL} +}; + +static PyType_Slot LimitedVectorallClass_slots[] = { + {Py_tp_new, LimitedVectorCallClass_new}, + {Py_tp_call, LimitedVectorCallClass_tpcall}, + {Py_tp_members, LimitedVectorCallClass_members}, + {0}, +}; + +static PyType_Spec LimitedVectorCallClass_spec = { + .name = "_testcapi.LimitedVectorCallClass", + .basicsize = (int)(sizeof(PyObject) + sizeof(vectorcallfunc)), + .flags = Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_HAVE_VECTORCALL + | Py_TPFLAGS_BASETYPE, + .slots = LimitedVectorallClass_slots, +}; + +static PyMethodDef TestMethods[] = { + /* Add module methods here. + * (Empty list left here as template/example, since using + * PyModule_AddFunctions isn't very common.) + */ + {NULL}, +}; + +int +_PyTestCapi_Init_VectorcallLimited(PyObject *m) { + if (PyModule_AddFunctions(m, TestMethods) < 0) { + return -1; + } + + PyObject *LimitedVectorCallClass = PyType_FromModuleAndSpec( + m, &LimitedVectorCallClass_spec, NULL); + if (!LimitedVectorCallClass) { + return -1; + } + if (PyModule_AddType(m, (PyTypeObject *)LimitedVectorCallClass) < 0) { + return -1; + } + + return 0; +} diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 517591465b4914..8004fa18bcc528 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -6865,6 +6865,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Vectorcall(m) < 0) { return NULL; } + if (_PyTestCapi_Init_VectorcallLimited(m) < 0) { + return NULL; + } if (_PyTestCapi_Init_Heaptype(m) < 0) { return NULL; } diff --git a/Objects/call.c b/Objects/call.c index ed168c9c4796e2..c2509db2a9a263 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -1047,3 +1047,11 @@ _PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs, PyMem_Free((PyObject **)stack - 1); Py_DECREF(kwnames); } + +// Export for the stable ABI +#undef PyVectorcall_NARGS +Py_ssize_t +PyVectorcall_NARGS(size_t n) +{ + return _PyVectorcall_NARGS(n); +} diff --git a/PC/python3dll.c b/PC/python3dll.c index 024ec49d68d797..89bbd05932b853 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -723,6 +723,8 @@ EXPORT_FUNC(PyUnicodeTranslateError_GetStart) EXPORT_FUNC(PyUnicodeTranslateError_SetEnd) EXPORT_FUNC(PyUnicodeTranslateError_SetReason) EXPORT_FUNC(PyUnicodeTranslateError_SetStart) +EXPORT_FUNC(PyVectorcall_Call) +EXPORT_FUNC(PyVectorcall_NARGS) EXPORT_FUNC(PyWeakref_GetObject) EXPORT_FUNC(PyWeakref_NewProxy) EXPORT_FUNC(PyWeakref_NewRef) diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index a88540cab19f9a..0cb4e44cf73444 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -95,6 +95,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index a43ab5ea0ff941..4da972f279c8a3 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -15,6 +15,9 @@ Source Files + + Source Files + Source Files