Commit 1fc34d3b authored by Mark Florisson's avatar Mark Florisson

Allow casting C arrays to cython.arrays & direct assignment to memoryviews

parent 538fa670
...@@ -636,6 +636,9 @@ class ExprNode(Node): ...@@ -636,6 +636,9 @@ class ExprNode(Node):
if not src.type.is_memoryviewslice: if not src.type.is_memoryviewslice:
if src.type.is_pyobject: if src.type.is_pyobject:
src = CoerceToMemViewSliceNode(src, dst_type, env) src = CoerceToMemViewSliceNode(src, dst_type, env)
elif src.type.is_array:
src = CythonArrayNode.from_carray(src, env).coerce_to(
dst_type, env)
else: else:
error(self.pos, error(self.pos,
"Cannot convert '%s' to memoryviewslice" % "Cannot convert '%s' to memoryviewslice" %
...@@ -6763,7 +6766,7 @@ ERR_START = "Start may not be given" ...@@ -6763,7 +6766,7 @@ ERR_START = "Start may not be given"
ERR_NOT_STOP = "Stop must be provided to indicate shape" ERR_NOT_STOP = "Stop must be provided to indicate shape"
ERR_STEPS = ("Strides may only be given to indicate contiguity. " ERR_STEPS = ("Strides may only be given to indicate contiguity. "
"Consider slicing it after conversion") "Consider slicing it after conversion")
ERR_NOT_POINTER = "Can only create cython.array from pointer" ERR_NOT_POINTER = "Can only create cython.array from pointer or array"
ERR_BASE_TYPE = "Pointer base type does not match cython.array base type" ERR_BASE_TYPE = "Pointer base type does not match cython.array base type"
class CythonArrayNode(ExprNode): class CythonArrayNode(ExprNode):
...@@ -6779,6 +6782,12 @@ class CythonArrayNode(ExprNode): ...@@ -6779,6 +6782,12 @@ class CythonArrayNode(ExprNode):
and less work. Acquiring a memoryviewslice from this will be just as and less work. Acquiring a memoryviewslice from this will be just as
efficient. ExprNode.coerce_to() will do the additional typecheck on efficient. ExprNode.coerce_to() will do the additional typecheck on
self.compile_time_type self.compile_time_type
This also handles <int[:, :]> my_c_array
operand ExprNode the thing we're casting
base_type_node MemoryViewSliceTypeNode the cast expression node
""" """
subexprs = ['operand', 'shapes'] subexprs = ['operand', 'shapes']
...@@ -6786,21 +6795,62 @@ class CythonArrayNode(ExprNode): ...@@ -6786,21 +6795,62 @@ class CythonArrayNode(ExprNode):
shapes = None shapes = None
is_temp = True is_temp = True
mode = "c" mode = "c"
array_dtype = None
shape_type = PyrexTypes.c_py_ssize_t_type shape_type = PyrexTypes.c_py_ssize_t_type
def analyse_types(self, env): def analyse_types(self, env):
import MemoryView import MemoryView
self.operand.analyse_types(env)
if self.array_dtype:
array_dtype = self.array_dtype
else:
array_dtype = self.base_type_node.base_type_node.analyse(env)
axes = self.base_type_node.axes
MemoryView.validate_memslice_dtype(self.pos, array_dtype)
self.type = error_type self.type = error_type
self.shapes = [] self.shapes = []
ndim = len(axes)
for axis_no, axis in enumerate(self.base_type_node.axes): # Base type of the pointer or C array we are converting
base_type = self.operand.type
# Dimension sizes of C array
array_dimension_sizes = []
if base_type.is_array:
while base_type.is_array:
array_dimension_sizes.append(base_type.size)
base_type = base_type.base_type
else:
base_type = base_type.base_type
if not self.operand.type.is_ptr and not self.operand.type.is_array:
return error(self.operand.pos, ERR_NOT_POINTER)
elif not base_type.same_as(array_dtype):
return error(self.operand.pos, ERR_BASE_TYPE)
elif self.operand.type.is_array and len(array_dimension_sizes) != ndim:
return error(self.operand.pos,
"Expected %d dimensions, array has %d dimensions" %
(ndim, len(array_dimension_sizes)))
# Verify the start, stop and step values
# In case of a C array, use the size of C array in each dimension to
# get an automatic cast
for axis_no, axis in enumerate(axes):
if not axis.start.is_none: if not axis.start.is_none:
return error(axis.start.pos, ERR_START) return error(axis.start.pos, ERR_START)
if axis.stop.is_none: if axis.stop.is_none:
return error(axis.pos, ERR_NOT_STOP) if array_dimension_sizes:
dimsize = array_dimension_sizes[axis_no]
axis.stop = IntNode(self.pos, value=dimsize,
constant_result=dimsize,
type=PyrexTypes.c_int_type)
else:
return error(axis.pos, ERR_NOT_STOP)
axis.stop.analyse_types(env) axis.stop.analyse_types(env)
shape = axis.stop.coerce_to(self.shape_type, env) shape = axis.stop.coerce_to(self.shape_type, env)
...@@ -6812,7 +6862,7 @@ class CythonArrayNode(ExprNode): ...@@ -6812,7 +6862,7 @@ class CythonArrayNode(ExprNode):
if not axis.stop.type.is_int: if not axis.stop.type.is_int:
return error(axis.stop.pos, "Expected an integer type") return error(axis.stop.pos, "Expected an integer type")
first_or_last = axis_no in (0, len(self.base_type_node.axes) - 1) first_or_last = axis_no in (0, ndim - 1)
if not axis.step.is_none and first_or_last: if not axis.step.is_none and first_or_last:
axis.step.analyse_types(env) axis.step.analyse_types(env)
if (not axis.step.type.is_int and axis.step.is_literal and not if (not axis.step.type.is_int and axis.step.is_literal and not
...@@ -6828,31 +6878,17 @@ class CythonArrayNode(ExprNode): ...@@ -6828,31 +6878,17 @@ class CythonArrayNode(ExprNode):
elif axis.step and not first_or_last: elif axis.step and not first_or_last:
return error(axis.step.pos, ERR_STEPS) return error(axis.step.pos, ERR_STEPS)
self.operand.analyse_types(env)
array_dtype = self.base_type_node.base_type_node.analyse(env)
MemoryView.validate_memslice_dtype(self.pos, array_dtype)
if not self.operand.type.is_ptr:
return error(self.operand.pos, ERR_NOT_POINTER)
elif not self.operand.type.base_type.same_as(array_dtype):
return error(self.operand.pos, ERR_BASE_TYPE)
if not self.operand.is_name: if not self.operand.is_name:
self.operand = self.operand.coerce_to_temp(env) self.operand = self.operand.coerce_to_temp(env)
axes = [('direct', 'follow')] * len(self.base_type_node.axes) axes = [('direct', 'follow')] * len(axes)
if self.mode == "fortran": if self.mode == "fortran":
axes[0] = ('direct', 'contig') axes[0] = ('direct', 'contig')
else: else:
axes[-1] = ('direct', 'contig') axes[-1] = ('direct', 'contig')
self.coercion_type = PyrexTypes.MemoryViewSliceType(array_dtype, axes) self.coercion_type = PyrexTypes.MemoryViewSliceType(array_dtype, axes)
#self.type = py_object_type
self.type = self.get_cython_array_type(env) self.type = self.get_cython_array_type(env)
assert self.type
MemoryView.use_cython_array_utility_code(env) MemoryView.use_cython_array_utility_code(env)
env.use_utility_code(MemoryView.typeinfo_to_format_code) env.use_utility_code(MemoryView.typeinfo_to_format_code)
...@@ -6881,11 +6917,12 @@ class CythonArrayNode(ExprNode): ...@@ -6881,11 +6917,12 @@ class CythonArrayNode(ExprNode):
itemsize = "sizeof(%s)" % dtype.declaration_code("") itemsize = "sizeof(%s)" % dtype.declaration_code("")
type_info = Buffer.get_type_information_cname(code, dtype) type_info = Buffer.get_type_information_cname(code, dtype)
code.putln("if (!%s) {" % self.operand.result()) if self.operand.type.is_ptr:
code.putln( 'PyErr_SetString(PyExc_ValueError,' code.putln("if (!%s) {" % self.operand.result())
'"Cannot create cython.array from NULL pointer");') code.putln( 'PyErr_SetString(PyExc_ValueError,'
code.putln(code.error_goto(self.operand.pos)) '"Cannot create cython.array from NULL pointer");')
code.putln("}") code.putln(code.error_goto(self.operand.pos))
code.putln("}")
code.putln("%s = __pyx_format_from_typeinfo(&%s);" % code.putln("%s = __pyx_format_from_typeinfo(&%s);" %
(format_temp, type_info)) (format_temp, type_info))
...@@ -6914,6 +6951,29 @@ class CythonArrayNode(ExprNode): ...@@ -6914,6 +6951,29 @@ class CythonArrayNode(ExprNode):
dispose(shapes_temp) dispose(shapes_temp)
dispose(format_temp) dispose(format_temp)
@classmethod
def from_carray(cls, src_node, env):
"""
Given a C array type, return a CythonArrayNode
"""
pos = src_node.pos
base_type = src_node.type
none_node = NoneNode(pos)
axes = []
while base_type.is_array:
axes.append(SliceNode(pos, start=none_node, stop=none_node,
step=none_node))
base_type = base_type.base_type
axes[-1].step = IntNode(pos, value="1", is_c_literal=True)
memslicenode = Nodes.MemoryViewSliceTypeNode(pos, axes=axes,
base_type_node=base_type)
result = CythonArrayNode(pos, base_type_node=memslicenode,
operand=src_node, array_dtype=base_type)
result.analyse_types(env)
return result
class SizeofNode(ExprNode): class SizeofNode(ExprNode):
# Abstract base class for sizeof(x) expression nodes. # Abstract base class for sizeof(x) expression nodes.
......
...@@ -193,11 +193,15 @@ It also takes an optional argument `mode` ('c' or 'fortran') and a boolean `allo ...@@ -193,11 +193,15 @@ It also takes an optional argument `mode` ('c' or 'fortran') and a boolean `allo
# define a function that can deallocate the data (if needed) # define a function that can deallocate the data (if needed)
my_array.callback_free_data = free my_array.callback_free_data = free
You can also cast pointers to arrays:: You can also cast pointers to array, or C arrays to arrays::
cdef cython.array my_array = <int[:10, :2]> my_data_pointer cdef cython.array my_array = <int[:10, :2]> my_data_pointer
cdef cython.array my_array = <int[:, :]> my_c_array
Of course, you can also immidiately assign a cython.array to a typed memoryview slice. Of course, you can also immediately assign a cython.array to a typed memoryview slice. A C array
may be assigned directly to a memoryview slice::
cdef int[:, ::1] myslice = my_2d_c_array
The arrays are indexable and slicable from Python space just like memoryview objects, and have the same The arrays are indexable and slicable from Python space just like memoryview objects, and have the same
attributes as memoryview objects. attributes as memoryview objects.
......
...@@ -64,7 +64,6 @@ def dont_allocate_buffer(): ...@@ -64,7 +64,6 @@ def dont_allocate_buffer():
""" """
cdef cy.array result = cy.array((10, 10), itemsize=sizeof(int), format='i', allocate_buffer=False) cdef cy.array result = cy.array((10, 10), itemsize=sizeof(int), format='i', allocate_buffer=False)
assert result.data == NULL assert result.data == NULL
result.data = <char *> 1
result.callback_free_data = callback result.callback_free_data = callback
result = None result = None
...@@ -115,7 +114,7 @@ cdef int *getp(int dim1=10, int dim2=10) except NULL: ...@@ -115,7 +114,7 @@ cdef int *getp(int dim1=10, int dim2=10) except NULL:
return p return p
cdef void callback_free_data(char *p): cdef void callback_free_data(void *p):
print 'callback free data called' print 'callback free data called'
free(p) free(p)
...@@ -126,11 +125,14 @@ def test_array_from_pointer(): ...@@ -126,11 +125,14 @@ def test_array_from_pointer():
69 69
c c
getp() getp()
callback free data called
fortran fortran
getp() getp()
56 56
getp() getp()
56 56
getp()
119
callback free data called callback free data called
""" """
cdef int *p = getp() cdef int *p = getp()
...@@ -139,14 +141,38 @@ def test_array_from_pointer(): ...@@ -139,14 +141,38 @@ def test_array_from_pointer():
print c_arr[6, 9] print c_arr[6, 9]
print c_arr.mode print c_arr.mode
print (<int[:10:1, :10]> getp()).mode c_arr = (<int[:10:1, :10]> getp())
print c_arr.mode
c_arr.callback_free_data = free
cdef int[:, ::1] mslice = <int[:10, :10]> getp() c_arr = <int[:10, :10]> getp()
c_arr.callback_free_data = free
cdef int[:, ::1] mslice = c_arr
print mslice[5, 6] print mslice[5, 6]
print (<int[:12, :10]> getp(12, 10))[5, 6]
# There is a reference cycle between the array object to its memoryview c_arr = <int[:12, :10]> getp(12, 10)
# object that it keeps c_arr.callback_free_data = free
del c_arr print c_arr[5, 6]
import gc
gc.collect() cdef int m = 12
cdef int n = 10
c_arr = <int[:m, :n]> getp(m, n)
c_arr.callback_free_data = callback_free_data
print c_arr[m - 1, n - 1]
def test_cyarray_from_carray():
"""
>>> test_cyarray_from_carray()
0 8 21
0 8 21
"""
cdef int a[7][8]
for i in range(7):
for j in range(8):
a[i][j] = i * 8 + j
cdef int[:, :] mslice = <int[:, :]> a
print mslice[0, 0], mslice[1, 0], mslice[2, 5]
mslice = a
print mslice[0, 0], mslice[1, 0], mslice[2, 5]
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