Skip to content

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

Closed

Description

@vstinner

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

Activity

  1. encukou commented on Jun 27, 2023

    @encukou
    Member

    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.

  2. added 4 commits that reference this issue on Jun 30, 2023
  3. vstinner commented on Jun 30, 2023

    @vstinner
    MemberAuthor

    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.

  4. added 2 commits that reference this issue on Jun 30, 2023
  5. vstinner commented on Jun 30, 2023

    @vstinner
    MemberAuthor

    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.

  6. vstinner commented on Jun 30, 2023

    @vstinner
    MemberAuthor

    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)(
    
  7. vstinner commented on Jun 30, 2023

    @vstinner
    MemberAuthor

    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.

  8. added 2 commits that reference this issue on Jun 30, 2023
  9. 1 remaining item

  10. encukou commented on Jun 30, 2023

    @encukou
    Member

    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.

  11. added 5 commits that reference this issue on Jun 30, 2023
  12. vstinner commented on Jun 30, 2023

    @vstinner
    MemberAuthor

    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.

  13. added a commit that references this issue on Jun 30, 2023
  14. added 3 commits that reference this issue on Apr 8, 2024
  15. added 2 commits that reference this issue on Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions