Commit 522cb284 authored by Mark Florisson's avatar Mark Florisson

Allow contig to follow indirect contig

parent a0e188d4
...@@ -197,24 +197,22 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -197,24 +197,22 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
flag = get_memoryview_flag(access, packing) flag = get_memoryview_flag(access, packing)
if flag == "generic": if flag in ("generic", "generic_contiguous"):
# Note: we cannot do cast tricks to avoid stride multiplication
# for generic_contiguous, as we may have to do (dtype *)
# or (dtype **) arithmetic, we won't know which unless
# we check suboffsets
code.globalstate.use_utility_code(memviewslice_index_helpers) code.globalstate.use_utility_code(memviewslice_index_helpers)
bufp = ('__pyx_memviewslice_index_full(%s, %s, %s, %s)' % bufp = ('__pyx_memviewslice_index_full(%s, %s, %s, %s)' %
(bufp, index, stride, suboffset)) (bufp, index, stride, suboffset))
elif flag == "generic_contiguous":
# We can skip stride multiplication with the cast
code.globalstate.use_utility_code(memviewslice_index_helpers)
bufp = '((char *) ((%s *) %s) + %s)' % (type_decl, bufp, index)
bufp = ('__pyx_memviewslice_index_full_contig(%s, %s)' %
(bufp, suboffset))
elif flag == "indirect": elif flag == "indirect":
bufp = ("(*((char **) %s + %s * %s) + %s)" % bufp = "(%s + %s * %s)" % (bufp, index, stride)
(bufp, index, stride, suboffset)) bufp = ("(*((char **) %s) + %s)" % (bufp, suboffset))
elif flag == "indirect_contiguous": elif flag == "indirect_contiguous":
bufp = "(*((char **) %s) + %s)" % (bufp, suboffset) # Note: we do char ** arithmetic
bufp = "(*((char **) %s + %s) + %s)" % (bufp, index, suboffset)
elif flag == "strided": elif flag == "strided":
bufp = "(%s + %s * %s)" % (bufp, index, stride) bufp = "(%s + %s * %s)" % (bufp, index, stride)
...@@ -621,7 +619,7 @@ def get_axes_specs(env, axes): ...@@ -621,7 +619,7 @@ def get_axes_specs(env, axes):
else: else:
raise CompileError(axis.step.pos, INVALID_ERR) raise CompileError(axis.step.pos, INVALID_ERR)
validate_axes_specs(axes[0].start.pos, axes_specs) validate_axes_specs([axis.start.pos for axis in axes], axes_specs)
return axes_specs return axes_specs
...@@ -677,16 +675,16 @@ def get_access_packing(view_scope_constant): ...@@ -677,16 +675,16 @@ def get_access_packing(view_scope_constant):
if view_scope_constant.name == 'generic': if view_scope_constant.name == 'generic':
return 'full', return 'full',
def validate_axes_specs(pos, specs): def validate_axes_specs(positions, specs):
packing_specs = ('contig', 'strided', 'follow') packing_specs = ('contig', 'strided', 'follow')
access_specs = ('direct', 'ptr', 'full') access_specs = ('direct', 'ptr', 'full')
is_c_contig, is_f_contig = is_cf_contig(specs) is_c_contig, is_f_contig = is_cf_contig(specs)
has_contig = has_follow = has_strided = False has_contig = has_follow = has_strided = has_generic_contig = False
for access, packing in specs: for pos, (access, packing) in zip(positions, specs):
if not (access in access_specs and if not (access in access_specs and
packing in packing_specs): packing in packing_specs):
...@@ -696,8 +694,16 @@ def validate_axes_specs(pos, specs): ...@@ -696,8 +694,16 @@ def validate_axes_specs(pos, specs):
has_strided = True has_strided = True
elif packing == 'contig': elif packing == 'contig':
if has_contig: if has_contig:
raise CompileError(pos, "Only one contiguous axis may be specified.") if access == 'ptr':
has_contig = True raise CompileError(pos, "Indirect contiguous dimensions must precede direct contiguous")
elif has_generic_contig or access == 'full':
raise CompileError(pos, "Generic contiguous cannot be combined with direct contiguous")
else:
raise CompileError(pos, "Only one direct contiguous axis may be specified.")
# Note: We do NOT allow access == 'full' to act as
# "additionally potentially contiguous"
has_contig = access != 'ptr'
has_generic_contig = has_generic_contig or access == 'full'
elif packing == 'follow': elif packing == 'follow':
if has_strided: if has_strided:
raise CompileError(pos, "A memoryview cannot have both follow and strided axis specifiers.") raise CompileError(pos, "A memoryview cannot have both follow and strided axis specifiers.")
......
...@@ -42,6 +42,11 @@ static int __Pyx_init_memviewslice( ...@@ -42,6 +42,11 @@ static int __Pyx_init_memviewslice(
int ndim, int ndim,
__Pyx_memviewslice *memviewslice); __Pyx_memviewslice *memviewslice);
#if CYTHON_REFNANNY
/* disable inlining when running tests */
#define CYTHON_INLINE
#endif
#define __PYX_INC_MEMVIEW(slice, have_gil) __Pyx_INC_MEMVIEW(slice, have_gil, __LINE__) #define __PYX_INC_MEMVIEW(slice, have_gil) __Pyx_INC_MEMVIEW(slice, have_gil, __LINE__)
#define __PYX_XDEC_MEMVIEW(slice, have_gil) __Pyx_XDEC_MEMVIEW(slice, have_gil, __LINE__) #define __PYX_XDEC_MEMVIEW(slice, have_gil) __Pyx_XDEC_MEMVIEW(slice, have_gil, __LINE__)
static CYTHON_INLINE void __Pyx_INC_MEMVIEW({{memviewslice_name}} *, int, int); static CYTHON_INLINE void __Pyx_INC_MEMVIEW({{memviewslice_name}} *, int, int);
...@@ -141,7 +146,13 @@ static int __Pyx_ValidateAndInit_memviewslice( ...@@ -141,7 +146,13 @@ static int __Pyx_ValidateAndInit_memviewslice(
spec = axes_specs[i]; spec = axes_specs[i];
if (spec & __Pyx_MEMVIEW_CONTIG) { if (spec & __Pyx_MEMVIEW_CONTIG) {
if (buf->strides[i] != buf->itemsize) { if (spec & (__Pyx_MEMVIEW_PTR|__Pyx_MEMVIEW_FULL)) {
if (buf->strides[i] != sizeof(void *)) {
PyErr_Format(PyExc_ValueError,
"Buffer is not indirectly contiguous in dimension %d.", i);
goto fail;
}
} else if (buf->strides[i] != buf->itemsize) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"Buffer and memoryview are not contiguous in the same dimension."); "Buffer and memoryview are not contiguous in the same dimension.");
goto fail; goto fail;
...@@ -156,26 +167,24 @@ static int __Pyx_ValidateAndInit_memviewslice( ...@@ -156,26 +167,24 @@ static int __Pyx_ValidateAndInit_memviewslice(
} }
} }
/* Todo: without PyBUF_INDIRECT we may not have suboffset information, i.e., the
ptr may not be set to NULL but may be uninitialized? */
if (spec & __Pyx_MEMVIEW_DIRECT) { if (spec & __Pyx_MEMVIEW_DIRECT) {
if (buf->suboffsets && buf->suboffsets[i] >= 0) { if (buf->suboffsets && buf->suboffsets[i] >= 0) {
PyErr_SetString(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"Buffer not compatible with direct access."); "Buffer not compatible with direct access in dimension %d.", i);
goto fail; goto fail;
} }
} }
if (spec & __Pyx_MEMVIEW_PTR) { if (spec & (__Pyx_MEMVIEW_PTR|__Pyx_MEMVIEW_FULL) && !buf->suboffsets) {
if (!buf->suboffsets) { memviewslice->suboffsets[i] = -1;
PyErr_SetString(PyExc_ValueError,
"Buffer not able to be indirectly accessed.");
goto fail;
}
} }
if (spec & __Pyx_MEMVIEW_PTR) { if (spec & __Pyx_MEMVIEW_PTR) {
if (buf->suboffsets[i] < 0) { if (buf->suboffsets && buf->suboffsets[i] < 0) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"Buffer not indirectly accessed in %d dimension, although memoryview is.", i); "Buffer is not indirectly accessisble in dimension %d.", i);
goto fail; goto fail;
} }
} }
......
...@@ -27,6 +27,22 @@ cdef object[:, ::1] unconformable2 = unconformable1 ...@@ -27,6 +27,22 @@ cdef object[:, ::1] unconformable2 = unconformable1
cdef int[::1, :] dtype_unconformable = object() cdef int[::1, :] dtype_unconformable = object()
unconformable1 = dtype_unconformable unconformable1 = dtype_unconformable
# These are INVALID
cdef int[::view.contiguous, ::1] a1
cdef int[::view.generic_contiguous, ::1] a2
cdef int[::view.contiguous, ::view.generic_contiguous] a3
cdef int[::view.generic_contiguous, ::view.generic_contiguous] a4
cdef int[::view.contiguous, ::view.contiguous] a5
cdef int[:, ::view.contiguous, ::view.indirect_contiguous] a6
cdef int[::view.generic_contiguous, ::view.contiguous] a7
cdef int[::view.contiguous, ::view.generic_contiguous] a8
# These are VALID
cdef int[::view.indirect_contiguous, ::view.contiguous] a9
_ERRORS = u''' _ERRORS = u'''
11:25: Cannot specify an array that is both C and Fortran contiguous. 11:25: Cannot specify an array that is both C and Fortran contiguous.
12:31: Cannot specify an array that is both C and Fortran contiguous. 12:31: Cannot specify an array that is both C and Fortran contiguous.
...@@ -42,4 +58,12 @@ _ERRORS = u''' ...@@ -42,4 +58,12 @@ _ERRORS = u'''
22:22: no expressions allowed in axis spec, only names and literals. 22:22: no expressions allowed in axis spec, only names and literals.
25:51: Memoryview 'object[::contiguous, :]' not conformable to memoryview 'object[:, ::contiguous]'. 25:51: Memoryview 'object[::contiguous, :]' not conformable to memoryview 'object[:, ::contiguous]'.
28:36: Different base types for memoryviews (int, Python object) 28:36: Different base types for memoryviews (int, Python object)
31:15: Invalid axis specification for a C/Fortran contiguous array.
32:15: Invalid axis specification for a C/Fortran contiguous array.
34:9: Generic contiguous cannot be combined with direct contiguous
35:9: Generic contiguous cannot be combined with direct contiguous
37:9: Only one direct contiguous axis may be specified.
38:9: Indirect contiguous dimensions must precede direct contiguous
40:9: Generic contiguous cannot be combined with direct contiguous
41:9: Generic contiguous cannot be combined with direct contiguous
''' '''
...@@ -431,7 +431,7 @@ def wraparound_directive(int[:] buf, int pos_idx, int neg_idx): ...@@ -431,7 +431,7 @@ def wraparound_directive(int[:] buf, int pos_idx, int neg_idx):
# #
# Test which flags are passed. # Test all kinds of indexing and flags
# #
@testcase @testcase
...@@ -512,6 +512,127 @@ def f_contig_2d(int[::1, :] buf): ...@@ -512,6 +512,127 @@ def f_contig_2d(int[::1, :] buf):
""" """
return buf[3, 1] return buf[3, 1]
@testcase
def generic(int[::view.generic, ::view.generic] buf1,
int[::view.generic, ::view.generic] buf2):
"""
>>> A = IntMockBuffer("A", [[0,1,2], [3,4,5], [6,7,8]])
>>> B = IntMockBuffer("B", [[0,1,2], [3,4,5], [6,7,8]], shape=(3, 3), strides=(1, 3))
>>> generic(A, B)
acquired A
acquired B
4
4
10
11
released A
released B
>>> [str(x) for x in A.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
>>> [str(x) for x in B.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
"""
print buf1[1, 1]
print buf2[1, 1]
buf1[2, -1] = 10
buf2[2, -1] = 11
print buf1[2, 2]
print buf2[2, 2]
@testcase
def generic_contig(int[::view.generic_contiguous, :] buf1,
int[::view.generic_contiguous, :] buf2):
"""
>>> A = IntMockBuffer("A", [[0,1,2], [3,4,5], [6,7,8]])
>>> B = IntMockBuffer("B", [[0,1,2], [3,4,5], [6,7,8]], shape=(3, 3), strides=(1, 3))
>>> generic_contig(A, B)
acquired A
acquired B
4
4
10
11
released A
released B
>>> [str(x) for x in A.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
>>> [str(x) for x in B.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
"""
print buf1[1, 1]
print buf2[1, 1]
buf1[2, -1] = 10
buf2[2, -1] = 11
print buf1[2, 2]
print buf2[2, 2]
@testcase
def indirect_strided_and_contig(
int[::view.indirect, ::view.strided] buf1,
int[::view.indirect, ::view.contiguous] buf2):
"""
>>> A = IntMockBuffer("A", [[0,1,2], [3,4,5], [6,7,8]])
>>> B = IntMockBuffer("B", [[0,1,2], [3,4,5], [6,7,8]], shape=(3, 3), strides=(1, 3))
>>> indirect_strided_and_contig(A, B)
acquired A
acquired B
4
4
10
11
released A
released B
>>> [str(x) for x in A.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
>>> [str(x) for x in B.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
"""
print buf1[1, 1]
print buf2[1, 1]
buf1[2, -1] = 10
buf2[2, -1] = 11
print buf1[2, 2]
print buf2[2, 2]
@testcase
def indirect_contig(
int[::view.indirect_contiguous, ::view.contiguous] buf1,
int[::view.indirect_contiguous, ::view.generic] buf2):
"""
>>> A = IntMockBuffer("A", [[0,1,2], [3,4,5], [6,7,8]])
>>> B = IntMockBuffer("B", [[0,1,2], [3,4,5], [6,7,8]], shape=(3, 3), strides=(1, 3))
>>> indirect_contig(A, B)
acquired A
acquired B
4
4
10
11
released A
released B
>>> [str(x) for x in A.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
>>> [str(x) for x in B.recieved_flags]
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
"""
print buf1[1, 1]
print buf2[1, 1]
buf1[2, -1] = 10
buf2[2, -1] = 11
print buf1[2, 2]
print buf2[2, 2]
# #
# Test compiler options for bounds checking. We create an array with a # Test compiler options for bounds checking. We create an array with a
# safe "boundary" (memory # safe "boundary" (memory
......
...@@ -99,18 +99,22 @@ cdef class MockBuffer: ...@@ -99,18 +99,22 @@ cdef class MockBuffer:
it += self.itemsize it += self.itemsize
return buf return buf
cdef void* create_indirect_buffer(self, data, shape): cdef void* create_indirect_buffer(self, data, shape) except NULL:
cdef size_t n = 0 cdef size_t n = 0
cdef void** buf cdef void** buf
assert shape[0] == len(data) assert shape[0] == len(data), (shape[0], len(data))
if len(shape) == 1: if len(shape) == 1:
return self.create_buffer(data) return self.create_buffer(data)
else: else:
shape = shape[1:] shape = shape[1:]
n = <size_t>len(data) * sizeof(void*) n = <size_t>len(data) * sizeof(void*)
buf = <void**>stdlib.malloc(n) buf = <void**>stdlib.malloc(n)
if buf == NULL:
return NULL
for idx, subdata in enumerate(data): for idx, subdata in enumerate(data):
buf[idx] = self.create_indirect_buffer(subdata, shape) buf[idx] = self.create_indirect_buffer(subdata, shape)
return buf return buf
cdef Py_ssize_t* list_to_sizebuf(self, l): cdef Py_ssize_t* list_to_sizebuf(self, l):
......
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