Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r78468:5fcc7ff9de54
Date: 2015-07-06 18:34 +0200
http://bitbucket.org/pypy/pypy/changeset/5fcc7ff9de54/

Log:    hg merge cffi-new-allocator

        Support ffi.new_allocator()

diff --git a/pypy/module/_cffi_backend/__init__.py 
b/pypy/module/_cffi_backend/__init__.py
--- a/pypy/module/_cffi_backend/__init__.py
+++ b/pypy/module/_cffi_backend/__init__.py
@@ -37,7 +37,6 @@
         'from_handle': 'handle.from_handle',
         '_get_types': 'func._get_types',
         'from_buffer': 'func.from_buffer',
-        'gcp': 'func.gcp',
 
         'string': 'func.string',
         'buffer': 'cbuffer.buffer',
diff --git a/pypy/module/_cffi_backend/allocator.py 
b/pypy/module/_cffi_backend/allocator.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_cffi_backend/allocator.py
@@ -0,0 +1,86 @@
+from pypy.interpreter.error import oefmt
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
+
+from rpython.rtyper.lltypesystem import lltype, rffi
+
+
+class W_Allocator(W_Root):
+    _immutable_ = True
+
+    def __init__(self, ffi, w_alloc, w_free, should_clear_after_alloc):
+        self.ffi = ffi    # may be None
+        self.w_alloc = w_alloc
+        self.w_free = w_free
+        self.should_clear_after_alloc = should_clear_after_alloc
+
+    def allocate(self, space, datasize, ctype, length=-1):
+        from pypy.module._cffi_backend import cdataobj, ctypeptr
+        if self.w_alloc is None:
+            if self.should_clear_after_alloc:
+                ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
+                                    flavor='raw', zero=True)
+            else:
+                ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
+                                    flavor='raw', zero=False)
+            return cdataobj.W_CDataNewStd(space, ptr, ctype, length)
+        else:
+            w_raw_cdata = space.call_function(self.w_alloc,
+                                              space.wrap(datasize))
+            if not isinstance(w_raw_cdata, cdataobj.W_CData):
+                raise oefmt(space.w_TypeError,
+                            "alloc() must return a cdata object (got %T)",
+                            w_raw_cdata)
+            if not isinstance(w_raw_cdata.ctype, ctypeptr.W_CTypePtrOrArray):
+                raise oefmt(space.w_TypeError,
+                            "alloc() must return a cdata pointer, not '%s'",
+                            w_raw_cdata.ctype.name)
+            #
+            ptr = w_raw_cdata.unsafe_escaping_ptr()
+            if not ptr:
+                raise oefmt(space.w_MemoryError, "alloc() returned NULL")
+            #
+            if self.should_clear_after_alloc:
+                rffi.c_memset(rffi.cast(rffi.VOIDP, ptr), 0,
+                              rffi.cast(rffi.SIZE_T, datasize))
+            #
+            if self.w_free is None:
+                # use this class which does not have a __del__, but still
+                # keeps alive w_raw_cdata
+                res = cdataobj.W_CDataNewNonStdNoFree(space, ptr, ctype, 
length)
+            else:
+                res = cdataobj.W_CDataNewNonStdFree(space, ptr, ctype, length)
+                res.w_free = self.w_free
+            res.w_raw_cdata = w_raw_cdata
+            return res
+
+    @unwrap_spec(w_init=WrappedDefault(None))
+    def descr_call(self, space, w_arg, w_init):
+        ffi = self.ffi
+        assert ffi is not None
+        w_ctype = ffi.ffi_type(w_arg, ffi.ACCEPT_STRING | ffi.ACCEPT_CTYPE)
+        return w_ctype.newp(w_init, self)
+
+
+W_Allocator.typedef = TypeDef(
+        'FFIAllocator',
+        __call__ = interp2app(W_Allocator.descr_call),
+        )
+W_Allocator.typedef.acceptable_as_base_class = False
+
+
+def new_allocator(ffi, w_alloc, w_free, should_clear_after_alloc):
+    space = ffi.space
+    if space.is_none(w_alloc):
+        w_alloc = None
+    if space.is_none(w_free):
+        w_free = None
+    if w_alloc is None and w_free is not None:
+        raise oefmt(space.w_TypeError, "cannot pass 'free' without 'alloc'")
+    alloc = W_Allocator(ffi, w_alloc, w_free, bool(should_clear_after_alloc))
+    return space.wrap(alloc)
+
+
+default_allocator = W_Allocator(None, None, None, 
should_clear_after_alloc=True)
+nonzero_allocator = W_Allocator(None, None, 
None,should_clear_after_alloc=False)
diff --git a/pypy/module/_cffi_backend/cdataobj.py 
b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -369,14 +369,13 @@
 
 
 class W_CDataMem(W_CData):
