Repository: cython/cython

Issue:
Change call signature of CyFunction to FASTCALL signature
The new FASTCALL calling convention in CPython 3.6 and later avoids creating argument tuples and passes them as `PyObject*` C array instead. Keyword arguments can also be passed as a key-value C array.

It is described here:
https://mail.python.org/pipermail/python-dev/2016-August/145793.html

For Python function signatures that do not require tuple/dict arguments (i.e. that do not use `*args` or `**kwargs`), Cython should
- switch the call signature of its `CyFunction` function type to the FASTCALL signature
- provide a converter that unpacks the argument tuple (and keywords) from the normal `tp_call` slot (can be done in the def-wrapper function)
- use FASTCALL internally when calling a `CyFunction`, thus avoiding the argument packing
- export the FASTCALL signature directly in CPython 3.8+ (where [PEP 575](https://www.python.org/dev/peps/pep-0575/) will hopefully get accepted)


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -3111,11 +3111,9 @@ def analyse_signature(self, env):
         #    METH_VARARGS to METH_FASTCALL
         # 2. Special methods like __call__ always use the METH_VARGARGS
         #    calling convention
-        # 3. For the moment, CyFunctions do not support METH_FASTCALL
         mf = sig.method_flags()
-        if (mf and TypeSlots.method_varargs in mf and
-                not self.entry.is_special and not self.is_cyfunction):
-            # 4. If the function uses the full args tuple, it's more
+        if mf and TypeSlots.method_varargs in mf and not self.entry.is_special:
+            # 3. If the function uses the full args tuple, it's more
             #    efficient to use METH_VARARGS. This happens when the function
             #    takes *args but no other positional arguments (apart from
             #    possibly self). We don't do the analogous check for keyword
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -1939,9 +1939,6 @@ def _synthesize_assignment(self, node, env):
             rhs.binding = True
 
         node.is_cyfunction = rhs.binding
-        if rhs.binding:
-            # For the moment, CyFunctions do not support METH_FASTCALL
-            node.entry.signature.use_fastcall = False
         return self._create_assignment(node, rhs, env)
 
     def _create_assignment(self, def_node, rhs, env):


Test patch:
diff --git a/tests/run/fastcall.pyx b/tests/run/fastcall.pyx
--- a/tests/run/fastcall.pyx
+++ b/tests/run/fastcall.pyx
@@ -68,17 +68,14 @@ cdef class SelfCast:
 
 
 cdef extern from *:
-    int PyCFunction_Check(op)
     int PyCFunction_GET_FLAGS(op)
 
 
 def has_fastcall(meth):
     """
-    Given a builtin_function_or_method ``meth``, return whether it uses
-    ``METH_FASTCALL``.
+    Given a builtin_function_or_method or cyfunction ``meth``,
+    return whether it uses ``METH_FASTCALL``.
     """
-    if not PyCFunction_Check(meth):
-        raise TypeError("not a builtin_function_or_method")
     # Hardcode METH_FASTCALL constant equal to 0x80 for simplicity
     return bool(PyCFunction_GET_FLAGS(meth) & 0x80)
 
@@ -100,6 +97,13 @@ def fastcall_function(**kw):
     """
     return kw
 
+@cython.binding(True)
+def fastcall_cyfunction(**kw):
+    """
+    >>> assert_fastcall(fastcall_cyfunction)
+    """
+    return kw
+
 cdef class Dummy:
     @cython.binding(False)
     def fastcall_method(self, x, *args, **kw):
@@ -107,3 +111,18 @@ cdef class Dummy:
         >>> assert_fastcall(Dummy().fastcall_method)
         """
         return tuple(args) + tuple(kw)
+
+cdef class CyDummy:
+    @cython.binding(True)
+    def fastcall_method(self, x, *args, **kw):
+        """
+        >>> assert_fastcall(CyDummy.fastcall_method)
+        """
+        return tuple(args) + tuple(kw)
+
+class PyDummy:
+    def fastcall_method(self, x, *args, **kw):
+        """
+        >>> assert_fastcall(PyDummy.fastcall_method)
+        """
+        return tuple(args) + tuple(kw)