From abf0d29c2bc1ea47200cb6e06024e348baae85a2 Mon Sep 17 00:00:00 2001 From: Dag Sverre Seljebotn Date: Sat, 26 Jul 2008 14:24:07 +0200 Subject: [PATCH] Passes proper buffer flags (including auto-detected readonly) --- Cython/Compiler/Buffer.py | 37 +++++++------ Cython/Compiler/ExprNodes.py | 5 ++ Cython/Compiler/ParseTreeTransforms.py | 2 +- Cython/Compiler/PyrexTypes.py | 2 +- Cython/Compiler/Symtab.py | 2 + tests/run/bufaccess.pyx | 77 ++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 18 deletions(-) diff --git a/Cython/Compiler/Buffer.py b/Cython/Compiler/Buffer.py index c087240b..e49182f0 100755 --- a/Cython/Compiler/Buffer.py +++ b/Cython/Compiler/Buffer.py @@ -8,6 +8,11 @@ from Cython.Compiler.Errors import CompileError import PyrexTypes from sets import Set as set +def get_flags(buffer_aux, buffer_type): + flags = 'PyBUF_FORMAT | PyBUF_INDIRECT' + if buffer_aux.writable_needed: flags += "| PyBUF_WRITABLE" + return flags + def used_buffer_aux_vars(entry): buffer_aux = entry.buffer_aux buffer_aux.buffer_info_var.used = True @@ -33,19 +38,26 @@ def put_zero_buffer_aux_into_scope(buffer_aux, code): code.putln(" ".join(["%s = 0;" % s.cname for s in buffer_aux.shapevars])) +def getbuffer_cond_code(obj_cname, buffer_aux, flags, ndim): + bufstruct = buffer_aux.buffer_info_var.cname + checker = buffer_aux.tschecker + return "PyObject_GetBuffer(%s, &%s, %s) == -1 || %s(&%s, %d) == -1" % ( + obj_cname, bufstruct, flags, checker, bufstruct, ndim) + def put_acquire_arg_buffer(entry, code, pos): buffer_aux = entry.buffer_aux cname = entry.cname bufstruct = buffer_aux.buffer_info_var.cname - flags = '0' + flags = get_flags(buffer_aux, entry.type) # Acquire any new buffer code.put('if (%s != Py_None) ' % cname) code.begin_block() code.putln('%s.buf = 0;' % bufstruct) # PEP requirement - code.put(code.error_goto_if( - 'PyObject_GetBuffer(%s, &%s, %s) == -1 || %s(&%s, %d) == -1' % ( - cname, bufstruct, flags, buffer_aux.tschecker, bufstruct, entry.type.ndim), - pos)) + code.put(code.error_goto_if(getbuffer_cond_code(cname, + buffer_aux, + flags, + entry.type.ndim), + pos)) # An exception raised in arg parsing cannot be catched, so no # need to do care about the buffer then. put_unpack_buffer_aux_into_scope(buffer_aux, code) @@ -58,7 +70,7 @@ def put_release_buffer(entry, code): def put_assign_to_buffer(lhs_cname, rhs_cname, buffer_aux, buffer_type, is_initialized, pos, code): bufstruct = buffer_aux.buffer_info_var.cname - flags = '0' + flags = get_flags(buffer_aux, buffer_type) if is_initialized: # Release any existing buffer @@ -71,14 +83,7 @@ def put_assign_to_buffer(lhs_cname, rhs_cname, buffer_aux, buffer_type, code.put('if (%s != Py_None) ' % rhs_cname) code.begin_block() code.putln('%s.buf = 0;' % bufstruct) # PEP requirement - code.put('if (%s) ' % code.unlikely( - 'PyObject_GetBuffer(%s, &%s, %s) == -1' % ( - rhs_cname, - bufstruct, - flags) - + ' || %s(&%s, %d) == -1' % ( - buffer_aux.tschecker, bufstruct, buffer_type.ndim - ))) + code.put('if (%s) ' % code.unlikely(getbuffer_cond_code(rhs_cname, buffer_aux, flags, buffer_type.ndim))) code.begin_block() # If acquisition failed, attempt to reacquire the old buffer # before raising the exception. A failure of reacquisition @@ -86,8 +91,8 @@ def put_assign_to_buffer(lhs_cname, rhs_cname, buffer_aux, buffer_type, # can consider working around this later. if is_initialized: put_zero_buffer_aux_into_scope(buffer_aux, code) - code.put('if (%s != Py_None && PyObject_GetBuffer(%s, &%s, %s) == -1) ' % ( - lhs_cname, lhs_cname, bufstruct, flags)) + code.put('if (%s != Py_None && (%s)) ' % (rhs_cname, + getbuffer_cond_code(rhs_cname, buffer_aux, flags, buffer_type.ndim))) code.begin_block() put_zero_buffer_aux_into_scope(buffer_aux, code) code.end_block() diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 47c13d98..bab722f6 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -1371,6 +1371,11 @@ class IndexNode(ExprNode): # we only need a temp because result_code isn't refactored to # generation time, but this seems an ok shortcut to take self.is_temp = True + if setting: + if not self.base.entry.type.writable: + error(self.pos, "Writing to readonly buffer") + else: + self.base.entry.buffer_aux.writable_needed = True else: if isinstance(self.index, TupleNode): self.index.analyse_types(env, skip_children=skip_child_analysis) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 5d6a03f9..5050ded0 100755 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -182,7 +182,7 @@ class PostParse(CythonTransform): node.ndim = int(ndimnode.value) else: node.ndim = 1 - + # We're done with the parse tree args node.positional_args = None node.keyword_args = None diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index e9331e47..37d28fa9 100755 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -198,7 +198,7 @@ class BufferType(BaseType): # ndim int is_buffer = 1 - + writable = True def __init__(self, base, dtype, ndim): self.base = base self.dtype = dtype diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index e0c6f8ad..17c4e6d0 100755 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -20,6 +20,8 @@ possible_identifier = re.compile(ur"(?![0-9])\w+$", re.U).match nice_identifier = re.compile('^[a-zA-Z0-0_]+$').match class BufferAux: + writable_needed = False + def __init__(self, buffer_info_var, stridevars, shapevars, tschecker): self.buffer_info_var = buffer_info_var self.stridevars = stridevars diff --git a/tests/run/bufaccess.pyx b/tests/run/bufaccess.pyx index b596dfa0..424695d0 100755 --- a/tests/run/bufaccess.pyx +++ b/tests/run/bufaccess.pyx @@ -1,7 +1,18 @@ cimport __cython__ +# Tests the buffer access syntax functionality by constructing +# mock buffer objects. +# +# Note that the buffers are mock objects created for testing +# the buffer access behaviour -- for instance there is no flag +# checking in the buffer objects (why test our test case?), rather +# what we want to test is what is passed into the flags argument. +# + + cimport stdlib +cimport python_buffer # Add all test_X function docstrings as unit tests __test__ = {} @@ -251,6 +262,50 @@ def ndim1(object[int, 2] buf): ValueError: Buffer has wrong number of dimensions (expected 2, got 1) """ +# +# Test which flags are passed. +# +@testcase +def readonly(obj): + """ + >>> R = UnsignedShortMockBuffer("R", range(27), shape=(3, 3, 3)) + >>> readonly(R) + acquired R + 25 + released R + >>> R.recieved_flags + ['FORMAT', 'INDIRECT', 'ND', 'STRIDES'] + """ + cdef object[unsigned short int, 3] buf = obj + print buf[2, 2, 1] + +@testcase +def writable(obj): + """ + >>> R = UnsignedShortMockBuffer("R", range(27), shape=(3, 3, 3)) + >>> writable(R) + acquired R + released R + >>> R.recieved_flags + ['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE'] + """ + cdef object[unsigned short int, 3] buf = obj + buf[2, 2, 1] = 23 + + +# +# Coercions +# +@testcase +def coercions(object[unsigned char] uc): + """ +TODO + """ + print type(uc[0]) + uc[0] = -1 + print uc[0] + uc[0] = 3.14 + print uc[0] @testcase def printbuf_float(o, shape): @@ -270,6 +325,14 @@ def printbuf_float(o, shape): print +available_flags = ( + ('FORMAT', python_buffer.PyBUF_FORMAT), + ('INDIRECT', python_buffer.PyBUF_INDIRECT), + ('ND', python_buffer.PyBUF_ND), + ('STRIDES', python_buffer.PyBUF_STRIDES), + ('WRITABLE', python_buffer.PyBUF_WRITABLE) +) + cdef class MockBuffer: cdef object format cdef char* buffer @@ -277,6 +340,7 @@ cdef class MockBuffer: cdef Py_ssize_t* strides cdef Py_ssize_t* shape cdef object label, log + cdef readonly object recieved_flags def __init__(self, label, data, shape=None, strides=None, format=None): self.label = label @@ -313,6 +377,12 @@ cdef class MockBuffer: if buffer is NULL: print u"locking!" return + + self.recieved_flags = [] + for name, value in available_flags: + if (value & flags) == value: + self.recieved_flags.append(name) + buffer.buf = self.buffer buffer.len = self.len buffer.readonly = 0 @@ -363,6 +433,13 @@ cdef class IntMockBuffer(MockBuffer): return 0 cdef get_itemsize(self): return sizeof(int) cdef get_default_format(self): return "=i" + +cdef class UnsignedShortMockBuffer(MockBuffer): + cdef int write(self, char* buf, object value) except -1: + (buf)[0] = value + return 0 + cdef get_itemsize(self): return sizeof(unsigned short) + cdef get_default_format(self): return "=H" cdef class ErrorBuffer: cdef object label -- 2.26.2