-    """This is the base class used for cdata objects that own and free
-    their memory.  Used directly by the results of cffi.cast('int', x)
-    or other primitive explicitly-casted types.  It is further subclassed
-    by W_CDataNewOwning."""
+    """This is used only by the results of cffi.cast('int', x)
+    or other primitive explicitly-casted types."""
     _attrs_ = []
 
-    def __init__(self, space, size, ctype):
-        cdata = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw', zero=True)
+    def __init__(self, space, ctype):
+        cdata = lltype.malloc(rffi.CCHARP.TO, ctype.size, flavor='raw',
+                              zero=False)
         W_CData.__init__(self, space, cdata, ctype)
 
     @rgc.must_be_light_finalizer
@@ -384,34 +383,63 @@
         lltype.free(self._ptr, flavor='raw')
 
 
-class W_CDataNewOwning(W_CDataMem):
-    """This is the class used for the cata objects created by newp()."""
-    _attrs_ = []
+class W_CDataNewOwning(W_CData):
+    """This is the abstract base class used for cdata objects created
+    by newp().  They create and free their own memory according to an
+    allocator."""
+
+    # the 'length' is either >= 0 for arrays, or -1 for pointers.
+    _attrs_ = ['length']
+    _immutable_fields_ = ['length']
+
+    def __init__(self, space, cdata, ctype, length=-1):
+        W_CData.__init__(self, space, cdata, ctype)
+        self.length = length
 
     def _repr_extra(self):
         return self._repr_extra_owning()
 
-
-class W_CDataNewOwningLength(W_CDataNewOwning):
-    """Subclass with an explicit length, for allocated instances of
-    the C type 'foo[]'."""
-    _attrs_ = ['length']
-    _immutable_fields_ = ['length']
-
-    def __init__(self, space, size, ctype, length):
-        W_CDataNewOwning.__init__(self, space, size, ctype)
-        self.length = length
-
     def _sizeof(self):
-        from pypy.module._cffi_backend import ctypearray
         ctype = self.ctype
-        assert isinstance(ctype, ctypearray.W_CTypeArray)
-        return self.length * ctype.ctitem.size
+        if self.length >= 0:
+            from pypy.module._cffi_backend import ctypearray
+            assert isinstance(ctype, ctypearray.W_CTypeArray)
+            return self.length * ctype.ctitem.size
+        else:
+            return ctype.size
 
     def get_array_length(self):
         return self.length
 
 
+class W_CDataNewStd(W_CDataNewOwning):
+    """Subclass using the standard allocator, lltype.malloc()/lltype.free()"""
+    _attrs_ = []
+
+    @rgc.must_be_light_finalizer
+    def __del__(self):
+        lltype.free(self._ptr, flavor='raw')
+
+
+class W_CDataNewNonStdNoFree(W_CDataNewOwning):
+    """Subclass using a non-standard allocator, no free()"""
+    _attrs_ = ['w_raw_cdata']
+
+class W_CDataNewNonStdFree(W_CDataNewNonStdNoFree):
+    """Subclass using a non-standard allocator, with a free()"""
+    _attrs_ = ['w_free']
+
+    def __del__(self):
+        self.clear_all_weakrefs()
+        self.enqueue_for_destruction(self.space,
+                                     W_CDataNewNonStdFree.call_destructor,
+                                     'destructor of ')
+
+    def call_destructor(self):
+        assert isinstance(self, W_CDataNewNonStdFree)
+        self.space.call_function(self.w_free, self.w_raw_cdata)
+
+
 class W_CDataPtrToStructOrUnion(W_CData):
     """This subclass is used for the pointer returned by new('struct foo *').
     It has a strong reference to a W_CDataNewOwning that really owns the
diff --git a/pypy/module/_cffi_backend/ctypearray.py 
b/pypy/module/_cffi_backend/ctypearray.py
--- a/pypy/module/_cffi_backend/ctypearray.py
+++ b/pypy/module/_cffi_backend/ctypearray.py
@@ -28,7 +28,7 @@
     def _alignof(self):
         return self.ctitem.alignof()
 
-    def newp(self, w_init):
+    def newp(self, w_init, allocator):
         space = self.space
         datasize = self.size
         #
@@ -40,12 +40,10 @@
             except OverflowError:
                 raise OperationError(space.w_OverflowError,
                     space.wrap("array size would overflow a ssize_t"))
-            #
-            cdata = cdataobj.W_CDataNewOwningLength(space, datasize,
-                                                    self, length)
+        else:
+            length = self.length
         #
-        else:
-            cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
+        cdata = allocator.allocate(space, datasize, self, length)
         #
         if not space.is_w(w_init, space.w_None):
             with cdata as ptr:
diff --git a/pypy/module/_cffi_backend/ctypeobj.py 
b/pypy/module/_cffi_backend/ctypeobj.py
--- a/pypy/module/_cffi_backend/ctypeobj.py
+++ b/pypy/module/_cffi_backend/ctypeobj.py
@@ -55,7 +55,7 @@
     def pack_list_of_items(self, cdata, w_ob):
         return False
 
-    def newp(self, w_init):
+    def newp(self, w_init, allocator):
         space = self.space
         raise oefmt(space.w_TypeError,
                     "expected a pointer or array ctype, got '%s'", self.name)
diff --git a/pypy/module/_cffi_backend/ctypeprim.py 
b/pypy/module/_cffi_backend/ctypeprim.py
--- a/pypy/module/_cffi_backend/ctypeprim.py
+++ b/pypy/module/_cffi_backend/ctypeprim.py
@@ -63,7 +63,7 @@
             value = self._cast_result(value)
         else:
             value = self._cast_generic(w_ob)
-        w_cdata = cdataobj.W_CDataMem(space, self.size, self)
+        w_cdata = cdataobj.W_CDataMem(space, self)
         self.write_raw_integer_data(w_cdata, value)
         return w_cdata
 
@@ -353,7 +353,7 @@
             value = self.cast_unicode(w_ob)
         else:
             value = space.float_w(w_ob)
-        w_cdata = cdataobj.W_CDataMem(space, self.size, self)
+        w_cdata = cdataobj.W_CDataMem(space, self)
         if not isinstance(self, W_CTypePrimitiveLongDouble):
             w_cdata.write_raw_float_data(value)
         else:
@@ -446,7 +446,7 @@
         return self.space.wrap(value)
 
     def convert_to_object(self, cdata):
-        w_cdata = cdataobj.W_CDataMem(self.space, self.size, self)
+        w_cdata = cdataobj.W_CDataMem(self.space, self)
         with w_cdata as ptr:
             self._copy_longdouble(cdata, ptr)
         return w_cdata
diff --git a/pypy/module/_cffi_backend/ctypeptr.py 
b/pypy/module/_cffi_backend/ctypeptr.py
--- a/pypy/module/_cffi_backend/ctypeptr.py
+++ b/pypy/module/_cffi_backend/ctypeptr.py
@@ -185,7 +185,7 @@
         self.is_void_ptr = isinstance(ctitem, ctypevoid.W_CTypeVoid)
         W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem)
 
-    def newp(self, w_init):
+    def newp(self, w_init, allocator):
         from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion
         space = self.space
         ctitem = self.ctitem
@@ -205,14 +205,14 @@
                     datasize = ctitem.convert_struct_from_object(
                         lltype.nullptr(rffi.CCHARP.TO), w_init, datasize)
             #
-            cdatastruct = cdataobj.W_CDataNewOwning(space, datasize, ctitem)
+            cdatastruct = allocator.allocate(space, datasize, ctitem)
             ptr = cdatastruct.unsafe_escaping_ptr()
             cdata = cdataobj.W_CDataPtrToStructOrUnion(space, ptr,
                                                        self, cdatastruct)
         else:
             if self.is_char_or_unichar_ptr_or_array():
                 datasize *= 2       # forcefully add a null character
-            cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
+            cdata = allocator.allocate(space, datasize, self)
         #
         if not space.is_w(w_init, space.w_None):
             with cdata as ptr:
diff --git a/pypy/module/_cffi_backend/ctypestruct.py 
b/pypy/module/_cffi_backend/ctypestruct.py
--- a/pypy/module/_cffi_backend/ctypestruct.py
+++ b/pypy/module/_cffi_backend/ctypestruct.py
@@ -81,10 +81,9 @@
     def copy_and_convert_to_object(self, source):
         space = self.space
         self.check_complete()
-        ob = cdataobj.W_CDataNewOwning(space, self.size, self)
-        with ob as target:
-            misc._raw_memcopy(source, target, self.size)
-        return ob
+        ptr = lltype.malloc(rffi.CCHARP.TO, self.size, flavor='raw', 
zero=False)
+        misc._raw_memcopy(source, ptr, self.size)
+        return cdataobj.W_CDataNewStd(space, ptr, self)
 
     def typeoffsetof_field(self, fieldname, following):
         self.force_lazy_struct()
diff --git a/pypy/module/_cffi_backend/ffi_obj.py 
b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -11,7 +11,7 @@
 from pypy.module._cffi_backend import newtype, cerrno, ccallback, ctypearray
 from pypy.module._cffi_backend import ctypestruct, ctypeptr, handle
 from pypy.module._cffi_backend import cbuffer, func, wrapper
-from pypy.module._cffi_backend import cffi_opcode
+from pypy.module._cffi_backend import cffi_opcode, allocator
 from pypy.module._cffi_backend.ctypeobj import W_CType
 from pypy.module._cffi_backend.cdataobj import W_CData
 
@@ -44,6 +44,10 @@
 
 
 class W_FFIObject(W_Root):
+    ACCEPT_STRING = ACCEPT_STRING
+    ACCEPT_CTYPE  = ACCEPT_CTYPE
+    ACCEPT_CDATA  = ACCEPT_CDATA
+
     w_gc_wref_remove = None
 
     @jit.dont_look_inside
@@ -411,7 +415,31 @@
 pointer to the memory somewhere else, e.g. into another structure."""
         #
         w_ctype = self.ffi_type(w_arg, ACCEPT_STRING | ACCEPT_CTYPE)
-        return w_ctype.newp(w_init)
+        return w_ctype.newp(w_init, allocator.default_allocator)
+
+
+    @unwrap_spec(w_alloc=WrappedDefault(None),
+                 w_free=WrappedDefault(None),
+                 should_clear_after_alloc=int)
+    def descr_new_allocator(self, w_alloc, w_free,
+                            should_clear_after_alloc=1):
+        """\
+Return a new allocator, i.e. a function that behaves like ffi.new()
+but uses the provided low-level 'alloc' and 'free' functions.
+
+'alloc' is called with the size as argument.  If it returns NULL, a
+MemoryError is raised.  'free' is called with the result of 'alloc'
+as argument.  Both can be either Python function or directly C
+functions.  If 'free' is None, then no free function is called.
+If both 'alloc' and 'free' are None, the default is used.
+
+If 'should_clear_after_alloc' is set to False, then the memory
+returned by 'alloc' is assumed to be already cleared (or you are
+fine with garbage); otherwise CFFI will clear it.
+        """
+        #
+        return allocator.new_allocator(self, w_alloc, w_free,
+                                       should_clear_after_alloc)
 
 
     def descr_new_handle(self, w_arg):
@@ -596,6 +624,7 @@
         getctype    = interp2app(W_FFIObject.descr_getctype),
         integer_const = interp2app(W_FFIObject.descr_integer_const),
         new         = interp2app(W_FFIObject.descr_new),
+        new_allocator = interp2app(W_FFIObject.descr_new_allocator),
         new_handle  = interp2app(W_FFIObject.descr_new_handle),
         offsetof    = interp2app(W_FFIObject.descr_offsetof),
         sizeof      = interp2app(W_FFIObject.descr_sizeof),
diff --git a/pypy/module/_cffi_backend/func.py 
b/pypy/module/_cffi_backend/func.py
--- a/pypy/module/_cffi_backend/func.py
+++ b/pypy/module/_cffi_backend/func.py
@@ -1,13 +1,13 @@
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.gateway import unwrap_spec, WrappedDefault
-from pypy.module._cffi_backend import ctypeobj, cdataobj
+from pypy.module._cffi_backend import ctypeobj, cdataobj, allocator
 
 
 # ____________________________________________________________
 
 @unwrap_spec(w_ctype=ctypeobj.W_CType, w_init=WrappedDefault(None))
 def newp(space, w_ctype, w_init):
-    return w_ctype.newp(w_init)
+    return w_ctype.newp(w_init, allocator.default_allocator)
 
 # ____________________________________________________________
 
diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py 
b/pypy/module/_cffi_backend/test/test_ffi_obj.py
--- a/pypy/module/_cffi_backend/test/test_ffi_obj.py
+++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py
@@ -265,10 +265,105 @@
             assert p1[0] == 123
             seen.append(1)
         ffi.gc(p, destructor=destructor)    # instantly forgotten
-        _cffi1_backend.gcp(p, destructor=destructor)
         for i in range(5):
             if seen:
                 break
             import gc
             gc.collect()
-        assert seen == [1, 1]
+        assert seen == [1]
+
+    def test_ffi_new_allocator_1(self):
+        import _cffi_backend as _cffi1_backend
+        ffi = _cffi1_backend.FFI()
+        alloc1 = ffi.new_allocator()
+        alloc2 = ffi.new_allocator(should_clear_after_alloc=False)
+        for retry in range(100):
+            p1 = alloc1("int[10]")
+            p2 = alloc2("int[10]")
+            combination = 0
+            for i in range(10):
+                assert p1[i] == 0
+                combination |= p2[i]
+                p1[i] = -42
+                p2[i] = -43
+            if combination != 0:
+                break
+            del p1, p2
+            import gc; gc.collect()
+        else:
+            raise AssertionError("cannot seem to get an int[10] not "
+                                 "completely cleared")
+
+    def test_ffi_new_allocator_2(self):
+        import _cffi_backend as _cffi1_backend
+        ffi = _cffi1_backend.FFI()
+        seen = []
+        def myalloc(size):
+            seen.append(size)
+            return ffi.new("char[]", "X" * size)
+        def myfree(raw):
+            seen.append(raw)
+        alloc1 = ffi.new_allocator(myalloc, myfree)
+        alloc2 = ffi.new_allocator(alloc=myalloc, free=myfree,
+                                   should_clear_after_alloc=False)
+        p1 = alloc1("int[10]")
+        p2 = alloc2("int[]", 10)
+        assert seen == [40, 40]
+        assert ffi.typeof(p1) == ffi.typeof("int[10]")
+        assert ffi.sizeof(p1) == 40
+        assert ffi.typeof(p2) == ffi.typeof("int[]")
+        assert ffi.sizeof(p2) == 40
+        assert p1[5] == 0
+        assert p2[6] == ord('X') * 0x01010101
+        raw1 = ffi.cast("char *", p1)
+        raw2 = ffi.cast("char *", p2)
+        del p1, p2
+        retries = 0
+        while len(seen) != 4:
+            retries += 1
+            assert retries <= 5
+            import gc; gc.collect()
+        assert seen == [40, 40, raw1, raw2]
+        assert repr(seen[2]) == "<cdata 'char[]' owning 41 bytes>"
+        assert repr(seen[3]) == "<cdata 'char[]' owning 41 bytes>"
+
+    def test_ffi_new_allocator_3(self):
+        import _cffi_backend as _cffi1_backend
+        ffi = _cffi1_backend.FFI()
+        seen = []
+        def myalloc(size):
+            seen.append(size)
+            return ffi.new("char[]", "X" * size)
+        alloc1 = ffi.new_allocator(myalloc)    # no 'free'
+        p1 = alloc1("int[10]")
+        assert seen == [40]
+        assert ffi.typeof(p1) == ffi.typeof("int[10]")
+        assert ffi.sizeof(p1) == 40
+        assert p1[5] == 0
+
+    def test_ffi_new_allocator_4(self):
+        import _cffi_backend as _cffi1_backend
+        ffi = _cffi1_backend.FFI()
+        raises(TypeError, ffi.new_allocator, free=lambda x: None)
+        #
+        def myalloc2(size):
+            raise LookupError
+        alloc2 = ffi.new_allocator(myalloc2)
+        raises(LookupError, alloc2, "int[5]")
+        #
+        def myalloc3(size):
+            return 42
+        alloc3 = ffi.new_allocator(myalloc3)
+        e = raises(TypeError, alloc3, "int[5]")
+        assert str(e.value) == "alloc() must return a cdata object (got int)"
+        #
+        def myalloc4(size):
+            return ffi.cast("int", 42)
+        alloc4 = ffi.new_allocator(myalloc4)
+        e = raises(TypeError, alloc4, "int[5]")
+        assert str(e.value) == "alloc() must return a cdata pointer, not 'int'"
+        #
+        def myalloc5(size):
+            return ffi.NULL
+        alloc5 = ffi.new_allocator(myalloc5)
+        raises(MemoryError, alloc5, "int[5]")
diff --git a/pypy/module/_cffi_backend/wrapper.py 
b/pypy/module/_cffi_backend/wrapper.py
--- a/pypy/module/_cffi_backend/wrapper.py
+++ b/pypy/module/_cffi_backend/wrapper.py
@@ -9,6 +9,7 @@
 from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray
 from pypy.module._cffi_backend.ctypefunc import W_CTypeFunc
 from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion
+from pypy.module._cffi_backend import allocator
 
 
 class W_FunctionWrapper(W_Root):
@@ -74,7 +75,8 @@
             # then internally allocate the struct and pass a pointer to it as
             # a first argument.
             if locs[0] == 'R':
-                w_result_cdata = ctype.fargs[0].newp(space.w_None)
+                w_result_cdata = ctype.fargs[0].newp(space.w_None,
+                                                    
allocator.nonzero_allocator)
                 args_w = [w_result_cdata] + args_w
                 prepare_args(space, rawfunctype, args_w, 1)
                 #
@@ -116,7 +118,7 @@
             # the equivalent of ffi.new()
             if space.is_w(w_arg, space.w_None):
                 continue
-            w_arg = farg.newp(w_arg)
+            w_arg = farg.newp(w_arg, allocator.default_allocator)
         args_w[i] = w_arg
 
 
diff --git a/rpython/rtyper/lltypesystem/rffi.py 
b/rpython/rtyper/lltypesystem/rffi.py
--- a/rpython/rtyper/lltypesystem/rffi.py
+++ b/rpython/rtyper/lltypesystem/rffi.py
@@ -1249,3 +1249,8 @@
             lltype.Void,
             releasegil=False
         )
+c_memset = llexternal("memset",
+            [VOIDP, lltype.Signed, SIZE_T],
+            lltype.Void,
+            releasegil=False
+        )
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to