Author: Armin Rigo <[email protected]>
Branch: cffi-1.0
Changeset: r1713:0b449baa47ff
Date: 2015-04-15 18:13 +0200
http://bitbucket.org/cffi/cffi/changeset/0b449baa47ff/

Log:    Starting working on the "recompiler"

diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -1,5 +1,6 @@
 import sys, types
 from .lock import allocate_lock
+import _cffi1_backend
 
 try:
     callable
@@ -27,7 +28,7 @@
         return '%s%s' % (line, self.args[0])
 
 
-class FFI(object):
+class FFI(_cffi1_backend.FFI):
     r'''
     The main top-level class that you instantiate once, or once per module.
 
@@ -45,21 +46,16 @@
         C.printf("hello, %s!\n", ffi.new("char[]", "world"))
     '''
 
-    def __init__(self, backend=None):
+    def __init__(self):
         """Create an FFI instance.  The 'backend' argument is used to
         select a non-default backend, mostly for tests.
         """
         from . import cparser, model
-        if backend is None:
-            # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with
-            # _cffi_backend.so compiled.
-            import _cffi_backend as backend
-            from . import __version__
-            assert backend.__version__ == __version__, \
-               "version mismatch, %s != %s" % (backend.__version__, 
__version__)
-            # (If you insist you can also try to pass the option
-            # 'backend=backend_ctypes.CTypesBackend()', but don't
-            # rely on it!  It's probably not going to work well.)
+        from . import __version__
+
+        backend = _cffi1_backend
+        assert backend.__version__ == __version__, \
+            "version mismatch, %s != %s" % (backend.__version__, __version__)
 
         self._backend = backend
         self._lock = allocate_lock()
@@ -80,15 +76,6 @@
         with self._lock:
             self.BVoidP = self._get_cached_btype(model.voidp_type)
             self.BCharA = self._get_cached_btype(model.char_array_type)
-        if isinstance(backend, types.ModuleType):
-            # _cffi_backend: attach these constants to the class
-            if not hasattr(FFI, 'NULL'):
-                FFI.NULL = self.cast(self.BVoidP, 0)
-                FFI.CData, FFI.CType = backend._get_types()
-        else:
-            # ctypes backend: attach these constants to the instance
-            self.NULL = self.cast(self.BVoidP, 0)
-            self.CData, self.CType = backend._get_types()
 
     def cdef(self, csource, override=False, packed=False):
         """Parse the given C source.  This registers all declared functions,
@@ -203,7 +190,7 @@
             cdecl = self._typeof(cdecl)
         return self._typeoffsetof(cdecl, *fields_or_indexes)[1]
 
-    def new(self, cdecl, init=None):
+    def XXXnew(self, cdecl, init=None):
         """Allocate an instance according to the specified C type and
         return a pointer to it.  The specified C type must be either a
         pointer or an array: ``new('X *')`` allocates an X and returns
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -169,6 +169,9 @@
         return global_cache(self, ffi, 'new_function_type',
                             tuple(args), result, self.ellipsis)
 
+    def as_raw_function(self):
+        return RawFunctionType(self.args, self.result, self.ellipsis)
+
 
 class PointerType(BaseType):
     _attrs_ = ('totype',)
diff --git a/new/cffi1 b/new/cffi1
new file mode 120000
--- /dev/null
+++ b/new/cffi1
@@ -0,0 +1,1 @@
+../cffi
\ No newline at end of file
diff --git a/new/cffi1_module.c b/new/cffi1_module.c
--- a/new/cffi1_module.c
+++ b/new/cffi1_module.c
@@ -28,6 +28,9 @@
     if (PyDict_SetItemString(FFI_Type.tp_dict, "CType",
                              (PyObject *)&CTypeDescr_Type) < 0)
         return -1;
+    if (PyDict_SetItemString(FFI_Type.tp_dict, "CData",
+                             (PyObject *)&CData_Type) < 0)
+        return -1;
 
     Py_INCREF(&FFI_Type);
     if (PyModule_AddObject(m, "FFI", (PyObject *)&FFI_Type) < 0)
diff --git a/new/cffi_opcode.py b/new/cffi_opcode.py
new file mode 100644
--- /dev/null
+++ b/new/cffi_opcode.py
@@ -0,0 +1,53 @@
+
+class CffiOp(object):
+    def __init__(self, op, arg):
+        self.op = op
+        self.arg = arg
+    def as_int(self):
+        return self.op | (self.arg << 8)
+    def __str__(self):
+        classname = CLASS_NAME.get(self.op, self.op)
+        return '(%s %d)' % (classname, self.arg)
+
+OP_PRIMITIVE       = 1
+OP_POINTER         = 3
+OP_ARRAY           = 5
+OP_OPEN_ARRAY      = 7
+OP_STRUCT_UNION    = 9
+OP_ENUM            = 11
+OP_TYPENAME        = 13
+OP_FUNCTION        = 15
+OP_FUNCTION_END    = 17
+OP_NOOP            = 19
+OP_BITFIELD        = 21
+OP_CPYTHON_BLTN_V  = 23   # varargs
+OP_CPYTHON_BLTN_N  = 25   # noargs
+OP_CPYTHON_BLTN_O  = 27   # O  (i.e. a single arg)
+
+PRIM_VOID          = 0
+PRIM_BOOL          = 1
+PRIM_CHAR          = 2
+PRIM_SCHAR         = 3
+PRIM_UCHAR         = 4
+PRIM_SHORT         = 5
+PRIM_USHORT        = 6
+PRIM_INT           = 7
+PRIM_UINT          = 8
+PRIM_LONG          = 9
+PRIM_ULONG         = 10
+PRIM_LONGLONG      = 11
+PRIM_ULONGLONG     = 12
+PRIM_FLOAT         = 13
+PRIM_DOUBLE        = 14
+PRIM_LONGDOUBLE    = 15
+
+PRIMITIVE_TO_INDEX = {
+    'int':    PRIM_INT,
+    'float':  PRIM_FLOAT,
+    'double': PRIM_DOUBLE,
+    }
+
+CLASS_NAME = {}
+for _name, _value in globals().items():
+    if _name.startswith('OP_') and isinstance(_value, int):
+        CLASS_NAME[_value] = _name[3:]
diff --git a/new/lib_obj.c b/new/lib_obj.c
--- a/new/lib_obj.c
+++ b/new/lib_obj.c
@@ -1,22 +1,14 @@
 
-/* A Lib object is what is returned by any of:
-
-   - the "lib" attribute of a C extension module originally created by
-     recompile()
-
-   - ffi.dlopen()
-
-   - ffi.verify()
+/* A Lib object is what is in the "lib" attribute of a C extension
+   module originally created by recompile().
 
    A Lib object is special in the sense that it has a custom
    __getattr__ which returns C globals, functions and constants.  It
-   raises AttributeError for anything else, like '__class__'.
+   raises AttributeError for anything else, even attrs like '__class__'.
 
    A Lib object has got a reference to the _cffi_type_context_s
    structure, which is used to create lazily the objects returned by
-   __getattr__.  For a dlopen()ed Lib object, all the 'address' fields
-   in _cffi_global_s are NULL, and instead dlsym() is used lazily on
-   the l_dl_lib.
+   __getattr__.
 */
 
 struct CPyExtFunc_s {
@@ -29,17 +21,13 @@
     PyObject_HEAD
     const struct _cffi_type_context_s *l_ctx;  /* ctx object */
     PyObject *l_dict;           /* content, built lazily */
-    void *l_dl_lib;             /* the result of 'dlopen()', or NULL */
     PyObject *l_libname;        /* some string that gives the name of the lib 
*/
 };
 
 #define LibObject_Check(ob)  ((Py_TYPE(ob) == &Lib_Type))
 
-static int lib_close(LibObject *lib);    /* forward */
-
 static void lib_dealloc(LibObject *lib)
 {
-    (void)lib_close(lib);
     Py_DECREF(lib->l_dict);
     Py_DECREF(lib->l_libname);
     PyObject_Del(lib);
@@ -228,33 +216,6 @@
     offsetof(LibObject, l_dict),                /* tp_dictoffset */
 };
 
-
-static void lib_dlerror(LibObject *lib)
-{
-    char *error = dlerror();
-    if (error == NULL)
-        error = "(no error reported)";
-    PyErr_Format(PyExc_OSError, "%s: %s", PyText_AS_UTF8(lib->l_libname),
-                 error);
-}
-
-static int lib_close(LibObject *lib)
-{
-    void *dll;
-    lib->l_ctx = NULL;
-    PyDict_Clear(lib->l_dict);
-
-    dll = lib->l_dl_lib;
-    if (dll != NULL) {
-        lib->l_dl_lib = NULL;
-        if (dlclose(dll) != 0) {
-            lib_dlerror(lib);
-            return -1;
-        }
-    }
-    return 0;
-}
-
 static LibObject *lib_internal_new(const struct _cffi_type_context_s *ctx,
                                    char *module_name)
 {
@@ -275,7 +236,6 @@
 
     lib->l_ctx = ctx;
     lib->l_dict = dict;
-    lib->l_dl_lib = NULL;
     lib->l_libname = libname;
     return lib;
 }
diff --git a/new/recompiler.py b/new/recompiler.py
new file mode 100644
--- /dev/null
+++ b/new/recompiler.py
@@ -0,0 +1,132 @@
+import os
+from cffi1 import ffiplatform, model
+from cffi_opcode import *
+
+
+class Recompiler:
+
+    def __init__(self, ffi):
+        self.ffi = ffi
+
+    def collect_type_table(self):
+        self._typesdict = {}
+        self._generate('collecttype')
+        #
+        all_decls = sorted(self._typesdict, key=str)
+        #
+        # prepare all FUNCTION bytecode sequences first
+        self.cffi_types = []
+        for tp in all_decls:
+            if tp.is_raw_function:
+                assert self._typesdict[tp] is None
+                self._typesdict[tp] = len(self.cffi_types)
+                self.cffi_types.append(tp)     # placeholder
+                for tp1 in tp.args:
+                    assert isinstance(tp1, (model.VoidType,
+                                            model.PrimitiveType,
+                                            model.PointerType,
+                                            model.StructOrUnionOrEnum,
+                                            model.FunctionPtrType))
+                    if self._typesdict[tp1] is None:
+                        self._typesdict[tp1] = len(self.cffi_types)
+                    self.cffi_types.append(tp1)   # placeholder
+                self.cffi_types.append('END')     # placeholder
+        #
+        # prepare all OTHER bytecode sequences
+        for tp in all_decls:
+            if not tp.is_raw_function and self._typesdict[tp] is None:
+                self._typesdict[tp] = len(self.cffi_types)
+                self.cffi_types.append(tp)        # placeholder
+                if tp.is_array_type and tp.length is not None:
+                    self.cffi_types.append('LEN') # placeholder
+        assert None not in self._typesdict.values()
+        #
+        # emit all bytecode sequences now
+        for tp in all_decls:
+            method = getattr(self, '_emit_bytecode_' + tp.__class__.__name__)
+            method(tp, self._typesdict[tp])
+        #
+        # consistency check
+        for op in self.cffi_types:
+            assert isinstance(op, CffiOp)
+
+    def _do_collect_type(self, tp):
+        if not isinstance(tp, model.BaseTypeByIdentity):
+            if isinstance(tp, tuple):
+                for x in tp:
+                    self._do_collect_type(x)
+            return
+        if tp not in self._typesdict:
+            self._typesdict[tp] = None
+            if isinstance(tp, model.FunctionPtrType):
+                self._do_collect_type(tp.as_raw_function())
+            else:
+                for _, x in tp._get_items():
+                    self._do_collect_type(x)
+
+    def _get_declarations(self):
+        return sorted(self.ffi._parser._declarations.items())
+
+    def _generate(self, step_name):
+        for name, tp in self._get_declarations():
+            kind, realname = name.split(' ', 1)
+            try:
+                method = getattr(self, '_generate_cpy_%s_%s' % (kind,
+                                                                step_name))
+            except AttributeError:
+                raise ffiplatform.VerificationError(
+                    "not implemented in verify(): %r" % name)
+            try:
+                method(tp, realname)
+            except Exception as e:
+                model.attach_exception_info(e, name)
+                raise
+
+    def _generate_cpy_function_collecttype(self, tp, name):
+        self._do_collect_type(tp.as_raw_function())
+
+    def _emit_bytecode_PrimitiveType(self, tp, index):
+        prim_index = PRIMITIVE_TO_INDEX[tp.name]
+        self.cffi_types[index] = CffiOp(OP_PRIMITIVE, prim_index)
+
+    def _emit_bytecode_RawFunctionType(self, tp, index):
+        self.cffi_types[index] = CffiOp(OP_FUNCTION, 
self._typesdict[tp.result])
+        index += 1
+        for tp1 in tp.args:
+            realindex = self._typesdict[tp1]
+            if index != realindex:
+                if isinstance(tp1, model.PrimitiveType):
+                    self._emit_bytecode_PrimitiveType(tp1, index)
+                else:
+                    self.cffi_types[index] = CffiOp(OP_NOOP, realindex)
+            index += 1
+        self.cffi_types[index] = CffiOp(OP_FUNCTION_END, tp.ellipsis)
+
+    def _emit_bytecode_PointerType(self, tp, index):
+        self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[tp.totype])
+
+    # ----------
+
+    def _prnt(self, what=''):
+        self._f.write(what + '\n')
+
+    def write_source_to_f(self, f, preamble):
+        self._f = f
+        prnt = self._prnt
+        # first copy some standard set of lines that are mostly '#define'
+        filename = os.path.join(os.path.dirname(__file__), '_cffi_include.h')
+        with open(filename, 'r') as g:
+            prnt(g.read())
+        prnt('/************************************************************/')
+        prnt()
+        # then paste the C source given by the user, verbatim.
+        prnt(preamble)
+        prnt()
+        #...
+        
+
+def make_c_source(ffi, target_c_file, preamble):
+    recompiler = Recompiler(ffi)
+    recompiler.collect_type_table()
+    with open(target_c_file, 'w') as f:
+        recompiler.write_source_to_f(f, preamble)
diff --git a/new/test_dlopen.py b/new/test_dlopen.py
new file mode 100644
--- /dev/null
+++ b/new/test_dlopen.py
@@ -0,0 +1,10 @@
+from cffi1 import FFI
+import math
+
+
+def test_math_sin():
+    ffi = FFI()
+    ffi.cdef("double sin(double);")
+    m = ffi.dlopen('m')
+    x = m.sin(1.23)
+    assert x == math.sin(1.23)
diff --git a/new/test_recompiler.py b/new/test_recompiler.py
new file mode 100644
--- /dev/null
+++ b/new/test_recompiler.py
@@ -0,0 +1,42 @@
+from recompiler import Recompiler, make_c_source
+from cffi1 import FFI
+from udir import udir
+
+
+def check_type_table(input, expected_output):
+    ffi = FFI()
+    ffi.cdef(input)
+    recompiler = Recompiler(ffi)
+    recompiler.collect_type_table()
+    assert ''.join(map(str, recompiler.cffi_types)) == expected_output
+
+def test_type_table_func():
+    check_type_table("double sin(double);",
+                     "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)")
+    check_type_table("float sin(double);",
+                     "(FUNCTION 3)(PRIMITIVE 14)(FUNCTION_END 0)(PRIMITIVE 
13)")
+    check_type_table("float sin(void);",
+                     "(FUNCTION 2)(FUNCTION_END 0)(PRIMITIVE 13)")
+    check_type_table("double sin(float); double cos(float);",
+                     "(FUNCTION 3)(PRIMITIVE 13)(FUNCTION_END 0)(PRIMITIVE 
14)")
+    check_type_table("double sin(float); double cos(double);",
+                     "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)"   # cos
+                     "(FUNCTION 1)(PRIMITIVE 13)(FUNCTION_END 0)")  # sin
+    check_type_table("float sin(double); float cos(float);",
+                     "(FUNCTION 4)(PRIMITIVE 14)(FUNCTION_END 0)"   # sin
+                     "(FUNCTION 4)(PRIMITIVE 13)(FUNCTION_END 0)")  # cos
+
+def test_use_noop_for_repeated_args():
+    check_type_table("double sin(double *, double *);",
+                     "(FUNCTION 4)(POINTER 4)(NOOP 1)(FUNCTION_END 0)"
+                     "(PRIMITIVE 14)")
+
+def test_dont_use_noop_for_primitives():
+    check_type_table("double sin(double, double);",
+                     "(FUNCTION 1)(PRIMITIVE 14)(PRIMITIVE 14)(FUNCTION_END 
0)")
+
+
+def test_math_sin():
+    ffi = FFI()
+    ffi.cdef("double sin(double);")
+    make_c_source(ffi, str(udir.join('math_sin.c')), '#include <math.h>')
diff --git a/testing/udir.py b/new/udir.py
copy from testing/udir.py
copy to new/udir.py
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to