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)
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'
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
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"
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):
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
+cimport python_buffer as pybuf
+
cdef extern from "Python.h":
ctypedef int Py_intptr_t
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"}
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)
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
"""
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
('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)
)
strides.reverse()
strides = [x * self.itemsize for x in strides]
suboffsets = [-1] * len(shape)
-
datashape = [len(data)]
p = data
while True:
>>> 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)
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