[Python-checkins] gh-93274: Expose receiving vectorcall in the Limited API (GH-95717)

encukou webhook-mailer at python.org
Mon Aug 8 08:12:16 EDT 2022


https://github.com/python/cpython/commit/656dad702d3b25bf678ee9bd7109d98876946258
commit: 656dad702d3b25bf678ee9bd7109d98876946258
branch: main
author: Petr Viktorin <encukou at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2022-08-08T14:12:05+02:00
summary:

gh-93274: Expose receiving vectorcall in the Limited API (GH-95717)

files:
A Misc/NEWS.d/next/C API/2022-08-01-16-21-39.gh-issue-93274.QoDHEu.rst
A Modules/_testcapi/vectorcall_limited.c
M Doc/data/stable_abi.dat
M Doc/whatsnew/3.12.rst
M Include/abstract.h
M Include/cpython/abstract.h
M Include/cpython/object.h
M Include/object.h
M Lib/test/test_call.py
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.toml
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M Objects/call.c
M PC/python3dll.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters

diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 82cd5796efd2..fde62eacd00a 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 ddf9e1f6a59b..f1696cc4584c 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 <vectorcall>` was added to the
+  :ref:`Limited API <stable>`:
+
+  * :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 <Py_TPFLAGS_IMMUTABLETYPE>` 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 576024e09c41..784ff7e92867 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 7038918f0188..6da29cde9f60 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 a26fc7f6aadf..c80fc1df0e0b 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 c00b9eb58342..7d499d8b306e 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 6c81a154f65f..d3a254f15b62 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 53e93ab6b9b4..a803e3a50259 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 000000000000..da6cce4a7b28
--- /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 84bec8270960..4da002a05862 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 c5dc1e8eb453..908e6df97667 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -169,7 +169,7 @@
 @MODULE__XXTESTFUZZ_TRUE at _xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE at _testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE at _testinternalcapi _testinternalcapi.c
- at MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c
+ at 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 e6d2ed23cb18..4b672c9d05bd 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 000000000000..63ea3b3101b2
--- /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 517591465b49..8004fa18bcc5 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 ed168c9c4796..c2509db2a9a2 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 024ec49d68d7..89bbd05932b8 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 a88540cab19f..0cb4e44cf734 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -95,6 +95,7 @@
   <ItemGroup>
     <ClCompile Include="..\Modules\_testcapimodule.c" />
     <ClCompile Include="..\Modules\_testcapi\vectorcall.c" />
+    <ClCompile Include="..\Modules\_testcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testcapi\heaptype.c" />
   </ItemGroup>
   <ItemGroup>
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index a43ab5ea0ff9..4da972f279c8 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -15,6 +15,9 @@
     <ClCompile Include="..\Modules\_testcapi\vectorcall.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\vectorcall_limited.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\_testcapi\heaptype.c">
       <Filter>Source Files</Filter>
     </ClCompile>



More information about the Python-checkins mailing list