Skip to content

Commit 656dad7

Browse files
authored
gh-93274: Expose receiving vectorcall in the Limited API (GH-95717)
1 parent cc9160a commit 656dad7

18 files changed

+152
-13
lines changed

Doc/data/stable_abi.dat

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.12.rst

+11-3
Original file line numberDiff line numberDiff line change
@@ -426,14 +426,22 @@ New Features
426426
an additional metaclass argument.
427427
(Contributed by Wenzel Jakob in :gh:`93012`.)
428428

429-
* (XXX: this should be combined with :gh:`93274` when that is done)
429+
* API for creating objects that can be called using
430+
:ref:`the vectorcall protocol <vectorcall>` was added to the
431+
:ref:`Limited API <stable>`:
432+
433+
* :const:`Py_TPFLAGS_HAVE_VECTORCALL`
434+
* :c:func:`PyVectorcall_NARGS`
435+
* :c:func:`PyVectorcall_Call`
436+
* :c:type:`vectorcallfunc`
437+
430438
The :const:`Py_TPFLAGS_HAVE_VECTORCALL` flag is now removed from a class
431439
when the class's :py:meth:`~object.__call__` method is reassigned.
432440
This makes vectorcall safe to use with mutable types (i.e. heap types
433441
without the :const:`immutable <Py_TPFLAGS_IMMUTABLETYPE>` flag).
434442
Mutable types that do not override :c:member:`~PyTypeObject.tp_call` now
435-
inherit the :const:`Py_TPFLAGS_HAVE_VECTORCALL` flag.
436-
(Contributed by Petr Viktorin in :gh:`93012`.)
443+
inherit the ``Py_TPFLAGS_HAVE_VECTORCALL`` flag.
444+
(Contributed by Petr Viktorin in :gh:`93274`.)
437445

438446
Porting to Python 3.12
439447
----------------------

Include/abstract.h

+10
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ PyAPI_FUNC(PyObject *) PyObject_CallMethodObjArgs(
228228
PyObject *name,
229229
...);
230230

231+
/* Given a vectorcall nargsf argument, return the actual number of arguments.
232+
* (For use outside the limited API, this is re-defined as a static inline
233+
* function in cpython/abstract.h)
234+
*/
235+
PyAPI_FUNC(Py_ssize_t) PyVectorcall_NARGS(size_t nargsf);
236+
237+
/* Call "callable" (which must support vectorcall) with positional arguments
238+
"tuple" and keyword arguments "dict". "dict" may also be NULL */
239+
PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict);
240+
231241

