Commits

Armin Rigo committed 331d828

Implement caching of the types across multiple FFI instances. The types
are shared as long as they don't depend on a particular name (i.e. if
they contain no struct or union or enum).

Comments (0)

Files changed (4)

+import weakref
 
 class BaseType(object):
 
         return 'void' + replace_with
 
     def new_backend_type(self, ffi):
-        return ffi._backend.new_void_type()
+        return global_cache(ffi, 'new_void_type')
 
 void_type = VoidType()
 
         return self.name in ('double', 'float')
 
     def new_backend_type(self, ffi):
-        return ffi._backend.new_primitive_type(self.name)
+        return global_cache(ffi, 'new_primitive_type', self.name)
 
 
 class BaseFunctionType(BaseType):
         return args
 
     def new_backend_type(self, ffi, result, *args):
-        return ffi._backend.new_function_type(args, result, self.ellipsis)
+        return global_cache(ffi, 'new_function_type',
+                            args, result, self.ellipsis)
 
 
 class PointerType(BaseType):
         return (ffi._get_cached_btype(self.totype),)
 
     def new_backend_type(self, ffi, BItem):
-        return ffi._backend.new_pointer_type(BItem)
+        return global_cache(ffi, 'new_pointer_type', BItem)
 
 
 class ConstPointerType(PointerType):
         return (ffi._get_cached_btype(PointerType(self.item)),)
 
     def new_backend_type(self, ffi, BPtrItem):
-        return ffi._backend.new_array_type(BPtrItem, self.length)
+        return global_cache(ffi, 'new_array_type', BPtrItem, self.length)
 
 
 class StructOrUnion(BaseType):
     tp = StructType('$%s' % name, None, None, None)
     tp.forcename = name
     return tp
+
+def global_cache(ffi, funcname, *args):
+    try:
+        return ffi._backend.__typecache[args]
+    except KeyError:
+        pass
+    except AttributeError:
+        # initialize the __typecache attribute, either at the module level
+        # if ffi._backend is a module, or at the class level if ffi._backend
+        # is some instance.
+        ModuleType = type(weakref)
+        if isinstance(ffi._backend, ModuleType):
+            ffi._backend.__typecache = weakref.WeakValueDictionary()
+        else:
+            type(ffi._backend).__typecache = weakref.WeakValueDictionary()
+    res = getattr(ffi._backend, funcname)(*args)
+    ffi._backend.__typecache[args] = res
+    return res

testing/backend_tests.py

         f = ffi.callback("long long cb(long i, ...)", cb)
         res = f(10, ffi.cast("int", 100), ffi.cast("long long", 1000))
         assert res == 20 + 300 + 5000
+
+    def test_unique_types(self):
+        ffi1 = FFI(backend=self.Backend())
+        ffi2 = FFI(backend=self.Backend())
+        assert ffi1.typeof("char") is ffi2.typeof("char ")
+        assert ffi1.typeof("long") is ffi2.typeof("signed long int")
+        assert ffi1.typeof("double *") is ffi2.typeof("double*")
+        assert ffi1.typeof("int ***") is ffi2.typeof(" int * * *")
+        assert ffi1.typeof("int[]") is ffi2.typeof("signed int[]")
+        assert ffi1.typeof("signed int*[17]") is ffi2.typeof("int *[17]")
+        assert ffi1.typeof("void") is ffi2.typeof("void")
+        assert ffi1.typeof("int(*)(int,int)") is ffi2.typeof("int(*)(int,int)")
+        #
+        # these depend on user-defined data, so should not be shared
+        assert ffi1.typeof("struct foo") is not ffi2.typeof("struct foo")
+        assert ffi1.typeof("union foo *") is not ffi2.typeof("union foo*")
+        assert ffi1.typeof("enum foo") is not ffi2.typeof("enum foo")
+        # sanity check: twice 'ffi1'
+        assert ffi1.typeof("struct foo*") is ffi1.typeof("struct foo *")

testing/test_cdata.py

         return "fake library"
 
     def new_primitive_type(self, name):
-        return FakePrimitiveType(name)
+        return FakeType("primitive " + name)
 
     def new_void_type(self):
-        return "void!"
+        return FakeType("void")
     def new_pointer_type(self, x):
-        return 'ptr-to-%r!' % (x,)
+        return FakeType('ptr-to-%r' % (x,))
     def cast(self, x, y):
         return 'casted!'
 
-class FakePrimitiveType(object):
 
+class FakeType(object):
     def __init__(self, cdecl):
         self.cdecl = cdecl
 
 def test_typeof():
     ffi = FFI(backend=FakeBackend())
     clong = ffi.typeof("signed long int")
-    assert isinstance(clong, FakePrimitiveType)
-    assert clong.cdecl == 'long'
+    assert isinstance(clong, FakeType)
+    assert clong.cdecl == 'primitive long'

testing/test_parsing.py

         return FakeLibrary()
 
     def new_function_type(self, args, result, has_varargs):
-        return '<func (%s), %s, %s>' % (', '.join(args), result, has_varargs)
+        args = [arg.cdecl for arg in args]
+        result = result.cdecl
+        return FakeType(
+            '<func (%s), %s, %s>' % (', '.join(args), result, has_varargs))
 
     def new_primitive_type(self, name):
         assert name == name.lower()
-        return '<%s>' % name
+        return FakeType('<%s>' % name)
 
     def new_pointer_type(self, itemtype):
-        return '<pointer to %s>' % (itemtype,)
+        return FakeType('<pointer to %s>' % (itemtype,))
 
     def new_struct_type(self, name):
         return FakeStruct(name)
         s.fields = fields
     
     def new_array_type(self, ptrtype, length):
-        return '<array %s x %s>' % (ptrtype, length)
+        return FakeType('<array %s x %s>' % (ptrtype, length))
 
     def new_void_type(self):
-        return "<void>"
+        return FakeType("<void>")
     def cast(self, x, y):
         return 'casted!'
 
+class FakeType(object):
+    def __init__(self, cdecl):
+        self.cdecl = cdecl
+    def __str__(self):
+        return self.cdecl
+
 class FakeStruct(object):
     def __init__(self, name):
         self.name = name
     def __str__(self):
-        return ', '.join([y + x for x, y, z in self.fields])
+        return ', '.join([str(y) + str(x) for x, y, z in self.fields])
 
 class FakeLibrary(object):
     
 class FakeFunction(object):
 
     def __init__(self, BType, name):
-        self.BType = BType
+        self.BType = str(BType)
         self.name = name
 
 
         UInt foo(void);
         """)
     C = ffi.dlopen(None)
-    assert ffi.typeof("UIntReally") == '<unsigned int>'
+    assert str(ffi.typeof("UIntReally")) == '<unsigned int>'
     assert C.foo.BType == '<func (), <unsigned int>, False>'
 
 def test_typedef_more_complex():
         """)
     C = ffi.dlopen(None)
     assert str(ffi.typeof("foo_t")) == '<int>a, <int>b'
-    assert ffi.typeof("foo_p") == '<pointer to <int>a, <int>b>'
+    assert str(ffi.typeof("foo_p")) == '<pointer to <int>a, <int>b>'
     assert C.foo.BType == ('<func (<pointer to <pointer to '
                            '<int>a, <int>b>>), <int>, False>')
 
         """)
     type = ffi._parser.parse_type("array_t", force_pointer=True)
     BType = ffi._get_cached_btype(type)
-    assert BType == '<array <pointer to <int>> x 5>'
+    assert str(BType) == '<array <pointer to <int>> x 5>'
 
 def test_typedef_array_convert_array_to_pointer():
     ffi = FFI(backend=FakeBackend())
         """)
     type = ffi._parser.parse_type("fn_t")
     BType = ffi._get_cached_btype(type)
-    assert BType == '<func (<pointer to <int>>), <int>, False>'
+    assert str(BType) == '<func (<pointer to <int>>), <int>, False>'
 
 def test_remove_comments():
     ffi = FFI(backend=FakeBackend())