[pypy-commit] pypy meth_fastcall: add python3.6 version of METH_FASTCALL, only becomes official in 3.8

mattip pypy.commits at gmail.com
Tue Dec 17 07:27:20 EST 2019


Author: Matti Picus <matti.picus at gmail.com>
Branch: meth_fastcall
Changeset: r98304:10a5fb96ac84
Date: 2019-12-17 14:26 +0200
http://bitbucket.org/pypy/pypy/changeset/10a5fb96ac84/

Log:	add python3.6 version of METH_FASTCALL, only becomes official in 3.8

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -128,7 +128,7 @@
 constant_names = """
 Py_TPFLAGS_READY Py_TPFLAGS_READYING
 METH_COEXIST METH_STATIC METH_CLASS Py_TPFLAGS_BASETYPE Py_MAX_FMT
-METH_NOARGS METH_VARARGS METH_KEYWORDS METH_O
+METH_NOARGS METH_VARARGS METH_KEYWORDS METH_O METH_FASTCALL
 Py_TPFLAGS_HEAPTYPE
 Py_LT Py_LE Py_EQ Py_NE Py_GT Py_GE Py_MAX_NDIMS
 Py_CLEANUP_SUPPORTED
diff --git a/pypy/module/cpyext/include/methodobject.h b/pypy/module/cpyext/include/methodobject.h
--- a/pypy/module/cpyext/include/methodobject.h
+++ b/pypy/module/cpyext/include/methodobject.h
@@ -27,6 +27,11 @@
 
 #define METH_COEXIST   0x0040
 
+/* In python3.7 this is equivalent to METH_FASTCALL | METH_KEYWORDS,
+   and is used with _PyCFunctionFast which becomes in 3.7
+   _PyCFunctionFastWithKeywords*/ 
+#define METH_FASTCALL  0x0080
+
 #define PyCFunction_New(ml, self) PyCFunction_NewEx((ml), (self), NULL)
 
 /* Macros for direct access to these values. Type checks are *not*
diff --git a/pypy/module/cpyext/methodobject.py b/pypy/module/cpyext/methodobject.py
--- a/pypy/module/cpyext/methodobject.py
+++ b/pypy/module/cpyext/methodobject.py
@@ -10,7 +10,7 @@
 from pypy.objspace.std.typeobject import W_TypeObject
 from pypy.module.cpyext.api import (
     CONST_STRING, METH_CLASS, METH_COEXIST, METH_KEYWORDS, METH_NOARGS, METH_O,
-    METH_STATIC, METH_VARARGS, PyObject, bootstrap_function,
+    METH_STATIC, METH_VARARGS, METH_FASTCALL, PyObject, bootstrap_function,
     cpython_api, generic_cpy_call, CANNOT_FAIL, slot_function, cts,
     build_type_checkers)
 from pypy.module.cpyext.pyobject import (
@@ -21,6 +21,7 @@
 PyMethodDef = cts.gettype('PyMethodDef')
 PyCFunction = cts.gettype('PyCFunction')
 PyCFunctionKwArgs = cts.gettype('PyCFunctionWithKeywords')
+_PyCFunctionFast = cts.gettype('_PyCFunctionFast')
 PyCFunctionObject = cts.gettype('PyCFunctionObject*')
 
 @bootstrap_function
@@ -57,6 +58,11 @@
         space.setitem(w_kwargs, space.newtext(key), w_obj)
     return w_kwargs
 
+def w_names_from_args(space, __args__):
+    if __args__.keywords is None:
+        return []
+    return [space.newtext(x) for x in __args__.keywords]
+
 def undotted_name(name):
     """Return the last component of a dotted name"""
     dotpos = name.rfind('.')
@@ -106,12 +112,14 @@
     def call(self, space, w_self, __args__):
         flags = self.flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST)
         length = len(__args__.arguments_w)
-        if not flags & METH_KEYWORDS and __args__.keywords:
+        if flags & METH_FASTCALL:
+            return self.call_fastcall(space, w_self, __args__)
+        elif flags & METH_KEYWORDS:
+            return self.call_keywords(space, w_self, __args__)
+        elif __args__.keywords:
             raise oefmt(space.w_TypeError,
                         "%s() takes no keyword arguments", self.name)
-        if flags & METH_KEYWORDS:
-            return self.call_keywords(space, w_self, __args__)
-        elif flags & METH_NOARGS:
+        if flags & METH_NOARGS:
             if length == 0:
                 return self.call_noargs(space, w_self, __args__)
             raise oefmt(space.w_TypeError,
@@ -154,6 +162,30 @@
         finally:
             decref(space, py_args)
 
+    def call_fastcall(self, space, w_self, __args__):
+        func = rffi.cast(_PyCFunctionFast, self.ml.c_ml_meth)
+        args_w = __args__.arguments_w
+        names = w_names_from_args(space, __args__)
+        nargs = len(__args__.arguments_w)
+        with lltype.scoped_alloc(rffi.CArray(PyObject), nargs + len(names)) as args:
+            i = 0
+            py_names = None
+            for w_arg in args_w:
+                args[i] = make_ref(space, w_arg)
+                i += 1
+            if names:
+                for w_val in __args__.keywords_w:
+                    args[i] = make_ref(space, w_val)
+                    i += 1
+                py_names = tuple_from_args_w(space, names)
+            try:
+                return generic_cpy_call(space, func, w_self, args, nargs, py_names)
+            finally:
+                for arg in args:
+                    decref(space, arg)
+                if py_names:
+                    decref(space, py_names)
+
     def get_doc(self, space):
         c_doc = self.ml.c_ml_doc
         if c_doc:
diff --git a/pypy/module/cpyext/parse/cpyext_object.h b/pypy/module/cpyext/parse/cpyext_object.h
--- a/pypy/module/cpyext/parse/cpyext_object.h
+++ b/pypy/module/cpyext/parse/cpyext_object.h
@@ -176,6 +176,8 @@
 
 /* from methodobject.h */
 typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
+typedef PyObject *(*_PyCFunctionFast) (PyObject *self, PyObject **args,
+                                       Py_ssize_t nargs, PyObject *kwnames);
 typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *,
                                              PyObject *);
 typedef PyObject *(*PyNoArgsFunction)(PyObject *);
diff --git a/pypy/module/cpyext/test/test_methodobject.py b/pypy/module/cpyext/test/test_methodobject.py
--- a/pypy/module/cpyext/test/test_methodobject.py
+++ b/pypy/module/cpyext/test/test_methodobject.py
@@ -89,6 +89,45 @@
         assert mod.getarg_KW.__name__ == "getarg_KW"
         assert mod.getarg_KW(*(), **{}) == ((), {})
 
+    def test_call_METH_FAST(self):
+        import sys
+        if sys.version_info[:2] != (3, 6):
+            skip('Python 3.6 only test, Python 3.7+ is different')
+        mod = self.import_extension('foo', [
+            ('getarg_FAST', 'METH_FASTCALL',
+             '''
+             int kwlen, i;
+             PyObject *pyargs;
+             if (kwnames == NULL) {
+                 kwlen = 0;
+             } else {
+                 kwlen = PySequence_Size(kwnames);
+             }
+             fprintf(stderr, "got %ld args, %d kwnames\\n", nargs, kwlen);
+             pyargs = PyTuple_New(nargs + kwlen);    
+             for (i=0; i<nargs + kwlen; i++) {
+                 if (args[i] == NULL) {
+                    PyErr_Format(PyExc_ValueError, "bad val at %d", i);
+                    return NULL;
+                 }
+                 PyTuple_SetItem(pyargs, i, args[i]);
+                 fprintf(stderr, "arg %d refcnt %ld\\n", i, (args[i])->ob_refcnt);
+             }
+             if (kwnames == NULL) {
+                 return Py_BuildValue("Oi", pyargs, nargs);
+             } else {
+                 fprintf(stderr, "got kwnames\\n");
+                 return Py_BuildValue("OiO", pyargs, nargs, kwnames);
+             }
+             '''
+             ),
+            ])
+        assert mod.getarg_FAST(1) == ((1,), 1)
+        assert mod.getarg_FAST(1, 2) == ((1, 2), 2)
+        assert mod.getarg_FAST(a=3, b=4) == ((3, 4), 0, ('a', 'b'))
+        assert mod.getarg_FAST(1, 2, a=3, b=4) == ((1, 2, 3, 4), 2, ('a', 'b'))
+        assert mod.getarg_FAST.__name__ == "getarg_FAST"
+        assert mod.getarg_FAST(*(), **{}) == ((), 0)
 
     def test_func_attributes(self):
         mod = self.import_extension('MyModule', [
diff --git a/pypy/tool/cpyext/extbuild.py b/pypy/tool/cpyext/extbuild.py
--- a/pypy/tool/cpyext/extbuild.py
+++ b/pypy/tool/cpyext/extbuild.py
@@ -118,7 +118,9 @@
     codes = []
     for funcname, flags, code in functions:
         cfuncname = "%s_%s" % (modname, funcname)
-        if 'METH_KEYWORDS' in flags:
+        if 'METH_FASTCALL' in flags:
+            signature = '(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)'
+        elif 'METH_KEYWORDS' in flags:
             signature = '(PyObject *self, PyObject *args, PyObject *kwargs)'
         else:
             signature = '(PyObject *self, PyObject *args)'


More information about the pypy-commit mailing list