From 90f1961901d46f8713b00960c285e6de6db255fc Mon Sep 17 00:00:00 2001 From: Dag Sverre Seljebotn Date: Fri, 18 Jul 2008 21:23:10 +0200 Subject: [PATCH] Buffers released at function exit --- Cython/Compiler/Buffer.py | 32 +++++++++-- Cython/Compiler/ExprNodes.py | 13 ++++- Cython/Compiler/Nodes.py | 11 ++++ tests/run/bufaccess.pyx | 107 ++++++++++++++++++++++++++++++----- 4 files changed, 141 insertions(+), 22 deletions(-) diff --git a/Cython/Compiler/Buffer.py b/Cython/Compiler/Buffer.py index dc1d5de2..ea8435dd 100644 --- a/Cython/Compiler/Buffer.py +++ b/Cython/Compiler/Buffer.py @@ -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 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: diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 5b039690..d9aead36 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -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: diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 00769e13..2827059e 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -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) diff --git a/tests/run/bufaccess.pyx b/tests/run/bufaccess.pyx index 0468b228..40cb810f 100644 --- a/tests/run/bufaccess.pyx +++ b/tests/run/bufaccess.pyx @@ -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): + (buf)[0] = value + return buf + sizeof(float) +cdef char* write_int(char* buf, object value): + (buf)[0] = 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), &write_float), + 'i': (sizeof(int), &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 = 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: - ((self.buffer + idx))[0] = 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) -- 2.26.2