232242
/* Implemented elsewhere:
233243

Include/cpython/abstract.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ PyAPI_FUNC(PyObject *) _PyObject_MakeTpCall(
5353
#define PY_VECTORCALL_ARGUMENTS_OFFSET \
5454
(_Py_STATIC_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
5555

56+
// PyVectorcall_NARGS() is exported as a function for the stable ABI.
57+
// Here (when we are not using the stable ABI), the name is overridden to
58+
// call a static inline function for best performance.
59+
#define PyVectorcall_NARGS(n) _PyVectorcall_NARGS(n)
5660
static inline Py_ssize_t
57-
PyVectorcall_NARGS(size_t n)
61+
_PyVectorcall_NARGS(size_t n)
5862
{
5963
return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;
6064
}
@@ -84,10 +88,6 @@ PyAPI_FUNC(PyObject *) PyObject_VectorcallDict(
8488
size_t nargsf,
8589
PyObject *kwargs);
8690

87-
/* Call "callable" (which must support vectorcall) with positional arguments
88-
"tuple" and keyword arguments "dict". "dict" may also be NULL */
89-
PyAPI_FUNC(PyObject *) PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict);
90-
9191
// Same as PyObject_Vectorcall(), except without keyword arguments
9292
PyAPI_FUNC(PyObject *) _PyObject_FastCall(
9393
PyObject *func,

Include/cpython/object.h

-3
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ typedef struct _Py_Identifier {
5454
typedef int (*getbufferproc)(PyObject *, Py_buffer *, int);
5555
typedef void (*releasebufferproc)(PyObject *, Py_buffer *);
5656

57-
typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args,
58-
size_t nargsf, PyObject *kwnames);
59-
6057

6158
typedef struct {
6259
/* Number implementations must check *both*

Include/object.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ typedef int (*initproc)(PyObject *, PyObject *, PyObject *);
228228
typedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *);
229229
typedef PyObject *(*allocfunc)(PyTypeObject *, Py_ssize_t);
230230

231+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000 // 3.12
232+
typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args,
233+
size_t nargsf, PyObject *kwnames);
234+
#endif
235+
231236
typedef struct{
232237
int slot; /* slot id, see below */
233238
void *pfunc; /* function pointer */
@@ -381,11 +386,13 @@ given type object has a specified feature.
381386
#define Py_TPFLAGS_BASETYPE (1UL << 10)
382387

383388
/* Set if the type implements the vectorcall protocol (PEP 590) */
384-
#ifndef Py_LIMITED_API
389+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
385390
#define Py_TPFLAGS_HAVE_VECTORCALL (1UL << 11)
391+
#ifndef Py_LIMITED_API
386392
// Backwards compatibility alias for API that was provisional in Python 3.8
387393
#define _Py_TPFLAGS_HAVE_VECTORCALL Py_TPFLAGS_HAVE_VECTORCALL
388394
#endif
395+
#endif
389396

390397
/* Set if the type is 'ready' -- fully initialized */
391398
#define Py_TPFLAGS_READY (1UL << 12)

Lib/test/test_call.py

+5
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,11 @@ def __call__(self, *args):
759759
self.assertEqual(expected, meth(*args1, **kwargs))
760760
self.assertEqual(expected, wrapped(*args, **kwargs))
761761

762+
def test_vectorcall_limited(self):
763+
from _testcapi import pyobject_vectorcall
764+
obj = _testcapi.LimitedVectorCallClass()
765+
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")
766+
762767

763768
class A:
764769
def method_two_args(self, x, y):

Lib/test/test_stable_abi_ctypes.py

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
API for implementing vectorcall (:c:data:`Py_TPFLAGS_HAVE_VECTORCALL`,
2+
:c:func:`PyVectorcall_NARGS` and :c:func:`PyVectorcall_Call`) was added to
3+
the limited API and stable ABI.

Misc/stable_abi.toml

+9
Original file line numberDiff line numberDiff line change
@@ -2275,5 +2275,14 @@
22752275
added = '3.11'
22762276
[function.PyErr_SetHandledException]
22772277
added = '3.11'
2278+
22782279
[function.PyType_FromMetaclass]
22792280
added = '3.12'
2281+
[const.Py_TPFLAGS_HAVE_VECTORCALL]
2282+
added = '3.12'
2283+
[function.PyVectorcall_NARGS]
2284+
added = '3.12'
2285+
[function.PyVectorcall_Call]
2286+
added = '3.12'
2287+
[typedef.vectorcallfunc]
2288+
added = '3.12'

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
170170
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
171171
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
172-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c
172+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c
173173

174174
# Some testing modules MUST be built as shared libraries.
175175
*shared*

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Python.h"
22

33
int _PyTestCapi_Init_Vectorcall(PyObject *module);
4+
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
45
int _PyTestCapi_Init_Heaptype(PyObject *module);
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#define Py_LIMITED_API 0x030c0000 // 3.12
2+
#include "parts.h"
3+
#include "structmember.h" // PyMemberDef
4+
5+
/* Test Vectorcall in the limited API */
6+
7+
static PyObject *
8+
LimitedVectorCallClass_tpcall(PyObject *self, PyObject *args, PyObject *kwargs) {
9+
return PyUnicode_FromString("tp_call called");
10+
}
11+
12+
static PyObject *
13+
LimitedVectorCallClass_vectorcall(PyObject *callable,
14+
PyObject *const *args,
15+
size_t nargsf,
16+
PyObject *kwnames) {
17+
return PyUnicode_FromString("vectorcall called");
18+
}
19+
20+
static PyObject *
21+
LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw)
22+
{
23+
PyObject *self = ((allocfunc)PyType_GetSlot(tp, Py_tp_alloc))(tp, 0);
24+
if (!self) {
25+
return NULL;
26+
}
27+
*(vectorcallfunc*)((char*)self + sizeof(PyObject)) = (
28+
LimitedVectorCallClass_vectorcall);
29+
return self;
30+
}
31+
32+
static PyMemberDef LimitedVectorCallClass_members[] = {
33+
{"__vectorcalloffset__", T_PYSSIZET, sizeof(PyObject), READONLY},
34+
{NULL}
35+
};
36+
37+
static PyType_Slot LimitedVectorallClass_slots[] = {
38+
{Py_tp_new, LimitedVectorCallClass_new},
39+
{Py_tp_call, LimitedVectorCallClass_tpcall},
40+
{Py_tp_members, LimitedVectorCallClass_members},
41+
{0},
42+
};
43+
44+
static PyType_Spec LimitedVectorCallClass_spec = {
45+
.name = "_testcapi.LimitedVectorCallClass",
46+
.basicsize = (int)(sizeof(PyObject) + sizeof(vectorcallfunc)),
47+
.flags = Py_TPFLAGS_DEFAULT
48+
| Py_TPFLAGS_HAVE_VECTORCALL
49+
| Py_TPFLAGS_BASETYPE,
50+
.slots = LimitedVectorallClass_slots,
51+
};
52+
53+
static PyMethodDef TestMethods[] = {
54+
/* Add module methods here.
55+
* (Empty list left here as template/example, since using
56+
* PyModule_AddFunctions isn't very common.)
57+
*/
58+
{NULL},
59+
};
60+
61+
int
62+
_PyTestCapi_Init_VectorcallLimited(PyObject *m) {
63+
if (PyModule_AddFunctions(m, TestMethods) < 0) {
64+
return -1;
65+
}
66+
67+
PyObject *LimitedVectorCallClass = PyType_FromModuleAndSpec(
68+
m, &LimitedVectorCallClass_spec, NULL);
69+
if (!LimitedVectorCallClass) {
70+
return -1;
71+
}
72+
if (PyModule_AddType(m, (PyTypeObject *)LimitedVectorCallClass) < 0) {
73+
return -1;
74+
}
75+
76+
return 0;
77+
}

Modules/_testcapimodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -6865,6 +6865,9 @@ PyInit__testcapi(void)
68656865
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
68666866
return NULL;
68676867
}
6868+
if (_PyTestCapi_Init_VectorcallLimited(m) < 0) {
6869+
return NULL;
6870+
}
68686871
if (_PyTestCapi_Init_Heaptype(m) < 0) {
68696872
return NULL;
68706873
}

