PEP 590: A new calling convention for CPython by markshannon · Pull Request #960 · python/peps (original) (raw)
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Conversation38 Commits9 Checks0 Files changed
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
[ Show hidden characters]({{ revealButtonHref }})
…clarify purpose of PY_VECTORCALL_ARGUMENTS_OFFSET.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some reST-related change that should probably be made before this gets committed.
Abstract |
---|
======== |
This PEP introduces a new calling convention [1] for use by CPython and other software and tools in the CPython ecosystem. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PEP introduces a new calling convention [1] for use by CPython and other software and tools in the CPython ecosystem. |
---|
This PEP introduces a new calling convention [1]_ for use by CPython and other software and tools in the CPython ecosystem. |
The choice of a calling convention impacts the performance and flexibility of code on either side of the call. |
---|
Often there is tension between performance and flexibility. |
The current ``tp_call`` [2] calling convention is sufficiently flexible to cover all cases, but its performance is poor. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current ``tp_call`` [2] calling convention is sufficiently flexible to cover all cases, but its performance is poor. |
---|
The current ``tp_call`` [2]_ calling convention is sufficiently flexible to cover all cases, but its performance is poor. |
The current ``tp_call`` [2] calling convention is sufficiently flexible to cover all cases, but its performance is poor. |
The poor performance is largely a result of having to create intermediate tuples, and possibly intermediate dicts, during the call. |
This is mitigated in CPython by including special case code to speed up calls to Python and builtin functions. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mitigated in CPython by including special case code to speed up calls to Python and builtin functions. |
---|
This is mitigated in CPython by including special-case code to speed up calls to Python and builtin functions. |
------------------------- |
---|
Calls are made through a function pointer taking the following parameters: |
* ``PyObject *callable`` The called object |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could make this a definition list. Otherwise I would suggest a colon to help visibly separate the definition from the explanation, e.g.:
* ``PyObject *callable`` The called object |
---|
* ``PyObject *callable``: The called object |
Additional flags |
---|
---------------- |
Currently only one flags is specified. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently only one flags is specified. |
---|
Currently only one flag is specified: ``PY_METHOD_DESCRIPTOR``. |
Calls ``args[0]`` with the remaining arguments. |
---|
Note that ``nargs`` is the number of positional arguments, including the callable; no offsetting is allowed. |
Both functions raise an exception if `obj` is not callable. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both functions raise an exception if `obj` is not callable. |
---|
Both functions raise an exception if ``obj`` is not callable. |
These functions are ``PyObject *``PyCall_MakeVectorCall(PyObject *obj, PyObject *tuple, PyObject **dict);`` and |
---|
``PyObject *PyCall_MakeTpCall(PyObject *obj, PyObject **args, Py_ssize_t nargs, PyTupleObject *kwnames);``, respectively. |
Both functions raise an exception if `obj` does not support the relevant protocol. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both functions raise an exception if `obj` does not support the relevant protocol. |
---|
Both functions raise an exception if ``obj`` does not support the relevant protocol. |
Both functions raise an exception if `obj` does not support the relevant protocol. |
METH_FASTCALL and METH_VECTORCALL flags |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
METH_FASTCALL and METH_VECTORCALL flags |
---|
``METH_FASTCALL`` and ``METH_VECTORCALL`` flags |
The `MethodDef` protocol and Argument Clinic |
---|
============================================ |
Argument Clinic [4] automatically generates wrapper functions around lower-level callables, providing safe unboxing of primitive types and |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argument Clinic [4] automatically generates wrapper functions around lower-level callables, providing safe unboxing of primitive types and |
---|
Argument Clinic [4]_ automatically generates wrapper functions around lower-level callables, providing safe unboxing of primitive types and |
The statement PEP 580 is specifically targetted at function-like objects, and doesn't support other callables like classes, partial functions, or proxies
is factually false. The motivation for PEP 580 is certainly function/method-like objects but it's a general protocol that every class can implement. For certain classes, it may not be easy or desirable to do that but it's always possible.
Given that PY_METHOD_DESCRIPTOR
is a flag for tp_flags
, shouldn't it be called Py_TPFLAGS_METHOD_DESCRIPTOR
or something?
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a PEP editor POV, this LGTM. I will let Mark handle the merge himself.
------------------------- |
---|
A longer, 6 argument, form combining both the vector and optional tuple and dictionary arguments was considered. |
However, it was found that the code to convert between it and the old `tp_call` form was overly cumbersome and inefficient. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it was found that the code to convert between it and the old `tp_call` form was overly cumbersome and inefficient. |
---|
However, it was found that the code to convert between it and the old ``tp_call`` form was overly cumbersome and inefficient. |
However, it was found that the code to convert between it and the old `tp_call` form was overly cumbersome and inefficient. |
---|
Also, since only 4 arguments are passed in registers on x64 Windows, the two extra arguments would have non-neglible costs. |
Removing any special cases and making all calls use the `tp_call` form was also considered. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing any special cases and making all calls use the `tp_call` form was also considered. |
---|
Removing any special cases and making all calls use the ``tp_call`` form was also considered. |
I don't really see the point of PyCall_MakeTpCall
: it seems to do exactly the same thing as PyObject_VectorCallWithCallable
. I don't think that the caller should care which protocol is used: PyObject_VectorCallWithCallable
should use the vectorcall protocol if available and fall back to tp_call
otherwise.
I still find the explanation of PY_VECTORCALL_ARGUMENTS_OFFSET
a bit unclear. Maybe a better way to explain it would be:
If the flag PY_VECTORCALL_ARGUMENTS_OFFSET
is used, then the callee is free to write to args[-1]
. This is used for example by bound methods to make their onward calls cheaply. Whenever they can do so cheaply (without allocation), callers are encouraged to offset the arguments.
``METH_FASTCALL`` and ``METH_VECTORCALL`` flags |
---|
----------------------------------------------- |
A new ``METH_VECTORCALL`` flag is added for specifying ``MethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: MethodDef
-> PyMethodDef
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new ``METH_VECTORCALL`` flag is added for specifying ``MethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
---|
A new ``METH_VECTORCALL`` flag is added for specifying ``PyMethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
------------------------------- |
---|
The a new slot called ``tp_vectorcall_offset`` is added. It has the type ``uint32_t``. |
A new flag is added, ``Py_TPFLAGS_HAVE_VECTOR_CALL``, which is set for any new PyTypeObjects that include the |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Py_TPFLAGS_HAVE_VECTOR_CALL
-> Py_TPFLAGS_HAVE_VECTORCALL
to be consistent with tp_vectorcall_offset
and other uses of "vectorcall" (not "vector call")
You write about using the vectorcall protocol in classes, as if that's a trivial thing to do. I'm not saying that it's impossible, but extending the vectorcall protocol (or the PEP 580 C call protocol) to type
requires some more thought. Personally, I would rather leave that for a follow-up PEP.
Any class the sets ``tp_vectorcall`` to non-zero should also implement the ``tp_call`` function and make sure its behaviour is consistent with the ``vectorcall`` function. |
---|
Setting ``tp_call`` to ``PyCall_MakeVectorCall`` will suffice. |
The `MethodDef` protocol and Argument Clinic |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MethodDef
-> PyMethodDef
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `MethodDef` protocol and Argument Clinic |
---|
The ``PyMethodDef`` protocol and Argument Clinic |
Mark, I assume you'll be OK with me doing typo/syntax fixes. I'll get to do them and merge this later today, so we have something concrete to talk about.
Jeroen, thank you for the reviews! But please save discussion for the mailing lists – this particular PR is for publishing the initial version of the PEP.
And please also remove the last part about PEP 580 (see #960 (comment)). I don't think it's fair to write that in a PEP.
And please also remove the last part about PEP 580 (see #960 (comment)). I don't think it's fair to write that in a PEP.
It's not, and it definitely won't go in an accepted PEP, but I'm not doing that kind of editing here.
Please take it as a misunderstanding which Mark didn't yet have time to clear up.
``METH_FASTCALL`` and ``METH_VECTORCALL`` flags |
---|
----------------------------------------------- |
A new ``METH_VECTORCALL`` flag is added for specifying ``MethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new ``METH_VECTORCALL`` flag is added for specifying ``MethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
---|
A new ``METH_VECTORCALL`` flag is added for specifying ``PyMethodDef`` structs. It is equivalent to the currently undocumented ``METH_FASTCALL | METH_KEYWORD`` flag. |
Any class the sets ``tp_vectorcall`` to non-zero should also implement the ``tp_call`` function and make sure its behaviour is consistent with the ``vectorcall`` function. |
---|
Setting ``tp_call`` to ``PyCall_MakeVectorCall`` will suffice. |
The `MethodDef` protocol and Argument Clinic |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `MethodDef` protocol and Argument Clinic |
---|
The ``PyMethodDef`` protocol and Argument Clinic |
To enable call performance on a par with Python functions and built-in functions, third-party callables should include a ``vectorcall`` function pointer |
and set ``tp_vectorcall`` to the correct value. |
Any class the sets ``tp_vectorcall`` to non-zero should also implement the ``tp_call`` function and make sure its behaviour is consistent with the ``vectorcall`` function. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any class the sets ``tp_vectorcall`` to non-zero should also implement the ``tp_call`` function and make sure its behaviour is consistent with the ``vectorcall`` function. |
---|
Any class that sets ``tp_vectorcall`` to non-zero should also implement the ``tp_call`` function and make sure its behaviour is consistent with the ``vectorcall`` function. |
The underlying calling convention is similar. Because PEP 576 only allows a fixed offset for the function pointer, |
---|
it would not allow the improvements to any objects with constraints on their layout. |
PEP 580 proposes a major change to the `MethodDef` protocol used to define builtin functions. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PEP 580 proposes a major change to the `MethodDef` protocol used to define builtin functions. |
---|
PEP 580 proposes a major change to the ``MethodDef`` protocol used to define builtin functions. |
PEP 580 proposes a major change to the `MethodDef` protocol used to define builtin functions. |
This PEP provides a more general and simpler mechanism in the form of a new calling convention. |
This PEP also extends the `MethodDef` protocol, but merely to formalise existing conventions. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PEP also extends the `MethodDef` protocol, but merely to formalise existing conventions. |
---|
This PEP also extends the ``MethodDef`` protocol, but merely to formalise existing conventions. |
Ah, my bad. I won't be merging this, as I'm not a PEP Editor.
Is it ready to merge? Because I am a pep editor and I can merge it for you.
Thank you! It is ready for initial discussion, but it needs some editing – committing the syntax fix suggestions, then checking if the build is clean, and merging them.
If you want to do that, it would be great.
@markshannon, if you could commit the suggestions, it would be even better :)
One detail which would be good to mention: whenever the vectorcall
pointer (at offset tp_vectorcall_offset
in the structure) is NULL
in a particular instance, then the vectorcall protocol is not used in that instance. This makes it easy for classes to implement the new protocol only for certain instances.
This contrasts with PEP 580: the C call protocol is all-or-nothing. If a class supports the C call protocol, every instance must use it.
A new flag is added, ``Py_TPFLAGS_HAVE_VECTOR_CALL``, which is set for any new PyTypeObjects that include the |
---|
``tp_vectorcall_offset`` member. |
If ``Py_TPFLAGS_HAS_VECTORCALL`` is set then ``tp_vectorcall_offset`` is the offset |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Py_TPFLAGS_HAS_VECTORCALL
-> Py_TPFLAGS_HAVE_VECTORCALL
-------- |
---|
The call takes the form ``((vectorcall)(((char *)o)+offset))(o, n, args, kwnames)`` where |
``offset`` is ``TYPE(o)->tp_vectorcall`` |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tp_vectorcall
-> tp_vectorcall_offset
------------------------------------------------------------------ |
---|
To enable call performance on a par with Python functions and built-in functions, third-party callables should include a ``vectorcall`` function pointer |
and set ``tp_vectorcall`` to the correct value. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tp_vectorcall
-> tp_vectorcall_offset
Thank you! It is ready for initial discussion, but it needs some editing – committing the syntax fix suggestions, then checking if the build is clean, and merging them.
If you want to do that, it would be great.
I didn't have time to make any edits, but I merged this, so it's public now.
@markshannon, if you could commit the suggestions, it would be even better :)
Please make a new PR for that. (@jdemeyer or you could send a PR and once Mark approves it can be merged -- IIRC Mark can merge peps PRs too.)
Great, thanks!
I'll make a PR with the various edits.