From db608ca87288ded6513bd2e7768a57b133c4838b Mon Sep 17 00:00:00 2001 From: Dag Sverre Seljebotn Date: Wed, 17 Sep 2008 11:56:30 +0200 Subject: [PATCH] Buffers: Added C and Fortran contiguous modes --- Cython/Compiler/Buffer.py | 96 +++++++++++++++++++++++++++++---------- Cython/Includes/numpy.pxd | 25 ++++++++-- tests/run/bufaccess.pyx | 51 ++++++++++++++++++++- tests/run/numpy_test.pyx | 33 ++++++++++++++ 4 files changed, 174 insertions(+), 31 deletions(-) diff --git a/Cython/Compiler/Buffer.py b/Cython/Compiler/Buffer.py index 985351ed..4843bce1 100644 --- a/Cython/Compiler/Buffer.py +++ b/Cython/Compiler/Buffer.py @@ -92,7 +92,7 @@ class IntroduceBufferAuxiliaryVars(CythonTransform): mode = entry.type.mode if mode == 'full': suboffsetvars = [var(Naming.bufsuboffset_prefix, i, "-1") for i in range(entry.type.ndim)] - elif mode == 'strided': + else: suboffsetvars = None entry.buffer_aux = Symtab.BufferAux(bufinfo, stridevars, shapevars, suboffsetvars) @@ -121,7 +121,7 @@ ERR_BUF_OPTION_UNKNOWN = '"%s" is not a buffer option' ERR_BUF_TOO_MANY = 'Too many buffer options' ERR_BUF_DUP = '"%s" buffer option already supplied' ERR_BUF_MISSING = '"%s" missing' -ERR_BUF_MODE = 'Only allowed buffer modes are "full" or "strided" (as a compile-time string)' +ERR_BUF_MODE = 'Only allowed buffer modes are: "c", "fortran", "full", "strided" (as a compile-time string)' ERR_BUF_NDIM = 'ndim must be a non-negative integer' ERR_BUF_DTYPE = 'dtype must be "object", numeric type or a struct' @@ -175,7 +175,7 @@ def analyse_buffer_options(globalpos, env, posargs, dictargs, defaults=None, nee raise CompileError(globalpos, ERR_BUF_NDIM) mode = options.get("mode") - if mode and not (mode in ('full', 'strided')): + if mode and not (mode in ('full', 'strided', 'c', 'fortran')): raise CompileError(globalpos, ERR_BUF_MODE) return options @@ -188,10 +188,15 @@ def analyse_buffer_options(globalpos, env, posargs, dictargs, defaults=None, nee def get_flags(buffer_aux, buffer_type): flags = 'PyBUF_FORMAT' - if buffer_type.mode == 'full': + mode = buffer_type.mode + if mode == 'full': flags += '| PyBUF_INDIRECT' - elif buffer_type.mode == 'strided': + elif mode == 'strided': flags += '| PyBUF_STRIDES' + elif mode == 'c': + flags += '| PyBUF_C_CONTIGUOUS' + elif mode == 'fortran': + flags += '| PyBUF_F_CONTIGUOUS' else: assert False if buffer_aux.writable_needed: flags += "| PyBUF_WRITABLE" @@ -367,28 +372,43 @@ def put_buffer_lookup_code(entry, index_signeds, index_cnames, options, pos, cod code.putln("if (%s < 0) %s += %s;" % (cname, cname, shape.cname)) # Create buffer lookup and return it + # This is done via utility macros/inline functions, which vary + # according to the access mode used. params = [] nd = entry.type.ndim - if entry.type.mode == 'full': + mode = entry.type.mode + if mode == 'full': for i, s, o in zip(index_cnames, bufaux.stridevars, bufaux.suboffsetvars): params.append(i) params.append(s.cname) params.append(o.cname) - funcname = "__Pyx_BufPtrFull%dd" % nd funcgen = buf_lookup_full_code else: + if mode == 'strided': + funcname = "__Pyx_BufPtrStrided%dd" % nd + funcgen = buf_lookup_strided_code + elif mode == 'c': + funcname = "__Pyx_BufPtrCContig%dd" % nd + funcgen = buf_lookup_c_code + elif mode == 'fortran': + funcname = "__Pyx_BufPtrFortranContig%dd" % nd + funcgen = buf_lookup_fortran_code + else: + assert False for i, s in zip(index_cnames, bufaux.stridevars): params.append(i) params.append(s.cname) - funcname = "__Pyx_BufPtrStrided%dd" % nd - funcgen = buf_lookup_strided_code # Make sure the utility code is available code.globalstate.use_generated_code(funcgen, name=funcname, nd=nd) - ptrcode = "%s(%s.buf, %s)" % (funcname, bufstruct, ", ".join(params)) - return entry.type.buffer_ptr_type.cast_code(ptrcode) + ptr_type = entry.type.buffer_ptr_type + ptrcode = "%s(%s, %s.buf, %s)" % (funcname, + ptr_type.declaration_code(""), + bufstruct, + ", ".join(params)) + return ptrcode def use_empty_bufstruct_code(env, max_ndim): @@ -399,33 +419,59 @@ def use_empty_bufstruct_code(env, max_ndim): env.use_utility_code([code, ""], "empty_bufstruct_code") -def buf_lookup_strided_code(proto, defin, name, nd): - """ - Generates a buffer lookup function for the right number - of dimensions. The function gives back a void* at the right location. - """ - # _i_ndex, _s_tride - args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)]) - offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)]) - proto.putln("#define %s(buf, %s) ((char*)buf + %s)" % (name, args, offset)) - def buf_lookup_full_code(proto, defin, name, nd): """ Generates a buffer lookup function for the right number of dimensions. The function gives back a void* at the right location. """ # _i_ndex, _s_tride, sub_o_ffset - args = ", ".join(["Py_ssize_t i%d, Py_ssize_t s%d, Py_ssize_t o%d" % (i, i, i) for i in range(nd)]) - proto.putln("static INLINE void* %s(void* buf, %s);" % (name, args)) + macroargs = ", ".join(["i%d, s%d, o%d" % (i, i, i) for i in range(nd)]) + proto.putln("#define %s(type, buf, %s) (type)(%s_imp(buf, %s))" % (name, macroargs, name, macroargs)) + + funcargs = ", ".join(["Py_ssize_t i%d, Py_ssize_t s%d, Py_ssize_t o%d" % (i, i, i) for i in range(nd)]) + proto.putln("static INLINE void* %s_imp(void* buf, %s);" % (name, funcargs)) defin.putln(dedent(""" - static INLINE void* %s(void* buf, %s) { + static INLINE void* %s_imp(void* buf, %s) { char* ptr = (char*)buf; - """) % (name, args) + "".join([dedent("""\ + """) % (name, funcargs) + "".join([dedent("""\ ptr += s%d * i%d; if (o%d >= 0) ptr = *((char**)ptr) + o%d; """) % (i, i, i, i) for i in range(nd)] ) + "\nreturn ptr;\n}") +def buf_lookup_strided_code(proto, defin, name, nd): + """ + Generates a buffer lookup function for the right number + of dimensions. The function gives back a void* at the right location. + """ + # _i_ndex, _s_tride + args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)]) + offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)]) + proto.putln("#define %s(type, buf, %s) (type)((char*)buf + %s)" % (name, args, offset)) + +def buf_lookup_c_code(proto, defin, name, nd): + """ + Similar to strided lookup, but can assume that the last dimension + doesn't need a multiplication as long as. + Still we keep the same signature for now. + """ + if nd == 1: + proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name) + else: + args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)]) + offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd - 1)]) + proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, nd - 1)) + +def buf_lookup_fortran_code(proto, defin, name, nd): + """ + Like C lookup, but the first index is optimized instead. + """ + if nd == 1: + proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name) + else: + args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)]) + offset = " + ".join(["i%d * s%d" % (i, i) for i in range(1, nd)]) + proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, 0)) # # Utils for creating type string checkers diff --git a/Cython/Includes/numpy.pxd b/Cython/Includes/numpy.pxd index 5d987418..23989de8 100644 --- a/Cython/Includes/numpy.pxd +++ b/Cython/Includes/numpy.pxd @@ -1,3 +1,5 @@ +cimport python_buffer as pybuf + cdef extern from "Python.h": ctypedef int Py_intptr_t @@ -19,7 +21,11 @@ cdef extern from "numpy/arrayobject.h": NPY_NTYPES, NPY_NOTYPE, NPY_CHAR, - NPY_USERDEF + NPY_USERDEF, + + NPY_C_CONTIGUOUS, + NPY_F_CONTIGUOUS + ctypedef class numpy.ndarray [object PyArrayObject]: cdef __cythonbufferdefaults__ = {"mode": "strided"} @@ -29,19 +35,27 @@ cdef extern from "numpy/arrayobject.h": int ndim "nd" npy_intp *shape "dimensions" npy_intp *strides + int flags # Note: This syntax (function definition in pxd files) is an # experimental exception made for __getbuffer__ and __releasebuffer__ # -- the details of this may change. def __getbuffer__(ndarray self, Py_buffer* info, int flags): # This implementation of getbuffer is geared towards Cython - # requirements, and does not yet fullfill the PEP (specifically, - # Cython always requests and we always provide strided access, - # so the flags are not even checked). - + # requirements, and does not yet fullfill the PEP. + # In particular strided access is always provided regardless + # of flags if sizeof(npy_intp) != sizeof(Py_ssize_t): raise RuntimeError("Py_intptr_t and Py_ssize_t differs in size, numpy.pxd does not support this") + if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) + and not PyArray_CHKFLAGS(self, NPY_C_CONTIGUOUS)): + raise ValueError("ndarray is not C contiguous") + + if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + and not PyArray_CHKFLAGS(self, NPY_F_CONTIGUOUS)): + raise ValueError("ndarray is not Fortran contiguous") + info.buf = PyArray_DATA(self) # info.obj = None # this is automatic info.ndim = PyArray_NDIM(self) @@ -82,6 +96,7 @@ cdef extern from "numpy/arrayobject.h": cdef npy_intp PyArray_STRIDES(ndarray arr) cdef npy_intp PyArray_DIMS(ndarray arr) cdef Py_ssize_t PyArray_ITEMSIZE(ndarray arr) + cdef int PyArray_CHKFLAGS(ndarray arr, int flags) ctypedef signed int npy_byte ctypedef signed int npy_short diff --git a/tests/run/bufaccess.pyx b/tests/run/bufaccess.pyx index 3a571297..7498a010 100644 --- a/tests/run/bufaccess.pyx +++ b/tests/run/bufaccess.pyx @@ -553,6 +553,54 @@ def strided(object[int, ndim=1, mode='strided'] buf): """ return buf[2] +@testcase +def c_contig(object[int, ndim=1, mode='c'] buf): + """ + >>> A = IntMockBuffer(None, range(4)) + >>> c_contig(A) + 2 + >>> [str(x) for x in A.recieved_flags] + ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS'] + """ + return buf[2] + +@testcase +def c_contig_2d(object[int, ndim=2, mode='c'] buf): + """ + Multi-dim has seperate implementation + + >>> A = IntMockBuffer(None, range(12), shape=(3,4)) + >>> c_contig_2d(A) + 7 + >>> [str(x) for x in A.recieved_flags] + ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS'] + """ + return buf[1, 3] + +@testcase +def f_contig(object[int, ndim=1, mode='fortran'] buf): + """ + >>> A = IntMockBuffer(None, range(4)) + >>> f_contig(A) + 2 + >>> [str(x) for x in A.recieved_flags] + ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS'] + """ + return buf[2] + +@testcase +def f_contig_2d(object[int, ndim=2, mode='fortran'] buf): + """ + Must set up strides manually to ensure Fortran ordering. + + >>> A = IntMockBuffer(None, range(12), shape=(4,3), strides=(1, 4)) + >>> f_contig_2d(A) + 7 + >>> [str(x) for x in A.recieved_flags] + ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS'] + """ + return buf[3, 1] + # # Test compiler options for bounds checking. We create an array with a # safe "boundary" (memory @@ -877,6 +925,8 @@ available_flags = ( ('INDIRECT', python_buffer.PyBUF_INDIRECT), ('ND', python_buffer.PyBUF_ND), ('STRIDES', python_buffer.PyBUF_STRIDES), + ('C_CONTIGUOUS', python_buffer.PyBUF_C_CONTIGUOUS), + ('F_CONTIGUOUS', python_buffer.PyBUF_F_CONTIGUOUS), ('WRITABLE', python_buffer.PyBUF_WRITABLE) ) @@ -913,7 +963,6 @@ cdef class MockBuffer: strides.reverse() strides = [x * self.itemsize for x in strides] suboffsets = [-1] * len(shape) - datashape = [len(data)] p = data while True: diff --git a/tests/run/numpy_test.pyx b/tests/run/numpy_test.pyx index a07fb6e1..6b141358 100644 --- a/tests/run/numpy_test.pyx +++ b/tests/run/numpy_test.pyx @@ -78,6 +78,30 @@ try: >>> print a [[0 0 0 0 0] [0 0 0 0 0]] + + Test contiguous access modes: + >>> c_arr = np.array(np.arange(12, dtype='i').reshape(3,4), order='C') + >>> f_arr = np.array(np.arange(12, dtype='i').reshape(3,4), order='F') + >>> test_c_contig(c_arr) + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + >>> test_f_contig(f_arr) + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + >>> test_c_contig(f_arr) + Traceback (most recent call last): + ... + ValueError: ndarray is not C contiguous + >>> test_f_contig(c_arr) + Traceback (most recent call last): + ... + ValueError: ndarray is not Fortran contiguous + >>> test_c_contig(c_arr[::2,::2]) + Traceback (most recent call last): + ... + ValueError: ndarray is not C contiguous >>> test_dtype('b', inc1_byte) >>> test_dtype('B', inc1_ubyte) @@ -153,6 +177,15 @@ def put_range_long_1d(np.ndarray[long] arr): arr[i] = value value += 1 +def test_c_contig(np.ndarray[int, ndim=2, mode='c'] arr): + cdef int i, j + for i in range(arr.shape[0]): + print " ".join([str(arr[i, j]) for j in range(arr.shape[1])]) + +def test_f_contig(np.ndarray[int, ndim=2, mode='fortran'] arr): + cdef int i, j + for i in range(arr.shape[0]): + print " ".join([str(arr[i, j]) for j in range(arr.shape[1])]) # Exhaustive dtype tests -- increments element [1] by 1 for all dtypes def inc1_byte(np.ndarray[char] arr): arr[1] += 1 -- 2.26.2