array expressions #144

Open
wants to merge 56 commits into
from
Select commit
+4,263 −858
View
10 Cython/Compiler/Buffer.py
@@ -195,12 +195,20 @@ def assert_bool(name):
#
class BufferEntry(object):
+
def __init__(self, entry):
self.entry = entry
self.type = entry.type
self.cname = entry.buffer_aux.buflocal_nd_var.cname
self.buf_ptr = "%s.rcbuffer->pybuffer.buf" % self.cname
- self.buf_ptr_type = self.entry.type.buffer_ptr_type
+ self.buf_ptr_type = entry.type.buffer_ptr_type
+
+ self.init_attributes()
+
+ def init_attributes(self):
+ self.shape = self.get_buf_shapevars()
+ self.strides = self.get_buf_stridevars()
+ self.suboffsets = self.get_buf_suboffsetvars()
def get_buf_suboffsetvars(self):
return self._for_all_ndim("%s.diminfo[%d].suboffsets")
View
2 Cython/Compiler/Code.py
@@ -1884,7 +1884,7 @@ def put_setup_refcount_context(self, name, acquire_gil=False):
if acquire_gil:
self.globalstate.use_utility_code(
UtilityCode.load_cached("ForceInitThreads", "ModuleSetupCode.c"))
- self.putln('__Pyx_RefNannySetupContext("%s", %d);' % (name, acquire_gil and 1 or 0))
+ self.putln('__Pyx_RefNannySetupContext("%s", %d);' % (name, int(acquire_gil)))
def put_finish_refcount_context(self):
self.putln("__Pyx_RefNannyFinishContext();")
View
4 Cython/Compiler/CythonScope.py
@@ -127,6 +127,10 @@ def create_cython_scope(context):
# it across different contexts)
return CythonScope(context)
+def get_cython_scope(env):
+ "Return the CythonScope given an env in some context"
+ return env.global_scope().context.cython_scope
+
# Load test utilities for the cython scope
def load_testscope_utility(cy_util_name, **kwargs):
View
1,474 Cython/Compiler/ExprNodes.py
@@ -3,6 +3,7 @@
#
import cython
+
cython.declare(error=object, warning=object, warn_once=object, InternalError=object,
CompileError=object, UtilityCode=object, TempitaUtilityCode=object,
StringEncoding=object, operator=object,
@@ -215,16 +216,23 @@ class ExprNode(Node):
is_string_literal = 0
is_attribute = 0
+ is_index = False
+ is_buffer_access = False
+ is_memview_index = False
+ is_memview_slice = False
+ is_memview_broadcast = False
+ is_memview_copy_assignment = False
+
+ is_slice = False
+
saved_subexpr_nodes = None
is_temp = 0
is_target = 0
is_starred = 0
+ is_elemental = 0
constant_result = constant_value_not_set
- # whether this node with a memoryview type should be broadcast
- memslice_broadcast = False
-
try:
_get_child_attrs = operator.attrgetter('subexprs')
except AttributeError:
@@ -439,6 +447,10 @@ def check_const_addr(self):
def addr_not_const(self):
error(self.pos, "Address is not constant")
+ def wrap_in_clone_node(self):
+ "Return a CloneNode for this node"
+ return CloneNode(ProxyNode(self))
+
# ----------------- Result Allocation -----------------
def result_in_temp(self):
@@ -648,6 +660,7 @@ def coerce_to(self, dst_type, env):
if dst_type.is_memoryviewslice:
import MemoryView
+
if not src.type.is_memoryviewslice:
if src.type.is_pyobject:
src = CoerceToMemViewSliceNode(src, dst_type, env)
@@ -658,8 +671,9 @@ def coerce_to(self, dst_type, env):
error(self.pos,
"Cannot convert '%s' to memoryviewslice" %
(src_type,))
- elif not MemoryView.src_conforms_to_dst(
- src.type, dst_type, broadcast=self.memslice_broadcast):
+ elif not src.type.conforms_to(
+ dst_type, broadcast=self.is_memview_broadcast,
+ copying=self.is_memview_copy_assignment):
if src.type.dtype.same_as(dst_type.dtype):
msg = "Memoryview '%s' not conformable to memoryview '%s'."
tup = src.type, dst_type
@@ -776,6 +790,10 @@ def as_none_safe_node(self, message, error="PyExc_TypeError", format_args=()):
else:
return self
+ def error(self, mess):
+ error(self.pos, mess)
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
class AtomicExprNode(ExprNode):
# Abstract base class for expression nodes which have
@@ -1554,10 +1572,6 @@ def nogil_check(self, env):
self.gil_error()
elif entry.is_pyglobal:
self.gil_error()
- elif self.entry.type.is_memoryviewslice:
- if self.cf_is_null or self.cf_maybe_null:
- import MemoryView
- MemoryView.err_if_nogil_initialized_check(self.pos, env)
gil_message = "Accessing Python global or builtin"
@@ -2444,7 +2458,6 @@ class IndexNode(ExprNode):
# base ExprNode
# index ExprNode
# indices [ExprNode]
- # is_buffer_access boolean Whether this is a buffer access.
#
# indices is used on buffer access, index on non-buffer access.
# The former contains a clean list of index parameters, the
@@ -2453,23 +2466,11 @@ class IndexNode(ExprNode):
# is_fused_index boolean Whether the index is used to specialize a
# c(p)def function
- subexprs = ['base', 'index', 'indices']
- indices = None
+ subexprs = ['base', 'index']
+ is_index = True
is_fused_index = False
-
- # Whether we're assigning to a buffer (in that case it needs to be
- # writable)
- writable_needed = False
-
- # Whether we are indexing or slicing a memoryviewslice
- memslice_index = False
- memslice_slice = False
- is_memslice_copy = False
- memslice_ellipsis_noop = False
- warned_untyped_idx = False
- # set by SingleAssignmentNode after analyse_types()
- is_memslice_scalar_assignment = False
+ replacement_node = None
def __init__(self, pos, index, *args, **kw):
ExprNode.__init__(self, pos, index=index, *args, **kw)
@@ -2491,10 +2492,8 @@ def is_ephemeral(self):
return self.base.is_ephemeral()
def is_simple(self):
- if self.is_buffer_access or self.memslice_index:
- return False
- elif self.memslice_slice:
- return True
+ if self.replacement_node:
+ return self.replacement_node.is_simple()
base = self.base
return (base.is_simple() and self.index.is_simple()
@@ -2594,19 +2593,6 @@ def analyse_base_and_index_types(self, env, getting = 0, setting = 0, analyse_ba
# Note: This might be cleaned up by having IndexNode
# parsed in a saner way and only construct the tuple if
# needed.
-
- # Note that this function must leave IndexNode in a cloneable state.
- # For buffers, self.index is packed out on the initial analysis, and
- # when cloning self.indices is copied.
- self.is_buffer_access = False
-
- # a[...] = b
- self.is_memslice_copy = False
- # incomplete indexing, Ellipsis indexing or slicing
- self.memslice_slice = False
- # integer indexing
- self.memslice_index = False
-
if analyse_base:
self.base.analyse_types(env)
@@ -2616,278 +2602,208 @@ def analyse_base_and_index_types(self, env, getting = 0, setting = 0, analyse_ba
self.type = PyrexTypes.error_type
return
- is_slice = isinstance(self.index, SliceNode)
-
# Potentially overflowing index value.
- if not is_slice and isinstance(self.index, IntNode) and Utils.long_literal(self.index.value):
+ if (not self.index.is_slice and isinstance(self.index, IntNode)
+ and Utils.long_literal(self.index.value)):
self.index = self.index.coerce_to_pyobject(env)
is_memslice = self.base.type.is_memoryviewslice
-
# Handle the case where base is a literal char* (and we expect a string, not an int)
- if not is_memslice and (isinstance(self.base, BytesNode) or is_slice):
- if self.base.type.is_string or not (self.base.type.is_ptr or self.base.type.is_array):
+ if not is_memslice and (isinstance(self.base, BytesNode) or
+ self.index.is_slice):
+ if self.base.type.is_string or not (self.base.type.is_ptr
+ or self.base.type.is_array):
self.base = self.base.coerce_to_pyobject(env)
- skip_child_analysis = False
- buffer_access = False
-
- if self.indices:
- indices = self.indices
- elif isinstance(self.index, TupleNode):
- indices = self.index.args
- else:
- indices = [self.index]
-
- if (is_memslice and not self.indices and
- isinstance(self.index, EllipsisNode)):
- # Memoryviewslice copying
- self.is_memslice_copy = True
-
- elif is_memslice:
- # memoryviewslice indexing or slicing
- import MemoryView
+ is_buffer_operation, skip_child_analysis = \
+ self.analyse_as_buffer_operation(env, getting)
- skip_child_analysis = True
- newaxes = [newaxis for newaxis in indices if newaxis.is_none]
- have_slices, indices = MemoryView.unellipsify(indices,
- newaxes,
- self.base.type.ndim)
+ if is_buffer_operation:
+ # self.replacement_node now contains the actual node and will
+ # replace this IndexNode
+ return
- self.memslice_index = (not newaxes and
- len(indices) == self.base.type.ndim)
- axes = []
+ self.nogil = env.nogil
+ base_type = self.base.type
- index_type = PyrexTypes.c_py_ssize_t_type
- new_indices = []
+ # First check for fused function indexing, we don't analyse the indices
+ if base_type.is_cfunction and base_type.is_fused:
+ self.parse_indexed_fused_cdef(env)
+ return
- if len(indices) - len(newaxes) > self.base.type.ndim:
- self.type = error_type
- return error(indices[self.base.type.ndim].pos,
- "Too many indices specified for type %s" %
- self.base.type)
+ if isinstance(self.index, TupleNode):
+ self.index.analyse_types(env, skip_children=skip_child_analysis)
+ elif not skip_child_analysis:
+ self.index.analyse_types(env)
+ self.original_index_type = self.index.type
+
+ if base_type.is_unicode_char:
+ # we infer Py_UNICODE/Py_UCS4 for unicode strings in some
+ # cases, but indexing must still work for them
+ if self.index.constant_result in (0, -1):
+ # FIXME: we know that this node is redundant -
+ # currently, this needs to get handled in Optimize.py
+ pass
+ self.base = self.base.coerce_to_pyobject(env)
+ base_type = self.base.type
- axis_idx = 0
- for i, index in enumerate(indices[:]):
- index.analyse_types(env)
- if not index.is_none:
- access, packing = self.base.type.axes[axis_idx]
- axis_idx += 1
-
- if isinstance(index, SliceNode):
- self.memslice_slice = True
- if index.step.is_none:
- axes.append((access, packing))
- else:
- axes.append((access, 'strided'))
-
- # Coerce start, stop and step to temps of the right type
- for attr in ('start', 'stop', 'step'):
- value = getattr(index, attr)
- if not value.is_none:
- value = value.coerce_to(index_type, env)
- #value = value.coerce_to_temp(env)
- setattr(index, attr, value)
- new_indices.append(value)
-
- elif index.is_none:
- self.memslice_slice = True
- new_indices.append(index)
- axes.append(('direct', 'strided'))
-
- elif index.type.is_int or index.type.is_pyobject:
- if index.type.is_pyobject and not self.warned_untyped_idx:
- warning(index.pos, "Index should be typed for more "
- "efficient access", level=2)
- IndexNode.warned_untyped_idx = True
-
- self.memslice_index = True
- index = index.coerce_to(index_type, env)
- indices[i] = index
- new_indices.append(index)
+ if self.analyse_as_object(base_type, env, getting):
+ self.wrap_in_nonecheck_node(env, getting)
+ elif self.analyse_as_c_array(base_type, env, getting):
+ pass
+ elif self.analyse_as_cpp(base_type, env, getting):
+ pass
+ else:
+ error(self.pos,
+ "Attempting to index non-array type '%s'" %
+ base_type)
+ self.type = PyrexTypes.error_type
+ def analyse_as_object(self, base_type, env, getting):
+ if base_type.is_pyobject:
+ if self.index.type.is_int:
+ if (getting
+ and (base_type in (list_type, tuple_type))
+ and (not self.index.type.signed
+ or not env.directives['wraparound']
+ or (isinstance(self.index, IntNode) and
+ self.index.has_constant_result() and
+ self.index.constant_result >= 0))
+ and not env.directives['boundscheck']):
+ self.is_temp = 0
else:
- self.type = error_type
- return error(index.pos, "Invalid index for memoryview specified")
-
- self.memslice_index = self.memslice_index and not self.memslice_slice
- self.original_indices = indices
- # All indices with all start/stop/step for slices.
- # We need to keep this around
- self.indices = new_indices
- self.env = env
-
- elif self.base.type.is_buffer:
- # Buffer indexing
- if len(indices) == self.base.type.ndim:
- buffer_access = True
- skip_child_analysis = True
- for x in indices:
- x.analyse_types(env)
- if not x.type.is_int:
- buffer_access = False
-
- if buffer_access and not self.base.type.is_memoryviewslice:
- assert hasattr(self.base, "entry") # Must be a NameNode-like node
-
- # On cloning, indices is cloned. Otherwise, unpack index into indices
- assert not (buffer_access and isinstance(self.index, CloneNode))
-
- self.nogil = env.nogil
-
- if buffer_access or self.memslice_index:
- #if self.base.type.is_memoryviewslice and not self.base.is_name:
- # self.base = self.base.coerce_to_temp(env)
- self.base = self.base.coerce_to_simple(env)
+ self.is_temp = 1
+ self.index = self.index.coerce_to(PyrexTypes.c_py_ssize_t_type,
+ env).coerce_to_simple(env)
+ else:
+ self.index = self.index.coerce_to_pyobject(env)
+ self.is_temp = 1
+ if self.index.type.is_int and base_type is unicode_type:
+ # Py_UNICODE/Py_UCS4 will automatically coerce to a unicode string
+ # if required, so this is fast and safe
+ self.type = PyrexTypes.c_py_ucs4_type
+ elif self.index.is_slice and base_type in (bytes_type, str_type,
+ unicode_type, list_type,
+ tuple_type):
+ self.type = base_type
+ else:
+ if base_type in (list_type, tuple_type, dict_type):
+ # do the None check explicitly (not in a helper) to allow optimising it away
+ self.base = self.base.as_none_safe_node("'NoneType' object is not subscriptable")
+ self.type = py_object_type
+ return True
- self.indices = indices
- self.index = None
- self.type = self.base.type.dtype
- self.is_buffer_access = True
- self.buffer_type = self.base.type #self.base.entry.type
+ def analyse_as_c_array(self, base_type, env, getting):
+ if base_type.is_ptr or base_type.is_array:
+ self.type = base_type.base_type
+ if self.index.is_slice:
+ self.type = base_type
+ elif self.index.type.is_pyobject:
+ self.index = self.index.coerce_to(
+ PyrexTypes.c_py_ssize_t_type, env)
+ elif not self.index.type.is_int:
+ error(self.pos,
+ "Invalid index type '%s'" %
+ self.index.type)
+ return True
- if getting and self.type.is_pyobject:
- self.is_temp = True
+ def analyse_as_cpp(self, base_type, env, getting):
+ if base_type.is_cpp_class:
+ function = env.lookup_operator("[]", [self.base, self.index])
+ if function is None:
+ error(self.pos, "Indexing '%s' not supported for index type '%s'" % (base_type, self.index.type))
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
+ return
+ func_type = function.type
+ if func_type.is_ptr:
+ func_type = func_type.base_type
+ self.index = self.index.coerce_to(func_type.args[0].type, env)
+ self.type = func_type.return_type
+ if not getting and not func_type.return_type.is_reference:
+ error(self.pos, "Can't set non-reference result '%s'" % self.type)
+ return True
- if setting and self.base.type.is_memoryviewslice:
- self.base.type.writable_needed = True
- elif setting:
- if not self.base.entry.type.writable:
- error(self.pos, "Writing to readonly buffer")
- else:
- self.writable_needed = True
- if self.base.type.is_buffer:
- self.base.entry.buffer_aux.writable_needed = True
+ def analyse_as_buffer_operation(self, env, getting):
+ """
+ Analyse buffer indexing and memoryview indexing/slicing
+ """
+ if isinstance(self.index, TupleNode):
+ indices = self.index.args
+ else:
+ indices = [self.index]
- elif self.is_memslice_copy:
- self.type = self.base.type
- if getting:
- self.memslice_ellipsis_noop = True
+ replacement_node = None
+ skip_child_analysis = False
+ if self.base.type.is_memoryviewslice:
+ # memoryviewslice indexing or slicing
+ import MemoryView
+ have_slices, indices, newaxes = MemoryView.unellipsify(
+ indices, self.base.type.ndim)
+ if have_slices:
+ replacement_node = MemoryViewSliceNode(
+ self.pos, indices=indices, base=self.base)
else:
- self.memslice_broadcast = True
-
- elif self.memslice_slice:
- self.index = None
- self.is_temp = True
- self.use_managed_ref = True
+ replacement_node = MemoryViewIndexNode(
+ self.pos, indices=indices, base=self.base)
- if not MemoryView.validate_axes(self.pos, axes):
- self.type = error_type
- return
+ elif self.base.type.is_buffer and len(indices) == self.base.type.ndim:
+ # Buffer indexing
+ buffer_access = True
+ for index in indices:
+ index.analyse_types(env)
+ if not index.type.is_int:
+ buffer_access = False
+ skip_child_analysis = True
- self.type = PyrexTypes.MemoryViewSliceType(
- self.base.type.dtype, axes)
+ if buffer_access:
+ replacement_node = BufferIndexNode(self.pos, indices=indices,
+ base=self.base)
- if (self.base.type.is_memoryviewslice and not
- self.base.is_name and not
- self.base.result_in_temp()):
- self.base = self.base.coerce_to_temp(env)
+ # On cloning, indices is cloned. Otherwise, unpack index into indices
+ assert not isinstance(self.index, CloneNode)
- if setting:
- self.memslice_broadcast = True
+ if not replacement_node:
+ return False, skip_child_analysis
- else:
- base_type = self.base.type
+ replacement_node.analyse_types(env, getting)
+ self.type = replacement_node.type
+ self.replacement_node = replacement_node
+ return True, True
- fused_index_operation = base_type.is_cfunction and base_type.is_fused
- if not fused_index_operation:
- if isinstance(self.index, TupleNode):
- self.index.analyse_types(env, skip_children=skip_child_analysis)
- elif not skip_child_analysis:
- self.index.analyse_types(env)
- self.original_index_type = self.index.type
-
- if base_type.is_unicode_char:
- # we infer Py_UNICODE/Py_UCS4 for unicode strings in some
- # cases, but indexing must still work for them
- if self.index.constant_result in (0, -1):
- # FIXME: we know that this node is redundant -
- # currently, this needs to get handled in Optimize.py
- pass
- self.base = self.base.coerce_to_pyobject(env)
- base_type = self.base.type
- if base_type.is_pyobject:
- if self.index.type.is_int:
- if (not setting
- and (base_type in (list_type, tuple_type))
- and (not self.index.type.signed
- or not env.directives['wraparound']
- or (isinstance(self.index, IntNode) and
- self.index.has_constant_result() and self.index.constant_result >= 0))
- and not env.directives['boundscheck']):
- self.is_temp = 0
- else:
- self.is_temp = 1
- self.index = self.index.coerce_to(PyrexTypes.c_py_ssize_t_type, env).coerce_to_simple(env)
- else:
- self.index = self.index.coerce_to_pyobject(env)
- self.is_temp = 1
- if self.index.type.is_int and base_type is unicode_type:
- # Py_UNICODE/Py_UCS4 will automatically coerce to a unicode string
- # if required, so this is fast and safe
- self.type = PyrexTypes.c_py_ucs4_type
- elif is_slice and base_type in (bytes_type, str_type, unicode_type, list_type, tuple_type):
- self.type = base_type
- else:
- if base_type in (list_type, tuple_type, dict_type):
- # do the None check explicitly (not in a helper) to allow optimising it away
- self.base = self.base.as_none_safe_node("'NoneType' object is not subscriptable")
- self.type = py_object_type
- else:
- if base_type.is_ptr or base_type.is_array:
- self.type = base_type.base_type
- if is_slice:
- self.type = base_type
- elif self.index.type.is_pyobject:
- self.index = self.index.coerce_to(
- PyrexTypes.c_py_ssize_t_type, env)
- elif not self.index.type.is_int:
- error(self.pos,
- "Invalid index type '%s'" %
- self.index.type)
- elif base_type.is_cpp_class:
- function = env.lookup_operator("[]", [self.base, self.index])
- if function is None:
- error(self.pos, "Indexing '%s' not supported for index type '%s'" % (base_type, self.index.type))
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
- return
- func_type = function.type
- if func_type.is_ptr:
- func_type = func_type.base_type
- self.index = self.index.coerce_to(func_type.args[0].type, env)
- self.type = func_type.return_type
- if setting and not func_type.return_type.is_reference:
- error(self.pos, "Can't set non-reference result '%s'" % self.type)
- elif fused_index_operation:
- self.parse_indexed_fused_cdef(env)
- else:
- error(self.pos,
- "Attempting to index non-array type '%s'" %
- base_type)
- self.type = PyrexTypes.error_type
+ def analyse_broadcast_operation(self, rhs):
+ """
+ Support broadcasting for slice assignment.
- self.wrap_in_nonecheck_node(env, getting)
+ E.g.
+ m_2d[...] = m_1d # or,
+ m_1d[...] = m_2d # if the leading dimension has extent 1
+ """
+ if self.type.is_memoryviewslice:
+ lhs = self.replacement_node
+ if lhs.is_memview_broadcast or rhs.is_memview_broadcast:
+ lhs.is_memview_broadcast = True
+ rhs.is_memview_broadcast = True
+
+ def analyse_as_memview_scalar_assignment(self, rhs):
+ if self.replacement_node:
+ lhs = self.replacement_node.analyse_assignment(rhs)
+ if lhs:
+ self.type = lhs.type
+ self.replacement_node = lhs
+ self.is_memview_copy_assignment = lhs.is_memview_copy_assignment
+ rhs.is_memview_copy_assignment = lhs.is_memview_copy_assignment
def wrap_in_nonecheck_node(self, env, getting):
if not env.directives['nonecheck'] or not self.base.may_be_none():
return
- if self.base.type.is_memoryviewslice:
- if self.is_memslice_copy and not getting:
- msg = "Cannot assign to None memoryview slice"
- elif self.memslice_slice:
- msg = "Cannot slice None memoryview slice"
- else:
- msg = "Cannot index None memoryview slice"
- else:
- msg = "'NoneType' object is not subscriptable"
-
+ msg = "'NoneType' object is not subscriptable"
self.base = self.base.as_none_safe_node(msg)
def parse_indexed_fused_cdef(self, env):
"""
- Interpret fused_cdef_func[specific_type1, ...]
+ Interpret fused_cdef_func[specialized_type1, ...]
Note that if this method is called, we are an indexed cdef function
with fused argument types, and this IndexNode will be replaced by the
@@ -2984,19 +2900,6 @@ def parse_indexed_fused_cdef(self, env):
gil_message = "Indexing Python object"
- def nogil_check(self, env):
- if self.is_buffer_access or self.memslice_index or self.memslice_slice:
- if not self.memslice_slice and env.directives['boundscheck']:
- # error(self.pos, "Cannot check buffer index bounds without gil; "
- # "use boundscheck(False) directive")
- warning(self.pos, "Use boundscheck(False) for faster access",
- level=1)
- if self.type.is_pyobject:
- error(self.pos, "Cannot access buffer with object dtype without gil")
- return
- super(IndexNode, self).nogil_check(env)
-
-
def check_const_addr(self):
return self.base.check_const_addr() and self.index.check_const()
@@ -3008,11 +2911,13 @@ def is_lvalue(self):
return True
def calculate_result_code(self):
- if self.is_buffer_access:
- return "(*%s)" % self.buffer_ptr_code
- elif self.is_memslice_copy:
- return self.base.result()
- elif self.base.type is list_type:
+ if self.replacement_node:
+ # fixme: something got a hold of this IndexNode during type analysis
+ # and did not list is as a subexpr or child_attr. Fake the replacement
+ # node at this point
+ return self.replacement_node.result()
+
+ if self.base.type is list_type:
return "PyList_GET_ITEM(%s, %s)" % (self.base.result(), self.index.result())
elif self.base.type is tuple_type:
return "PyTuple_GET_ITEM(%s, %s)" % (self.base.result(), self.index.result())
@@ -3032,86 +2937,54 @@ def extra_index_params(self):
else:
return ""
- def generate_subexpr_evaluation_code(self, code):
- self.base.generate_evaluation_code(code)
- if self.indices is None:
- self.index.generate_evaluation_code(code)
- else:
- for i in self.indices:
- i.generate_evaluation_code(code)
-
- def generate_subexpr_disposal_code(self, code):
- self.base.generate_disposal_code(code)
- if self.indices is None:
- self.index.generate_disposal_code(code)
- else:
- for i in self.indices:
- i.generate_disposal_code(code)
-
- def free_subexpr_temps(self, code):
- self.base.free_temps(code)
- if self.indices is None:
- self.index.free_temps(code)
- else:
- for i in self.indices:
- i.free_temps(code)
-
def generate_result_code(self, code):
- if self.is_buffer_access or self.memslice_index:
- buffer_entry, self.buffer_ptr_code = self.buffer_lookup_code(code)
- if self.type.is_pyobject:
- # is_temp is True, so must pull out value and incref it.
- code.putln("%s = *%s;" % (self.result(), self.buffer_ptr_code))
- code.putln("__Pyx_INCREF((PyObject*)%s);" % self.result())
-
- elif self.memslice_slice:
- self.put_memoryviewslice_slice_code(code)
+ if not self.is_temp:
+ return
- elif self.is_temp:
- if self.type.is_pyobject:
- if self.index.type.is_int:
- index_code = self.index.result()
- if self.base.type is list_type:
- function = "__Pyx_GetItemInt_List"
- elif self.base.type is tuple_type:
- function = "__Pyx_GetItemInt_Tuple"
- else:
- function = "__Pyx_GetItemInt"
- code.globalstate.use_utility_code(
- TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c"))
- else:
- index_code = self.index.py_result()
- if self.base.type is dict_type:
- function = "__Pyx_PyDict_GetItem"
- code.globalstate.use_utility_code(
- UtilityCode.load_cached("DictGetItem", "ObjectHandling.c"))
- else:
- function = "PyObject_GetItem"
- code.putln(
- "%s = %s(%s, %s%s); if (!%s) %s" % (
- self.result(),
- function,
- self.base.py_result(),
- index_code,
- self.extra_index_params(),
- self.result(),
- code.error_goto(self.pos)))
- code.put_gotref(self.py_result())
- elif self.type.is_unicode_char and self.base.type is unicode_type:
- assert self.index.type.is_int
+ if self.type.is_pyobject:
+ if self.index.type.is_int:
index_code = self.index.result()
- function = "__Pyx_GetItemInt_Unicode"
+ if self.base.type is list_type:
+ function = "__Pyx_GetItemInt_List"
+ elif self.base.type is tuple_type:
+ function = "__Pyx_GetItemInt_Tuple"
+ else:
+ function = "__Pyx_GetItemInt"
code.globalstate.use_utility_code(
- UtilityCode.load_cached("GetItemIntUnicode", "StringTools.c"))
- code.putln(
- "%s = %s(%s, %s%s); if (unlikely(%s == (Py_UCS4)-1)) %s;" % (
- self.result(),
- function,
- self.base.py_result(),
- index_code,
- self.extra_index_params(),
- self.result(),
- code.error_goto(self.pos)))
+ TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c"))
+ else:
+ index_code = self.index.py_result()
+ if self.base.type is dict_type:
+ function = "__Pyx_PyDict_GetItem"
+ code.globalstate.use_utility_code(
+ UtilityCode.load_cached("DictGetItem", "ObjectHandling.c"))
+ else:
+ function = "PyObject_GetItem"
+ code.putln(
+ "%s = %s(%s, %s%s); if (!%s) %s" % (
+ self.result(),
+ function,
+ self.base.py_result(),
+ index_code,
+ self.extra_index_params(),
+ self.result(),
+ code.error_goto(self.pos)))
+ code.put_gotref(self.py_result())
+ elif self.type.is_unicode_char and self.base.type is unicode_type:
+ assert self.index.type.is_int
+ index_code = self.index.result()
+ function = "__Pyx_GetItemInt_Unicode"
+ code.globalstate.use_utility_code(
+ UtilityCode.load_cached("GetItemIntUnicode", "StringTools.c"))
+ code.putln(
+ "%s = %s(%s, %s%s); if (unlikely(%s == (Py_UCS4)-1)) %s;" % (
+ self.result(),
+ function,
+ self.base.py_result(),
+ index_code,
+ self.extra_index_params(),
+ self.result(),
+ code.error_goto(self.pos)))
def generate_setitem_code(self, value_code, code):
if self.index.type.is_int:
@@ -3141,54 +3014,18 @@ def generate_setitem_code(self, value_code, code):
self.extra_index_params(),
code.error_goto(self.pos)))
- def generate_buffer_setitem_code(self, rhs, code, op=""):
- # Used from generate_assignment_code and InPlaceAssignmentNode
- buffer_entry, ptrexpr = self.buffer_lookup_code(code)
-
- if self.buffer_type.dtype.is_pyobject:
- # Must manage refcounts. Decref what is already there
- # and incref what we put in.
- ptr = code.funcstate.allocate_temp(buffer_entry.buf_ptr_type,
- manage_ref=False)
- rhs_code = rhs.result()
- code.putln("%s = %s;" % (ptr, ptrexpr))
- code.put_gotref("*%s" % ptr)
- code.putln("__Pyx_INCREF(%s); __Pyx_DECREF(*%s);" % (
- rhs_code, ptr))
- code.putln("*%s %s= %s;" % (ptr, op, rhs_code))
- code.put_giveref("*%s" % ptr)
- code.funcstate.release_temp(ptr)
- else:
- # Simple case
- code.putln("*%s %s= %s;" % (ptrexpr, op, rhs.result()))
-
def generate_assignment_code(self, rhs, code):
- generate_evaluation_code = (self.is_memslice_scalar_assignment or
- self.memslice_slice)
- if generate_evaluation_code:
- self.generate_evaluation_code(code)
- else:
- self.generate_subexpr_evaluation_code(code)
-
- if self.is_buffer_access or self.memslice_index:
- self.generate_buffer_setitem_code(rhs, code)
- elif self.is_memslice_scalar_assignment:
- self.generate_memoryviewslice_assign_scalar_code(rhs, code)
- elif self.memslice_slice or self.is_memslice_copy:
- self.generate_memoryviewslice_setslice_code(rhs, code)
- elif self.type.is_pyobject:
+ self.generate_subexpr_evaluation_code(code)
+
+ if self.type.is_pyobject:
self.generate_setitem_code(rhs.py_result(), code)
else:
code.putln(
"%s = %s;" % (
self.result(), rhs.result()))
- if generate_evaluation_code:
- self.generate_disposal_code(code)
- else:
- self.generate_subexpr_disposal_code(code)
- self.free_subexpr_temps(code)
-
+ self.generate_subexpr_disposal_code(code)
+ self.free_subexpr_temps(code)
rhs.generate_disposal_code(code)
rhs.free_temps(code)
@@ -3216,58 +3053,333 @@ def generate_deletion_code(self, code):
self.generate_subexpr_disposal_code(code)
self.free_subexpr_temps(code)
+
+class BufferIndexNode(ExprNode):
+ """
+ Indexing of buffers and memoryviews. This node is created during type
+ analysis from IndexNode and replaced later by the transform.
+
+ Attributes:
+ base - base node being indexed
+ indices - list of indexing expressions
+ """
+
+ subexprs = ['base', 'indices']
+
+ is_buffer_access = True
+
+ # Whether we're assigning to a buffer (in that case it needs to be
+ # writable)
+ writable_needed = False
+
+ def analyse_target_types(self, env):
+ self.analyse_types(env, getting=False)
+
+ def analyse_types(self, env, getting=True):
+ """
+ Analyse types for buffer indexing only. Overridden by memoryview
+ indexing and slicing subclasses
+ """
+ # self.indices are already analyzed
+
+ if not self.base.is_name:
+ error(self.pos, "Can only index buffer variables")
+ self.type = error_type
+ return
+
+ if not getting:
+ if not self.base.entry.type.writable:
+ error(self.pos, "Writing to readonly buffer")
+ else:
+ self.writable_needed = True
+ if self.base.type.is_buffer:
+ self.base.entry.buffer_aux.writable_needed = True
+
+ self.none_error_message = "'NoneType' object is not subscriptable"
+ self.analyse_buffer_index(env, getting)
+ self.wrap_in_nonecheck_node(env)
+
+ def analyse_buffer_index(self, env, getting):
+ self.base = self.base.coerce_to_simple(env)
+ self.type = self.base.type.dtype
+ self.buffer_type = self.base.type
+
+ if getting and self.type.is_pyobject:
+ self.is_temp = True
+
+ def analyse_assignment(self, rhs):
+ """
+ Called by IndexNode when this node is assigned to,
+ with the rhs of the assignment
+ """
+
+ def wrap_in_nonecheck_node(self, env):
+ if not env.directives['nonecheck'] or not self.base.may_be_none():
+ return
+
+ self.base = self.base.as_none_safe_node(self.none_error_message)
+
+ def is_simple(self):
+ return False
+
+ def nogil_check(self, env):
+ if self.is_buffer_access or self.is_memview_index:
+ if env.directives['boundscheck']:
+ warning(self.pos, "Use boundscheck(False) for faster access",
+ level=1)
+
+ if self.type.is_pyobject:
+ error(self.pos,
+ "Cannot access buffer with object dtype without gil")
+ self.type = error_type
+ return
+
+ def calculate_result_code(self):
+ return "(*%s)" % self.buffer_ptr_code
+
def buffer_entry(self):
import Buffer, MemoryView
base = self.base
if self.base.is_nonecheck:
base = base.arg
- if base.is_name:
- entry = base.entry
- else:
- # SimpleCallNode is_simple is not consistent with coerce_to_simple
- assert base.is_simple() or base.is_temp
- cname = base.result()
- entry = Symtab.Entry(cname, cname, self.base.type, self.base.pos)
-
- if entry.type.is_buffer:
- buffer_entry = Buffer.BufferEntry(entry)
- else:
- buffer_entry = MemoryView.MemoryViewSliceBufferEntry(entry)
-
- return buffer_entry
+ return base.type.get_entry(base)
def buffer_lookup_code(self, code):
"ndarray[1, 2, 3] and memslice[1, 2, 3]"
# Assign indices to temps
index_temps = [code.funcstate.allocate_temp(i.type, manage_ref=False)
- for i in self.indices]
+ for i in self.indices]
+
+ for temp, index in zip(index_temps, self.indices):
+ code.putln("%s = %s;" % (temp, index.result()))
+
+ # Generate buffer access code using these temps
+ import Buffer, MemoryView
+
+ buffer_entry = self.buffer_entry()
+
+ if buffer_entry.type.is_buffer:
+ negative_indices = buffer_entry.type.negative_indices
+ else:
+ negative_indices = Buffer.buffer_defaults['negative_indices']
+
+ return buffer_entry, Buffer.put_buffer_lookup_code(
+ entry=buffer_entry,
+ index_signeds=[i.type.signed for i in self.indices],
+ index_cnames=index_temps,
+ directives=code.globalstate.directives,
+ pos=self.pos, code=code,
+ negative_indices=negative_indices,
+ in_nogil_context=self.in_nogil_context)
+
+ def generate_assignment_code(self, rhs, code):
+ self.generate_subexpr_evaluation_code(code)
+ self.generate_buffer_setitem_code(rhs, code)
+ self.generate_subexpr_disposal_code(code)
+ self.free_subexpr_temps(code)
+ rhs.generate_disposal_code(code)
+ rhs.free_temps(code)
+
+ def generate_buffer_setitem_code(self, rhs, code, op=""):
+ # Used from generate_assignment_code and InPlaceAssignmentNode
+ buffer_entry, ptrexpr = self.buffer_lookup_code(code)
+
+ if self.buffer_type.dtype.is_pyobject:
+ # Must manage refcounts. Decref what is already there
+ # and incref what we put in.
+ ptr = code.funcstate.allocate_temp(buffer_entry.buf_ptr_type,
+ manage_ref=False)
+ rhs_code = rhs.result()
+ code.putln("%s = %s;" % (ptr, ptrexpr))
+ code.put_gotref("*%s" % ptr)
+ code.putln("__Pyx_INCREF(%s); __Pyx_DECREF(*%s);" % (
+ ptr, rhs_code
+ ))
+ code.putln("*%s %s= %s;" % (ptr, op, rhs_code))
+ code.put_giveref("*%s" % ptr)
+ code.funcstate.release_temp(ptr)
+ else:
+ # Simple case
+ code.putln("*%s %s= %s;" % (ptrexpr, op, rhs.result()))
+
+ def generate_result_code(self, code):
+ buffer_entry, self.buffer_ptr_code = self.buffer_lookup_code(code)
+ if self.type.is_pyobject:
+ # is_temp is True, so must pull out value and incref it.
+ code.putln("%s = *%s;" % (self.result(), self.buffer_ptr_code))
+ code.putln("__Pyx_INCREF((PyObject*)%s);" % self.result())
+
+
+class MemoryViewIndexNode(BufferIndexNode):
+
+ is_memview_index = True
+ is_buffer_access = False
+ warned_untyped_idx = False
+
+ type = None
+
+ def analyse_types(self, env, getting=True):
+ # memoryviewslice indexing or slicing
+ import MemoryView
+
+ if self.type:
+ return
+
+ skip_child_analysis = True
+ indices = self.indices
+
+ have_slices, indices, newaxes = MemoryView.unellipsify(
+ indices, self.base.type.ndim)
+
+ self.is_memview_index = (not newaxes and
+ len(indices) == self.base.type.ndim)
+ axes = []
+
+ index_type = PyrexTypes.c_py_ssize_t_type
+ new_indices = []
+
+ if len(indices) - len(newaxes) > self.base.type.ndim:
+ self.type = error_type
+ return error(self.indices[self.base.type.ndim].pos,
+ "Too many indices specified for type %s" %
+ self.base.type)
+
+ axis_idx = 0
+ for i, index in enumerate(indices[:]):
+ index.analyse_types(env)
+ if not index.is_none:
+ access, packing = self.base.type.axes[axis_idx]
+ axis_idx += 1
+
+ if isinstance(index, SliceNode):
+ self.is_memview_slice = True
+ if index.step.is_none:
+ axes.append((access, packing))
+ else:
+ axes.append((access, 'strided'))
+
+ # Coerce start, stop and step to temps of the right type
+ for attr in ('start', 'stop', 'step'):
+ value = getattr(index, attr)
+ if not value.is_none:
+ value = value.coerce_to(index_type, env)
+ #value = value.coerce_to_temp(env)
+ setattr(index, attr, value)
+ new_indices.append(value)
+
+ elif index.is_none:
+ self.is_memview_slice = True
+ new_indices.append(index)
+ axes.append(('direct', 'strided'))
+
+ elif index.type.is_int or index.type.is_pyobject:
+ if index.type.is_pyobject and not self.warned_untyped_idx:
+ warning(index.pos, "Index should be typed for more "
+ "efficient access", level=2)
+ IndexNode.warned_untyped_idx = True
+
+ self.is_memview_index = True
+ index = index.coerce_to(index_type, env)
+ indices[i] = index
+ new_indices.append(index)
+
+ else:
+ self.type = error_type
+ return error(index.pos, "Invalid index for memoryview specified")
+
+ self.is_memview_index = self.is_memview_index and not self.is_memview_slice
+ self.indices = new_indices
+ # All indices with all start/stop/step for slices.
+ # We need to keep this around
+ self.original_indices = indices
+ self.nogil = env.nogil
+
+ self.analyse_operation(env, getting, axes)
+ self.wrap_in_nonecheck_node(env)
+
+ def analyse_operation(self, env, getting, axes):
+ self.none_error_message = "Cannot index None memoryview slice"
+ self.analyse_buffer_index(env, getting)
+
+
+class MemoryViewSliceNode(MemoryViewIndexNode):
+
+ is_memview_slice = True
+
+ # No-op slicing operation, this node will be replaced
+ is_ellipsis_noop = False
+ is_memview_scalar_assignment = False
+ is_memview_index = False
+ is_memview_broadcast = False
+
+ def analyse_ellipsis_noop(self, env, getting):
+ "Slicing operations needing no evaluation, i.e. m[...] or m[:, :]"
+ self.is_ellipsis_noop = True
+ for index in self.indices:
+ self.is_ellipsis_noop = (self.is_ellipsis_noop and
+ isinstance(index, SliceNode) and
+ index.start.is_none and
+ index.stop.is_none and
+ index.step.is_none)
+
+ if self.is_ellipsis_noop:
+ self.type = self.base.type
+
+ def analyse_operation(self, env, getting, axes):
+ import MemoryView
+
+ if not getting:
+ self.is_memview_broadcast = True
+ self.none_error_message = "Cannot assign to None memoryview slice"
+ else:
+ self.none_error_message ="Cannot slice None memoryview slice"
+
+ self.analyse_ellipsis_noop(env, getting)
+ if self.is_ellipsis_noop:
+ return
+
+ self.index = None
+ self.is_temp = True
+ self.use_managed_ref = True
+
+ if not MemoryView.validate_axes(self.pos, axes):
+ self.type = error_type
+ return
+
+ self.type = PyrexTypes.MemoryViewSliceType(
+ self.base.type.dtype, axes)
+
+ if not (self.base.is_simple() or self.base.result_in_temp()):
+ self.base = self.base.coerce_to_simple(env)
+
+ def analyse_assignment(self, rhs):
+ if not rhs.type.is_memoryviewslice and (
+ self.type.dtype.assignable_from(rhs.type) or
+ rhs.type.is_pyobject):
+ # scalar assignment
+ return MemoryCopyScalar(self.pos, self)
+ else:
+ result = MemoryCopySlice(self.pos, self)
+ result.is_elemental = rhs.is_elemental
+ return result
- for temp, index in zip(index_temps, self.indices):
- code.putln("%s = %s;" % (temp, index.result()))
+ def is_simple(self):
+ if self.is_ellipsis_noop:
+ # Todo: fix SimpleCallNode.is_simple()
+ return self.base.is_simple() or self.base.result_in_temp()
- # Generate buffer access code using these temps
- import Buffer, MemoryView
+ return self.result_in_temp()
- buffer_entry = self.buffer_entry()
+ def calculate_result_code(self):
+ "This is called in case this is a no-op slicing node"
+ return self.base.result()
- if buffer_entry.type.is_buffer:
- negative_indices = buffer_entry.type.negative_indices
- else:
- negative_indices = Buffer.buffer_defaults['negative_indices']
+ def generate_result_code(self, code):
+ if self.is_ellipsis_noop:
+ return
- return buffer_entry, Buffer.put_buffer_lookup_code(
- entry=buffer_entry,
- index_signeds=[i.type.signed for i in self.indices],
- index_cnames=index_temps,
- directives=code.globalstate.directives,
- pos=self.pos, code=code,
- negative_indices=negative_indices,
- in_nogil_context=self.in_nogil_context)
-
- def put_memoryviewslice_slice_code(self, code):
- "memslice[:]"
buffer_entry = self.buffer_entry()
have_gil = not self.in_nogil_context
@@ -3277,6 +3389,7 @@ def next_(it):
else:
next_ = next
+ # Todo: this is insane, do it better
have_slices = False
it = iter(self.indices)
for index in self.original_indices:
@@ -3299,16 +3412,96 @@ def next_(it):
have_gil=have_gil,
have_slices=have_slices)
- def generate_memoryviewslice_setslice_code(self, rhs, code):
- "memslice1[...] = memslice2 or memslice1[:] = memslice2"
- import MemoryView
- MemoryView.copy_broadcast_memview_src_to_dst(rhs, self, code)
+class MemoryCopyNode(ExprNode):
+ """
+ Wraps a memoryview slice for slice assignment.
+
+ dst: destination mememoryview slice
+ """
+
+ subexprs = ['dst']
+
+ def __init__(self, pos, dst):
+ super(MemoryCopyNode, self).__init__(pos)
+ self.dst = dst
+ self.type = dst.type
+
+ def generate_assignment_code(self, rhs, code):
+ self.dst.generate_evaluation_code(code)
+ self._generate_assignment_code(rhs, code)
+ self.dst.generate_disposal_code(code)
+ rhs.generate_disposal_code(code)
+ rhs.free_temps(code)
+
+class MemoryCopySlice(MemoryCopyNode):
+ """
+ Copy the contents of slice src to slice dst. Does not support indirect
+ slices.
+
+ memslice1[...] = memslice2
+ memslice1[:] = memslice2
+ """
+
+ is_memview_copy_assignment = True
+ copy_slice_cname = "__pyx_memoryview_copy_contents"
+
+ def _generate_assignment_code(self, src, code):
+ dst = self.dst
+
+ src.type.assert_direct_dims(src.pos)
+ dst.type.assert_direct_dims(dst.pos)
- def generate_memoryviewslice_assign_scalar_code(self, rhs, code):
- "memslice1[...] = 0.0 or memslice1[:] = 0.0"
+ code.putln(code.error_goto_if_neg(
+ "%s(%s, %s, %d, %d, %d)" % (self.copy_slice_cname,
+ src.result(), dst.result(),
+ src.type.ndim, dst.type.ndim,
+ dst.type.dtype.is_pyobject),
+ dst.pos))
+
+class MemoryCopyScalar(MemoryCopyNode):
+ """
+ Assign a scalar to a slice. dst must be simple, scalar will be assigned
+ to a correct type and not just something assignable.
+
+ memslice1[...] = 0.0
+ memslice1[:] = 0.0
+ """
+
+ def __init__(self, pos, dst):
+ super(MemoryCopyScalar, self).__init__(pos, dst)
+ self.type = dst.type.dtype
+
+ def _generate_assignment_code(self, scalar, code):
import MemoryView
- MemoryView.assign_scalar(self, rhs, code)
+ self.dst.type.assert_direct_dims(self.dst.pos)
+
+ dtype = self.dst.type.dtype
+ type_decl = dtype.declaration_code("")
+ slice_decl = self.dst.type.declaration_code("")
+
+ code.begin_block()
+ code.putln("%s __pyx_temp_scalar = %s;" % (type_decl, scalar.result()))
+ if self.dst.result_in_temp() or self.dst.is_simple():
+ dst_temp = self.dst.result()
+ else:
+ code.putln("%s __pyx_temp_slice = %s;" % (slice_decl, self.dst.result()))
+ dst_temp = "__pyx_temp_slice"
+
+ slice_iter_obj = MemoryView.slice_iter(self.dst.type, dst_temp,
+ self.dst.type.ndim, code)
+ p = slice_iter_obj.start_loops()
+
+ if dtype.is_pyobject:
+ code.putln("Py_DECREF(*(PyObject **) %s);" % p)
+
+ code.putln("*((%s *) %s) = __pyx_temp_scalar;" % (type_decl, p))
+
+ if dtype.is_pyobject:
+ code.putln("Py_INCREF(__pyx_temp_scalar);")
+
+ slice_iter_obj.end_loops()
+ code.end_block()
class SliceIndexNode(ExprNode):
# 2-element slice indexing
@@ -3565,7 +3758,7 @@ class SliceNode(ExprNode):
# step ExprNode
subexprs = ['start', 'stop', 'step']
-
+ is_slice = True
type = py_object_type
is_temp = 1
@@ -3818,7 +4011,7 @@ def analyse_types(self, env):
def function_type(self):
# Return the type of the function being called, coercing a function
# pointer to a function if necessary. If the function has fused
- # arguments, return the specific type.
+ # arguments, return the specialized type.
func_type = self.function.type
if func_type.is_ptr:
@@ -3833,62 +4026,47 @@ def is_simple(self):
# sequence for a function call or comparing values.
return False
- def analyse_c_function_call(self, env):
- if self.function.type is error_type:
- self.type = error_type
- return
-
- if self.function.type.is_cpp_class:
- overloaded_entry = self.function.type.scope.lookup("operator()")
- if overloaded_entry is None:
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
- return
- elif hasattr(self.function, 'entry'):
- overloaded_entry = self.function.entry
- elif (isinstance(self.function, IndexNode) and
- self.function.is_fused_index):
- overloaded_entry = self.function.type.entry
+ def _analyse_overloaded_entry(self, env, overloaded_entry):
+ if self.function.type.is_fused:
+ functypes = self.function.type.get_all_specialized_function_types()
+ alternatives = [f.entry for f in functypes]
else:
- overloaded_entry = None
+ alternatives = overloaded_entry.all_alternatives()
- if overloaded_entry:
- if self.function.type.is_fused:
- functypes = self.function.type.get_all_specialized_function_types()
- alternatives = [f.entry for f in functypes]
- else:
- alternatives = overloaded_entry.all_alternatives()
+ entry = PyrexTypes.best_match(self.args, alternatives, self.pos, env)
- entry = PyrexTypes.best_match(self.args, alternatives, self.pos, env)
+ if not entry:
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
+ return None
- if not entry:
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
- return
+ entry.used = True
+ self.function.entry = entry
+ self.function.type = entry.type
+ func_type = self.function_type()
+ return func_type
- entry.used = True
- self.function.entry = entry
- self.function.type = entry.type
- func_type = self.function_type()
- else:
- func_type = self.function_type()
- if not func_type.is_cfunction:
- error(self.pos, "Calling non-function type '%s'" % func_type)
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
- return
- # Check no. of args
- max_nargs = len(func_type.args)
- expected_nargs = max_nargs - func_type.optional_arg_count
- actual_nargs = len(self.args)
- if func_type.optional_arg_count and expected_nargs != actual_nargs:
- self.has_optional_args = 1
- self.is_temp = 1
- # Coerce arguments
+ def _coerce_arguments(self, actual_nargs, env, func_type, max_nargs):
+ "Coerce arguments for a native function call"
some_args_in_temps = False
+ self.elemental_args = []
for i in xrange(min(max_nargs, actual_nargs)):
formal_type = func_type.args[i].type
- arg = self.args[i].coerce_to(formal_type, env)
+ arg = self.args[i]
+ if (arg.type.is_memoryviewslice and not
+ formal_type.is_memoryviewslice and
+ formal_type.assignable_from(arg.type.dtype)):
+ # elemental function call, this node will be replaced later
+ self.is_elemental = True
+ self.elemental_args.append(arg)
+ if not isinstance(self.function, NameNode):
+ error(self.function.pos,
+ "Function must be a C function name")
+ self.type = error_type
+ return
+ else:
+ arg = arg.coerce_to(formal_type, env)
+
if arg.is_temp:
if i > 0:
# first argument in temp doesn't impact subsequent arguments
@@ -3909,6 +4087,7 @@ def analyse_c_function_call(self, env):
some_args_in_temps = True
arg = arg.coerce_to_temp(env)
self.args[i] = arg
+
# handle additional varargs parameters
for i in xrange(max_nargs, actual_nargs):
arg = self.args[i]
@@ -3921,6 +4100,7 @@ def analyse_c_function_call(self, env):
self.args[i] = arg = arg.coerce_to(arg_ctype, env)
if arg.is_temp and i > 0:
some_args_in_temps = True
+
if some_args_in_temps:
# if some args are temps and others are not, they may get
# constructed in the wrong order (temps first) => make
@@ -3946,15 +4126,75 @@ def analyse_c_function_call(self, env):
#self.args[i] = arg.coerce_to_temp(env)
# instead: issue a warning
if i > 0 or i == 1 and self.self is not None: # skip first arg
- warning(arg.pos, "Argument evaluation order in C function call is undefined and may not be as expected", 0)
+ warning(arg.pos,
+ "Argument evaluation order in C function call "
+ "is undefined and may not be as expected", 0)
break
+ def _analyse_elemental_call(self, func_type):
+ if self.has_optional_args:
+ error(self.pos, "Default argument calls not supported for "
+ "elemental function calls")
+ self.type = error_type
+ else:
+ # Fake a memoryview return type. This type won't be used directly,
+ # but rather the types of the elemental arguments will be used
+ max_ndim = max(arg.type.ndim for arg in self.elemental_args)
+ dtype = self.elemental_args[0].type.dtype
+ return_type = PyrexTypes.MemoryViewSliceType(
+ dtype, [('direct', 'strided')] * max_ndim)
+ # func_type.return_type = return_type
+ self.type = return_type
+
+ def analyse_c_function_call(self, env):
+ if self.function.type is error_type:
+ self.type = error_type
+ return
+
+ if self.function.type.is_cpp_class:
+ overloaded_entry = self.function.type.scope.lookup("operator()")
+ if overloaded_entry is None:
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
+ return
+ elif hasattr(self.function, 'entry'):
+ overloaded_entry = self.function.entry
+ elif self.function.is_index and self.function.is_fused_index:
+ overloaded_entry = self.function.type.entry
+ else:
+ overloaded_entry = None
+
+ if overloaded_entry:
+ func_type = self._analyse_overloaded_entry(env, overloaded_entry)
+ if func_type is None:
+ return
+ else:
+ func_type = self.function_type()
+ if not func_type.is_cfunction:
+ error(self.pos, "Calling non-function type '%s'" % func_type)
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
+ return
+
+ # Check no. of args
+ max_nargs = len(func_type.args)
+ expected_nargs = max_nargs - func_type.optional_arg_count
+ actual_nargs = len(self.args)
+ if func_type.optional_arg_count and expected_nargs != actual_nargs:
+ self.has_optional_args = 1
+ self.is_temp = 1
+
+ self._coerce_arguments(actual_nargs, env, func_type, max_nargs)
+
# Calc result type and code fragment
if isinstance(self.function, NewExprNode):
self.type = PyrexTypes.CPtrType(self.function.class_type)
else:
self.type = func_type.return_type
+ if self.is_elemental:
+ self._analyse_elemental_call(func_type)
+
if self.function.is_name or self.function.is_attribute:
if self.function.entry and self.function.entry.utility_code:
self.is_temp = 1 # currently doesn't work for self.calculate_result_code()
@@ -4582,7 +4822,7 @@ def analyse_attribute(self, env, obj_type = None):
self.is_memslice_transpose = True
self.is_temp = True
self.use_managed_ref = True
- self.type = self.obj.type
+ self.type = self.obj.type.transpose(self.pos)
return
else:
obj_type.declare_attribute(self.attribute, env, self.pos)
@@ -4664,9 +4904,6 @@ def wrap_obj_in_nonecheck(self, env):
def nogil_check(self, env):
if self.is_py_attr:
self.gil_error()
- elif self.type.is_memoryviewslice:
- import MemoryView
- MemoryView.err_if_nogil_initialized_check(self.pos, env, 'attribute')
gil_message = "Accessing Python attribute"
@@ -7001,9 +7238,39 @@ def analyse_types(self, env):
self.is_temp = 1
elif self.is_cpp_operation():
self.analyse_cpp_operation(env)
+ elif self.operand.type.is_memoryviewslice:
+ self.analyse_memoryview_operation(env)
else:
self.analyse_c_operation(env)
+ def type_error(self):
+ if not self.operand.type.is_error:
+ error(self.pos, "Invalid operand type for '%s' (%s)" %
+ (self.operator, self.operand.type))
+ self.type = PyrexTypes.error_type
+
+ def analyse_cpp_operation(self, env):
+ type = self.operand.type
+ if type.is_ptr:
+ type = type.base_type
+ function = type.scope.lookup("operator%s" % self.operator)
+ if not function:
+ error(self.pos, "'%s' operator not defined for %s"
+ % (self.operator, type))
+ self.type_error()
+ return
+ func_type = function.type
+ if func_type.is_ptr:
+ func_type = func_type.base_type
+ self.type = func_type.return_type
+
+ def analyse_memoryview_operation(self, env):
+ if self.operator not in elementwise_unop_operators:
+ self.error("Operator must be elementwise (got '%s'" % self.operator)
+ else:
+ self.type = self.operand.type
+ self.is_elemental = True
+
def check_const(self):
return self.operand.check_const()
@@ -7035,28 +7302,6 @@ def generate_py_operation_code(self, code):
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
- def type_error(self):
- if not self.operand.type.is_error:
- error(self.pos, "Invalid operand type for '%s' (%s)" %
- (self.operator, self.operand.type))
- self.type = PyrexTypes.error_type
-
- def analyse_cpp_operation(self, env):
- type = self.operand.type
- if type.is_ptr:
- type = type.base_type
- function = type.scope.lookup("operator%s" % self.operator)
- if not function:
- error(self.pos, "'%s' operator not defined for %s"
- % (self.operator, type))
- self.type_error()
- return
- func_type = function.type
- if func_type.is_ptr:
- func_type = func_type.base_type
- self.type = func_type.return_type
-
-
class NotNode(ExprNode):
# 'not' operator
#
@@ -7240,11 +7485,6 @@ def analyse_types(self, env):
def check_const(self):
return self.operand.check_const_addr()
- def error(self, mess):
- error(self.pos, mess)
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
-
def calculate_result_code(self):
return "(&%s)" % self.operand.result()
@@ -7395,6 +7635,34 @@ def generate_result_code(self, code):
self.operand.result()))
code.put_incref(self.result(), self.ctype())
+class FormatStringNode(ExprNode):
+ """
+ Create a format string for a cython.view.array.
+
+ Attributes:
+ dtype
+ """
+
+ subexprs = []
+
+ def analyse_types(self, env):
+ self.type = py_object_type
+ self.is_temp = True
+ load_cython_array_utilities(env)
+
+ def generate_result_code(self, code):
+ import Buffer
+ type_info = Buffer.get_type_information_cname(code, self.dtype)
+ code.putln("%s = __pyx_format_from_typeinfo(&%s);" %
+ (self.result(), type_info))
+ err = "!%s || !PyBytes_AsString(%s)" % (self.result(), self.result())
+ code.putln(code.error_goto_if(err, self.pos))
+ code.put_gotref(self.result())
+
+def load_cython_array_utilities(env):
+ import MemoryView
+ MemoryView.use_cython_array_utility_code(env)
+ env.use_utility_code(MemoryView.typeinfo_to_format_code)
ERR_START = "Start may not be given"
ERR_NOT_STOP = "Stop must be provided to indicate shape"
@@ -7424,9 +7692,10 @@ class CythonArrayNode(ExprNode):
base_type_node MemoryViewSliceTypeNode the cast expression node
"""
- subexprs = ['operand', 'shapes']
+ subexprs = ['format_string', 'operand', 'shapes']
shapes = None
+ format_string = None
is_temp = True
mode = "c"
array_dtype = None
@@ -7443,8 +7712,6 @@ def analyse_types(self, env):
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.shapes = []
ndim = len(axes)
@@ -7473,6 +7740,9 @@ def analyse_types(self, env):
"Expected %d dimensions, array has %d dimensions" %
(ndim, len(array_dimension_sizes)))
+ self.format_string = FormatStringNode(self.pos, dtype=array_dtype)
+ self.format_string.analyse_types(env)
+
# 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
@@ -7524,9 +7794,9 @@ def analyse_types(self, env):
axes[-1] = ('direct', 'contig')
self.coercion_type = PyrexTypes.MemoryViewSliceType(array_dtype, axes)
+ self.coercion_type.validate_memslice_dtype(self.pos)
self.type = self.get_cython_array_type(env)
- MemoryView.use_cython_array_utility_code(env)
- env.use_utility_code(MemoryView.typeinfo_to_format_code)
+ load_cython_array_utilities(env)
def allocate_temp_result(self, code):
if self.temp_code:
@@ -7538,7 +7808,9 @@ def infer_type(self, env):
return self.get_cython_array_type(env)
def get_cython_array_type(self, env):
- return env.global_scope().context.cython_scope.viewscope.lookup("array").type
+ from Cython.Compiler import CythonScope
+ cython_scope = CythonScope.get_cython_scope(env)
+ return cython_scope.viewscope.lookup("array").type
def generate_result_code(self, code):
import Buffer
@@ -7547,34 +7819,25 @@ def generate_result_code(self, code):
for shape in self.shapes]
dtype = self.coercion_type.dtype
- shapes_temp = code.funcstate.allocate_temp(py_object_type, True)
- format_temp = code.funcstate.allocate_temp(py_object_type, True)
-
- itemsize = "sizeof(%s)" % dtype.declaration_code("")
- type_info = Buffer.get_type_information_cname(code, dtype)
-
if self.operand.type.is_ptr:
code.putln("if (!%s) {" % self.operand.result())
code.putln( 'PyErr_SetString(PyExc_ValueError,'
'"Cannot create cython.array from NULL pointer");')
code.putln(code.error_goto(self.operand.pos))
code.putln("}")
- code.putln("%s = __pyx_format_from_typeinfo(&%s);" %
- (format_temp, type_info))
+ shapes_temp = code.funcstate.allocate_temp(py_object_type, True)
buildvalue_fmt = " __PYX_BUILD_PY_SSIZE_T " * len(shapes)
code.putln('%s = Py_BuildValue("(" %s ")", %s);' % (shapes_temp,
buildvalue_fmt,
", ".join(shapes)))
- err = "!%s || !%s || !PyBytes_AsString(%s)" % (format_temp,
- shapes_temp,
- format_temp)
- code.putln(code.error_goto_if(err, self.pos))
- code.put_gotref(format_temp)
+ code.putln(code.error_goto_if("!%s" % shapes_temp, self.pos))
code.put_gotref(shapes_temp)
- tup = (self.result(), shapes_temp, itemsize, format_temp,
+ tup = (self.result(), shapes_temp,
+ "sizeof(%s)" % dtype.declaration_code(""),
+ self.format_string.result(),
self.mode, self.operand.result())
code.putln('%s = __pyx_array_new('
'%s, %s, PyBytes_AS_STRING(%s), '
@@ -7587,7 +7850,6 @@ def dispose(temp):
code.funcstate.release_temp(temp)
dispose(shapes_temp)
- dispose(format_temp)
@classmethod
def from_carray(cls, src_node, env):
@@ -7761,6 +8023,29 @@ def _not_in(x, seq):
'not_in': _not_in,
}
+elementwise_operators = {
+ '<': operator.lt,
+ '<=': operator.le,
+ '==': operator.eq,
+ '!=': operator.ne,
+ '>=': operator.ge,
+ '>': operator.gt,
+ '+': operator.add,
+ '&': operator.and_,
+ '/': operator.truediv,
+ '//': operator.floordiv,
+ '<<': operator.lshift,
+ '%': operator.mod,
+ '*': operator.mul,
+ '|': operator.or_,
+ '**': operator.pow,
+ '>>': operator.rshift,
+ '-': operator.sub,
+ '^': operator.xor,
+}
+
+elementwise_unop_operators = set(('+', '-', '~'))
+
def get_compile_time_binop(node):
func = compile_time_binary_operators.get(node.operator)
if not func:
@@ -7818,6 +8103,8 @@ def analyse_operation(self, env):
self.is_temp = 1
elif self.is_cpp_operation():
self.analyse_cpp_operation(env)
+ elif self.is_memoryview_operation():
+ self.analyse_memoryview_operation(env)
else:
self.analyse_c_operation(env)
@@ -7831,6 +8118,10 @@ def is_cpp_operation(self):
return (self.operand1.type.is_cpp_class
or self.operand2.type.is_cpp_class)
+ def is_memoryview_operation(self):
+ return (self.operand1.type.is_memoryviewslice or
+ self.operand2.type.is_memoryviewslice)
+
def analyse_cpp_operation(self, env):
type1 = self.operand1.type
type2 = self.operand2.type
@@ -7848,6 +8139,41 @@ def analyse_cpp_operation(self, env):
self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env)
self.type = func_type.return_type
+ def analyse_memoryview_operation(self, env):
+ type1 = self.operand1.type
+ type2 = self.operand2.type
+ ndim1 = ndim2 = 0
+ dtype1 = type1
+ dtype2 = type2
+
+ if self.operator not in elementwise_operators:
+ error(self.pos, "operator must be element-wise")
+ self.type = error_type
+ return
+
+ if type1.is_memoryviewslice:
+ type1.assert_direct_dims(self.pos)
+ ndim1 = type1.ndim
+ dtype1 = type1.dtype
+
+ if type2.is_memoryviewslice:
+ type2.assert_direct_dims(self.pos)
+ ndim2 = type2.ndim
+ dtype2 = type2.dtype
+
+ ndim = max(ndim1, ndim2)
+ dtype = self.result_type(dtype1, dtype2)
+ if dtype is None:
+ if dtype1.same_as(dtype2):
+ dtype = dtype1
+ else:
+ error(self.pos,
+ "Unsupported types in binary operation (%s, %s)" %
+ (dtype1, dtype2))
+ axes = [('direct', 'strided')] * ndim
+ self.type = PyrexTypes.MemoryViewSliceType(dtype, axes)
+ self.is_elemental = True
+
def result_type(self, type1, type2):
if self.is_py_operation_types(type1, type2):
if type2.is_string:
@@ -8128,7 +8454,7 @@ def analyse_operation(self, env):
else:
self.ctruedivision = self.truedivision
NumBinopNode.analyse_operation(self, env)
- if self.is_cpp_operation():
+ if self.is_cpp_operation() or self.is_elemental:
self.cdivision = True
if not self.type.is_pyobject:
self.zerodivision_check = (
@@ -9369,9 +9695,9 @@ def __init__(self, arg, env, type=py_object_type):
# FIXME: check that the target type and the resulting type are compatible
pass
- if arg.type.is_memoryviewslice:
- # Register utility codes at this point
- arg.type.get_to_py_function(env, arg)
+ if self.arg.type.is_memoryviewslice:
+ # register utility codes for conversion to/from the memoryview dtype
+ self.arg.type.dtype_object_conversion_funcs(env)
self.env = env
@@ -9555,6 +9881,9 @@ def __init__(self, arg, dst_type, env):
CoercionNode.__init__(self, arg)
dst_type.create_declaration_utility_code(env)
+ def analyse_types(self, env):
+ self.arg.analyse_types(env)
+
def calculate_result_code(self):
if self.arg.type.is_complex:
real_part = "__Pyx_CREAL(%s)" % self.arg.result()
@@ -9622,13 +9951,13 @@ class ProxyNode(CoercionNode):
def __init__(self, arg):
super(ProxyNode, self).__init__(arg)
- self._proxy_type()
+ self.proxy_type()
- def analyse_expressions(self, env):
- self.arg.analyse_expressions(env)
- self._proxy_type()
+ def analyse_types(self, env):
+ self.arg.analyse_types(env)
+ self.proxy_type()
- def _proxy_type(self):
+ def proxy_type(self):
if hasattr(self.arg, 'type'):
self.type = self.arg.type
self.result_ctype = self.arg.result_ctype
@@ -9659,6 +9988,10 @@ def generate_disposal_code(self, code):
def free_temps(self, code):
self.arg.free_temps(code)
+ def generate_assignment_code(self, rhs, code):
+ self.arg.generate_assignment_code(rhs, code)
+
+
class CloneNode(CoercionNode):
# This node is employed when the result of another node needs
# to be used multiple times. The argument node's result must
@@ -9671,6 +10004,8 @@ class CloneNode(CoercionNode):
nogil_check = None
def __init__(self, arg):
+ if arg.is_index and arg.replacement_node:
+ arg = arg.replacement_node
CoercionNode.__init__(self, arg)
if hasattr(arg, 'type'):
self.type = arg.type
@@ -9697,6 +10032,9 @@ def analyse_types(self, env):
if hasattr(self.arg, 'entry'):
self.entry = self.arg.entry
+ def result_in_temp(self):
+ return True
+
def is_simple(self):
return True # result is always in a temp (or a name)
View
2 Cython/Compiler/FusedNode.py
@@ -184,7 +184,7 @@ def _specialize_function_args(self, args, fused_to_specific):
if arg.type.is_fused:
arg.type = arg.type.specialize(fused_to_specific)
if arg.type.is_memoryviewslice:
- MemoryView.validate_memslice_dtype(arg.pos, arg.type.dtype)
+ arg.type.validate_memslice_dtype(arg.pos)
def create_new_local_scope(self, node, env, f2s):
"""
View
197 Cython/Compiler/MemoryView.py
@@ -4,9 +4,7 @@
import Options
from Code import UtilityCode, TempitaUtilityCode
from UtilityCode import CythonUtilityCode
-import Buffer
-import PyrexTypes
-import ModuleNode
+from Cython.Compiler import Buffer, PyrexTypes, ModuleNode, Symtab
START_ERR = "Start must not be given."
STOP_ERR = "Axis specification only allowed in the 'step' slot."
@@ -19,12 +17,6 @@
ERR_UNINITIALIZED = ("Cannot check if memoryview %s is initialized without the "
"GIL, consider using initializedcheck(False)")
-def err_if_nogil_initialized_check(pos, env, name='variable'):
- "This raises an exception at runtime now"
- pass
- #if env.nogil and env.directives['initializedcheck']:
- #error(pos, ERR_UNINITIALIZED % name)
-
def concat_flags(*flags):