[pypy-commit] pypy cffi-static-callback: import cffi/919e3cdd4fb6 (branch static-callback)

arigo noreply at buildbot.pypy.org
Mon Nov 16 05:55:49 EST 2015


Author: Armin Rigo <arigo at tunes.org>
Branch: cffi-static-callback
Changeset: r80706:50da830ec2ce
Date: 2015-11-16 11:00 +0000
http://bitbucket.org/pypy/pypy/changeset/50da830ec2ce/

Log:	import cffi/919e3cdd4fb6 (branch static-callback)

diff --git a/lib_pypy/cffi/_cffi_include.h b/lib_pypy/cffi/_cffi_include.h
--- a/lib_pypy/cffi/_cffi_include.h
+++ b/lib_pypy/cffi/_cffi_include.h
@@ -146,7 +146,9 @@
     ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23])
 #define _cffi_convert_array_from_object                                  \
     ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24])
-#define _CFFI_NUM_EXPORTS 25
+#define _cffi_call_python                                                \
+    ((void(*)(struct _cffi_callpy_s *, char *))_cffi_exports[25])
+#define _CFFI_NUM_EXPORTS 26
 
 typedef struct _ctypedescr CTypeDescrObject;
 
@@ -201,8 +203,11 @@
                                                   the others follow */
 }
 
+/**********  end CPython-specific section  **********/
+#else
+_CFFI_UNUSED_FN
+static void (*_cffi_call_python)(struct _cffi_callpy_s *, char *);
 #endif
-/**********  end CPython-specific section  **********/
 
 
 #define _cffi_array_len(array)   (sizeof(array) / sizeof((array)[0]))
diff --git a/lib_pypy/cffi/cffi_opcode.py b/lib_pypy/cffi/cffi_opcode.py
--- a/lib_pypy/cffi/cffi_opcode.py
+++ b/lib_pypy/cffi/cffi_opcode.py
@@ -54,6 +54,7 @@
 OP_DLOPEN_FUNC     = 35
 OP_DLOPEN_CONST    = 37
 OP_GLOBAL_VAR_F    = 39
+OP_CALL_PYTHON     = 41
 
 PRIM_VOID          = 0
 PRIM_BOOL          = 1
diff --git a/lib_pypy/cffi/cparser.py b/lib_pypy/cffi/cparser.py
--- a/lib_pypy/cffi/cparser.py
+++ b/lib_pypy/cffi/cparser.py
@@ -29,6 +29,7 @@
 _r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b")
 _r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b")
 _r_cdecl = re.compile(r"\b__cdecl\b")
+_r_cffi_call_python = re.compile(r"\bCFFI_CALL_PYTHON\b")
 _r_star_const_space = re.compile(       # matches "* const "
     r"[*]\s*((const|volatile|restrict)\b\s*)+")
 
@@ -62,7 +63,8 @@
         if csource.startswith('*', endpos):
             parts.append('('); closing += ')'
         level = 0
-        for i in xrange(endpos, len(csource)):
+        i = endpos
+        while i < len(csource):
             c = csource[i]
             if c == '(':
                 level += 1
@@ -73,6 +75,7 @@
             elif c in ',;=':
                 if level == 0:
                     break
+            i += 1
         csource = csource[endpos:i] + closing + csource[i:]
         #print repr(''.join(parts)+csource)
     parts.append(csource)
@@ -101,8 +104,13 @@
     csource = _r_stdcall2.sub(' volatile volatile const(', csource)
     csource = _r_stdcall1.sub(' volatile volatile const ', csource)
     csource = _r_cdecl.sub(' ', csource)
+    #
+    # Replace "CFFI_CALL_PYTHON" with "void CFFI_CALL_PYTHON;"
+    csource = _r_cffi_call_python.sub('void CFFI_CALL_PYTHON;', csource)
+    #
     # Replace "[...]" with "[__dotdotdotarray__]"
     csource = _r_partial_array.sub('[__dotdotdotarray__]', csource)
+    #
     # Replace "...}" with "__dotdotdotNUM__}".  This construction should
     # occur only at the end of enums; at the end of structs we have "...;}"
     # and at the end of vararg functions "...);".  Also replace "=...[,}]"
@@ -255,7 +263,9 @@
                 break
         #
         try:
+            self._found_cffi_call_python = False
             for decl in iterator:
+                old_cffi_call_python = self._found_cffi_call_python
                 if isinstance(decl, pycparser.c_ast.Decl):
                     self._parse_decl(decl)
                 elif isinstance(decl, pycparser.c_ast.Typedef):
@@ -278,6 +288,8 @@
                     self._declare('typedef ' + decl.name, realtype, quals=quals)
                 else:
                     raise api.CDefError("unrecognized construct", decl)
+                if old_cffi_call_python and self._found_cffi_call_python:
+                    raise api.CDefError("CFFI_CALL_PYTHON misplaced")
         except api.FFIError as e:
             msg = self._convert_pycparser_error(e, csource)
             if msg:
@@ -324,13 +336,20 @@
                     '  #define %s %s'
                     % (key, key, key, value))
 
+    def _declare_function(self, tp, quals, decl):
+        tp = self._get_type_pointer(tp, quals)
+        if self._found_cffi_call_python:
+            self._declare('call_python ' + decl.name, tp)
+            self._found_cffi_call_python = False
+        else:
+            self._declare('function ' + decl.name, tp)
+
     def _parse_decl(self, decl):
         node = decl.type
         if isinstance(node, pycparser.c_ast.FuncDecl):
             tp, quals = self._get_type_and_quals(node, name=decl.name)
             assert isinstance(tp, model.RawFunctionType)
-            tp = self._get_type_pointer(tp, quals)
-            self._declare('function ' + decl.name, tp)
+            self._declare_function(tp, quals, decl)
         else:
             if isinstance(node, pycparser.c_ast.Struct):
                 self._get_struct_union_enum_type('struct', node)
@@ -346,8 +365,7 @@
                 tp, quals = self._get_type_and_quals(node,
                                                      partial_length_ok=True)
                 if tp.is_raw_function:
-                    tp = self._get_type_pointer(tp, quals)
-                    self._declare('function ' + decl.name, tp)
+                    self._declare_function(tp, quals, decl)
                 elif (tp.is_integer_type() and
                         hasattr(decl, 'init') and
                         hasattr(decl.init, 'value') and
@@ -360,6 +378,11 @@
                         _r_int_literal.match(decl.init.expr.value)):
                     self._add_integer_constant(decl.name,
                                                '-' + decl.init.expr.value)
+                elif tp is model.void_type and decl.name == 'CFFI_CALL_PYTHON':
+                    # hack: "CFFI_CALL_PYTHON" in the C source is replaced
+                    # with "void CFFI_CALL_PYTHON;", which when parsed arrives
+                    # at this point and sets this flag:
+                    self._found_cffi_call_python = True
                 elif (quals & model.Q_CONST) and not tp.is_array_type:
                     self._declare('constant ' + decl.name, tp, quals=quals)
                 else:
diff --git a/lib_pypy/cffi/parse_c_type.h b/lib_pypy/cffi/parse_c_type.h
--- a/lib_pypy/cffi/parse_c_type.h
+++ b/lib_pypy/cffi/parse_c_type.h
@@ -27,6 +27,7 @@
 #define _CFFI_OP_DLOPEN_FUNC    35
 #define _CFFI_OP_DLOPEN_CONST   37
 #define _CFFI_OP_GLOBAL_VAR_F   39
+#define _CFFI_OP_CALL_PYTHON    41
 
 #define _CFFI_PRIM_VOID          0
 #define _CFFI_PRIM_BOOL          1
@@ -160,6 +161,12 @@
     const char *error_message;
 };
 
+struct _cffi_callpy_s {
+    const char *name;
+    size_t size_of_result;
+    void *reserved1, *reserved2;
+};
+
 #ifdef _CFFI_INTERNAL
 static int parse_c_type(struct _cffi_parse_info_s *info, const char *input);
 static int search_in_globals(const struct _cffi_type_context_s *ctx,
diff --git a/lib_pypy/cffi/recompiler.py b/lib_pypy/cffi/recompiler.py
--- a/lib_pypy/cffi/recompiler.py
+++ b/lib_pypy/cffi/recompiler.py
@@ -118,6 +118,7 @@
 
 
 class Recompiler:
+    _num_callpy = 0
 
     def __init__(self, ffi, module_name, target_is_python=False):
         self.ffi = ffi
@@ -356,7 +357,10 @@
         else:
             prnt('  NULL,  /* no includes */')
         prnt('  %d,  /* num_types */' % (len(self.cffi_types),))
-        prnt('  0,  /* flags */')
+        flags = 0
+        if self._num_callpy:
+            flags |= 1     # set to mean "uses _cffi_call_python"
+        prnt('  %d,  /* flags */' % flags)
         prnt('};')
         prnt()
         #
@@ -366,6 +370,9 @@
         prnt('PyMODINIT_FUNC')
         prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,))
         prnt('{')
+        prnt('    if (((intptr_t)p[0]) >= 0x0A03) {')
+        prnt('        _cffi_call_python = p[1];')
+        prnt('    }')
         prnt('    p[0] = (const void *)%s;' % VERSION)
         prnt('    p[1] = &_cffi_type_context;')
         prnt('}')
@@ -1108,6 +1115,75 @@
             GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index)))
 
     # ----------
+    # CFFI_CALL_PYTHON
+
+    def _generate_cpy_call_python_collecttype(self, tp, name):
+        assert isinstance(tp, model.FunctionPtrType)
+        self._do_collect_type(tp)
+
+    def _generate_cpy_call_python_decl(self, tp, name):
+        prnt = self._prnt
+        if isinstance(tp.result, model.VoidType):
+            size_of_result = '0'
+        else:
+            context = 'result of %s' % name
+            size_of_result = '(int)sizeof(%s)' % (
+                tp.result.get_c_name('', context),)
+        prnt('static struct _cffi_callpy_s _cffi_callpy__%s =' % name)
+        prnt('  { "%s", %s };' % (name, size_of_result))
+        prnt()
+        #
+        arguments = []
+        context = 'argument of %s' % name
+        for i, type in enumerate(tp.args):
+            arg = type.get_c_name(' a%d' % i, context)
+            arguments.append(arg)
+        #
+        repr_arguments = ', '.join(arguments)
+        repr_arguments = repr_arguments or 'void'
+        name_and_arguments = '%s(%s)' % (name, repr_arguments)
+        #
+        def may_need_128_bits(tp):
+            return (isinstance(tp, model.PrimitiveType) and
+                    tp.name == 'long double')
+        #
+        size_of_a = max(len(tp.args)*8, 8)
+        if may_need_128_bits(tp.result):
+            size_of_a = max(size_of_a, 16)
+        if isinstance(tp.result, model.StructOrUnion):
+            size_of_a = 'sizeof(%s) > %d ? sizeof(%s) : %d' % (
+                tp.result.get_c_name(''), size_of_a,
+                tp.result.get_c_name(''), size_of_a)
+        prnt('static %s' % tp.result.get_c_name(name_and_arguments))
+        prnt('{')
+        prnt('  char a[%s];' % size_of_a)
+        prnt('  char *p = a;')
+        for i, type in enumerate(tp.args):
+            arg = 'a%d' % i
+            if (isinstance(type, model.StructOrUnion) or
+                    may_need_128_bits(type)):
+                arg = '&' + arg
+                type = model.PointerType(type)
+            prnt('  *(%s)(p + %d) = %s;' % (type.get_c_name('*'), i*8, arg))
+        prnt('  _cffi_call_python(&_cffi_callpy__%s, p);' % name)
+        if not isinstance(tp.result, model.VoidType):
+            prnt('  return *(%s)p;' % (tp.result.get_c_name('*'),))
+        prnt('}')
+        prnt()
+        self._num_callpy += 1
+
+    def _generate_cpy_call_python_ctx(self, tp, name):
+        if self.target_is_python:
+            raise ffiplatform.VerificationError(
+                "cannot use CFFI_CALL_PYTHON in the ABI mode")
+        if tp.ellipsis:
+            raise NotImplementedError("CFFI_CALL_PYTHON with a vararg function")
+        type_index = self._typesdict[tp]
+        type_op = CffiOp(OP_CALL_PYTHON, type_index)
+        self._lsts["global"].append(
+            GlobalExpr(name, '&_cffi_callpy__%s' % name, type_op, name))
+
+    # ----------
     # emitting the opcodes for individual types
 
     def _emit_bytecode_VoidType(self, tp, index):
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_parsing.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_parsing.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_parsing.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_parsing.py
@@ -406,3 +406,17 @@
         "<ctype 'int(*)(int(%s*)(int), "
                         "long(*)(), "
                         "short(%s*)(short))'>" % (stdcall, stdcall))
+
+def test_CFFI_CALL_PYTHON():
+    ffi = FFI()
+    ffi.cdef("""
+        int baz(int, int);
+        CFFI_CALL_PYTHON int foobar(int, int);
+    """)
+    assert 'variable CFFI_CALL_PYTHON' not in ffi._parser._declarations
+    assert 'function baz' in ffi._parser._declarations
+    assert 'call_python baz' not in ffi._parser._declarations
+    assert 'function foobar' not in ffi._parser._declarations
+    assert 'call_python foobar' in ffi._parser._declarations
+    assert (ffi._parser._declarations['function baz'] ==
+            ffi._parser._declarations['call_python foobar'])
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
@@ -5,6 +5,7 @@
 from cffi import recompiler
 from pypy.module.test_lib_pypy.cffi_tests.udir import udir
 from pypy.module.test_lib_pypy.cffi_tests.support import u
+from StringIO import StringIO
 
 
 def check_type_table(input, expected_output, included=None):
@@ -1485,3 +1486,221 @@
     assert (pt.x, pt.y) == (99*500*999, -99*500*999)
     pt = ptr_call2(ffi.addressof(lib, 'cb2'))
     assert (pt.x, pt.y) == (99*500*999, -99*500*999)
+
+class StdErrCapture(object):
+    def __enter__(self):
+        self.old_stderr = sys.stderr
+        sys.stderr = f = StringIO()
+        return f
+    def __exit__(self, *args):
+        sys.stderr = self.old_stderr
+
+def test_call_python_1():
+    ffi = FFI()
+    ffi.cdef("""
+        CFFI_CALL_PYTHON int bar(int, int);
+        CFFI_CALL_PYTHON void baz(int, int);
+        CFFI_CALL_PYTHON int bok(void);
+        CFFI_CALL_PYTHON void boz(void);
+    """)
+    lib = verify(ffi, 'test_call_python_1', "")
+    assert ffi.typeof(lib.bar) == ffi.typeof("int(*)(int, int)")
+    with StdErrCapture() as f:
+        res = lib.bar(4, 5)
+    assert res == 0
+    assert f.getvalue() == (
+        "CFFI_CALL_PYTHON: function bar() called, but no code was attached "
+        "to it yet with ffi.call_python('bar').  Returning 0.\n")
+
+    @ffi.call_python("bar")
+    def my_bar(x, y):
+        seen.append(("Bar", x, y))
+        return x * y
+    assert my_bar == lib.bar
+    seen = []
+    res = lib.bar(6, 7)
+    assert seen == [("Bar", 6, 7)]
+    assert res == 42
+
+    @ffi.call_python()
+    def baz(x, y):
+        seen.append(("Baz", x, y))
+    seen = []
+    res = baz(50L, 8L)
+    assert res is None
+    assert seen == [("Baz", 50, 8)]
+    assert type(seen[0][1]) is type(seen[0][2]) is int
+    assert baz == lib.baz
+
+    @ffi.call_python(name="bok")
+    def bok():
+        seen.append("Bok")
+        return 42
+    seen = []
+    assert lib.bok() == bok() == 42
+    assert seen == ["Bok", "Bok"]
+
+    @ffi.call_python()
+    def boz():
+        seen.append("Boz")
+    seen = []
+    assert lib.boz() is boz() is None
+    assert seen == ["Boz", "Boz"]
+
+def test_call_python_bogus_name():
+    ffi = FFI()
+    ffi.cdef("int abc;")
+    lib = verify(ffi, 'test_call_python_bogus_name', "int abc;")
+    def fn():
+        pass
+    py.test.raises(ffi.error, ffi.call_python("unknown_name"), fn)
+    py.test.raises(ffi.error, ffi.call_python("abc"), fn)
+    assert lib.abc == 0
+    e = py.test.raises(ffi.error, ffi.call_python("abc"), fn)
+    assert str(e.value) == ("ffi.call_python('abc'): name not found as a "
+                            "CFFI_CALL_PYTHON line from the cdef")
+    e = py.test.raises(ffi.error, ffi.call_python(), fn)
+    assert str(e.value) == ("ffi.call_python('fn'): name not found as a "
+                            "CFFI_CALL_PYTHON line from the cdef")
+    #
+    py.test.raises(TypeError, ffi.call_python(42), fn)
+    py.test.raises((TypeError, AttributeError), ffi.call_python(), "foo")
+    class X:
+        pass
+    x = X()
+    x.__name__ = x
+    py.test.raises(TypeError, ffi.call_python(), x)
+
+def test_call_python_bogus_result_type():
+    ffi = FFI()
+    ffi.cdef("CFFI_CALL_PYTHON void bar(int);")
+    lib = verify(ffi, 'test_call_python_bogus_result_type', "")
+    #
+    def bar(n):
+        return n * 10
+    bar1 = ffi.call_python()(bar)
+    with StdErrCapture() as f:
+        res = bar1(321)
+    assert res is None
+    assert f.getvalue() == (
+        "From cffi callback %r:\n" % (bar,) +
+        "Trying to convert the result back to C:\n"
+        "TypeError: callback with the return type 'void' must return None\n")
+
+def test_call_python_redefine():
+    ffi = FFI()
+    ffi.cdef("CFFI_CALL_PYTHON int bar(int);")
+    lib = verify(ffi, 'test_call_python_redefine', "")
+    #
+    @ffi.call_python()
+    def bar(n):
+        return n * 10
+    assert lib.bar(42) == 420
+    #
+    @ffi.call_python()
+    def bar(n):
+        return -n
+    assert lib.bar(42) == -42
+
+def test_call_python_struct():
+    ffi = FFI()
+    ffi.cdef("""
+        struct foo_s { int a, b, c; };
+        CFFI_CALL_PYTHON int bar(int, struct foo_s, int);
+        CFFI_CALL_PYTHON struct foo_s baz(int, int);
+        CFFI_CALL_PYTHON struct foo_s bok(void);
+    """)
+    lib = verify(ffi, 'test_call_python_struct',
+                 "struct foo_s { int a, b, c; };")
+    #
+    @ffi.call_python()
+    def bar(x, s, z):
+        return x + s.a + s.b + s.c + z
+    res = lib.bar(1000, [1001, 1002, 1004], 1008)
+    assert res == 5015
+    #
+    @ffi.call_python()
+    def baz(x, y):
+        return [x + y, x - y, x * y]
+    res = lib.baz(1000, 42)
+    assert res.a == 1042
+    assert res.b == 958
+    assert res.c == 42000
+    #
+    @ffi.call_python()
+    def bok():
+        return [10, 20, 30]
+    res = lib.bok()
+    assert [res.a, res.b, res.c] == [10, 20, 30]
+
+def test_call_python_long_double():
+    ffi = FFI()
+    ffi.cdef("""
+        CFFI_CALL_PYTHON int bar(int, long double, int);
+        CFFI_CALL_PYTHON long double baz(int, int);
+        CFFI_CALL_PYTHON long double bok(void);
+    """)
+    lib = verify(ffi, 'test_call_python_long_double', "")
+    #
+    @ffi.call_python()
+    def bar(x, l, z):
+        seen.append((x, l, z))
+        return 6
+    seen = []
+    lib.bar(10, 3.5, 20)
+    expected = ffi.cast("long double", 3.5)
+    assert repr(seen) == repr([(10, expected, 20)])
+    #
+    @ffi.call_python()
+    def baz(x, z):
+        assert x == 10 and z == 20
+        return expected
+    res = lib.baz(10, 20)
+    assert repr(res) == repr(expected)
+    #
+    @ffi.call_python()
+    def bok():
+        return expected
+    res = lib.bok()
+    assert repr(res) == repr(expected)
+
+def test_call_python_signature():
+    ffi = FFI()
+    lib = verify(ffi, 'test_call_python_signature', "")
+    py.test.raises(TypeError, ffi.call_python(425), None)
+    py.test.raises(TypeError, ffi.call_python, 'a', 'b', 'c', 'd')
+
+def test_call_python_errors():
+    ffi = FFI()
+    ffi.cdef("""
+        CFFI_CALL_PYTHON int bar(int);
+    """)
+    lib = verify(ffi, 'test_call_python_errors', "")
+
+    seen = []
+    def oops(*args):
+        seen.append(args)
+
+    @ffi.call_python(onerror=oops)
+    def bar(x):
+        return x + ""
+    assert bar(10) == 0
+
+    @ffi.call_python(name="bar", onerror=oops, error=-66)
+    def bar2(x):
+        return x + ""
+    assert bar(10) == -66
+
+    assert len(seen) == 2
+    exc, val, tb = seen[0]
+    assert exc is TypeError
+    assert isinstance(val, TypeError)
+    assert tb.tb_frame.f_code.co_name == "bar"
+    exc, val, tb = seen[1]
+    assert exc is TypeError
+    assert isinstance(val, TypeError)
+    assert tb.tb_frame.f_code.co_name == "bar2"
+    #
+    # a case where 'onerror' is not callable
+    py.test.raises(TypeError, ffi.call_python(name='bar', onerror=42),
+                   lambda x: x)


More information about the pypy-commit mailing list