Skip to content
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 and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C API: What's the status of the half-private FASTCALL calling convention? #106023

Closed
vstinner opened this issue Jun 23, 2023 · 8 comments
Closed
Labels
topic-C-API type-bug An unexpected behavior, bug, or error

Comments

@vstinner
Copy link
Member

vstinner commented Jun 23, 2023

Hi,

In 2017, I added a new experimental FASTCALL calling convention. See my articles about it: The start of the FASTCALL project and FASTCALL microbenchmarks. It avoids the need to create a temporary tuple to pass positional arguments and the need to create a temporary dictionary to pass keyword arguments. I did my best to keep this API private. I added functions prefixed with _Py: _PyObject_Fastcall(). Cython is eager to always use the fastest code and quickly adopted this new METH_FASTCALL calling convention... oops, I forgot to add a _Py prefix since these METH constants don't start with Py.

In 2019, this calling convention was extended to support also method calls (pass the self argument): PEP 590 – Vectorcall: a fast calling protocol for CPython. This new API is public and standardized. For example, it added public PyVectorcall_Function() and PyObject_Vectorcall() functions.

In 2023, the FASTCALL API is still around in the public C API:

  • METH_FASTCALL
  • _PyObject_FastCall()
  • _PyObject_FastCallTstate()
  • _PyObject_FastCallDict()
  • _PyObject_FastCallDictTstate()
  • _PyStack_AsDict()
  • _PY_FASTCALL_SMALL_STACK

Can it be deprecated? Removed? I suppose that if we are in the unknown, the safe option is to start by deprecating it in Python 3.13 and plan its removal in Python 3.15.

cc @encukou

Victor

Linked PRs

@vstinner vstinner added the type-bug An unexpected behavior, bug, or error label Jun 23, 2023
@encukou
Copy link
Member

encukou commented Jun 27, 2023

Does vectorcall replace this completely? If that's the case, let's first remove all uses from CPython, and then decide.

If not, then it's probably genuinely useful for Cython or others. Let's move it to unstable API in that case. (Or even public?)

_PY_FASTCALL_SMALL_STACK is internal detail that's easy for users to replace (possibly with a more appropriate value). IMO it's safe to move to private headers.

vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
Replace _PyObject_FastCall() calls with PyObject_Vectorcall().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
Replace _PyObject_FastCall() calls with PyObject_Vectorcall().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
Replace _PyObject_FastCall() calls with PyObject_Vectorcall().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
Remove _PY_FASTCALL_SMALL_STACK constant from the C API: move it to
the internal C API (pycore_call.h).
@vstinner
Copy link
Member Author

Does vectorcall replace this completely? If that's the case, let's first remove all uses from CPython, and then decide.

_PyObject_FastCall(func, nargs, args) can be safely replaced with PyObject_Vectorcall(func, nargs, args, NULL).

PR #106257 removes _PyObject_FastCall() usage in CPython: replace it with PyObject_Vectorcall().

_PY_FASTCALL_SMALL_STACK is internal detail that's easy for users to replace (possibly with a more appropriate value). IMO it's safe to move to private headers.

PR #106258 removes the constant from the public C API.

vstinner added a commit that referenced this issue Jun 30, 2023
Replace _PyObject_FastCall() calls with PyObject_Vectorcall().
vstinner added a commit that referenced this issue Jun 30, 2023
Remove _PY_FASTCALL_SMALL_STACK constant from the C API: move it to
the internal C API (pycore_call.h).
@vstinner
Copy link
Member Author

In 2019, a commit implementing PEP 590 removed _PyObject_FastCallKeywords().

My recent commit 00e75a3 removed _PyObject_FastCallDict(). Oh, I didn't notice that I was removing a C function of the FastCall family. I planned to handle these differently.

@vstinner
Copy link
Member Author

On PyPI top 5,000 projects, 10 projects use it. 3 of them are related to pydevd or copies of pydevd.

Usage:

PYPI-2023-04-13/scipy-1.10.1.tar.gz: scipy-1.10.1/scipy/_lib/_uarray/vectorcall.cxx: return _PyObject_FastCallKeywords(
PYPI-2023-04-13/scipy-1.10.1.tar.gz: scipy-1.10.1/scipy/_lib/_uarray/vectorcall.cxx: return _PyObject_FastCallDict(callable, (PyObject **)args, nargs, kwdict);
PYPI-2023-04-13/mypy-1.2.0.tar.gz: mypy-1.2.0/mypyc/lib-rt/pythonsupport.h: new_base = _PyObject_FastCall(meth, stack, 1);
PYPI-2023-04-13/mypy-1.2.0.tar.gz: mypy-1.2.0/mypyc/lib-rt/pythonsupport.h: super = _PyObject_FastCall((PyObject *)&PySuper_Type, args, 2);
PYPI-2023-04-13/mypy-1.2.0.tar.gz: mypy-1.2.0/mypyc/lib-rt/pythonsupport.h: result = _PyObject_FastCallDict(func, NULL, 0, kwds);
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/local_dependencies/pyo3-ffi/src/cpython/abstract_.rs: pub unsafe fn _PyObject_FastCallTstate(
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/local_dependencies/pyo3-ffi/src/cpython/abstract_.rs: pub unsafe fn _PyObject_FastCall(
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/local_dependencies/pyo3-ffi/src/cpython/abstract_.rs: _PyObject_FastCallTstate(PyThreadState_GET(), func, args, nargs)
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs: pub unsafe fn _PyObject_FastCallTstate(
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs: pub unsafe fn _PyObject_FastCall(
PYPI-2023-04-13/orjson-3.8.10.tar.gz: orjson-3.8.10/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs: _PyObject_FastCallTstate(PyThreadState_GET(), func, args, nargs)
PYPI-2023-04-13/typed_ast-1.5.4.tar.gz: typed_ast-1.5.4/ast3/Python/ast.c: _PyObject_FastCall(PyObject *func, PyObject *const *args, int nargs)
PYPI-2023-04-13/typed_ast-1.5.4.tar.gz: typed_ast-1.5.4/ast3/Python/ast.c: id2 = _PyObject_FastCall(c->c_normalize, args, 2);
PYPI-2023-04-13/gevent-22.10.2.tar.gz: gevent-22.10.2/src/gevent/_generated_include/PyObjectFastCall_impl_f6ad1ccc7f137e4637d2bbaa621a6295ceb3138b.h: static PyObject* __Pyx_PyObject_FastCall_fallback(PyObject *func, PyObject **args, size_t nargs, PyObject *kwargs) {
PYPI-2023-04-13/gevent-22.10.2.tar.gz: gevent-22.10.2/src/gevent/_generated_include/PyObjectFastCall_impl_f6ad1ccc7f137e4637d2bbaa621a6295ceb3138b.h: static CYTHON_INLINE PyObject* __Pyx_PyObject_FastCallDict(PyObject *func, PyObject **args, size_t _nargs, PyObject *kwargs) {
PYPI-2023-04-13/gevent-22.10.2.tar.gz: gevent-22.10.2/src/gevent/_generated_include/PyObjectFastCall_impl_f6ad1ccc7f137e4637d2bbaa621a6295ceb3138b.h: return __Pyx_PyObject_FastCall_fallback(func, args, (size_t)nargs, kwargs);
PYPI-2023-04-13/iteration_utilities-0.11.0.tar.gz: iteration_utilities-0.11.0/src/iteration_utilities/_iteration_utilities/helper.h: return _PyObject_FastCall(callable, args, 1);
PYPI-2023-04-13/iteration_utilities-0.11.0.tar.gz: iteration_utilities-0.11.0/src/iteration_utilities/_iteration_utilities/helper.h: return _PyObject_FastCall(callable, args, 2);

PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: PyObject_FastCallDictCustom(PyObject* callback, PyObject *stack[3], int ignoredStackSizeAlways3, void* ignored)
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // result = _PyObject_FastCall(callback, stack, 3);
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // Note that _PyObject_FastCall is actually a define:
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // #define _PyObject_FastCall(func, args, nargs) _PyObject_FastCallDict((func), (args), (nargs), NULL)
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_custom_pyeval_settrace_common.hpp: _PyObject_FastCallDict* pyObject_FastCallDict;
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace.hpp: DEFINE_PROC_NO_CHECK(pyObject_FastCallDict, _PyObject_FastCallDict*, "_PyObject_FastCallDict", 530);
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace.hpp: DEFINE_PROC_NO_CHECK(pyObject_FastCallDict, _PyObject_FastCallDict*, "PyObject_VectorcallDict", 533);
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace.hpp: // we have to use PyObject_FastCallDictCustom for older versions of CPython (pre 3.7).
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace.hpp: pyObject_FastCallDict = reinterpret_cast<_PyObject_FastCallDict*>(&PyObject_FastCallDictCustom);
PYPI-2023-04-13/debugpy-1.6.7.zip: debugpy-1.6.7/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/common/py_utils.hpp: typedef PyObject * (_PyObject_FastCallDict)(

PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: PyObject_FastCallDictCustom(PyObject* callback, PyObject *stack[3], int ignoredStackSizeAlways3, void* ignored)
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // result = _PyObject_FastCall(callback, stack, 3);
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // Note that _PyObject_FastCall is actually a define:
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_custom_pyeval_settrace.hpp: // #define _PyObject_FastCall(func, args, nargs) _PyObject_FastCallDict((func), (args), (nargs), NULL)
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_custom_pyeval_settrace_common.hpp: _PyObject_FastCallDict* pyObject_FastCallDict;
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_settrace.hpp: DEFINE_PROC_NO_CHECK(pyObject_FastCallDict, _PyObject_FastCallDict*, "_PyObject_FastCallDict", 530);
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_settrace.hpp: DEFINE_PROC_NO_CHECK(pyObject_FastCallDict, _PyObject_FastCallDict*, "PyObject_VectorcallDict", 533);
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_settrace.hpp: // we have to use PyObject_FastCallDictCustom for older versions of CPython (pre 3.7).
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_settrace.hpp: pyObject_FastCallDict = reinterpret_cast<_PyObject_FastCallDict*>(&PyObject_FastCallDictCustom);
PYPI-2023-04-13/pydevd-2.9.5.tar.gz: pydevd-2.9.5/pydevd_attach_to_process/common/py_utils.hpp: typedef PyObject * (_PyObject_FastCallDict)(

PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace.hpp: DEFINE_PROC(pyObject_FastCallDict, _PyObject_FastCallDict*, "_PyObject_FastCallDict", 530);
PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace_37.hpp: _PyObject_FastCallDict* pyObject_FastCallDict;
PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace_37.hpp: // result = _PyObject_FastCall(callback, stack, 3);
PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace_37.hpp: // Note that _PyObject_FastCall is actually a define:
PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_settrace_37.hpp: // #define _PyObject_FastCall(func, args, nargs) _PyObject_FastCallDict((func), (args), (nargs), NULL)
PYPI-2023-04-13/ptvsd-4.3.2.zip: ptvsd-4.3.2/src/ptvsd/_vendored/pydevd/pydevd_attach_to_process/common/py_utils.hpp: typedef PyObject * (_PyObject_FastCallDict)(

@vstinner
Copy link
Member Author

PyObject_Vectorcall() is available since Python 3.8 and PyObject_VectorcallDict() is available since Python 3.9.

_PyObject_FastCall() and _PyObject_FastCallDict() are private functions. IMO it's ok to remove them.

The METH_FASTCALL calling convention remains useful and I don't see a need to remove.

vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
Rename _PyObject_FastCallDictTstate() to
_PyObject_VectorcallDictTstate().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
@vstinner
Copy link
Member Author

I wrote PR #106265 to remove the private _PyObject_FastCall() function.

@encukou
Copy link
Member

encukou commented Jun 30, 2023

When removing them, it would be nice to add them to What's New, mentioning that they were private but documenting what to call instead.
When you've inherited code that stops compiling, it's nice to have instructions like that.

vstinner added a commit that referenced this issue Jun 30, 2023
Rename _PyObject_FastCallDictTstate() to
_PyObject_VectorcallDictTstate().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
vstinner added a commit to vstinner/cpython that referenced this issue Jun 30, 2023
@vstinner
Copy link
Member Author

I removed the FastCall C API and explained how to migrate in What's New in Python 3.13.

I removed _PyObject_FastCall(), _PyObject_FastCallDict(), _PY_FASTCALL_SMALL_STACK and _PyStack_AsDict() from the public C API. I removed _PyObject_FastCallTstate() and _PyObject_FastCallDictTstate() of the internal C API.

The METH_FASTCALL constant stays for now.

vstinner added a commit that referenced this issue Apr 8, 2024
The function _PyObject_FastCall() was restored.
vstinner added a commit to vstinner/cpython that referenced this issue Apr 9, 2024
…l() (python#117633)"

This reverts commit 9a12f5d.

I was wrong: the _PyObject_FastCall() function was removed. But we
kept the _PyObject_FastCallDict() function.
vstinner added a commit that referenced this issue Apr 9, 2024
…117633)" (#117676)

This reverts commit 9a12f5d.

I was wrong: the _PyObject_FastCall() function was removed. But we
kept the _PyObject_FastCallDict() function.
diegorusso pushed a commit to diegorusso/cpython that referenced this issue Apr 17, 2024
diegorusso pushed a commit to diegorusso/cpython that referenced this issue Apr 17, 2024
…l() (python#117633)" (python#117676)

This reverts commit 9a12f5d.

I was wrong: the _PyObject_FastCall() function was removed. But we
kept the _PyObject_FastCallDict() function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-C-API type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants