Buffers released at function exit
authorDag Sverre Seljebotn <dagss@student.matnat.uio.no>
Fri, 18 Jul 2008 19:23:10 +0000 (21:23 +0200)
committerDag Sverre Seljebotn <dagss@student.matnat.uio.no>
Fri, 18 Jul 2008 19:23:10 +0000 (21:23 +0200)
Cython/Compiler/Buffer.py
Cython/Compiler/ExprNodes.py
Cython/Compiler/Nodes.py
tests/run/bufaccess.pyx

index dc1d5de21f6064bfd107478adb6ef6dd5a4212b2..ea8435dd912f5f9262c003e29e104bed01076ede 100644 (file)
@@ -97,12 +97,12 @@ class BufferTransform(CythonTransform):
         # For all buffers, insert extra variables in the scope.
         # The variables are also accessible from the buffer_info
         # on the buffer entry
-        bufvars = [(name, entry) for name, entry
+        bufvars = [entry for name, entry
                    in scope.entries.iteritems()
                    if entry.type.is_buffer]
                    
-        for name, entry in bufvars:
-            
+        for entry in bufvars:
+            name = entry.name
             buftype = entry.type
 
             # Get or make a type string checker
@@ -130,6 +130,7 @@ class BufferTransform(CythonTransform):
             entry.buffer_aux = Symtab.BufferAux(bufinfo, stridevars, 
                                                 shapevars, tschecker)
             entry.buffer_aux.temp_var = temp_var
+        scope.buffer_entries = bufvars
         self.scope = scope
 
     # Notes: The cast to <char*> gets around Cython not supporting const types
@@ -223,10 +224,31 @@ class BufferTransform(CythonTransform):
 
         return result
 
-
+    buffer_cleanup_fragment = TreeFragment(u"""
+        if BUF is not None:
+            __cython__.PyObject_ReleaseBuffer(<__cython__.PyObject*>BUF, &BUFINFO)
+    """)
+    def funcdef_buffer_cleanup(self, node):
+        pos = node.pos
+        env = node.local_scope
+        cleanups = [self.buffer_cleanup_fragment.substitute({
+                u"BUF" : NameNode(pos, name=entry.name),
+                u"BUFINFO": NameNode(pos, name=entry.buffer_aux.buffer_info_var.name)
+                })
+            for entry in node.local_scope.buffer_entries]
+        cleanup_stats = []
+        for c in cleanups: cleanup_stats += c.stats
+        cleanup = StatListNode(pos, stats=cleanup_stats)
+        cleanup.analyse_expressions(env) 
+
+        result = TryFinallyStatNode.create_analysed(pos, env, body=node.body, finally_clause=cleanup)
+        node.body = result
+        return node
+        
     #
     # Transforms
     #
+    
     def visit_ModuleNode(self, node):
         self.handle_scope(node, node.scope)
         self.visitchildren(node)
@@ -235,7 +257,7 @@ class BufferTransform(CythonTransform):
     def visit_FuncDefNode(self, node):
         self.handle_scope(node, node.local_scope)
         self.visitchildren(node)
-        return node
+        return self.funcdef_buffer_cleanup(node)
 
     def visit_SingleAssignmentNode(self, node):
         # On assignments, two buffer-related things can happen:
index 5b039690b0275c2e07ba342a6d17aca15c8a7d6b..d9aead36263455999fac36fc8190ba683e0d1fb7 100644 (file)
@@ -809,6 +809,13 @@ class NameNode(AtomicExprNode):
     #  interned_cname  string
     
     is_name = 1
+
+    def create_analysed_rvalue(pos, env, entry):
+        node = NameNode(pos)
+        node.analyse_types(env, entry=entry)
+        return node
+    
+    create_analysed_rvalue = staticmethod(create_analysed_rvalue)
     
     def compile_time_value(self, denv):
         try:
@@ -862,8 +869,10 @@ class NameNode(AtomicExprNode):
         if self.entry.is_pyglobal and self.entry.is_member:
             env.use_utility_code(type_cache_invalidation_code)
     
-    def analyse_types(self, env):
-        self.entry = env.lookup(self.name)
+    def analyse_types(self, env, entry=None):
+        if entry is None:
+            entry = env.lookup(self.name)
+        self.entry = entry
         if not self.entry:
             self.entry = env.declare_builtin(self.name, self.pos)
         if not self.entry:
index 00769e135e9de54dd0ebafd5008a803a35eed917..2827059eb5ce9fe5d1ff42ec21e3bb0f74f6d0fc 100644 (file)
@@ -251,6 +251,11 @@ class StatListNode(Node):
     # stats     a list of StatNode
     
     child_attrs = ["stats"]
+
+    def create_analysed(pos, env, *args, **kw):
+        node = StatListNode(pos, *args, **kw)
+        return node # No node-specific analysis necesarry
+    create_analysed = staticmethod(create_analysed)
     
     def analyse_control_flow(self, env):
         for stat in self.stats:
@@ -3522,6 +3527,12 @@ class TryFinallyStatNode(StatNode):
     # There doesn't seem to be any point in disallowing
     # continue in the try block, since we have no problem
     # handling it.
+
+    def create_analysed(pos, env, body, finally_clause):
+        node = TryFinallyStatNode(pos, body=body, finally_clause=finally_clause)
+        node.cleanup_list = []
+        return node
+    create_analysed = staticmethod(create_analysed)
     
     def analyse_control_flow(self, env):
         env.start_branching(self.pos)
index 0468b228e78f468041f2c685ea68f3574f757832..40cb810fd03745ac1910f299b7033373d0374ab3 100644 (file)
@@ -1,16 +1,54 @@
 cimport __cython__
 
 __doc__ = u"""
-    >>> fb = MockBuffer("=f", "f", [1.0, 1.25, 0.75, 1.0], (2,2))
-    >>> printbuf_float(fb, (2,2))
-    1.0 1.25
-    0.75 1.0
+    >>> A = MockBuffer("i", range(10), label="A")
+    >>> B = MockBuffer("i", range(10), label="B")
+    >>> E = ErrorBuffer("E")
+    >>> acquire_release(A, B)
+    acquired A
+    released A
+    acquired B
+    released B
+    >>> acquire_raise(A)
+    acquired A
+    released A
+    Traceback (most recent call last):
+        ...
+    Exception: on purpose
+    >>> printbuf_float(MockBuffer("f", [1.0, 1.25, 0.75, 1.0]), (4,))
+    acquired
+    1.0 1.25 0.75 1.0
+    released
+    >>> printbuf_int_2d(MockBuffer("i", range(6), (2,3)), (2,3))
+    acquired
+    0 1 2
+    3 4 5
+    released
 """
 
+def acquire_release(o1, o2):
+    cdef object[int] buf
+    buf = o1
+    buf = o2
 
+def acquire_raise(o):
+    cdef object[int] buf
+    buf = o
+    raise Exception("on purpose")
+    
 def printbuf_float(o, shape):
     # should make shape builtin
-    cdef object[float, 2] buf
+    cdef object[float] buf
+    buf = o
+    cdef int i, j
+    for i in range(shape[0]):
+        print buf[i],
+    print
+
+def printbuf_int_2d(o, shape):
+    # should make shape builtin
+    cdef object[int, 2] buf
     buf = o
     cdef int i, j
     for i in range(shape[0]):
@@ -19,10 +57,21 @@ def printbuf_float(o, shape):
         print
  
 
+ctypedef char* (*write_func_ptr)(char*, object)
+cdef char* write_float(char* buf, object value):
+    (<float*>buf)[0] = <float>value
+    return buf + sizeof(float)
+cdef char* write_int(char* buf, object value):
+    (<int*>buf)[0] = <int>value
+    return buf + sizeof(int)
 
-sizes = {
-    'f': sizeof(float)
-} 
+# long can hold  a pointer on all target platforms,
+# though really we should have a seperate typedef for this..
+# TODO: Should create subclasses of MockBuffer instead.
+typemap = {
+    'f': (sizeof(float), <unsigned long>&write_float),
+    'i': (sizeof(int), <unsigned long>&write_int)
+}
  
 cimport stdlib
 
@@ -32,14 +81,21 @@ cdef class MockBuffer:
     cdef int len, itemsize, ndim
     cdef Py_ssize_t* strides
     cdef Py_ssize_t* shape
+    cdef write_func_ptr wfunc
+    cdef object label
     
-    def __init__(self, format, typechar, data, shape=None, strides=None):
-        self.itemsize = sizes[typechar]
+    def __init__(self, typechar, data, shape=None, strides=None, format=None, label=None):
+        self.label = label
+        if format is None: format = "=%s" % typechar
+        self.itemsize, x = typemap[typechar]
+        self.wfunc = <write_func_ptr><unsigned long>x
         if shape is None: shape = (len(data),)
         if strides is None:
             strides = []
             cumprod = 1
-            for s in shape:
+            rshape = list(shape)
+            rshape.reverse()
+            for s in rshape:
                 strides.append(cumprod)
                 cumprod *= s
             strides.reverse()
@@ -68,11 +124,32 @@ cdef class MockBuffer:
         buffer.suboffsets = NULL
         buffer.itemsize = self.itemsize
         buffer.internal = NULL
+        print "acquired",
+        if self.label:
+            print self.label
+        else:
+            print
+
+    def __releasebuffer__(MockBuffer self, Py_buffer* buffer):
+        print "released",
+        if self.label:
+            print self.label
+        else:
+            print
         
     cdef fill_buffer(self, typechar, object data):
-        cdef int idx = 0
+        cdef char* it = self.buffer
         for value in data:
-            (<float*>(self.buffer + idx))[0] = <float>value
-            idx += sizeof(float)
-            
+            it = self.wfunc(it, value)
             
+cdef class ErrorBuffer:
+    cdef object label
+    
+    def __init__(self, label):
+        self.label = label
+
+    def __getbuffer__(MockBuffer self, Py_buffer* buffer, int flags):
+        raise Exception("acquiring %s" % self.label)
+
+    def __releasebuffer__(MockBuffer self, Py_buffer* buffer):
+        raise Exception("releasing %s" % self.label)