Objects/call.c

+8
Original file line numberDiff line numberDiff line change
@@ -1047,3 +1047,11 @@ _PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs,
10471047
PyMem_Free((PyObject **)stack - 1);
10481048
Py_DECREF(kwnames);
10491049
}
1050+
1051+
// Export for the stable ABI
1052+
#undef PyVectorcall_NARGS
1053+
Py_ssize_t
1054+
PyVectorcall_NARGS(size_t n)
1055+
{
1056+
return _PyVectorcall_NARGS(n);
1057+
}

PC/python3dll.c

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PCbuild/_testcapi.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<ItemGroup>
9696
<ClCompile Include="..\Modules\_testcapimodule.c" />
9797
<ClCompile Include="..\Modules\_testcapi\vectorcall.c" />
98+
<ClCompile Include="..\Modules\_testcapi\vectorcall_limited.c" />
9899
<ClCompile Include="..\Modules\_testcapi\heaptype.c" />
99100
</ItemGroup>
100101
<ItemGroup>

PCbuild/_testcapi.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<ClCompile Include="..\Modules\_testcapi\vectorcall.c">
1616
<Filter>Source Files</Filter>
1717
</ClCompile>
18+
<ClCompile Include="..\Modules\_testcapi\vectorcall_limited.c">
19+
<Filter>Source Files</Filter>
20+
</ClCompile>
1821
<ClCompile Include="..\Modules\_testcapi\heaptype.c">
1922
<Filter>Source Files</Filter>
2023
</ClCompile>

0 commit comments

Comments
 (0)