Commit db608ca8 authored by Dag Sverre Seljebotn's avatar Dag Sverre Seljebotn

Buffers: Added C and Fortran contiguous modes

parent e31b14ad
......@@ -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
......
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
......
......@@ -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:
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment