Commit 305e355c authored by Mark Florisson's avatar Mark Florisson

Merge fixes from fused_cdef

parents e2bd21ab ca393626
......@@ -8,6 +8,7 @@ Cython/Runtime/refnanny.c
BUILD/
build/
!tests/build/
dist/
.gitrev
.coverage
......
......@@ -11,6 +11,7 @@ Cython/Runtime/refnanny.c
BUILD/
build/
dist/
.git/
.gitrev
.coverage
*.orig
......
......@@ -30,7 +30,7 @@ class TestInline(CythonTest):
self.assertEquals(inline("""
cimport cython
return cython.typeof(a), cython.typeof(b)
""", a=1.0, b=[], **self.test_kwds), ('double', 'list object'))
""", a=1.0, b=[], **self.test_kwds), ('double', 'list'))
def test_locals(self):
a = 1
......
......@@ -85,12 +85,10 @@ def parse_command_line(args):
options.use_listing_file = 1
elif option in ("-+", "--cplus"):
options.cplus = 1
elif option.startswith("--embed"):
ix = option.find('=')
if ix == -1:
Options.embed = "main"
else:
Options.embed = option[ix+1:]
elif option == "--embed":
Options.embed = "main"
elif option.startswith("--embed="):
Options.embed = options[8:]
elif option.startswith("-I"):
options.include_path.append(get_param(option))
elif option == "--include-dir":
......
......@@ -35,6 +35,7 @@ cdef class FunctionState:
cdef public size_t temp_counter
cdef public object closure_temps
cdef public bint should_declare_error_indicator
@cython.locals(n=size_t)
cpdef new_label(self, name=*)
......
......@@ -134,6 +134,12 @@ class FunctionState(object):
self.temp_counter = 0
self.closure_temps = None
# This is used for the error indicator, which needs to be local to the
# function. It used to be global, which relies on the GIL being held.
# However, exceptions may need to be propagated through 'nogil'
# sections, in which case we introduce a race condition.
self.should_declare_error_indicator = False
# labels
def new_label(self, name=None):
......@@ -1166,39 +1172,19 @@ class CCodeWriter(object):
self.funcstate.use_label(lbl)
self.putln("goto %s;" % lbl)
def put_var_declarations(self, entries, static = 0, dll_linkage = None,
definition = True):
for entry in entries:
if not entry.in_cinclude:
self.put_var_declaration(entry, static, dll_linkage, definition)
def put_var_declaration(self, entry, static = 0, dll_linkage = None,
definition = True):
def put_var_declaration(self, entry, storage_class="",
dll_linkage = None, definition = True):
#print "Code.put_var_declaration:", entry.name, "definition =", definition ###
if entry.in_closure:
if entry.visibility == 'private' and not (definition or entry.defined_in_pxd):
#print "...private and not definition, skipping", entry.cname ###
return
visibility = entry.visibility
if visibility == 'private' and not definition:
#print "...private and not definition, skipping" ###
if entry.visibility == "private" and not entry.used:
#print "...private and not used, skipping", entry.cname ###
return
if not entry.used and visibility == "private":
#print "not used and private, skipping", entry.cname ###
return
storage_class = ""
if visibility == 'extern':
storage_class = Naming.extern_c_macro
elif visibility == 'public':
if not definition:
storage_class = Naming.extern_c_macro
elif visibility == 'private':
if static:
storage_class = "static"
if storage_class:
self.put("%s " % storage_class)
if visibility != 'public':
dll_linkage = None
self.put(entry.type.declaration_code(entry.cname,
dll_linkage = dll_linkage))
self.put(entry.type.declaration_code(
entry.cname, dll_linkage = dll_linkage))
if entry.init is not None:
self.put_safe(" = %s" % entry.type.literal_code(entry.init))
self.putln(";")
......@@ -1382,6 +1368,52 @@ class CCodeWriter(object):
doc_code,
term))
# GIL methods
def put_ensure_gil(self, declare_gilstate=True):
"""
Acquire the GIL. The generated code is safe even when no PyThreadState
has been allocated for this thread (for threads not initialized by
using the Python API). Additionally, the code generated by this method
may be called recursively.
"""
from Cython.Compiler import Nodes
self.globalstate.use_utility_code(Nodes.force_init_threads_utility_code)
self.putln("#ifdef WITH_THREAD")
if declare_gilstate:
self.put("PyGILState_STATE ")
self.putln("_save = PyGILState_Ensure();")
self.putln("#endif")
def put_release_ensured_gil(self):
"""
Releases the GIL, corresponds to `put_ensure_gil`.
"""
self.putln("#ifdef WITH_THREAD")
self.putln("PyGILState_Release(_save);")
self.putln("#endif")
def put_acquire_gil(self):
"""
Acquire the GIL. The thread's thread state must have been initialized
by a previous `put_release_gil`
"""
self.putln("Py_BLOCK_THREADS")
def put_release_gil(self):
"Release the GIL, corresponds to `put_acquire_gil`."
self.putln("#ifdef WITH_THREAD")
self.putln("PyThreadState *_save = NULL;")
self.putln("#endif")
self.putln("Py_UNBLOCK_THREADS")
def declare_gilstate(self):
self.putln("#ifdef WITH_THREAD")
self.putln("PyGILState_STATE _save;")
self.putln("#endif")
# error handling
def put_error_if_neg(self, pos, value):
......@@ -1389,10 +1421,12 @@ class CCodeWriter(object):
return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos)))
def set_error_info(self, pos):
self.funcstate.should_declare_error_indicator = True
if self.c_line_in_traceback:
cinfo = " %s = %s;" % (Naming.clineno_cname, Naming.line_c_macro)
else:
cinfo = ""
return "%s = %s[%s]; %s = %s;%s" % (
Naming.filename_cname,
Naming.filetable_cname,
......@@ -1432,6 +1466,20 @@ class CCodeWriter(object):
def put_finish_refcount_context(self):
self.putln("__Pyx_RefNannyFinishContext();")
def put_add_traceback(self, qualified_name):
"""
Build a Python traceback for propagating exceptions.
qualified_name should be the qualified name of the function
"""
format_tuple = (
qualified_name,
Naming.clineno_cname,
Naming.lineno_cname,
Naming.filename_cname,
)
self.putln('__Pyx_AddTraceback("%s", %s, %s, %s);' % format_tuple)
def put_trace_declarations(self):
self.putln('__Pyx_TraceDeclarations');
......@@ -1444,6 +1492,10 @@ class CCodeWriter(object):
def put_trace_return(self, retvalue_cname):
self.putln("__Pyx_TraceReturn(%s);" % retvalue_cname)
def putln_openmp(self, string):
self.putln("#ifdef _OPENMP")
self.putln(string)
self.putln("#endif /* _OPENMP */")
class PyrexCodeWriter(object):
# f file output file
......
......@@ -1006,7 +1006,8 @@ class BytesNode(ConstNode):
def coerce_to_boolean(self, env):
# This is special because testing a C char* for truth directly
# would yield the wrong result.
return BoolNode(self.pos, value=bool(self.value))
bool_value = bool(self.value)
return BoolNode(self.pos, value=bool_value, constant_result=bool_value)
def coerce_to(self, dst_type, env):
if self.type == dst_type:
......@@ -1809,14 +1810,14 @@ class ImportNode(ExprNode):
class IteratorNode(ExprNode):
# Used as part of for statement implementation.
#
# allocate_counter_temp/release_counter_temp needs to be called
# by parent (ForInStatNode)
#
# Implements result = iter(sequence)
#
# sequence ExprNode
type = py_object_type
iter_func_ptr = None
counter_cname = None
reversed = False # currently only used for list/tuple types (see Optimize.py)
subexprs = ['sequence']
......@@ -1835,42 +1836,120 @@ class IteratorNode(ExprNode):
gil_message = "Iterating over Python object"
def allocate_counter_temp(self, code):
self.counter_cname = code.funcstate.allocate_temp(
PyrexTypes.c_py_ssize_t_type, manage_ref=False)
def release_counter_temp(self, code):
code.funcstate.release_temp(self.counter_cname)
_func_iternext_type = PyrexTypes.CPtrType(PyrexTypes.CFuncType(
PyrexTypes.py_object_type, [
PyrexTypes.CFuncTypeArg("it", PyrexTypes.py_object_type, None),
]))
def generate_result_code(self, code):
if self.sequence.type.is_array or self.sequence.type.is_ptr:
sequence_type = self.sequence.type
if sequence_type.is_array or sequence_type.is_ptr:
raise InternalError("for in carray slice not transformed")
is_builtin_sequence = self.sequence.type is list_type or \
self.sequence.type is tuple_type
may_be_a_sequence = not self.sequence.type.is_builtin_type
if may_be_a_sequence:
is_builtin_sequence = sequence_type is list_type or \
sequence_type is tuple_type
if not is_builtin_sequence:
# reversed() not currently optimised (see Optimize.py)
assert not self.reversed, "internal error: reversed() only implemented for list/tuple objects"
self.may_be_a_sequence = not sequence_type.is_builtin_type
if self.may_be_a_sequence:
code.putln(
"if (PyList_CheckExact(%s) || PyTuple_CheckExact(%s)) {" % (
self.sequence.py_result(),
self.sequence.py_result()))
if is_builtin_sequence or may_be_a_sequence:
if is_builtin_sequence or self.may_be_a_sequence:
self.counter_cname = code.funcstate.allocate_temp(
PyrexTypes.c_py_ssize_t_type, manage_ref=False)
if self.reversed:
if sequence_type is list_type:
init_value = 'PyList_GET_SIZE(%s) - 1' % self.result()
else:
init_value = 'PyTuple_GET_SIZE(%s) - 1' % self.result()
else:
init_value = '0'
code.putln(
"%s = 0; %s = %s; __Pyx_INCREF(%s);" % (
self.counter_cname,
"%s = %s; __Pyx_INCREF(%s); %s = %s;" % (
self.result(),
self.sequence.py_result(),
self.result()))
self.result(),
self.counter_cname,
init_value
))
if not is_builtin_sequence:
if may_be_a_sequence:
self.iter_func_ptr = code.funcstate.allocate_temp(self._func_iternext_type, manage_ref=False)
if self.may_be_a_sequence:
code.putln("%s = NULL;" % self.iter_func_ptr)
code.putln("} else {")
code.putln("%s = -1; %s = PyObject_GetIter(%s); %s" % (
self.counter_cname,
code.put("%s = -1; " % self.counter_cname)
code.putln("%s = PyObject_GetIter(%s); %s" % (
self.result(),
self.sequence.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
if may_be_a_sequence:
code.putln("}")
code.putln("%s = Py_TYPE(%s)->tp_iternext;" % (self.iter_func_ptr, self.py_result()))
if self.may_be_a_sequence:
code.putln("}")
def generate_next_sequence_item(self, test_name, result_name, code):
assert self.counter_cname, "internal error: counter_cname temp not prepared"
code.putln(
"if (%s >= Py%s_GET_SIZE(%s)) break;" % (
self.counter_cname,
test_name,
self.py_result()))
if self.reversed:
inc_dec = '--'
else:
inc_dec = '++'
code.putln(
"%s = Py%s_GET_ITEM(%s, %s); __Pyx_INCREF(%s); %s%s;" % (
result_name,
test_name,
self.py_result(),
self.counter_cname,
result_name,
self.counter_cname,
inc_dec))
def generate_iter_next_result_code(self, result_name, code):
sequence_type = self.sequence.type
if self.reversed:
code.putln("if (%s < 0) break;" % self.counter_cname)
if sequence_type is list_type:
self.generate_next_sequence_item('List', result_name, code)
return
elif sequence_type is tuple_type:
self.generate_next_sequence_item('Tuple', result_name, code)
return
if self.may_be_a_sequence:
for test_name in ('List', 'Tuple'):
code.putln("if (Py%s_CheckExact(%s)) {" % (test_name, self.py_result()))
self.generate_next_sequence_item(test_name, result_name, code)
code.put("} else ")
code.putln("{")
code.putln(
"%s = %s(%s);" % (
result_name,
self.iter_func_ptr,
self.py_result()))
code.putln("if (unlikely(!%s)) {" % result_name)
code.putln("if (PyErr_Occurred()) {")
code.putln("if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) PyErr_Clear();")
code.putln("else %s" % code.error_goto(self.pos))
code.putln("}")
code.putln("break;")
code.putln("}")
code.put_gotref(result_name)
code.putln("}")
def free_temps(self, code):
if self.counter_cname:
code.funcstate.release_temp(self.counter_cname)
if self.iter_func_ptr:
code.funcstate.release_temp(self.iter_func_ptr)
self.iter_func_ptr = None
ExprNode.free_temps(self, code)
class NextNode(AtomicExprNode):
......@@ -1879,11 +1958,11 @@ class NextNode(AtomicExprNode):
# Created during analyse_types phase.
# The iterator is not owned by this node.
#
# iterator ExprNode
# iterator IteratorNode
type = py_object_type
def __init__(self, iterator, env):
def __init__(self, iterator):
self.pos = iterator.pos
self.iterator = iterator
if iterator.type.is_ptr or iterator.type.is_array:
......@@ -1891,51 +1970,7 @@ class NextNode(AtomicExprNode):
self.is_temp = 1
def generate_result_code(self, code):
sequence_type = self.iterator.sequence.type
if sequence_type is list_type:
type_checks = [(list_type, "List")]
elif sequence_type is tuple_type:
type_checks = [(tuple_type, "Tuple")]
elif not sequence_type.is_builtin_type:
type_checks = [(list_type, "List"), (tuple_type, "Tuple")]
else:
type_checks = []
for py_type, prefix in type_checks:
if len(type_checks) > 1:
code.putln(
"if (likely(Py%s_CheckExact(%s))) {" % (
prefix, self.iterator.py_result()))
code.putln(
"if (%s >= Py%s_GET_SIZE(%s)) break;" % (
self.iterator.counter_cname,
prefix,
self.iterator.py_result()))
code.putln(
"%s = Py%s_GET_ITEM(%s, %s); __Pyx_INCREF(%s); %s++;" % (
self.result(),
prefix,
self.iterator.py_result(),
self.iterator.counter_cname,
self.result(),
self.iterator.counter_cname))
if len(type_checks) > 1:
code.put("} else ")
if len(type_checks) == 1:
return
code.putln("{")
code.putln(
"%s = PyIter_Next(%s);" % (
self.result(),
self.iterator.py_result()))
code.putln(
"if (!%s) {" %
self.result())
code.putln(code.error_goto_if_PyErr(self.pos))
code.putln("break;")
code.putln("}")
code.put_gotref(self.py_result())
code.putln("}")
self.iterator.generate_iter_next_result_code(self.result(), code)
class WithExitCallNode(ExprNode):
......@@ -2069,6 +2104,64 @@ class RawCNameExprNode(ExprNode):
pass
#-------------------------------------------------------------------
#
# Parallel nodes (cython.parallel.thread(savailable|id))
#
#-------------------------------------------------------------------
class ParallelThreadsAvailableNode(AtomicExprNode):
"""
Note: this is disabled and not a valid directive at this moment
Implements cython.parallel.threadsavailable(). If we are called from the
sequential part of the application, we need to call omp_get_max_threads(),
and in the parallel part we can just call omp_get_num_threads()
"""
type = PyrexTypes.c_int_type
def analyse_types(self, env):
self.is_temp = True
# env.add_include_file("omp.h")
return self.type
def generate_result_code(self, code):
code.putln("#ifdef _OPENMP")
code.putln("if (omp_in_parallel()) %s = omp_get_max_threads();" %
self.temp_code)
code.putln("else %s = omp_get_num_threads();" % self.temp_code)
code.putln("#else")
code.putln("%s = 1;" % self.temp_code)
code.putln("#endif")
def result(self):
return self.temp_code
class ParallelThreadIdNode(AtomicExprNode): #, Nodes.ParallelNode):
"""
Implements cython.parallel.threadid()
"""
type = PyrexTypes.c_int_type
def analyse_types(self, env):
self.is_temp = True
# env.add_include_file("omp.h")
return self.type
def generate_result_code(self, code):
code.putln("#ifdef _OPENMP")
code.putln("%s = omp_get_thread_num();" % self.temp_code)
code.putln("#else")
code.putln("%s = 0;" % self.temp_code)
code.putln("#endif")
def result(self):
return self.temp_code
#-------------------------------------------------------------------
#
# Trailer nodes
......@@ -3599,8 +3692,11 @@ class AttributeNode(ExprNode):
needs_none_check = True
def as_cython_attribute(self):
if isinstance(self.obj, NameNode) and self.obj.is_cython_module:
if (isinstance(self.obj, NameNode) and
self.obj.is_cython_module and not
self.attribute == u"parallel"):
return self.attribute
cy = self.obj.as_cython_attribute()
if cy:
return "%s.%s" % (cy, self.attribute)
......@@ -3881,31 +3977,31 @@ class AttributeNode(ExprNode):
return "%s%s%s" % (obj_code, self.op, self.member)
def generate_result_code(self, code):
interned_attr_cname = code.intern_identifier(self.attribute)
if self.is_py_attr:
code.putln(
'%s = PyObject_GetAttr(%s, %s); %s' % (
self.result(),
self.obj.py_result(),
interned_attr_cname,
code.intern_identifier(self.attribute),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
else:
# result_code contains what is needed, but we may need to insert
# a check and raise an exception
if (self.obj.type.is_extension_type
and self.needs_none_check
and code.globalstate.directives['nonecheck']):
self.put_nonecheck(code)
if self.obj.type.is_extension_type:
if self.needs_none_check and code.globalstate.directives['nonecheck']:
self.put_nonecheck(code)
elif self.entry and self.entry.is_cmethod and self.entry.utility_code:
# C method implemented as function call with utility code
code.globalstate.use_utility_code(self.entry.utility_code)
def generate_assignment_code(self, rhs, code):
interned_attr_cname = code.intern_identifier(self.attribute)
self.obj.generate_evaluation_code(code)
if self.is_py_attr:
code.put_error_if_neg(self.pos,
'PyObject_SetAttr(%s, %s, %s)' % (
self.obj.py_result(),
interned_attr_cname,
code.intern_identifier(self.attribute),
rhs.py_result()))
rhs.generate_disposal_code(code)
rhs.free_temps(code)
......@@ -3937,14 +4033,13 @@ class AttributeNode(ExprNode):
self.obj.free_temps(code)
def generate_deletion_code(self, code):
interned_attr_cname = code.intern_identifier(self.attribute)
self.obj.generate_evaluation_code(code)
if self.is_py_attr or (isinstance(self.entry.scope, Symtab.PropertyScope)
and u'__del__' in self.entry.scope.entries):
code.put_error_if_neg(self.pos,
'PyObject_DelAttr(%s, %s)' % (
self.obj.py_result(),
interned_attr_cname))
code.intern_identifier(self.attribute)))
else:
error(self.pos, "Cannot delete C attribute of extension type")
self.obj.generate_disposal_code(code)
......@@ -4019,7 +4114,6 @@ class SequenceNode(ExprNode):
# Contains common code for performing sequence unpacking.
#
# args [ExprNode]
# iterator ExprNode
# unpacked_items [ExprNode] or None
# coerced_unpacked_items [ExprNode] or None
......@@ -4062,9 +4156,9 @@ class SequenceNode(ExprNode):
return False
def analyse_target_types(self, env):
self.iterator = PyTempNode(self.pos, env)
self.unpacked_items = []
self.coerced_unpacked_items = []
self.any_coerced_items = False
for arg in self.args:
arg.analyse_target_types(env)
if arg.is_starred:
......@@ -4075,6 +4169,8 @@ class SequenceNode(ExprNode):
arg.type = Builtin.list_type
unpacked_item = PyTempNode(self.pos, env)
coerced_unpacked_item = unpacked_item.coerce_to(arg.type, env)
if unpacked_item is not coerced_unpacked_item:
self.any_coerced_items = True
self.unpacked_items.append(unpacked_item)
self.coerced_unpacked_items.append(coerced_unpacked_item)
self.type = py_object_type
......@@ -4092,87 +4188,124 @@ class SequenceNode(ExprNode):
item.release(code)
rhs.free_temps(code)
_func_iternext_type = PyrexTypes.CPtrType(PyrexTypes.CFuncType(
PyrexTypes.py_object_type, [
PyrexTypes.CFuncTypeArg("it", PyrexTypes.py_object_type, None),
]))
def generate_parallel_assignment_code(self, rhs, code):
# Need to work around the fact that generate_evaluation_code
# allocates the temps in a rather hacky way -- the assignment
# is evaluated twice, within each if-block.
special_unpack = (rhs.type is py_object_type
or rhs.type in (tuple_type, list_type)
or not rhs.type.is_builtin_type)
if special_unpack:
tuple_check = 'likely(PyTuple_CheckExact(%s))' % rhs.py_result()
list_check = 'PyList_CheckExact(%s)' % rhs.py_result()
if rhs.type is list_type:
sequence_types = ['List']
sequence_type_test = list_check
elif rhs.type is tuple_type:
sequence_types = ['Tuple']
sequence_type_test = tuple_check
else:
sequence_types = ['Tuple', 'List']
sequence_type_test = "(%s) || (%s)" % (tuple_check, list_check)
code.putln("if (%s) {" % sequence_type_test)
code.putln("PyObject* sequence = %s;" % rhs.py_result())
for item in self.unpacked_items:
item.allocate(code)
if len(sequence_types) == 2:
code.putln("if (likely(Py%s_CheckExact(sequence))) {" % sequence_types[0])
self.generate_special_parallel_unpacking_code(code, sequence_types[0])
if len(sequence_types) == 2:
code.putln("} else {")
self.generate_special_parallel_unpacking_code(code, sequence_types[1])
code.putln("}")
for item in self.unpacked_items:
code.put_incref(item.result(), item.ctype())
rhs.generate_disposal_code(code)
code.putln("} else {")
if rhs.type is tuple_type:
tuple_check = "likely(%s != Py_None)"
if special_unpack and rhs.type is tuple_type:
code.globalstate.use_utility_code(tuple_unpacking_error_code)
code.putln("__Pyx_UnpackTupleError(%s, %s);" % (
rhs.py_result(), len(self.args)))
code.putln(code.error_goto(self.pos))
else:
tuple_check = "PyTuple_CheckExact(%s)"
code.putln(
"if (%s && likely(PyTuple_GET_SIZE(%s) == %s)) {" % (
tuple_check % rhs.py_result(),
rhs.py_result(),
len(self.args)))
code.putln("PyObject* tuple = %s;" % rhs.py_result())
for item in self.unpacked_items:
item.allocate(code)
for i in range(len(self.args)):
item = self.unpacked_items[i]
code.put(
"%s = PyTuple_GET_ITEM(tuple, %s); " % (
item.result(),
i))
code.put_incref(item.result(), item.ctype())
value_node = self.coerced_unpacked_items[i]
value_node.generate_evaluation_code(code)
rhs.generate_disposal_code(code)
self.generate_generic_parallel_unpacking_code(code, rhs)
if special_unpack:
code.putln("}")
for value_node in self.coerced_unpacked_items:
value_node.generate_evaluation_code(code)
for i in range(len(self.args)):
self.args[i].generate_assignment_code(
self.coerced_unpacked_items[i], code)
code.putln("} else {")
def generate_special_parallel_unpacking_code(self, code, sequence_type):
code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
code.globalstate.use_utility_code(raise_too_many_values_to_unpack)
code.putln("if (unlikely(Py%s_GET_SIZE(sequence) != %d)) {" % (
sequence_type, len(self.args)))
code.putln("if (Py%s_GET_SIZE(sequence) > %d) __Pyx_RaiseTooManyValuesError(%d);" % (
sequence_type, len(self.args), len(self.args)))
code.putln("else __Pyx_RaiseNeedMoreValuesError(Py%s_GET_SIZE(sequence));" % sequence_type)
code.putln(code.error_goto(self.pos))
code.putln("}")
for i, item in enumerate(self.unpacked_items):
code.putln("%s = Py%s_GET_ITEM(sequence, %d); " % (item.result(), sequence_type, i))
if rhs.type is tuple_type:
code.globalstate.use_utility_code(tuple_unpacking_error_code)
code.putln("__Pyx_UnpackTupleError(%s, %s);" % (
rhs.py_result(), len(self.args)))
code.putln(code.error_goto(self.pos))
else:
code.globalstate.use_utility_code(unpacking_utility_code)
def generate_generic_parallel_unpacking_code(self, code, rhs):
code.globalstate.use_utility_code(iternext_unpacking_end_utility_code)
code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
code.putln("Py_ssize_t index = -1;")
self.iterator.allocate(code)
code.putln(
"%s = PyObject_GetIter(%s); %s" % (
self.iterator.result(),
rhs.py_result(),
code.error_goto_if_null(self.iterator.result(), self.pos)))
code.put_gotref(self.iterator.py_result())
rhs.generate_disposal_code(code)
for i in range(len(self.args)):
item = self.unpacked_items[i]
unpack_code = "__Pyx_UnpackItem(%s, %d)" % (
self.iterator.py_result(), i)
code.putln(
"%s = %s; %s" % (
item.result(),
typecast(item.ctype(), py_object_type, unpack_code),
code.error_goto_if_null(item.result(), self.pos)))
code.put_gotref(item.py_result())
value_node = self.coerced_unpacked_items[i]
value_node.generate_evaluation_code(code)
code.put_error_if_neg(self.pos, "__Pyx_EndUnpack(%s, %d)" % (
self.iterator.py_result(),
len(self.args)))
if debug_disposal_code:
print("UnpackNode.generate_assignment_code:")
print("...generating disposal code for %s" % self.iterator)
self.iterator.generate_disposal_code(code)
self.iterator.free_temps(code)
self.iterator.release(code)
iterator_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
code.putln(
"%s = PyObject_GetIter(%s); %s" % (
iterator_temp,
rhs.py_result(),
code.error_goto_if_null(iterator_temp, self.pos)))
code.put_gotref(iterator_temp)
rhs.generate_disposal_code(code)
for i in range(len(self.args)):
self.args[i].generate_assignment_code(
self.coerced_unpacked_items[i], code)
iternext_func = code.funcstate.allocate_temp(self._func_iternext_type, manage_ref=False)
code.putln("%s = Py_TYPE(%s)->tp_iternext;" % (
iternext_func, iterator_temp))
code.putln("}")
unpacking_error_label = code.new_label('unpacking_failed')
code.use_label(unpacking_error_label)
unpack_code = "%s(%s)" % (iternext_func, iterator_temp)
for i in range(len(self.args)):
item = self.unpacked_items[i]
code.putln(
"index = %d; %s = %s; if (unlikely(!%s)) goto %s;" % (
i,
item.result(),
typecast(item.ctype(), py_object_type, unpack_code),
item.result(),
unpacking_error_label))
code.put_gotref(item.py_result())
code.put_error_if_neg(self.pos, "__Pyx_IternextUnpackEndCheck(%s(%s), %d)" % (
iternext_func,
iterator_temp,
len(self.args)))
code.put_decref_clear(iterator_temp, py_object_type)
code.funcstate.release_temp(iterator_temp)
code.funcstate.release_temp(iternext_func)
unpacking_done_label = code.new_label('unpacking_done')
code.put_goto(unpacking_done_label)
code.put_label(unpacking_error_label)
code.put_decref_clear(iterator_temp, py_object_type)
code.putln("if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) PyErr_Clear();")
code.putln("if (!PyErr_Occurred()) __Pyx_RaiseNeedMoreValuesError(index);")
code.putln(code.error_goto(self.pos))
code.put_label(unpacking_done_label)
def generate_starred_assignment_code(self, rhs, code):
code.globalstate.use_utility_code(unpacking_utility_code)
for i, arg in enumerate(self.args):
if arg.is_starred:
starred_target = self.unpacked_items[i]
......@@ -4180,21 +4313,22 @@ class SequenceNode(ExprNode):
fixed_args_right = self.args[i+1:]
break
self.iterator.allocate(code)
iterator_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
code.putln(
"%s = PyObject_GetIter(%s); %s" % (
self.iterator.result(),
iterator_temp,
rhs.py_result(),
code.error_goto_if_null(self.iterator.result(), self.pos)))
code.put_gotref(self.iterator.py_result())
code.error_goto_if_null(iterator_temp, self.pos)))
code.put_gotref(iterator_temp)
rhs.generate_disposal_code(code)
for item in self.unpacked_items:
item.allocate(code)
code.globalstate.use_utility_code(unpacking_utility_code)
for i in range(len(fixed_args_left)):
item = self.unpacked_items[i]
unpack_code = "__Pyx_UnpackItem(%s, %d)" % (
self.iterator.py_result(), i)
iterator_temp, i)
code.putln(
"%s = %s; %s" % (
item.result(),
......@@ -4206,7 +4340,7 @@ class SequenceNode(ExprNode):
target_list = starred_target.result()
code.putln("%s = PySequence_List(%s); %s" % (
target_list, self.iterator.py_result(),
target_list, iterator_temp,
code.error_goto_if_null(target_list, self.pos)))
code.put_gotref(target_list)
if fixed_args_right:
......@@ -4229,9 +4363,8 @@ class SequenceNode(ExprNode):
code.put_gotref(arg.py_result())
coerced_arg.generate_evaluation_code(code)
self.iterator.generate_disposal_code(code)
self.iterator.free_temps(code)
self.iterator.release(code)
code.put_decref_clear(iterator_temp, py_object_type)
code.funcstate.release_temp(iterator_temp)
for i in range(len(self.args)):
self.args[i].generate_assignment_code(
......@@ -4666,11 +4799,6 @@ class InlinedGeneratorExpressionNode(ScopedExprNode):
def analyse_scoped_declarations(self, env):
self.loop.analyse_declarations(env)
def analyse_types(self, env):
if not self.has_local_scope:
self.loop.analyse_expressions(env)
self.is_temp = True
def may_be_none(self):
return False
......@@ -5356,7 +5484,6 @@ class YieldExprNode(ExprNode):
self.arg.analyse_types(env)
if not self.arg.type.is_pyobject:
self.arg = self.arg.coerce_to_pyobject(env)
env.use_utility_code(generator_utility_code)
def generate_evaluation_code(self, code):
self.label_name = code.new_label('resume_from_yield')
......@@ -6404,7 +6531,12 @@ class DivNode(NumBinopNode):
self.operand1.result(),
self.operand2.result()))
code.putln(code.set_error_info(self.pos));
code.put("if (__Pyx_cdivision_warning()) ")
code.put("if (__Pyx_cdivision_warning(%(FILENAME)s, "
"%(LINENO)s)) " % {
'FILENAME': Naming.filename_cname,
'LINENO': Naming.lineno_cname,
})
code.put_goto(code.error_label)
code.putln("}")
......@@ -7603,7 +7735,7 @@ class NoneCheckNode(CoercionNode):
def generate_result_code(self, code):
code.putln(
"if (unlikely(%s == Py_None)) {" % self.arg.result())
"if (unlikely(%s == Py_None)) {" % self.arg.py_result())
code.putln('PyErr_SetString(%s, "%s"); %s ' % (
self.exception_type_cname,
StringEncoding.escape_byte_string(
......@@ -8601,7 +8733,6 @@ requires = [raise_none_iter_error_utility_code,
unpacking_utility_code = UtilityCode(
proto = """
static PyObject *__Pyx_UnpackItem(PyObject *, Py_ssize_t index); /*proto*/
static int __Pyx_EndUnpack(PyObject *, Py_ssize_t expected); /*proto*/
""",
impl = """
static PyObject *__Pyx_UnpackItem(PyObject *iter, Py_ssize_t index) {
......@@ -8613,22 +8744,32 @@ static PyObject *__Pyx_UnpackItem(PyObject *iter, Py_ssize_t index) {
}
return item;
}
""",
requires = [raise_need_more_values_to_unpack]
)
static int __Pyx_EndUnpack(PyObject *iter, Py_ssize_t expected) {
PyObject *item;
if ((item = PyIter_Next(iter))) {
Py_DECREF(item);
iternext_unpacking_end_utility_code = UtilityCode(
proto = """
static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected); /*proto*/
""",
impl = """
static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) {
if (unlikely(retval)) {
Py_DECREF(retval);
__Pyx_RaiseTooManyValuesError(expected);
return -1;
} else if (PyErr_Occurred()) {
if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) {
PyErr_Clear();
return 0;
} else {
return -1;
}
}
else if (!PyErr_Occurred())
return 0;
else
return -1;
return 0;
}
""",
requires = [raise_need_more_values_to_unpack,
raise_too_many_values_to_unpack]
requires = [raise_too_many_values_to_unpack]
)
#------------------------------------------------------------------------------------
......@@ -8730,21 +8871,18 @@ static CYTHON_INLINE %(type)s __Pyx_mod_%(type_name)s(%(type)s a, %(type)s b) {
cdivision_warning_utility_code = UtilityCode(
proto="""
static int __Pyx_cdivision_warning(void); /* proto */
static int __Pyx_cdivision_warning(const char *, int); /* proto */
""",
impl="""
static int __Pyx_cdivision_warning(void) {
static int __Pyx_cdivision_warning(const char *filename, int lineno) {
return PyErr_WarnExplicit(PyExc_RuntimeWarning,
"division with oppositely signed operands, C and Python semantics differ",
%(FILENAME)s,
%(LINENO)s,
filename,
lineno,
__Pyx_MODULE_NAME,
NULL);
}
""" % {
'FILENAME': Naming.filename_cname,
'LINENO': Naming.lineno_cname,
})
""")
# from intobject.c
division_overflow_test_code = UtilityCode(
......
......@@ -106,9 +106,10 @@ class Context(object):
from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
from ParseTreeTransforms import ExpandInplaceOperators
from ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
from TypeInference import MarkAssignments, MarkOverflowingArithmetic
from ParseTreeTransforms import AlignFunctionDefinitions, GilCheck
from ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
from ParseTreeTransforms import RemoveUnreachableCode, GilCheck
from AnalysedTreeTransforms import AutoTestDictTransform
from AutoDocTransforms import EmbedSignature
from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
......@@ -135,8 +136,11 @@ class Context(object):
PostParse(self),
_specific_post_parse,
InterpretCompilerDirectives(self, self.compiler_directives),
ParallelRangeTransform(self),
AdjustDefByDirectives(self),
MarkClosureVisitor(self),
_align_function_definitions,
RemoveUnreachableCode(self),
ConstantFolding(),
FlattenInListTransform(),
WithTransform(self),
......
......@@ -201,7 +201,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
h_code.putln("")
for entry in api_vars:
type = CPtrType(entry.type)
cname = env.mangle(Naming.var_prefix, entry.name)
cname = env.mangle(Naming.varptr_prefix, entry.name)
h_code.putln("static %s = 0;" % type.declaration_code(cname))
h_code.putln("#define %s (*%s)" % (entry.name, cname))
h_code.put(import_module_utility_code.impl)
......@@ -223,7 +223,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
'if (__Pyx_ImportFunction(module, "%s", (void (**)(void))&%s, "%s") < 0) goto bad;'
% (entry.name, cname, sig))
for entry in api_vars:
cname = env.mangle(Naming.var_prefix, entry.name)
cname = env.mangle(Naming.varptr_prefix, entry.name)
sig = entry.type.declaration_code("")
h_code.putln(
'if (__Pyx_ImportVoidPtr(module, "%s", (void **)&%s, "%s") < 0) goto bad;'
......@@ -290,7 +290,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln('#define __Pyx_MODULE_NAME "%s"' % self.full_module_name)
code.putln("int %s%s = 0;" % (Naming.module_is_main, self.full_module_name.replace('.', '__')))
code.putln("")
code.putln("/* Implementation of %s */" % env.qualified_name)
code.putln("/* Implementation of '%s' */" % env.qualified_name)
code = globalstate['all_the_rest']
......@@ -449,17 +449,18 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
def generate_declarations_for_modules(self, env, modules, globalstate):
typecode = globalstate['type_declarations']
typecode.putln("")
typecode.putln("/* Type declarations */")
typecode.putln("/*--- Type declarations ---*/")
vtab_list, vtabslot_list = self.sort_type_hierarchy(modules, env)
self.generate_type_definitions(
env, modules, vtab_list, vtabslot_list, typecode)
modulecode = globalstate['module_declarations']
for module in modules:
defined_here = module is env
modulecode.putln("/* Module declarations from %s */" %
module.qualified_name)
self.generate_global_declarations(module, modulecode, defined_here)
self.generate_cfunction_predeclarations(module, modulecode, defined_here)
modulecode.putln("")
modulecode.putln("/* Module declarations from '%s' */" % module.qualified_name)
self.generate_c_class_declarations(module, modulecode, defined_here)
self.generate_cvariable_declarations(module, modulecode, defined_here)
self.generate_cfunction_declarations(module, modulecode, defined_here)
def generate_module_preamble(self, env, cimported_modules, code):
code.putln("/* Generated by Cython %s on %s */" % (
......@@ -756,6 +757,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
else:
code.putln('#include "%s"' % byte_decoded_filenname)
code.putln_openmp("#include <omp.h>")
def generate_filename_table(self, code):
code.putln("")
code.putln("static const char *%s[] = {" % Naming.filetable_cname)
......@@ -976,16 +979,59 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# Only for exposing public typedef name.
code.putln("typedef struct %s %s;" % (type.objstruct_cname, type.objtypedef_cname))
def generate_global_declarations(self, env, code, definition):
code.putln("")
def generate_c_class_declarations(self, env, code, definition):
for entry in env.c_class_entries:
if definition or entry.defined_in_pxd:
code.putln("static PyTypeObject *%s = 0;" %
entry.type.typeptr_cname)
code.put_var_declarations(env.var_entries, static = 1,
dll_linkage = "DL_EXPORT", definition = definition)
def generate_cfunction_predeclarations(self, env, code, definition):
def generate_cvariable_declarations(self, env, code, definition):
for entry in env.var_entries:
if (entry.in_cinclude or entry.in_closure or
(entry.visibility == 'private' and
not (entry.defined_in_pxd or entry.used))):
continue
storage_class = None
dll_linkage = None
cname = None
init = None
if entry.visibility == 'extern':
storage_class = Naming.extern_c_macro
dll_linkage = "DL_IMPORT"
elif entry.visibility == 'public':
if definition:
dll_linkage = "DL_EXPORT"
else:
storage_class = Naming.extern_c_macro
dll_linkage = "DL_IMPORT"
elif entry.visibility == 'private':
storage_class = "static"
if entry.defined_in_pxd and not definition:
type = CPtrType(entry.type)
storage_class = "static"
dll_linkage = None
cname = env.mangle(Naming.varptr_prefix, entry.name)
init = 0
else:
type = entry.type
cname = entry.cname
if entry.init is not None:
init = type.literal_code(entry.init)
if storage_class:
code.put("%s " % storage_class)
code.put(type.declaration_code(
cname, dll_linkage = dll_linkage))
if init is not None:
code.put_safe(" = %s" % init)
code.putln(";")
if entry.cname != cname:
code.putln("#define %s (*%s)" % (entry.cname, cname))
def generate_cfunction_declarations(self, env, code, definition):
for entry in env.cfunc_entries:
should_declare = (not entry.in_cinclude and
(definition or entry.defined_in_pxd or
......@@ -998,6 +1044,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
elif entry.visibility == 'extern':
storage_class = "%s " % Naming.extern_c_macro
dll_linkage = "DL_IMPORT"
elif entry.visibility == 'public':
storage_class = ""
dll_linkage = "DL_EXPORT"
elif entry.visibility == 'private':
storage_class = "static "
dll_linkage = None
......@@ -1007,7 +1056,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
type = entry.type
if not definition and entry.defined_in_pxd:
storage_class = "static "
dll_linkage = None
type = CPtrType(type)
header = type.declaration_code(entry.cname,
dll_linkage = dll_linkage)
if entry.func_modifiers:
......@@ -1756,7 +1808,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if code.label_used(code.error_label):
code.put_label(code.error_label)
# This helps locate the offending name.
code.putln('__Pyx_AddTraceback("%s");' % self.full_module_name);
code.put_add_traceback(self.full_module_name)
code.error_label = old_error_label
code.putln("bad:")
code.putln("return -1;")
......@@ -1850,6 +1902,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
for module in imported_modules:
self.generate_type_import_code_for_module(module, env, code)
code.putln("/*--- Variable import code ---*/")
for module in imported_modules:
self.generate_c_variable_import_code_for_module(module, env, code)
code.putln("/*--- Function import code ---*/")
for module in imported_modules:
self.specialize_fused_types(module, env)
......@@ -1870,7 +1926,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
for cname, type in code.funcstate.all_managed_temps():
code.put_xdecref(cname, type)
code.putln('if (%s) {' % env.module_cname)
code.putln('__Pyx_AddTraceback("init %s");' % env.qualified_name)
code.put_add_traceback("init %s" % env.qualified_name)
env.use_utility_code(Nodes.traceback_utility_code)
code.put_decref_clear(env.module_cname, py_object_type, nanny=False)
code.putln('} else if (!PyErr_Occurred()) {')
......@@ -2030,21 +2086,27 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
def generate_c_variable_export_code(self, env, code):
# Generate code to create PyCFunction wrappers for exported C functions.
entries = []
for entry in env.var_entries:
if entry.api or entry.defined_in_pxd:
env.use_utility_code(voidptr_export_utility_code)
entries.append(entry)
if entries:
env.use_utility_code(voidptr_export_utility_code)
for entry in entries:
signature = entry.type.declaration_code("")
code.putln('if (__Pyx_ExportVoidPtr("%s", (void *)&%s, "%s") < 0) %s' % (
entry.name,
entry.cname,
signature,
entry.name, entry.cname, signature,
code.error_goto(self.pos)))
def generate_c_function_export_code(self, env, code):
# Generate code to create PyCFunction wrappers for exported C functions.
entries = []
for entry in env.cfunc_entries:
if entry.api or entry.defined_in_pxd:
env.use_utility_code(function_export_utility_code)
entries.append(entry)
if entries:
env.use_utility_code(function_export_utility_code)
for entry in entries:
signature = entry.type.signature_string()
code.putln('if (__Pyx_ExportFunction("%s", (void (*)(void))%s, "%s") < 0) %s' % (
entry.name,
......@@ -2072,6 +2134,34 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# This call modifies the cfunc_entries in-place
entry.type.get_all_specific_function_types()
def generate_c_variable_import_code_for_module(self, module, env, code):
# Generate import code for all exported C functions in a cimported module.
entries = []
for entry in module.var_entries:
if entry.defined_in_pxd:
entries.append(entry)
if entries:
env.use_utility_code(import_module_utility_code)
env.use_utility_code(voidptr_import_utility_code)
temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
code.putln(
'%s = __Pyx_ImportModule("%s"); if (!%s) %s' % (
temp,
module.qualified_name,
temp,
code.error_goto(self.pos)))
for entry in entries:
if env is module:
cname = entry.cname
else:
cname = module.mangle(Naming.varptr_prefix, entry.name)
signature = entry.type.declaration_code("")
code.putln(
'if (__Pyx_ImportVoidPtr(%s, "%s", (void **)&%s, "%s") < 0) %s' % (
temp, entry.name, cname, signature,
code.error_goto(self.pos)))
code.putln("Py_DECREF(%s); %s = 0;" % (temp, temp))
def generate_c_function_import_code_for_module(self, module, env, code):
# Generate import code for all exported C functions in a cimported module.
entries = []
......
......@@ -36,6 +36,7 @@ prop_set_prefix = pyrex_prefix + "setprop_"
type_prefix = pyrex_prefix + "t_"
typeobj_prefix = pyrex_prefix + "type_"
var_prefix = pyrex_prefix + "v_"
varptr_prefix = pyrex_prefix + "vp_"
wrapperbase_prefix= pyrex_prefix + "wrapperbase_"
bufstruct_prefix = pyrex_prefix + "bstruct_"
bufstride_prefix = pyrex_prefix + "bstride_"
......
......@@ -2,7 +2,6 @@
#
# Pyrex - Parse tree nodes
#
import cython
from cython import set
cython.declare(sys=object, os=object, time=object, copy=object,
......@@ -131,6 +130,7 @@ class Node(object):
is_name = 0
is_literal = 0
is_terminator = 0
temps = None
# All descandants should set child_attrs to a list of the attributes
......@@ -559,6 +559,9 @@ class CFuncDeclaratorNode(CDeclaratorNode):
if name_declarator.cname:
error(self.pos,
"Function argument cannot have C name specification")
if i==0 and env.is_c_class_scope and type.is_unspecified:
# fix the type of self
type = env.parent_type
# Turn *[] argument into **
if type.is_array:
type = PyrexTypes.c_ptr_type(type.base_type)
......@@ -1010,15 +1013,6 @@ class CVarDefNode(StatNode):
base_type = self.base_type.analyse(env)
self.entry = None
# If the field is an external typedef, we cannot be sure about the type,
# so do conversion ourself rather than rely on the CPython mechanism (through
# a property; made in AnalyseDeclarationsTransform).
if (dest_scope.is_c_class_scope
and self.visibility in ('public', 'readonly')):
need_property = True
else:
need_property = False
visibility = self.visibility
for declarator in self.declarators:
......@@ -1053,7 +1047,6 @@ class CVarDefNode(StatNode):
"Only 'extern' C variable declaration allowed in .pxd file")
self.entry = dest_scope.declare_var(name, type, declarator.pos,
cname=cname, visibility=visibility, api=self.api, is_cdef=1)
self.entry.needs_property = need_property
class CStructOrUnionDefNode(StatNode):
......@@ -1413,9 +1406,11 @@ class FuncDefNode(StatNode, BlockNode):
code.put(cenv.scope_class.type.declaration_code(Naming.outer_scope_cname))
code.putln(";")
self.generate_argument_declarations(lenv, code)
for entry in lenv.var_entries:
if not entry.in_closure:
code.put_var_declaration(entry)
init = ""
if not self.return_type.is_void:
if self.return_type.is_pyobject:
......@@ -1425,23 +1420,31 @@ class FuncDefNode(StatNode, BlockNode):
(self.return_type.declaration_code(Naming.retval_cname),
init))
tempvardecl_code = code.insertion_point()
if not lenv.nogil:
code.put_declare_refcount_context()
self.generate_keyword_list(code)
if profile:
code.put_trace_declarations()
# ----- Extern library function declarations
lenv.generate_library_function_declarations(code)
# ----- GIL acquisition
acquire_gil = self.acquire_gil
if acquire_gil:
env.use_utility_code(force_init_threads_utility_code)
code.putln("#ifdef WITH_THREAD")
code.putln("PyGILState_STATE _save = PyGILState_Ensure();")
code.putln("#endif")
# See if we need to acquire the GIL for variable declarations and
acquire_gil_for_var_decls_only = (lenv.nogil and
lenv.has_with_gil_block)
use_refnanny = not lenv.nogil or acquire_gil_for_var_decls_only
if acquire_gil or acquire_gil_for_var_decls_only:
code.put_ensure_gil()
# ----- set up refnanny
if not lenv.nogil:
if use_refnanny:
tempvardecl_code.put_declare_refcount_context()
code.put_setup_refcount_context(self.entry.name)
# ----- Automatic lead-ins for certain special functions
if is_getbuffer_slot:
self.getbuffer_init(code)
......@@ -1456,8 +1459,12 @@ class FuncDefNode(StatNode, BlockNode):
code.putln("if (unlikely(!%s)) {" % Naming.cur_scope_cname)
if is_getbuffer_slot:
self.getbuffer_error_cleanup(code)
if not lenv.nogil:
if use_refnanny:
code.put_finish_refcount_context()
if acquire_gil_for_var_decls_only:
code.put_release_ensured_gil()
# FIXME: what if the error return value is a Python value?
code.putln("return %s;" % self.error_value())
code.putln("}")
......@@ -1503,6 +1510,9 @@ class FuncDefNode(StatNode, BlockNode):
if entry.type.is_buffer:
Buffer.put_acquire_arg_buffer(entry, code, self.pos)
if acquire_gil_for_var_decls_only:
code.put_release_ensured_gil()
# -------------------------
# ----- Function body -----
# -------------------------
......@@ -1545,7 +1555,7 @@ class FuncDefNode(StatNode, BlockNode):
# TODO: Fix exception tracing (though currently unused by cProfile).
# code.globalstate.use_utility_code(get_exception_tuple_utility_code)
# code.put_trace_exception()
code.putln('__Pyx_AddTraceback("%s");' % self.entry.qualified_name)
code.put_add_traceback(self.entry.qualified_name)
else:
warning(self.entry.pos, "Unraisable exception in function '%s'." \
% self.entry.qualified_name, 0)
......@@ -1569,7 +1579,6 @@ class FuncDefNode(StatNode, BlockNode):
if buffers_present or is_getbuffer_slot:
code.put_goto(code.return_from_error_cleanup_label)
# ----- Non-error return cleanup
code.put_label(code.return_label)
for entry in lenv.buffer_entries:
......@@ -1617,13 +1626,13 @@ class FuncDefNode(StatNode, BlockNode):
code.put_trace_return(Naming.retval_cname)
else:
code.put_trace_return("Py_None")
if not lenv.nogil:
# GIL holding funcion
code.put_finish_refcount_context()
if acquire_gil:
code.putln("#ifdef WITH_THREAD")
code.putln("PyGILState_Release(_save);")
code.putln("#endif")
if acquire_gil or acquire_gil_for_var_decls_only:
code.put_release_ensured_gil()
if not self.return_type.is_void:
code.putln("return %s;" % Naming.retval_cname)
......@@ -1635,6 +1644,14 @@ class FuncDefNode(StatNode, BlockNode):
# ----- Go back and insert temp variable declarations
tempvardecl_code.put_temp_declarations(code.funcstate)
if code.funcstate.should_declare_error_indicator:
# Initialize these variables to shut up compiler warnings
tempvardecl_code.putln("int %s = 0;" % Naming.lineno_cname)
tempvardecl_code.putln("const char *%s = NULL;" %
Naming.filename_cname)
if code.c_line_in_traceback:
tempvardecl_code.putln("int %s = 0;" % Naming.clineno_cname)
# ----- Python version
code.exit_cfunc_scope()
if self.py_func:
......@@ -1812,10 +1829,8 @@ class CFuncDefNode(FuncDefNode):
self.entry = env.declare_cfunction(
name, type, self.pos,
cname = cname, visibility = self.visibility,
defining = self.body is not None,
api = self.api, modifiers = self.modifiers)
cname = cname, visibility = self.visibility, api = self.api,
defining = self.body is not None, modifiers = self.modifiers)
self.entry.inline_func_in_pxd = self.inline_in_pxd
self.return_type = type.return_type
if self.return_type.is_array and self.visibility != 'extern':
......@@ -1897,7 +1912,7 @@ class CFuncDefNode(FuncDefNode):
error(self.pos,
"Function with Python return type cannot be declared nogil")
for entry in self.local_scope.var_entries:
if entry.type.is_pyobject:
if entry.type.is_pyobject and not entry.in_with_gil_block:
error(self.pos, "Function declared nogil has Python locals or temporaries")
def analyse_expressions(self, env):
......@@ -2374,7 +2389,7 @@ class DefNode(FuncDefNode):
self.num_required_kw_args = rk
self.num_required_args = r
def as_cfunction(self, cfunc=None, scope=None):
def as_cfunction(self, cfunc=None, scope=None, overridable=True):
if self.star_arg:
error(self.star_arg.pos, "cdef function cannot have star argument")
if self.starstar_arg:
......@@ -2394,7 +2409,7 @@ class DefNode(FuncDefNode):
exception_check = False,
nogil = False,
with_gil = False,
is_overridable = True)
is_overridable = overridable)
cfunc = CVarDefNode(self.pos, type=cfunc_type)
else:
if scope is None:
......@@ -2863,7 +2878,7 @@ class DefNode(FuncDefNode):
code.put_var_xdecref_clear(self.starstar_arg.entry)
else:
code.put_var_decref_clear(self.starstar_arg.entry)
code.putln('__Pyx_AddTraceback("%s");' % self.entry.qualified_name)
code.put_add_traceback(self.entry.qualified_name)
# The arguments are put into the closure one after the
# other, so when type errors are found, all references in
# the closure instance must be properly ref-counted to
......@@ -3406,6 +3421,9 @@ class GeneratorDefNode(DefNode):
code.putln("return (PyObject *) %s;" % Naming.cur_scope_cname);
def generate_function_definitions(self, env, code):
from ExprNodes import generator_utility_code
env.use_utility_code(generator_utility_code)
self.gbody.generate_function_header(code, proto=True)
super(GeneratorDefNode, self).generate_function_definitions(env, code)
self.gbody.generate_function_definitions(env, code)
......@@ -3484,7 +3502,7 @@ class GeneratorBodyDefNode(DefNode):
code.put_label(code.error_label)
for cname, type in code.funcstate.all_managed_temps():
code.put_xdecref(cname, type)
code.putln('__Pyx_AddTraceback("%s");' % self.entry.qualified_name)
code.put_add_traceback(self.entry.qualified_name)
# ----- Non-error return cleanup
code.put_label(code.return_label)
......@@ -4265,8 +4283,7 @@ class InPlaceAssignmentNode(AssignmentNode):
#
# lhs ExprNode Left hand side
# rhs ExprNode Right hand side
# op char one of "+-*/%^&|"
# dup (ExprNode) copy of lhs used for operation (auto-generated)
# operator char one of "+-*/%^&|"
#
# This code is a bit tricky because in order to obey Python
# semantics the sub-expressions (e.g. indices) of the lhs must
......@@ -4482,6 +4499,7 @@ class PassStatNode(StatNode):
class BreakStatNode(StatNode):
child_attrs = []
is_terminator = True
def analyse_expressions(self, env):
pass
......@@ -4496,6 +4514,7 @@ class BreakStatNode(StatNode):
class ContinueStatNode(StatNode):
child_attrs = []
is_terminator = True
def analyse_expressions(self, env):
pass
......@@ -4516,6 +4535,7 @@ class ReturnStatNode(StatNode):
# return_type PyrexType
child_attrs = ["value"]
is_terminator = True
def analyse_expressions(self, env):
return_type = env.return_type
......@@ -4589,6 +4609,7 @@ class RaiseStatNode(StatNode):
# cause ExprNode or None
child_attrs = ["exc_type", "exc_value", "exc_tb", "cause"]
is_terminator = True
def analyse_expressions(self, env):
if self.exc_type:
......@@ -4683,6 +4704,7 @@ class RaiseStatNode(StatNode):
class ReraiseStatNode(StatNode):
child_attrs = []
is_terminator = True
def analyse_expressions(self, env):
env.use_utility_code(restore_exception_utility_code)
......@@ -4940,8 +4962,8 @@ class WhileStatNode(LoopNode, StatNode):
self.else_clause.analyse_declarations(env)
def analyse_expressions(self, env):
self.condition = \
self.condition.analyse_temp_boolean_expression(env)
if self.condition:
self.condition = self.condition.analyse_temp_boolean_expression(env)
self.body.analyse_expressions(env)
if self.else_clause:
self.else_clause.analyse_expressions(env)
......@@ -4950,12 +4972,13 @@ class WhileStatNode(LoopNode, StatNode):
old_loop_labels = code.new_loop_labels()
code.putln(
"while (1) {")
self.condition.generate_evaluation_code(code)
self.condition.generate_disposal_code(code)
code.putln(
"if (!%s) break;" %
self.condition.result())
self.condition.free_temps(code)
if self.condition:
self.condition.generate_evaluation_code(code)
self.condition.generate_disposal_code(code)
code.putln(
"if (!%s) break;" %
self.condition.result())
self.condition.free_temps(code)
self.body.generate_execution_code(code)
code.put_label(code.continue_label)
code.putln("}")
......@@ -4968,18 +4991,64 @@ class WhileStatNode(LoopNode, StatNode):
code.put_label(break_label)
def generate_function_definitions(self, env, code):
self.condition.generate_function_definitions(env, code)
if self.condition:
self.condition.generate_function_definitions(env, code)
self.body.generate_function_definitions(env, code)
if self.else_clause is not None:
self.else_clause.generate_function_definitions(env, code)
def annotate(self, code):
self.condition.annotate(code)
if self.condition:
self.condition.annotate(code)
self.body.annotate(code)
if self.else_clause:
self.else_clause.annotate(code)
class DictIterationNextNode(Node):
# Helper node for calling PyDict_Next() inside of a WhileStatNode
# and checking the dictionary size for changes. Created in
# Optimize.py.
child_attrs = ['dict_obj', 'expected_size', 'pos_index_addr', 'key_addr', 'value_addr']
def __init__(self, dict_obj, expected_size, pos_index_addr, key_addr, value_addr):
Node.__init__(
self, dict_obj.pos,
dict_obj = dict_obj,
expected_size = expected_size,
pos_index_addr = pos_index_addr,
key_addr = key_addr,
value_addr = value_addr,
type = PyrexTypes.c_bint_type)
def analyse_expressions(self, env):
self.dict_obj.analyse_types(env)
self.expected_size.analyse_types(env)
self.pos_index_addr.analyse_types(env)
self.key_addr.analyse_types(env)
self.value_addr.analyse_types(env)
def generate_function_definitions(self, env, code):
self.dict_obj.generate_function_definitions(env, code)
def generate_execution_code(self, code):
self.dict_obj.generate_evaluation_code(code)
code.putln("if (unlikely(%s != PyDict_Size(%s))) {" % (
self.expected_size.result(),
self.dict_obj.py_result(),
))
code.putln('PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration"); %s' % (
code.error_goto(self.pos)))
code.putln("}")
self.pos_index_addr.generate_evaluation_code(code)
code.putln("if (!PyDict_Next(%s, %s, %s, %s)) break;" % (
self.dict_obj.py_result(),
self.pos_index_addr.result(),
self.key_addr.result(),
self.value_addr.result()))
def ForStatNode(pos, **kw):
if 'iterator' in kw:
return ForInStatNode(pos, **kw)
......@@ -5008,7 +5077,7 @@ class ForInStatNode(LoopNode, StatNode):
import ExprNodes
self.target.analyse_target_types(env)
self.iterator.analyse_expressions(env)
self.item = ExprNodes.NextNode(self.iterator, env)
self.item = ExprNodes.NextNode(self.iterator)
if (self.iterator.type.is_ptr or self.iterator.type.is_array) and \
self.target.type.assignable_from(self.iterator.type):
# C array slice optimization.
......@@ -5021,16 +5090,13 @@ class ForInStatNode(LoopNode, StatNode):
def generate_execution_code(self, code):
old_loop_labels = code.new_loop_labels()
self.iterator.allocate_counter_temp(code)
self.iterator.generate_evaluation_code(code)
code.putln(
"for (;;) {")
code.putln("for (;;) {")
self.item.generate_evaluation_code(code)
self.target.generate_assignment_code(self.item, code)
self.body.generate_execution_code(code)
code.put_label(code.continue_label)
code.putln(
"}")
code.putln("}")
break_label = code.break_label
code.set_loop_labels(old_loop_labels)
......@@ -5055,7 +5121,6 @@ class ForInStatNode(LoopNode, StatNode):
if code.label_used(break_label):
code.put_label(break_label)
self.iterator.release_counter_temp(code)
self.iterator.generate_disposal_code(code)
self.iterator.free_temps(code)
......@@ -5648,7 +5713,7 @@ class ExceptClauseNode(Node):
exc_vars = [code.funcstate.allocate_temp(py_object_type,
manage_ref=True)
for i in xrange(3)]
code.putln('__Pyx_AddTraceback("%s");' % self.function_name)
code.put_add_traceback(self.function_name)
# We always have to fetch the exception value even if
# there is no target, because this also normalises the
# exception and stores it in the thread state.
......@@ -5749,6 +5814,8 @@ class TryFinallyStatNode(StatNode):
# continue in the try block, since we have no problem
# handling it.
is_try_finally_in_nogil = False
def create_analysed(pos, env, body, finally_clause):
node = TryFinallyStatNode(pos, body=body, finally_clause=finally_clause)
return node
......@@ -5780,20 +5847,24 @@ class TryFinallyStatNode(StatNode):
if not self.handle_error_case:
code.error_label = old_error_label
catch_label = code.new_label()
code.putln(
"/*try:*/ {")
code.putln("/*try:*/ {")
if self.disallow_continue_in_try_finally:
was_in_try_finally = code.funcstate.in_try_finally
code.funcstate.in_try_finally = 1
self.body.generate_execution_code(code)
if self.disallow_continue_in_try_finally:
code.funcstate.in_try_finally = was_in_try_finally
code.putln(
"}")
code.putln("}")
temps_to_clean_up = code.funcstate.all_free_managed_temps()
code.mark_pos(self.finally_clause.pos)
code.putln(
"/*finally:*/ {")
code.putln("/*finally:*/ {")
cases_used = []
error_label_used = 0
for i, new_label in enumerate(new_labels):
......@@ -5802,22 +5873,25 @@ class TryFinallyStatNode(StatNode):
if new_label == new_error_label:
error_label_used = 1
error_label_case = i
if cases_used:
code.putln(
"int __pyx_why;")
code.putln("int __pyx_why;")
if error_label_used and self.preserve_exception:
code.putln(
"PyObject *%s, *%s, *%s;" % Naming.exc_vars)
code.putln(
"int %s;" % Naming.exc_lineno_name)
exc_var_init_zero = ''.join(["%s = 0; " % var for var in Naming.exc_vars])
code.putln("PyObject *%s, *%s, *%s;" % Naming.exc_vars)
code.putln("int %s;" % Naming.exc_lineno_name)
exc_var_init_zero = ''.join(
["%s = 0; " % var for var in Naming.exc_vars])
exc_var_init_zero += '%s = 0;' % Naming.exc_lineno_name
code.putln(exc_var_init_zero)
if self.is_try_finally_in_nogil:
code.declare_gilstate()
else:
exc_var_init_zero = None
code.use_label(catch_label)
code.putln(
"__pyx_why = 0; goto %s;" % catch_label)
code.putln("__pyx_why = 0; goto %s;" % catch_label)
for i in cases_used:
new_label = new_labels[i]
#if new_label and new_label != "<try>":
......@@ -5828,27 +5902,36 @@ class TryFinallyStatNode(StatNode):
code.put('%s: ' % new_label)
if exc_var_init_zero:
code.putln(exc_var_init_zero)
code.putln("__pyx_why = %s; goto %s;" % (
i+1,
catch_label))
code.putln("__pyx_why = %s; goto %s;" % (i+1, catch_label))
code.put_label(catch_label)
code.set_all_labels(old_labels)
if error_label_used:
code.new_error_label()
finally_error_label = code.error_label
self.finally_clause.generate_execution_code(code)
if error_label_used:
if finally_error_label in code.labels_used and self.preserve_exception:
over_label = code.new_label()
code.put_goto(over_label);
code.put_goto(over_label)
code.put_label(finally_error_label)
code.putln("if (__pyx_why == %d) {" % (error_label_case + 1))
if self.is_try_finally_in_nogil:
code.put_ensure_gil(declare_gilstate=False)
for var in Naming.exc_vars:
code.putln("Py_XDECREF(%s);" % var)
if self.is_try_finally_in_nogil:
code.put_release_ensured_gil()
code.putln("}")
code.put_goto(old_error_label)
code.put_label(over_label)
code.error_label = old_error_label
if cases_used:
code.putln(
"switch (__pyx_why) {")
......@@ -5858,12 +5941,13 @@ class TryFinallyStatNode(StatNode):
self.put_error_uncatcher(code, i+1, old_error_label)
else:
code.use_label(old_label)
code.putln(
"case %s: goto %s;" % (
i+1,
old_label))
code.putln("case %s: goto %s;" % (i+1, old_label))
# End the switch
code.putln(
"}")
# End finally
code.putln(
"}")
......@@ -5871,40 +5955,45 @@ class TryFinallyStatNode(StatNode):
self.body.generate_function_definitions(env, code)
self.finally_clause.generate_function_definitions(env, code)
def put_error_catcher(self, code, error_label, i, catch_label, temps_to_clean_up):
def put_error_catcher(self, code, error_label, i, catch_label,
temps_to_clean_up):
code.globalstate.use_utility_code(restore_exception_utility_code)
code.putln(
"%s: {" %
error_label)
code.putln(
"__pyx_why = %s;" %
i)
code.putln("%s: {" % error_label)
code.putln("__pyx_why = %s;" % i)
if self.is_try_finally_in_nogil:
code.put_ensure_gil(declare_gilstate=False)
for temp_name, type in temps_to_clean_up:
code.put_xdecref_clear(temp_name, type)
code.putln(
"__Pyx_ErrFetch(&%s, &%s, &%s);" %
Naming.exc_vars)
code.putln(
"%s = %s;" % (
Naming.exc_lineno_name, Naming.lineno_cname))
code.putln("__Pyx_ErrFetch(&%s, &%s, &%s);" % Naming.exc_vars)
code.putln("%s = %s;" % (Naming.exc_lineno_name, Naming.lineno_cname))
if self.is_try_finally_in_nogil:
code.put_release_ensured_gil()
code.put_goto(catch_label)
code.putln("}")
def put_error_uncatcher(self, code, i, error_label):
code.globalstate.use_utility_code(restore_exception_utility_code)
code.putln(
"case %s: {" %
i)
code.putln(
"__Pyx_ErrRestore(%s, %s, %s);" %
Naming.exc_vars)
code.putln(
"%s = %s;" % (
Naming.lineno_cname, Naming.exc_lineno_name))
"case %s: {" % i)
if self.is_try_finally_in_nogil:
code.put_ensure_gil(declare_gilstate=False)
code.putln("__Pyx_ErrRestore(%s, %s, %s);" % Naming.exc_vars)
code.putln("%s = %s;" % (Naming.lineno_cname, Naming.exc_lineno_name))
if self.is_try_finally_in_nogil:
code.put_release_ensured_gil()
for var in Naming.exc_vars:
code.putln(
"%s = 0;" %
var)
"%s = 0;" % var)
code.put_goto(error_label)
code.putln(
"}")
......@@ -5914,50 +6003,59 @@ class TryFinallyStatNode(StatNode):
self.finally_clause.annotate(code)
class GILStatNode(TryFinallyStatNode):
class NogilTryFinallyStatNode(TryFinallyStatNode):
"""
A try/finally statement that may be used in nogil code sections.
"""
preserve_exception = False
nogil_check = None
class GILStatNode(NogilTryFinallyStatNode):
# 'with gil' or 'with nogil' statement
#
# state string 'gil' or 'nogil'
# child_attrs = []
preserve_exception = 0
def __init__(self, pos, state, body):
self.state = state
TryFinallyStatNode.__init__(self, pos,
body = body,
finally_clause = GILExitNode(pos, state = state))
def analyse_declarations(self, env):
env._in_with_gil_block = (self.state == 'gil')
if self.state == 'gil':
env.has_with_gil_block = True
return super(GILStatNode, self).analyse_declarations(env)
def analyse_expressions(self, env):
env.use_utility_code(force_init_threads_utility_code)
was_nogil = env.nogil
env.nogil = 1
env.nogil = self.state == 'nogil'
TryFinallyStatNode.analyse_expressions(self, env)
env.nogil = was_nogil
nogil_check = None
def generate_execution_code(self, code):
code.mark_pos(self.pos)
code.putln("{")
code.begin_block()
if self.state == 'gil':
code.putln("#ifdef WITH_THREAD")
code.putln("PyGILState_STATE _save = PyGILState_Ensure();")
code.putln("#endif")
code.put_ensure_gil()
else:
code.putln("#ifdef WITH_THREAD")
code.putln("PyThreadState *_save = NULL;")
code.putln("#endif")
code.putln("Py_UNBLOCK_THREADS")
code.put_release_gil()
TryFinallyStatNode.generate_execution_code(self, code)
code.putln("}")
code.end_block()
class GILExitNode(StatNode):
# Used as the 'finally' block in a GILStatNode
#
# state string 'gil' or 'nogil'
"""
Used as the 'finally' block in a GILStatNode
state string 'gil' or 'nogil'
"""
child_attrs = []
......@@ -5966,11 +6064,18 @@ class GILExitNode(StatNode):
def generate_execution_code(self, code):
if self.state == 'gil':
code.putln("#ifdef WITH_THREAD")
code.putln("PyGILState_Release(_save);")
code.putln("#endif")
code.put_release_ensured_gil()
else:
code.putln("Py_BLOCK_THREADS")
code.put_acquire_gil()
class EnsureGILNode(GILExitNode):
"""
Ensure the GIL in nogil functions for cleanup before returning.
"""
def generate_execution_code(self, code):
code.put_ensure_gil(declare_gilstate=False)
class CImportStatNode(StatNode):
......@@ -6161,6 +6266,487 @@ class FromImportStatNode(StatNode):
self.module.free_temps(code)
class ParallelNode(Node):
"""
Base class for cython.parallel constructs.
"""
nogil_check = None
class ParallelStatNode(StatNode, ParallelNode):
"""
Base class for 'with cython.parallel.parallel():' and 'for i in prange():'.
assignments { Entry(var) : (var.pos, inplace_operator_or_None) }
assignments to variables in this parallel section
parent parent ParallelStatNode or None
is_parallel indicates whether this is a parallel node
is_parallel is true for:
#pragma omp parallel
#pragma omp parallel for
sections, but NOT for
#pragma omp for
We need this to determine the sharing attributes.
privatization_insertion_point a code insertion point used to make temps
private (esp. the "nsteps" temp)
"""
child_attrs = ['body']
body = None
is_prange = False
def __init__(self, pos, **kwargs):
super(ParallelStatNode, self).__init__(pos, **kwargs)
# All assignments in this scope
self.assignments = kwargs.get('assignments') or {}
# All seen closure cnames and their temporary cnames
self.seen_closure_vars = set()
# Dict of variables that should be declared (first|last|)private or
# reduction { Entry: op }. If op is not None, it's a reduction.
self.privates = {}
def analyse_declarations(self, env):
self.body.analyse_declarations(env)
def analyse_expressions(self, env):
self.body.analyse_expressions(env)
def analyse_sharing_attributes(self, env):
"""
Analyse the privates for this block and set them in self.privates.
This should be called in a post-order fashion during the
analyse_expressions phase
"""
for entry, (pos, op) in self.assignments.iteritems():
if self.is_private(entry):
self.propagate_var_privatization(entry, op)
def is_private(self, entry):
"""
True if this scope should declare the variable private, lastprivate
or reduction.
"""
return (self.is_parallel or
(self.parent and entry not in self.parent.privates))
def propagate_var_privatization(self, entry, op):
"""
Propagate the sharing attributes of a variable. If the privatization is
determined by a parent scope, done propagate further.
If we are a prange, we propagate our sharing attributes outwards to
other pranges. If we are a prange in parallel block and the parallel
block does not determine the variable private, we propagate to the
parent of the parent. Recursion stops at parallel blocks, as they have
no concept of lastprivate or reduction.
So the following cases propagate:
sum is a reduction for all loops:
for i in prange(n):
for j in prange(n):
for k in prange(n):
sum += i * j * k
sum is a reduction for both loops, local_var is private to the
parallel with block:
for i in prange(n):
with parallel:
local_var = ... # private to the parallel
for j in prange(n):
sum += i * j
Nested with parallel blocks are disallowed, because they wouldn't
allow you to propagate lastprivates or reductions:
#pragma omp parallel for lastprivate(i)
for i in prange(n):
sum = 0
#pragma omp parallel private(j, sum)
with parallel:
#pragma omp parallel
with parallel:
#pragma omp for lastprivate(j) reduction(+:sum)
for j in prange(n):
sum += i
# sum and j are well-defined here
# sum and j are undefined here
# sum and j are undefined here
"""
self.privates[entry] = op
if self.is_prange:
if not self.is_parallel and entry not in self.parent.assignments:
# Parent is a parallel with block
parent = self.parent.parent
else:
parent = self.parent
if parent:
parent.propagate_var_privatization(entry, op)
def _allocate_closure_temp(self, code, entry):
"""
Helper function that allocate a temporary for a closure variable that
is assigned to.
"""
if self.parent:
return self.parent._allocate_closure_temp(code, entry)
if entry.cname in self.seen_closure_vars:
return entry.cname
cname = code.funcstate.allocate_temp(entry.type, False)
# Add both the actual cname and the temp cname, as the actual cname
# will be replaced with the temp cname on the entry
self.seen_closure_vars.add(entry.cname)
self.seen_closure_vars.add(cname)
self.modified_entries.append((entry, entry.cname))
code.putln("%s = %s;" % (cname, entry.cname))
entry.cname = cname
def declare_closure_privates(self, code):
"""
Set self.privates to a dict mapping C variable names that are to be
declared (first|last)private or reduction, to the reduction operator.
If the private is not a reduction, the operator is None.
This is used by subclasses.
If a variable is in a scope object, we need to allocate a temp and
assign the value from the temp to the variable in the scope object
after the parallel section. This kind of copying should be done only
in the outermost parallel section.
"""
self.modified_entries = []
for entry, (pos, op) in self.assignments.iteritems():
if entry.from_closure or entry.in_closure:
self._allocate_closure_temp(code, entry)
def release_closure_privates(self, code):
"""
Release any temps used for variables in scope objects. As this is the
outermost parallel block, we don't need to delete the cnames from
self.seen_closure_vars
"""
for entry, original_cname in self.modified_entries:
code.putln("%s = %s;" % (original_cname, entry.cname))
code.funcstate.release_temp(entry.cname)
entry.cname = original_cname
class ParallelWithBlockNode(ParallelStatNode):
"""
This node represents a 'with cython.parallel.parallel():' block
"""
nogil_check = None
def analyse_expressions(self, env):
super(ParallelWithBlockNode, self).analyse_expressions(env)
self.analyse_sharing_attributes(env)
def generate_execution_code(self, code):
self.declare_closure_privates(code)
code.putln("#ifdef _OPENMP")
code.put("#pragma omp parallel ")
if self.privates:
code.put(
'private(%s)' % ', '.join([e.cname for e in self.privates]))
self.privatization_insertion_point = code.insertion_point()
code.putln("")
code.putln("#endif /* _OPENMP */")
code.begin_block()
self.body.generate_execution_code(code)
code.end_block()
self.release_closure_privates(code)
class ParallelRangeNode(ParallelStatNode):
"""
This node represents a 'for i in cython.parallel.prange():' construct.
target NameNode the target iteration variable
else_clause Node or None the else clause of this loop
args tuple the arguments passed to prange()
kwargs DictNode the keyword arguments passed to prange()
(replaced by its compile time value)
is_nogil bool indicates whether this is a nogil prange() node
"""
child_attrs = ['body', 'target', 'else_clause', 'args']
body = target = else_clause = args = None
start = stop = step = None
is_prange = True
is_nogil = False
def analyse_declarations(self, env):
super(ParallelRangeNode, self).analyse_declarations(env)
self.target.analyse_target_declaration(env)
if self.else_clause is not None:
self.else_clause.analyse_declarations(env)
if not self.args or len(self.args) > 3:
error(self.pos, "Invalid number of positional arguments to prange")
return
if len(self.args) == 1:
self.stop, = self.args
elif len(self.args) == 2:
self.start, self.stop = self.args
else:
self.start, self.stop, self.step = self.args
if self.kwargs:
self.kwargs = self.kwargs.compile_time_value(env)
else:
self.kwargs = {}
self.is_nogil = self.kwargs.pop('nogil', False)
self.schedule = self.kwargs.pop('schedule', None)
if hasattr(self.schedule, 'decode'):
self.schedule = self.schedule.decode('ascii')
if self.schedule not in (None, 'static', 'dynamic', 'guided',
'runtime'):
error(self.pos, "Invalid schedule argument to prange: %s" %
(self.schedule,))
for kw in self.kwargs:
error(self.pos, "Invalid keyword argument to prange: %s" % kw)
def analyse_expressions(self, env):
if self.target is None:
error(self.pos, "prange() can only be used as part of a for loop")
return
self.target.analyse_target_types(env)
if not self.target.type.is_numeric:
# Not a valid type, assume one for now anyway
if not self.target.type.is_pyobject:
# nogil_check will catch the is_pyobject case
error(self.target.pos,
"Must be of numeric type, not %s" % self.target.type)
self.index_type = PyrexTypes.c_py_ssize_t_type
else:
self.index_type = self.target.type
# Setup start, stop and step, allocating temps if needed
self.names = 'start', 'stop', 'step'
start_stop_step = self.start, self.stop, self.step
for node, name in zip(start_stop_step, self.names):
if node is not None:
node.analyse_types(env)
if not node.type.is_numeric:
error(node.pos, "%s argument must be numeric or a pointer "
"(perhaps if a numeric literal is too "
"big, use 1000LL)" % name)
if not node.is_literal:
node = node.coerce_to_temp(env)
setattr(self, name, node)
# As we range from 0 to nsteps, computing the index along the
# way, we need a fitting type for 'i' and 'nsteps'
self.index_type = PyrexTypes.widest_numeric_type(
self.index_type, node.type)
super(ParallelRangeNode, self).analyse_expressions(env)
if self.else_clause is not None:
self.else_clause.analyse_expressions(env)
# Although not actually an assignment in this scope, it should be
# treated as such to ensure it is unpacked if a closure temp, and to
# ensure lastprivate behaviour and propagation. If the target index is
# not a NameNode, it won't have an entry, and an error was issued by
# ParallelRangeTransform
if hasattr(self.target, 'entry'):
self.assignments[self.target.entry] = self.target.pos, None
self.analyse_sharing_attributes(env)
def nogil_check(self, env):
names = 'start', 'stop', 'step', 'target'
nodes = self.start, self.stop, self.step, self.target
for name, node in zip(names, nodes):
if node is not None and node.type.is_pyobject:
error(node.pos, "%s may not be a Python object "
"as we don't have the GIL" % name)
def generate_execution_code(self, code):
"""
Generate code in the following steps
1) copy any closure variables determined thread-private
into temporaries
2) allocate temps for start, stop and step
3) generate a loop that calculates the total number of steps,
which then computes the target iteration variable for every step:
for i in prange(start, stop, step):
...
becomes
nsteps = (stop - start) / step;
i = start;
#pragma omp parallel for lastprivate(i)
for (temp = 0; temp < nsteps; temp++) {
i = start + step * temp;
...
}
Note that accumulation of 'i' would have a data dependency
between iterations.
Also, you can't do this
for (i = start; i < stop; i += step)
...
as the '<' operator should become '>' for descending loops.
'for i from x < i < y:' does not suffer from this problem
as the relational operator is known at compile time!
4) release our temps and write back any private closure variables
"""
self.declare_closure_privates(code)
# This can only be a NameNode
target_index_cname = self.target.entry.cname
# This will be used as the dict to format our code strings, holding
# the start, stop , step, temps and target cnames
fmt_dict = {
'target': target_index_cname,
}
# Setup start, stop and step, allocating temps if needed
start_stop_step = self.start, self.stop, self.step
defaults = '0', '0', '1'
for node, name, default in zip(start_stop_step, self.names, defaults):
if node is None:
result = default
elif node.is_literal:
result = node.get_constant_c_result_code()
else:
node.generate_evaluation_code(code)
result = node.result()
fmt_dict[name] = result
fmt_dict['i'] = code.funcstate.allocate_temp(self.index_type, False)
fmt_dict['nsteps'] = code.funcstate.allocate_temp(self.index_type, False)
# TODO: check if the step is 0 and if so, raise an exception in a
# 'with gil' block. For now, just abort
code.putln("if (%(step)s == 0) abort();" % fmt_dict)
# Note: nsteps is private in an outer scope if present
code.putln("%(nsteps)s = (%(stop)s - %(start)s) / %(step)s;" % fmt_dict)
# The target iteration variable might not be initialized, do it only if
# we are executing at least 1 iteration, otherwise we should leave the
# target unaffected. The target iteration variable is firstprivate to
# shut up compiler warnings caused by lastprivate, as the compiler
# erroneously believes that nsteps may be <= 0, leaving the private
# target index uninitialized
code.putln("if (%(nsteps)s > 0)" % fmt_dict)
code.begin_block()
code.putln("%(target)s = 0;" % fmt_dict)
self.generate_loop(code, fmt_dict)
code.end_block()
# And finally, release our privates and write back any closure
# variables
for temp in start_stop_step:
if temp is not None:
temp.generate_disposal_code(code)
temp.free_temps(code)
code.funcstate.release_temp(fmt_dict['i'])
code.funcstate.release_temp(fmt_dict['nsteps'])
self.release_closure_privates(code)
def generate_loop(self, code, fmt_dict):
code.putln("#ifdef _OPENMP")
if not self.is_parallel:
code.put("#pragma omp for")
else:
code.put("#pragma omp parallel for")
for entry, op in self.privates.iteritems():
# Don't declare the index variable as a reduction
if op and op in "+*-&^|" and entry != self.target.entry:
code.put(" reduction(%s:%s)" % (op, entry.cname))
else:
if entry == self.target.entry:
code.put(" firstprivate(%s)" % entry.cname)
code.put(" lastprivate(%s)" % entry.cname)
if self.schedule:
code.put(" schedule(%s)" % self.schedule)
if self.parent:
c = self.parent.privatization_insertion_point
c.put(" private(%(nsteps)s)" % fmt_dict)
self.privatization_insertion_point = code.insertion_point()
code.putln("")
code.putln("#endif /* _OPENMP */")
code.put("for (%(i)s = 0; %(i)s < %(nsteps)s; %(i)s++)" % fmt_dict)
code.begin_block()
code.putln("%(target)s = %(start)s + %(step)s * %(i)s;" % fmt_dict)
self.body.generate_execution_code(code)
code.end_block()
#------------------------------------------------------------------------------------
#
......@@ -6993,15 +7579,22 @@ requires=[raise_double_keywords_utility_code])
#------------------------------------------------------------------------------------
traceback_utility_code = UtilityCode(
proto = """
static void __Pyx_AddTraceback(const char *funcname); /*proto*/
""",
impl = """
proto = """
static void __Pyx_AddTraceback(const char *funcname, int %(CLINENO)s,
int %(LINENO)s, const char *%(FILENAME)s); /*proto*/
""" % {
'FILENAME': Naming.filename_cname,
'LINENO': Naming.lineno_cname,
'CLINENO': Naming.clineno_cname,
},
impl = """
#include "compile.h"
#include "frameobject.h"
#include "traceback.h"
static void __Pyx_AddTraceback(const char *funcname) {
static void __Pyx_AddTraceback(const char *funcname, int %(CLINENO)s,
int %(LINENO)s, const char *%(FILENAME)s) {
PyObject *py_srcfile = 0;
PyObject *py_funcname = 0;
PyObject *py_globals = 0;
......
......@@ -63,18 +63,15 @@ class IterationTransform(Visitor.VisitorTransform):
- for-in-enumerate is replaced by an external counter variable
- for-in-range loop becomes a plain C for loop
"""
PyDict_Next_func_type = PyrexTypes.CFuncType(
PyrexTypes.c_bint_type, [
PyDict_Size_func_type = PyrexTypes.CFuncType(
PyrexTypes.c_py_ssize_t_type, [
PyrexTypes.CFuncTypeArg("dict", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("pos", PyrexTypes.c_py_ssize_t_ptr_type, None),
PyrexTypes.CFuncTypeArg("key", PyrexTypes.CPtrType(PyrexTypes.py_object_type), None),
PyrexTypes.CFuncTypeArg("value", PyrexTypes.CPtrType(PyrexTypes.py_object_type), None)
])
PyDict_Next_name = EncodedString("PyDict_Next")
PyDict_Size_name = EncodedString("PyDict_Size")
PyDict_Next_entry = Symtab.Entry(
PyDict_Next_name, PyDict_Next_name, PyDict_Next_func_type)
PyDict_Size_entry = Symtab.Entry(
PyDict_Size_name, PyDict_Size_name, PyDict_Size_func_type)
visit_Node = Visitor.VisitorTransform.recurse_to_children
......@@ -144,26 +141,22 @@ class IterationTransform(Visitor.VisitorTransform):
def visit_ForInStatNode(self, node):
self.visitchildren(node)
return self._optimise_for_loop(node)
return self._optimise_for_loop(node, node.iterator.sequence)
def _optimise_for_loop(self, node):
iterator = node.iterator.sequence
def _optimise_for_loop(self, node, iterator, reversed=False):
if iterator.type is Builtin.dict_type:
# like iterating over dict.keys()
if reversed:
# CPython raises an error here: not a sequence
return node
return self._transform_dict_iteration(
node, dict_obj=iterator, keys=True, values=False)
# C array (slice) iteration?
if False:
plain_iterator = unwrap_coerced_node(iterator)
if isinstance(plain_iterator, ExprNodes.SliceIndexNode) and \
(plain_iterator.base.type.is_array or plain_iterator.base.type.is_ptr):
return self._transform_carray_iteration(node, plain_iterator)
if iterator.type.is_ptr or iterator.type.is_array:
return self._transform_carray_iteration(node, iterator)
return self._transform_carray_iteration(node, iterator, reversed=reversed)
if iterator.type in (Builtin.bytes_type, Builtin.unicode_type):
return self._transform_string_iteration(node, iterator)
return self._transform_string_iteration(node, iterator, reversed=reversed)
# the rest is based on function calls
if not isinstance(iterator, ExprNodes.SimpleCallNode):
......@@ -173,6 +166,9 @@ class IterationTransform(Visitor.VisitorTransform):
# dict iteration?
if isinstance(function, ExprNodes.AttributeNode) and \
function.obj.type == Builtin.dict_type:
if reversed:
# CPython raises an error here: not a sequence
return node
dict_obj = function.obj
method = function.attribute
......@@ -189,21 +185,49 @@ class IterationTransform(Visitor.VisitorTransform):
return self._transform_dict_iteration(
node, dict_obj, keys, values)
# enumerate() ?
# enumerate/reversed ?
if iterator.self is None and function.is_name and \
function.entry and function.entry.is_builtin and \
function.name == 'enumerate':
return self._transform_enumerate_iteration(node, iterator)
function.entry and function.entry.is_builtin:
if function.name == 'enumerate':
if reversed:
# CPython raises an error here: not a sequence
return node
return self._transform_enumerate_iteration(node, iterator)
elif function.name == 'reversed':
if reversed:
# CPython raises an error here: not a sequence
return node
return self._transform_reversed_iteration(node, iterator)
# range() iteration?
if Options.convert_range and node.target.type.is_int:
if iterator.self is None and function.is_name and \
function.entry and function.entry.is_builtin and \
function.name in ('range', 'xrange'):
return self._transform_range_iteration(node, iterator)
return self._transform_range_iteration(node, iterator, reversed=reversed)
return node
def _transform_reversed_iteration(self, node, reversed_function):
args = reversed_function.arg_tuple.args
if len(args) == 0:
error(reversed_function.pos,
"reversed() requires an iterable argument")
return node
elif len(args) > 1:
error(reversed_function.pos,
"reversed() takes exactly 1 argument")
return node
arg = args[0]
# reversed(list/tuple) ?
if arg.type in (Builtin.tuple_type, Builtin.list_type):
node.iterator.sequence = arg.as_none_safe_node("'NoneType' object is not iterable")
node.iterator.reversed = True
return node
return self._optimise_for_loop(node, arg, reversed=True)
PyUnicode_AS_UNICODE_func_type = PyrexTypes.CFuncType(
PyrexTypes.c_py_unicode_ptr_type, [
PyrexTypes.CFuncTypeArg("s", Builtin.unicode_type, None)
......@@ -224,15 +248,18 @@ class IterationTransform(Visitor.VisitorTransform):
PyrexTypes.CFuncTypeArg("s", Builtin.bytes_type, None)
])
def _transform_string_iteration(self, node, slice_node):
if not node.target.type.is_int:
return self._transform_carray_iteration(node, slice_node)
def _transform_string_iteration(self, node, slice_node, reversed=False):
if slice_node.type is Builtin.unicode_type:
unpack_func = "PyUnicode_AS_UNICODE"
len_func = "PyUnicode_GET_SIZE"
unpack_func_type = self.PyUnicode_AS_UNICODE_func_type
len_func_type = self.PyUnicode_GET_SIZE_func_type
elif slice_node.type is Builtin.bytes_type:
target_type = node.target.type
if not target_type.is_int:
# bytes iteration returns bytes objects in Py2, but
# integers in Py3
return node
unpack_func = "PyBytes_AS_STRING"
unpack_func_type = self.PyBytes_AS_STRING_func_type
len_func = "PyBytes_GET_SIZE"
......@@ -266,9 +293,10 @@ class IterationTransform(Visitor.VisitorTransform):
stop = len_node,
type = slice_base_node.type,
is_temp = 1,
)))
),
reversed = reversed))
def _transform_carray_iteration(self, node, slice_node):
def _transform_carray_iteration(self, node, slice_node, reversed=False):
neg_step = False
if isinstance(slice_node, ExprNodes.SliceIndexNode):
slice_base = slice_node.base
......@@ -279,8 +307,9 @@ class IterationTransform(Visitor.VisitorTransform):
if not slice_base.type.is_pyobject:
error(slice_node.pos, "C array iteration requires known end index")
return node
elif isinstance(slice_node, ExprNodes.IndexNode):
# slice_node.index must be a SliceNode
assert isinstance(slice_node.index, ExprNodes.SliceNode)
slice_base = slice_node.base
index = slice_node.index
start = index.start
......@@ -298,10 +327,14 @@ class IterationTransform(Visitor.VisitorTransform):
return node
else:
# step sign is handled internally by ForFromStatNode
neg_step = step.constant_result < 0
step_value = step.constant_result
if reversed:
step_value = -step_value
neg_step = step_value < 0
step = ExprNodes.IntNode(step.pos, type=PyrexTypes.c_py_ssize_t_type,
value=abs(step.constant_result),
constant_result=abs(step.constant_result))
value=str(abs(step_value)),
constant_result=abs(step_value))
elif slice_node.type.is_array:
if slice_node.type.size is None:
error(step.pos, "C array iteration requires known end index")
......@@ -336,6 +369,13 @@ class IterationTransform(Visitor.VisitorTransform):
error(slice_node.pos, "C array iteration requires known step size and end index")
return node
if reversed:
if not start:
start = ExprNodes.IntNode(slice_node.pos, value="0", constant_result=0,
type=PyrexTypes.c_py_ssize_t_type)
# if step was provided, it was already negated above
start, stop = stop, start
ptr_type = slice_base.type
if ptr_type.is_array:
ptr_type = ptr_type.element_ptr_type()
......@@ -351,13 +391,16 @@ class IterationTransform(Visitor.VisitorTransform):
else:
start_ptr_node = carray_ptr
stop_ptr_node = ExprNodes.AddNode(
stop.pos,
operand1=ExprNodes.CloneNode(carray_ptr),
operator='+',
operand2=stop,
type=ptr_type
).coerce_to_simple(self.current_scope)
if stop and stop.constant_result != 0:
stop_ptr_node = ExprNodes.AddNode(
stop.pos,
operand1=ExprNodes.CloneNode(carray_ptr),
operator='+',
operand2=stop,
type=ptr_type
).coerce_to_simple(self.current_scope)
else:
stop_ptr_node = ExprNodes.CloneNode(carray_ptr)
counter = UtilNodes.TempHandle(ptr_type)
counter_temp = counter.ref(node.target.pos)
......@@ -401,11 +444,13 @@ class IterationTransform(Visitor.VisitorTransform):
node.pos,
stats = [target_assign, node.body])
relation1, relation2 = self._find_for_from_node_relations(neg_step, reversed)
for_node = Nodes.ForFromStatNode(
node.pos,
bound1=start_ptr_node, relation1=neg_step and '>=' or '<=',
bound1=start_ptr_node, relation1=relation1,
target=counter_temp,
relation2=neg_step and '>' or '<', bound2=stop_ptr_node,
relation2=relation2, bound2=stop_ptr_node,
step=step, body=body,
else_clause=node.else_clause,
from_range=True)
......@@ -482,9 +527,21 @@ class IterationTransform(Visitor.VisitorTransform):
node.iterator.sequence = enumerate_function.arg_tuple.args[0]
# recurse into loop to check for further optimisations
return UtilNodes.LetNode(temp, self._optimise_for_loop(node))
return UtilNodes.LetNode(temp, self._optimise_for_loop(node, node.iterator.sequence))
def _transform_range_iteration(self, node, range_function):
def _find_for_from_node_relations(self, neg_step_value, reversed):
if reversed:
if neg_step_value:
return '<', '<='
else:
return '>', '>='
else:
if neg_step_value:
return '>=', '>'
else:
return '<=', '<'
def _transform_range_iteration(self, node, range_function, reversed=False):
args = range_function.arg_tuple.args
if len(args) < 3:
step_pos = range_function.pos
......@@ -505,14 +562,6 @@ class IterationTransform(Visitor.VisitorTransform):
step = ExprNodes.IntNode(step_pos, value=str(step_value),
constant_result=step_value)
if step_value < 0:
step.value = str(-step_value)
relation1 = '>='
relation2 = '>'
else:
relation1 = '<='
relation2 = '<'
if len(args) == 1:
bound1 = ExprNodes.IntNode(range_function.pos, value='0',
constant_result=0)
......@@ -520,6 +569,19 @@ class IterationTransform(Visitor.VisitorTransform):
else:
bound1 = args[0].coerce_to_integer(self.current_scope)
bound2 = args[1].coerce_to_integer(self.current_scope)
relation1, relation2 = self._find_for_from_node_relations(step_value < 0, reversed)
if reversed:
bound1, bound2 = bound2, bound1
if step_value < 0:
step_value = -step_value
else:
if step_value < 0:
step_value = -step_value
step.value = str(step_value)
step.constant_result = step_value
step = step.coerce_to_integer(self.current_scope)
if not bound2.is_literal:
......@@ -544,7 +606,7 @@ class IterationTransform(Visitor.VisitorTransform):
return for_node
def _transform_dict_iteration(self, node, dict_obj, keys, values):
py_object_ptr = PyrexTypes.c_void_ptr_type
py_object_ptr = PyrexTypes.py_object_type
temps = []
temp = UtilNodes.TempHandle(PyrexTypes.py_object_type)
......@@ -556,9 +618,12 @@ class IterationTransform(Visitor.VisitorTransform):
pos_temp_addr = ExprNodes.AmpersandNode(
node.pos, operand=pos_temp,
type=PyrexTypes.c_ptr_type(PyrexTypes.c_py_ssize_t_type))
target_temps = []
if keys:
temp = UtilNodes.TempHandle(py_object_ptr)
temps.append(temp)
temp = UtilNodes.TempHandle(
py_object_ptr, needs_cleanup=False) # ref will be stolen
target_temps.append(temp)
key_temp = temp.ref(node.target.pos)
key_temp_addr = ExprNodes.AmpersandNode(
node.target.pos, operand=key_temp,
......@@ -567,8 +632,9 @@ class IterationTransform(Visitor.VisitorTransform):
key_temp_addr = key_temp = ExprNodes.NullNode(
pos=node.target.pos)
if values:
temp = UtilNodes.TempHandle(py_object_ptr)
temps.append(temp)
temp = UtilNodes.TempHandle(
py_object_ptr, needs_cleanup=False) # ref will be stolen
target_temps.append(temp)
value_temp = temp.ref(node.target.pos)
value_temp_addr = ExprNodes.AmpersandNode(
node.target.pos, operand=value_temp,
......@@ -602,7 +668,7 @@ class IterationTransform(Visitor.VisitorTransform):
return (result, None)
else:
temp = UtilNodes.TempHandle(dest_type)
temps.append(temp)
target_temps.append(temp)
temp_result = temp.ref(obj_node.pos)
class CoercedTempNode(ExprNodes.CoerceFromPyTypeNode):
def result(self):
......@@ -611,12 +677,6 @@ class IterationTransform(Visitor.VisitorTransform):
self.generate_result_code(code)
return (temp_result, CoercedTempNode(dest_type, obj_node, self.current_scope))
if isinstance(node.body, Nodes.StatListNode):
body = node.body
else:
body = Nodes.StatListNode(pos = node.body.pos,
stats = [node.body])
if tuple_target:
tuple_result = ExprNodes.TupleNode(
pos = tuple_target.pos,
......@@ -624,11 +684,12 @@ class IterationTransform(Visitor.VisitorTransform):
is_temp = 1,
type = Builtin.tuple_type,
)
body.stats.insert(
0, Nodes.SingleAssignmentNode(
body_init_stats = [
Nodes.SingleAssignmentNode(
pos = tuple_target.pos,
lhs = tuple_target,
rhs = tuple_result))
rhs = tuple_result)
]
else:
# execute all coercions before the assignments
coercion_stats = []
......@@ -653,7 +714,29 @@ class IterationTransform(Visitor.VisitorTransform):
pos = value_temp.pos,
lhs = value_target,
rhs = temp_result))
body.stats[0:0] = coercion_stats + assign_stats
body_init_stats = coercion_stats + assign_stats
if isinstance(node.body, Nodes.StatListNode):
body = node.body
else:
body = Nodes.StatListNode(pos = node.body.pos,
stats = [node.body])
# keep original length to guard against dict modification
dict_len_temp = UtilNodes.TempHandle(PyrexTypes.c_py_ssize_t_type)
temps.append(dict_len_temp)
body_init_stats.insert(0, Nodes.DictIterationNextNode(
dict_temp,
dict_len_temp.ref(dict_obj.pos),
pos_temp_addr, key_temp_addr, value_temp_addr
))
body.stats[0:0] = [UtilNodes.TempsBlockNode(
node.pos,
temps = target_temps,
body = Nodes.StatListNode(pos = node.pos,
stats = body_init_stats)
)]
result_code = [
Nodes.SingleAssignmentNode(
......@@ -665,19 +748,22 @@ class IterationTransform(Visitor.VisitorTransform):
lhs = pos_temp,
rhs = ExprNodes.IntNode(node.pos, value='0',
constant_result=0)),
Nodes.WhileStatNode(
pos = node.pos,
condition = ExprNodes.SimpleCallNode(
Nodes.SingleAssignmentNode(
pos = dict_obj.pos,
lhs = dict_len_temp.ref(dict_obj.pos),
rhs = ExprNodes.SimpleCallNode(
pos = dict_obj.pos,
type = PyrexTypes.c_bint_type,
type = PyrexTypes.c_py_ssize_t_type,
function = ExprNodes.NameNode(
pos = dict_obj.pos,
name = self.PyDict_Next_name,
type = self.PyDict_Next_func_type,
entry = self.PyDict_Next_entry),
args = [dict_temp, pos_temp_addr,
key_temp_addr, value_temp_addr]
),
name = self.PyDict_Size_name,
type = self.PyDict_Size_func_type,
entry = self.PyDict_Size_entry),
args = [dict_temp],
)),
Nodes.WhileStatNode(
pos = node.pos,
condition = None,
body = body,
else_clause = node.else_clause
)
......@@ -2983,10 +3069,16 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
into a single node.
"""
check_constant_value_not_set = True
def __init__(self, reevaluate=False):
"""
The reevaluate argument specifies whether constant values that were
previously computed should be recomputed.
"""
super(ConstantFolding, self).__init__()
self.reevaluate = reevaluate
def _calculate_const(self, node):
if (self.check_constant_value_not_set and
if (not self.reevaluate and
node.constant_result is not ExprNodes.constant_value_not_set):
return
......
......@@ -94,6 +94,10 @@ directive_defaults = {
'warn': None,
'warn.undeclared': False,
'warn.unreachable': True,
# remove unreachable code
'remove_unreachable': True,
# test support
'test_assert_path_exists' : [],
......@@ -108,6 +112,9 @@ directive_types = {
'final' : bool, # final cdef classes and methods
'internal' : bool, # cdef class visibility in the module dict
'infer_types' : bool, # values can be True/None/False
'cfunc' : None, # decorators do not take directive value
'ccall' : None,
'cclass' : None,
}
for key, val in directive_defaults.items():
......
......@@ -613,9 +613,16 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
special_methods = cython.set(['declare', 'union', 'struct', 'typedef',
'sizeof', 'cast', 'pointer', 'compiled',
'NULL', 'fused_type'])
'NULL', 'fused_type', 'parallel'])
special_methods.update(unop_method_nodes.keys())
valid_parallel_directives = cython.set([
"parallel",
"prange",
"threadid",
# "threadsavailable",
])
def __init__(self, context, compilation_directive_defaults):
super(InterpretCompilerDirectives, self).__init__(context)
self.compilation_directive_defaults = {}
......@@ -623,6 +630,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
self.compilation_directive_defaults[unicode(key)] = copy.deepcopy(value)
self.cython_module_names = cython.set()
self.directive_names = {}
self.parallel_directives = {}
def check_directive_scope(self, pos, directive, scope):
legal_scopes = Options.directive_scopes.get(directive, None)
......@@ -631,6 +639,8 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
'is not allowed in %s scope' % (directive, scope)))
return False
else:
if directive not in Options.directive_defaults:
error(pos, "Invalid directive: '%s'." % (directive,))
return True
# Set up processing and handle the cython: comments.
......@@ -645,6 +655,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
directives.update(node.directive_comments)
self.directives = directives
node.directives = directives
node.parallel_directives = self.parallel_directives
self.visitchildren(node)
node.cython_module_names = self.cython_module_names
return node
......@@ -656,11 +667,39 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
name in self.special_methods or
PyrexTypes.parse_basic_type(name))
def is_parallel_directive(self, full_name, pos):
"""
Checks to see if fullname (e.g. cython.parallel.prange) is a valid
parallel directive. If it is a star import it also updates the
parallel_directives.
"""
result = (full_name + ".").startswith("cython.parallel.")
if result:
directive = full_name.split('.')
if full_name == u"cython.parallel.*":
for name in self.valid_parallel_directives:
self.parallel_directives[name] = u"cython.parallel.%s" % name
elif (len(directive) != 3 or
directive[-1] not in self.valid_parallel_directives):
error(pos, "No such directive: %s" % full_name)
return result
def visit_CImportStatNode(self, node):
if node.module_name == u"cython":
self.cython_module_names.add(node.as_name or u"cython")
elif node.module_name.startswith(u"cython."):
if node.as_name:
if node.module_name.startswith(u"cython.parallel."):
error(node.pos, node.module_name + " is not a module")
if node.module_name == u"cython.parallel":
if node.as_name and node.as_name != u"cython":
self.parallel_directives[node.as_name] = node.module_name
else:
self.cython_module_names.add(u"cython")
self.parallel_directives[
u"cython.parallel"] = node.module_name
elif node.as_name:
self.directive_names[node.as_name] = node.module_name[7:]
else:
self.cython_module_names.add(u"cython")
......@@ -674,19 +713,29 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
node.module_name.startswith(u"cython."):
submodule = (node.module_name + u".")[7:]
newimp = []
for pos, name, as_name, kind in node.imported_names:
full_name = submodule + name
if self.is_cython_directive(full_name):
qualified_name = u"cython." + full_name
if self.is_parallel_directive(qualified_name, node.pos):
# from cython cimport parallel, or
# from cython.parallel cimport parallel, prange, ...
self.parallel_directives[as_name or name] = qualified_name
elif self.is_cython_directive(full_name):
if as_name is None:
as_name = full_name
self.directive_names[as_name] = full_name
if kind is not None:
self.context.nonfatal_error(PostParseError(pos,
"Compiler directive imports must be plain imports"))
else:
newimp.append((pos, name, as_name, kind))
if not newimp:
return None
node.imported_names = newimp
return node
......@@ -697,7 +746,10 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
newimp = []
for name, name_node in node.items:
full_name = submodule + name
if self.is_cython_directive(full_name):
qualified_name = u"cython." + full_name
if self.is_parallel_directive(qualified_name, node.pos):
self.parallel_directives[name_node.name] = qualified_name
elif self.is_cython_directive(full_name):
self.directive_names[name_node.name] = full_name
else:
newimp.append((name, name_node))
......@@ -707,14 +759,23 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
return node
def visit_SingleAssignmentNode(self, node):
if (isinstance(node.rhs, ExprNodes.ImportNode) and
node.rhs.module_name.value == u'cython'):
if isinstance(node.rhs, ExprNodes.ImportNode):
module_name = node.rhs.module_name.value
is_parallel = (module_name + u".").startswith(u"cython.parallel.")
if module_name != u"cython" and not is_parallel:
return node
module_name = node.rhs.module_name.value
as_name = node.lhs.name
node = Nodes.CImportStatNode(node.pos,
module_name = u'cython',
as_name = node.lhs.name)
self.visit_CImportStatNode(node)
module_name = module_name,
as_name = as_name)
node = self.visit_CImportStatNode(node)
else:
self.visitchildren(node)
return node
def visit_NameNode(self, node):
......@@ -888,9 +949,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
else:
name, value = directive
if name == 'nogil':
if name in ('nogil', 'gil'):
# special case: in pure mode, "with nogil" spells "with cython.nogil"
node = Nodes.GILStatNode(node.pos, state = "nogil", body = node.body)
node = Nodes.GILStatNode(node.pos, state = name, body = node.body)
return self.visit_Node(node)
if self.check_directive_scope(node.pos, name, 'with statement'):
directive_dict[name] = value
......@@ -928,6 +989,205 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
return node
class ParallelRangeTransform(CythonTransform, SkipDeclarations):
"""
Transform cython.parallel stuff. The parallel_directives come from the
module node, set there by InterpretCompilerDirectives.
x = cython.parallel.threadavailable() -> ParallelThreadAvailableNode
with nogil, cython.parallel.parallel(): -> ParallelWithBlockNode
print cython.parallel.threadid() -> ParallelThreadIdNode
for i in cython.parallel.prange(...): -> ParallelRangeNode
...
"""
# a list of names, maps 'cython.parallel.prange' in the code to
# ['cython', 'parallel', 'prange']
parallel_directive = None
# Indicates whether a namenode in an expression is the cython module
namenode_is_cython_module = False
# Keep track of whether we are the context manager of a 'with' statement
in_context_manager_section = False
# Keep track of whether we are in a parallel range section
in_prange = False
# One of 'prange' or 'with parallel'. This is used to disallow closely
# nested 'with parallel:' blocks
state = None
directive_to_node = {
u"cython.parallel.parallel": Nodes.ParallelWithBlockNode,
# u"cython.parallel.threadsavailable": ExprNodes.ParallelThreadsAvailableNode,
u"cython.parallel.threadid": ExprNodes.ParallelThreadIdNode,
u"cython.parallel.prange": Nodes.ParallelRangeNode,
}
def node_is_parallel_directive(self, node):
return node.name in self.parallel_directives or node.is_cython_module
def get_directive_class_node(self, node):
"""
Figure out which parallel directive was used and return the associated
Node class.
E.g. for a cython.parallel.prange() call we return ParallelRangeNode
Also disallow break, continue and return in a prange section
"""
if self.namenode_is_cython_module:
directive = '.'.join(self.parallel_directive)
else:
directive = self.parallel_directives[self.parallel_directive[0]]
directive = '%s.%s' % (directive,
'.'.join(self.parallel_directive[1:]))
directive = directive.rstrip('.')
cls = self.directive_to_node.get(directive)
if cls is None:
error(node.pos, "Invalid directive: %s" % directive)
self.namenode_is_cython_module = False
self.parallel_directive = None
return cls
def visit_ModuleNode(self, node):
"""
If any parallel directives were imported, copy them over and visit
the AST
"""
if node.parallel_directives:
self.parallel_directives = node.parallel_directives
self.assignment_stack = []
return self.visit_Node(node)
# No parallel directives were imported, so they can't be used :)
return node
def visit_NameNode(self, node):
if self.node_is_parallel_directive(node):
self.parallel_directive = [node.name]
self.namenode_is_cython_module = node.is_cython_module
return node
def visit_AttributeNode(self, node):
self.visitchildren(node)
if self.parallel_directive:
self.parallel_directive.append(node.attribute)
return node
def visit_CallNode(self, node):
self.visit(node.function)
if not self.parallel_directive:
return node
# We are a parallel directive, replace this node with the
# corresponding ParallelSomethingSomething node
if isinstance(node, ExprNodes.GeneralCallNode):
args = node.positional_args.args
kwargs = node.keyword_args
else:
args = node.args
kwargs = {}
parallel_directive_class = self.get_directive_class_node(node)
if parallel_directive_class:
# Note: in case of a parallel() the body is set by
# visit_WithStatNode
node = parallel_directive_class(node.pos, args=args, kwargs=kwargs)
return node
def visit_WithStatNode(self, node):
"Rewrite with cython.parallel.parallel() blocks"
newnode = self.visit(node.manager)
if isinstance(newnode, Nodes.ParallelWithBlockNode):
if self.state == 'parallel with':
error(node.manager.pos,
"Closely nested 'with parallel:' blocks are disallowed")
self.state = 'parallel with'
body = self.visit(node.body)
self.state = None
newnode.body = body
return newnode
elif self.parallel_directive:
parallel_directive_class = self.get_directive_class_node(node)
if not parallel_directive_class:
# There was an error, stop here and now
return None
if parallel_directive_class is Nodes.ParallelWithBlockNode:
error(node.pos, "The parallel directive must be called")
return None
node.body = self.visit(node.body)
return node
def visit_ForInStatNode(self, node):
"Rewrite 'for i in cython.parallel.prange(...):'"
self.visit(node.iterator)
self.visit(node.target)
was_in_prange = self.in_prange
self.in_prange = isinstance(node.iterator.sequence,
Nodes.ParallelRangeNode)
previous_state = self.state
if self.in_prange:
# This will replace the entire ForInStatNode, so copy the
# attributes
parallel_range_node = node.iterator.sequence
parallel_range_node.target = node.target
parallel_range_node.body = node.body
parallel_range_node.else_clause = node.else_clause
node = parallel_range_node
if not isinstance(node.target, ExprNodes.NameNode):
error(node.target.pos,
"Can only iterate over an iteration variable")
self.state = 'prange'
self.visit(node.body)
self.state = previous_state
self.in_prange = was_in_prange
self.visit(node.else_clause)
return node
def ensure_not_in_prange(name):
"Creates error checking functions for break, continue and return"
def visit_method(self, node):
if self.in_prange:
error(node.pos,
name + " not allowed in a parallel range section")
# Do a visit for 'return'
self.visitchildren(node)
return node
return visit_method
visit_BreakStatNode = ensure_not_in_prange("break")
visit_ContinueStatNode = ensure_not_in_prange("continue")
visit_ReturnStatNode = ensure_not_in_prange("return")
def visit(self, node):
"Visit a node that may be None"
if node is not None:
return super(ParallelRangeTransform, self).visit(node)
class WithTransform(CythonTransform, SkipDeclarations):
def visit_WithStatNode(self, node):
self.visitchildren(node, 'body')
......@@ -1148,6 +1408,18 @@ if VALUE is not None:
node.stats.append(node.py_func)
else:
node.body.analyse_declarations(lenv)
if lenv.nogil and lenv.has_with_gil_block:
# Acquire the GIL for cleanup in 'nogil' functions, by wrapping
# the entire function body in try/finally.
# The corresponding release will be taken care of by
# Nodes.FuncDefNode.generate_function_definitions()
node.body = Nodes.NogilTryFinallyStatNode(
node.body.pos,
body = node.body,
finally_clause = Nodes.EnsureGILNode(node.body.pos),
)
self.env_stack.append(lenv)
self.visitchildren(node)
self.env_stack.pop()
......@@ -1320,7 +1592,8 @@ if VALUE is not None:
# ---------------------------------------
return property
class AnalyseExpressionsTransform(EnvTransform):
class AnalyseExpressionsTransform(CythonTransform):
def visit_ModuleNode(self, node):
self.env_stack = [node.scope]
......@@ -1422,6 +1695,58 @@ class ExpandInplaceOperators(EnvTransform):
# In-place assignments can't happen within an expression.
return node
class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
"""
Adjust function and class definitions by the decorator directives:
@cython.cfunc
@cython.cclass
@cython.ccall
"""
def visit_ModuleNode(self, node):
self.directives = node.directives
self.in_py_class = False
self.visitchildren(node)
return node
def visit_CompilerDirectivesNode(self, node):
old_directives = self.directives
self.directives = node.directives
self.visitchildren(node)
self.directives = old_directives
return node
def visit_DefNode(self, node):
if 'ccall' in self.directives:
node = node.as_cfunction(overridable=True)
return self.visit(node)
if 'cfunc' in self.directives:
if self.in_py_class:
error(node.pos, "cfunc directive is not allowed here")
else:
node = node.as_cfunction(overridable=False)
return self.visit(node)
self.visitchildren(node)
return node
def visit_PyClassDefNode(self, node):
if 'cclass' in self.directives:
node = node.as_cclass()
return self.visit(node)
else:
old_in_pyclass = self.in_py_class
self.in_py_class = True
self.visitchildren(node)
self.in_py_class = old_in_pyclass
return node
def visit_CClassDefNode(self, node):
old_in_pyclass = self.in_py_class
self.in_py_class = False
self.visitchildren(node)
self.in_py_class = old_in_pyclass
return node
class AlignFunctionDefinitions(CythonTransform):
"""
......@@ -1476,6 +1801,43 @@ class AlignFunctionDefinitions(CythonTransform):
return node
class RemoveUnreachableCode(CythonTransform):
def visit_Node(self, node):
self.visitchildren(node)
return node
def visit_StatListNode(self, node):
if not self.current_directives['remove_unreachable']:
return node
self.visitchildren(node)
for idx, stat in enumerate(node.stats):
idx += 1
if stat.is_terminator:
if idx < len(node.stats):
if self.current_directives['warn.unreachable']:
warning(node.stats[idx].pos, "Unreachable code", 2)
node.stats = node.stats[:idx]
node.is_terminator = True
break
return node
def visit_IfClauseNode(self, node):
self.visitchildren(node)
if node.body.is_terminator:
node.is_terminator = True
return node
def visit_IfStatNode(self, node):
self.visitchildren(node)
if node.else_clause and node.else_clause.is_terminator:
for clause in node.if_clauses:
if not clause.is_terminator:
break
else:
node.is_terminator = True
return node
class YieldNodeCollector(TreeVisitor):
def __init__(self):
......@@ -1737,32 +2099,118 @@ class GilCheck(VisitorTransform):
Call `node.gil_check(env)` on each node to make sure we hold the
GIL when we need it. Raise an error when on Python operations
inside a `nogil` environment.
Additionally, raise exceptions for closely nested with gil or with nogil
statements. The latter would abort Python.
"""
def __call__(self, root):
self.env_stack = [root.scope]
self.nogil = False
# True for 'cdef func() nogil:' functions, as the GIL may be held while
# calling this function (thus contained 'nogil' blocks may be valid).
self.nogil_declarator_only = False
return super(GilCheck, self).__call__(root)
def visit_FuncDefNode(self, node):
self.env_stack.append(node.local_scope)
was_nogil = self.nogil
self.nogil = node.local_scope.nogil
if self.nogil:
self.nogil_declarator_only = True
if self.nogil and node.nogil_check:
node.nogil_check(node.local_scope)
self.visitchildren(node)
# This cannot be nested, so it doesn't need backup/restore
self.nogil_declarator_only = False
self.env_stack.pop()
self.nogil = was_nogil
return node
def visit_GILStatNode(self, node):
env = self.env_stack[-1]
if self.nogil and node.nogil_check: node.nogil_check()
if self.nogil and node.nogil_check:
node.nogil_check()
was_nogil = self.nogil
self.nogil = (node.state == 'nogil')
if was_nogil == self.nogil and not self.nogil_declarator_only:
if not was_nogil:
error(node.pos, "Trying to acquire the GIL while it is "
"already held.")
else:
error(node.pos, "Trying to release the GIL while it was "
"previously released.")
if isinstance(node.finally_clause, Nodes.StatListNode):
# The finally clause of the GILStatNode is a GILExitNode,
# which is wrapped in a StatListNode. Just unpack that.
node.finally_clause, = node.finally_clause.stats
if node.state == 'gil':
self.seen_with_gil_statement = True
self.visitchildren(node)
self.nogil = was_nogil
return node
def visit_ParallelRangeNode(self, node):
if node.is_nogil:
node.is_nogil = False
node = Nodes.GILStatNode(node.pos, state='nogil', body=node)
return self.visit_GILStatNode(node)
if not self.nogil:
error(node.pos, "prange() can only be used without the GIL")
# Forget about any GIL-related errors that may occur in the body
return None
node.nogil_check(self.env_stack[-1])
self.visitchildren(node)
return node
def visit_ParallelWithBlockNode(self, node):
if not self.nogil:
error(node.pos, "The parallel section may only be used without "
"the GIL")
return None
if node.nogil_check:
# It does not currently implement this, but test for it anyway to
# avoid potential future surprises
node.nogil_check(self.env_stack[-1])
self.visitchildren(node)
return node
def visit_TryFinallyStatNode(self, node):
"""
Take care of try/finally statements in nogil code sections. The
'try' must contain a 'with gil:' statement somewhere.
"""
if not self.nogil or isinstance(node, Nodes.GILStatNode):
return self.visit_Node(node)
node.nogil_check = None
node.is_try_finally_in_nogil = True
# First, visit the body and check for errors
self.seen_with_gil_statement = False
self.visitchildren(node.body)
if not self.seen_with_gil_statement:
error(node.pos, "Cannot use try/finally in nogil sections unless "
"it contains a 'with gil' statement.")
self.visitchildren(node.finally_clause)
return node
def visit_Node(self, node):
if self.env_stack and self.nogil and node.nogil_check:
node.nogil_check(self.env_stack[-1])
......@@ -1801,29 +2249,34 @@ class TransformBuiltinMethods(EnvTransform):
return node
def _inject_locals(self, node, func_name):
# locals()/dir() builtins
# locals()/dir()/vars() builtins
lenv = self.current_env()
entry = lenv.lookup_here(func_name)
if entry:
# not the builtin
return node
pos = node.pos
if func_name == 'locals':
if len(node.args) > 0:
if func_name in ('locals', 'vars'):
if func_name == 'locals' and len(node.args) > 0:
error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d"
% len(node.args))
return node
elif func_name == 'vars':
if len(node.args) > 1:
error(self.pos, "Builtin 'vars()' called with wrong number of args, expected 0-1, got %d"
% len(node.args))
if len(node.args) > 0:
return node # nothing to do
items = [ ExprNodes.DictItemNode(pos,
key=ExprNodes.StringNode(pos, value=var),
value=ExprNodes.NameNode(pos, name=var))
for var in lenv.entries ]
return ExprNodes.DictNode(pos, key_value_pairs=items)
else:
else: # dir()
if len(node.args) > 1:
error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
% len(node.args))
return node
elif len(node.args) == 1:
if len(node.args) > 0:
# optimised in Builtin.py
return node
items = [ ExprNodes.StringNode(pos, value=var) for var in lenv.entries ]
......@@ -1832,7 +2285,7 @@ class TransformBuiltinMethods(EnvTransform):
def visit_SimpleCallNode(self, node):
if isinstance(node.function, ExprNodes.NameNode):
func_name = node.function.name
if func_name in ('dir', 'locals'):
if func_name in ('dir', 'locals', 'vars'):
return self._inject_locals(node, func_name)
# cython.foo
......@@ -1901,9 +2354,7 @@ class ReplaceFusedTypeChecks(VisitorTransform):
# Defer the import until now to avoid circularity...
from Cython.Compiler import Optimize
transform = Optimize.ConstantFolding()
transform.check_constant_value_not_set = False
transform = Optimize.ConstantFolding(reevaluate=True)
def __init__(self, local_scope):
super(ReplaceFusedTypeChecks, self).__init__()
......@@ -1922,8 +2373,8 @@ class ReplaceFusedTypeChecks(VisitorTransform):
type2 = node.operand2.analyse_as_type(self.local_scope)
if type1 and type2:
false = ExprNodes.BoolNode(node.pos, value=False)
true = ExprNodes.BoolNode(node.pos, value=True)
false_node = ExprNodes.BoolNode(node.pos, value=False)
true_node = ExprNodes.BoolNode(node.pos, value=True)
type1 = self.specialize_type(type1, node.operand1.pos)
op = node.operator
......@@ -1935,7 +2386,7 @@ class ReplaceFusedTypeChecks(VisitorTransform):
eq = op in ('is', '==')
if (is_same and eq) or (not is_same and not eq):
return true
return true_node
elif op in ('in', 'not_in'):
# We have to do an instance check directly, as operand2
......@@ -1955,14 +2406,14 @@ class ReplaceFusedTypeChecks(VisitorTransform):
for specific_type in types:
if type1.same_as(specific_type):
if op == 'in':
return true
return true_node
else:
return false
return false_node
if op == 'not_in':
return true
return true_node
return false
return false_node
return node
......@@ -1980,8 +2431,7 @@ class ReplaceFusedTypeChecks(VisitorTransform):
class DebugTransform(CythonTransform):
"""
Create debug information and all functions' visibility to extern in order
to enable debugging.
Write debug information for this Cython module.
"""
def __init__(self, context, options, result):
......
......@@ -1580,7 +1580,7 @@ def p_with_statement(s):
def p_with_items(s):
pos = s.position()
if not s.in_python_file and s.sy == 'IDENT' and s.systring == 'nogil':
if not s.in_python_file and s.sy == 'IDENT' and s.systring in ('nogil', 'gil'):
state = s.systring
s.next()
if s.sy == ',':
......@@ -1750,7 +1750,7 @@ def p_statement(s, ctx, first_statement = 0):
elif s.sy == 'IF':
return p_IF_statement(s, ctx)
elif s.sy == 'DECORATOR':
if ctx.level not in ('module', 'class', 'c_class', 'function', 'property', 'module_pxd', 'c_class_pxd'):
if ctx.level not in ('module', 'class', 'c_class', 'function', 'property', 'module_pxd', 'c_class_pxd', 'other'):
s.error('decorator not allowed here')
s.level = ctx.level
decorators = p_decorators(s)
......
......@@ -2163,7 +2163,10 @@ def get_all_specific_permutations(fused_types, id="", f2s=()):
result = []
for newid, specific_type in enumerate(fused_type.types):
f2s = dict(f2s, **{ fused_type: specific_type })
# f2s = dict(f2s, **{ fused_type: specific_type })
f2s = dict(f2s)
f2s.update({ fused_type: specific_type })
if id:
cname = '%s_%s' % (id, newid)
else:
......
......@@ -177,6 +177,7 @@ class Entry(object):
prev_entry = None
might_overflow = 0
fused_cfunction = None
in_with_gil_block = 0
def __init__(self, name, cname, type, pos = None, init = None):
self.name = name
......@@ -515,10 +516,11 @@ class Scope(object):
return entry
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0):
# Add an entry for a variable.
if not cname:
if visibility != 'private':
if visibility != 'private' or api:
cname = name
else:
cname = self.mangle(Naming.var_prefix, name)
......@@ -528,7 +530,12 @@ class Scope(object):
error(pos, "C++ class must have a default constructor to be stack allocated")
entry = self.declare(name, cname, type, pos, visibility)
entry.is_variable = 1
entry.api = api
if in_pxd and visibility != 'extern':
entry.defined_in_pxd = 1
entry.used = 1
if api:
entry.api = 1
entry.used = 1
self.control_flow.set_state((), (name, 'initialized'), False)
return entry
......@@ -586,11 +593,11 @@ class Scope(object):
self.pyfunc_entries.append(entry)
def declare_cfunction(self, name, type, pos,
cname = None, visibility = 'private', defining = 0,
api = 0, in_pxd = 0, modifiers = (), utility_code = None):
cname = None, visibility = 'private', api = 0, in_pxd = 0,
defining = 0, modifiers = (), utility_code = None):
# Add an entry for a C function.
if not cname:
if api or visibility != 'private':
if visibility != 'private' or api:
cname = name
else:
cname = self.mangle(Naming.func_prefix, name)
......@@ -734,6 +741,9 @@ class Scope(object):
else:
return outer.is_cpp()
def add_include_file(self, filename):
self.outer_scope.add_include_file(filename)
class PreImportScope(Scope):
......@@ -1033,13 +1043,12 @@ class ModuleScope(Scope):
return entry
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0):
# Add an entry for a global variable. If it is a Python
# object type, and not declared with cdef, it will live
# in the module dictionary, otherwise it will be a C
# global variable.
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility, api=api, is_cdef=is_cdef)
if not visibility in ('private', 'public', 'extern'):
error(pos, "Module-level variable cannot be declared %s" % visibility)
if not is_cdef:
......@@ -1048,12 +1057,66 @@ class ModuleScope(Scope):
if not (type.is_pyobject and not type.is_extension_type):
raise InternalError(
"Non-cdef global variable is not a generic Python object")
entry.is_pyglobal = 1
else:
if not cname:
defining = not in_pxd
if (visibility == 'extern' or (visibility == 'public' and defining)):
cname = name
else:
cname = self.mangle(Naming.var_prefix, name)
entry = self.lookup_here(name)
if entry and entry.defined_in_pxd:
#if visibility != 'private' and visibility != entry.visibility:
# warning(pos, "Variable '%s' previously declared as '%s'" % (name, entry.visibility), 1)
if not entry.type.same_as(type):
if visibility == 'extern' and entry.visibility == 'extern':
warning(pos, "Variable '%s' type does not match previous declaration" % name, 1)
entry.type = type
#else:
# error(pos, "Variable '%s' type does not match previous declaration" % name)
if entry.visibility != "private":
mangled_cname = self.mangle(Naming.var_prefix, name)
if entry.cname == mangled_cname:
cname = name
entry.cname = name
if not entry.is_implemented:
entry.is_implemented = True
return entry
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility,
api=api, in_pxd=in_pxd, is_cdef=is_cdef)
if is_cdef:
entry.is_cglobal = 1
if entry.type.is_pyobject:
entry.init = 0
self.var_entries.append(entry)
else:
entry.is_pyglobal = 1
return entry
def declare_cfunction(self, name, type, pos,
cname = None, visibility = 'private', api = 0, in_pxd = 0,
defining = 0, modifiers = (), utility_code = None):
# Add an entry for a C function.
if not cname:
if (visibility == 'extern' or (visibility == 'public' and defining)):
cname = name
else:
cname = self.mangle(Naming.func_prefix, name)
entry = self.lookup_here(name)
if entry and entry.defined_in_pxd:
if entry.visibility != "private":
mangled_cname = self.mangle(Naming.var_prefix, name)
if entry.cname == mangled_cname:
cname = name
entry.cname = cname
entry.func_cname = cname
entry = Scope.declare_cfunction(
self, name, type, pos,
cname = cname, visibility = visibility, api = api, in_pxd = in_pxd,
defining = defining, modifiers = modifiers, utility_code = utility_code)
return entry
def declare_global(self, name, pos):
......@@ -1299,6 +1362,12 @@ class ModuleScope(Scope):
class LocalScope(Scope):
# Does the function have a 'with gil:' block?
has_with_gil_block = False
# Transient attribute, used for symbol table variable declarations
_in_with_gil_block = False
def __init__(self, name, outer_scope, parent_scope = None):
if parent_scope is None:
parent_scope = outer_scope
......@@ -1321,16 +1390,20 @@ class LocalScope(Scope):
return entry
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0):
# Add an entry for a local variable.
if visibility in ('public', 'readonly'):
error(pos, "Local variable cannot be declared %s" % visibility)
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility, api=api, is_cdef=is_cdef)
cname=cname, visibility=visibility,
api=api, in_pxd=in_pxd, is_cdef=is_cdef)
if type.is_pyobject and not Options.init_local_none:
entry.init = "0"
entry.init_to_none = (type.is_pyobject or type.is_unspecified) and Options.init_local_none
entry.is_local = 1
entry.in_with_gil_block = self._in_with_gil_block
self.var_entries.append(entry)
return entry
......@@ -1403,7 +1476,8 @@ class GeneratorExpressionScope(Scope):
return '%s%s' % (self.genexp_prefix, self.parent_scope.mangle(prefix, name))
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = True):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = True):
if type is unspecified_type:
# if the outer scope defines a type for this variable, inherit it
outer_entry = self.outer_scope.lookup(name)
......@@ -1452,7 +1526,9 @@ class StructOrUnionScope(Scope):
Scope.__init__(self, name, None, None)
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0, allow_pyobject = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0,
allow_pyobject = 0):
# Add an entry for an attribute.
if not cname:
cname = name
......@@ -1472,8 +1548,8 @@ class StructOrUnionScope(Scope):
return entry
def declare_cfunction(self, name, type, pos,
cname = None, visibility = 'private', defining = 0,
api = 0, in_pxd = 0, modifiers = ()): # currently no utility code ...
cname = None, visibility = 'private', api = 0, in_pxd = 0,
defining = 0, modifiers = ()): # currently no utility code ...
return self.declare_var(name, type, pos,
cname=cname, visibility=visibility)
......@@ -1519,12 +1595,14 @@ class PyClassScope(ClassScope):
is_py_class_scope = 1
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0):
if type is unspecified_type:
type = py_object_type
# Add an entry for a class attribute.
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility, api=api, is_cdef=is_cdef)
cname=cname, visibility=visibility,
api=api, in_pxd=in_pxd, is_cdef=is_cdef)
entry.is_pyglobal = 1 # FIXME: WTF?
entry.is_pyclass_attr = 1
return entry
......@@ -1583,7 +1661,8 @@ class CClassScope(ClassScope):
self.parent_type.base_type.scope.needs_gc())
def declare_var(self, name, type, pos,
cname = None, visibility = 'private', api = 0, is_cdef = 0):
cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0):
if is_cdef:
# Add an entry for an attribute.
if self.defined:
......@@ -1609,6 +1688,10 @@ class CClassScope(ClassScope):
error(pos,
"Attribute of extension type cannot be declared %s" % visibility)
if visibility in ('public', 'readonly'):
# If the field is an external typedef, we cannot be sure about the type,
# so do conversion ourself rather than rely on the CPython mechanism (through
# a property; made in AnalyseDeclarationsTransform).
entry.needs_property = True
if name == "__weakref__":
error(pos, "Special attribute __weakref__ cannot be exposed to Python")
if not type.is_pyobject:
......@@ -1617,13 +1700,16 @@ class CClassScope(ClassScope):
type.create_from_py_utility_code(self))):
error(pos,
"C attribute of type '%s' cannot be accessed from Python" % type)
else:
entry.needs_property = False
return entry
else:
if type is unspecified_type:
type = py_object_type
# Add an entry for a class attribute.
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility, api=api, is_cdef=is_cdef)
cname=cname, visibility=visibility,
api=api, in_pxd=in_pxd, is_cdef=is_cdef)
entry.is_member = 1
entry.is_pyglobal = 1 # xxx: is_pyglobal changes behaviour in so many places that
# I keep it in for now. is_member should be enough
......@@ -1660,9 +1746,8 @@ class CClassScope(ClassScope):
return ClassScope.lookup_here(self, name)
def declare_cfunction(self, name, type, pos,
cname = None, visibility = 'private',
defining = 0, api = 0, in_pxd = 0, modifiers = (),
utility_code = None):
cname = None, visibility = 'private', api = 0, in_pxd = 0,
defining = 0, modifiers = (), utility_code = None):
if get_special_method_signature(name):
error(pos, "Special methods must be declared with 'def', not 'cdef'")
args = type.args
......@@ -1774,8 +1859,9 @@ class CppClassScope(Scope):
self.inherited_var_entries = []
def declare_var(self, name, type, pos,
cname = None, visibility = 'extern', api = 0,
is_cdef = 0, allow_pyobject = 0):
cname = None, visibility = 'extern',
api = 0, in_pxd = 0, is_cdef = 0,
allow_pyobject = 0):
# Add an entry for an attribute.
if not cname:
cname = name
......@@ -1816,9 +1902,9 @@ class CppClassScope(Scope):
error(pos, "no matching function for call to %s::%s()" %
(self.default_constructor, self.default_constructor))
def declare_cfunction(self, name, type, pos, cname = None,
visibility = 'extern', api = 0, defining = 0,
in_pxd = 0, modifiers = (), utility_code = None):
def declare_cfunction(self, name, type, pos,
cname = None, visibility = 'extern', api = 0, in_pxd = 0,
defining = 0, modifiers = (), utility_code = None):
if name == self.name.split('::')[-1] and cname is None:
self.check_base_default_constructor(pos)
name = '<init>'
......@@ -1878,8 +1964,6 @@ class CppClassScope(Scope):
utility_code = e.utility_code)
return scope
def add_include_file(self, filename):
self.outer_scope.add_include_file(filename)
class PropertyScope(Scope):
# Scope holding the __get__, __set__ and __del__ methods for
......
......@@ -4,6 +4,7 @@ from Cython.Compiler import CmdLine
from Cython.TestUtils import TransformTest
from Cython.Compiler.ParseTreeTransforms import *
from Cython.Compiler.Nodes import *
from Cython.Compiler import Main
class TestNormalizeTree(TransformTest):
......@@ -144,6 +145,62 @@ class TestWithTransform(object): # (TransformTest): # Disabled!
""", t)
class TestInterpretCompilerDirectives(TransformTest):
"""
This class tests the parallel directives AST-rewriting and importing.
"""
# Test the parallel directives (c)importing
import_code = u"""
cimport cython.parallel
cimport cython.parallel as par
from cython cimport parallel as par2
from cython cimport parallel
from cython.parallel cimport threadid as tid
from cython.parallel cimport threadavailable as tavail
from cython.parallel cimport prange
"""
expected_directives_dict = {
u'cython.parallel': u'cython.parallel',
u'par': u'cython.parallel',
u'par2': u'cython.parallel',
u'parallel': u'cython.parallel',
u"tid": u"cython.parallel.threadid",
u"tavail": u"cython.parallel.threadavailable",
u"prange": u"cython.parallel.prange",
}
def setUp(self):
super(TestInterpretCompilerDirectives, self).setUp()
compilation_options = Main.CompilationOptions(Main.default_options)
ctx = compilation_options.create_context()
self.pipeline = [
InterpretCompilerDirectives(ctx, ctx.compiler_directives),
]
self.debug_exception_on_error = DebugFlags.debug_exception_on_error
def tearDown(self):
DebugFlags.debug_exception_on_error = self.debug_exception_on_error
def test_parallel_directives_cimports(self):
self.run_pipeline(self.pipeline, self.import_code)
parallel_directives = self.pipeline[0].parallel_directives
self.assertEqual(parallel_directives, self.expected_directives_dict)
def test_parallel_directives_imports(self):
self.run_pipeline(self.pipeline,
self.import_code.replace(u'cimport', u'import'))
parallel_directives = self.pipeline[0].parallel_directives
self.assertEqual(parallel_directives, self.expected_directives_dict)
# TODO: Re-enable once they're more robust.
if sys.version_info[:2] >= (2, 5) and False:
from Cython.Debugger import DebugWriter
......
......@@ -23,12 +23,24 @@ object_expr = TypedExprNode(py_object_type)
class MarkAssignments(CythonTransform):
def mark_assignment(self, lhs, rhs):
def __init__(self, context):
super(CythonTransform, self).__init__()
self.context = context
# Track the parallel block scopes (with parallel, for i in prange())
self.parallel_block_stack = []
def mark_assignment(self, lhs, rhs, inplace_op=None):
if isinstance(lhs, (ExprNodes.NameNode, Nodes.PyArgDeclNode)):
if lhs.entry is None:
# TODO: This shouldn't happen...
return
lhs.entry.assignments.append(rhs)
if self.parallel_block_stack:
parallel_node = self.parallel_block_stack[-1]
parallel_node.assignments[lhs.entry] = (lhs.pos, inplace_op)
elif isinstance(lhs, ExprNodes.SequenceNode):
for arg in lhs.args:
self.mark_assignment(arg, object_expr)
......@@ -48,7 +60,7 @@ class MarkAssignments(CythonTransform):
return node
def visit_InPlaceAssignmentNode(self, node):
self.mark_assignment(node.lhs, node.create_binop_node())
self.mark_assignment(node.lhs, node.create_binop_node(), node.operator)
self.visitchildren(node)
return node
......@@ -56,6 +68,11 @@ class MarkAssignments(CythonTransform):
# TODO: Remove redundancy with range optimization...
is_special = False
sequence = node.iterator.sequence
if isinstance(sequence, ExprNodes.SimpleCallNode):
function = sequence.function
if sequence.self is None and function.is_name:
if function.name == 'reversed' and len(sequence.args) == 1:
sequence = sequence.args[0]
if isinstance(sequence, ExprNodes.SimpleCallNode):
function = sequence.function
if sequence.self is None and function.is_name:
......@@ -70,6 +87,7 @@ class MarkAssignments(CythonTransform):
'+',
sequence.args[0],
sequence.args[2]))
if not is_special:
# A for-loop basically translates to subsequent calls to
# __getitem__(), so using an IndexNode here allows us to
......@@ -127,6 +145,27 @@ class MarkAssignments(CythonTransform):
self.visitchildren(node)
return node
def visit_ParallelStatNode(self, node):
if self.parallel_block_stack:
node.parent = self.parallel_block_stack[-1]
else:
node.parent = None
if node.is_prange:
if not node.parent:
node.is_parallel = True
else:
node.is_parallel = (node.parent.is_prange or not
node.parent.is_parallel)
else:
node.is_parallel = True
self.parallel_block_stack.append(node)
self.visitchildren(node)
self.parallel_block_stack.pop()
return node
class MarkOverflowingArithmetic(CythonTransform):
# It may be possible to integrate this with the above for
......
......@@ -14,9 +14,12 @@ class TempHandle(object):
# THIS IS DEPRECATED, USE LetRefNode instead
temp = None
needs_xdecref = False
def __init__(self, type):
def __init__(self, type, needs_cleanup=None):
self.type = type
self.needs_cleanup = type.is_pyobject
if needs_cleanup is None:
self.needs_cleanup = type.is_pyobject
else:
self.needs_cleanup = needs_cleanup
def ref(self, pos):
return TempRefNode(pos, handle=self, type=self.type)
......
......@@ -64,7 +64,7 @@ class TreeVisitor(object):
u'gil_message', u'cpp_message',
u'subexprs']
values = []
pos = node.pos
pos = getattr(node, 'pos', None)
if pos:
source = pos[0]
if source:
......@@ -131,7 +131,7 @@ class TreeVisitor(object):
trace.append(u"File '%s', line %d, in %s: %s" % (
pos[0], pos[1], method_name, self.dump_node(node)))
raise Errors.CompilerCrash(
last_node.pos, self.__class__.__name__,
getattr(last_node, 'pos', None), self.__class__.__name__,
u'\n'.join(trace), e, stacktrace)
def find_handler(self, obj):
......
......@@ -19,10 +19,6 @@ from distutils.dir_util import mkpath
from distutils.command import build_ext as _build_ext
from distutils import sysconfig
if sys.version_info < (3, 0):
from Cython.Utils import any
extension_name_re = _build_ext.extension_name_re
show_compilers = _build_ext.show_compilers
......@@ -122,8 +118,8 @@ class build_ext(_build_ext.build_ext):
# If --pyrex-gdb is in effect as a command line option or as option
# of any Extension module, disable optimization for the C or C++
# compiler.
if (self.pyrex_gdb or any([getattr(ext, 'pyrex_gdb', False)
for ext in self.extensions])):
if self.pyrex_gdb or [1 for ext in self.extensions
if getattr(ext, 'pyrex_gdb', False)]:
optimization.disable_optimization()
_build_ext.build_ext.run(self)
......
......@@ -146,6 +146,7 @@ from cpython.method cimport *
from cpython.weakref cimport *
from cpython.getargs cimport *
from cpython.pythread cimport *
from cpython.pystate cimport *
# Python <= 2.x
from cpython.cobject cimport *
......
# Thread and interpreter state structures and their interfaces
from cpython.ref cimport PyObject
cdef extern from "Python.h":
# We make these an opague types. If the user wants specific attributes,
# they can be declared manually.
ctypedef struct PyInterpreterState:
pass
ctypedef struct PyThreadState:
pass
ctypedef struct PyFrameObject:
pass
# This is not actually a struct, but make sure it can never be coerced to
# an int or used in arithmetic expressions
ctypedef struct PyGILState_STATE
# The type of the trace function registered using PyEval_SetProfile() and
# PyEval_SetTrace().
# Py_tracefunc return -1 when raising an exception, or 0 for success.
ctypedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *)
# The following values are used for 'what' for tracefunc functions
enum:
PyTrace_CALL
PyTrace_EXCEPTION
PyTrace_LINE
PyTrace_RETURN
PyTrace_C_CALL
PyTrace_C_EXCEPTION
PyTrace_C_RETURN
PyInterpreterState * PyInterpreterState_New()
void PyInterpreterState_Clear(PyInterpreterState *)
void PyInterpreterState_Delete(PyInterpreterState *)
PyThreadState * PyThreadState_New(PyInterpreterState *)
void PyThreadState_Clear(PyThreadState *)
void PyThreadState_Delete(PyThreadState *)
PyThreadState * PyThreadState_Get()
PyThreadState * PyThreadState_Swap(PyThreadState *)
PyObject * PyThreadState_GetDict()
int PyThreadState_SetAsyncExc(long, PyObject *)
# Ensure that the current thread is ready to call the Python
# C API, regardless of the current state of Python, or of its
# thread lock. This may be called as many times as desired
# by a thread so long as each call is matched with a call to
# PyGILState_Release(). In general, other thread-state APIs may
# be used between _Ensure() and _Release() calls, so long as the
# thread-state is restored to its previous state before the Release().
# For example, normal use of the Py_BEGIN_ALLOW_THREADS/
# Py_END_ALLOW_THREADS macros are acceptable.
# The return value is an opaque "handle" to the thread state when
# PyGILState_Ensure() was called, and must be passed to
# PyGILState_Release() to ensure Python is left in the same state. Even
# though recursive calls are allowed, these handles can *not* be shared -
# each unique call to PyGILState_Ensure must save the handle for its
# call to PyGILState_Release.
# When the function returns, the current thread will hold the GIL.
# Failure is a fatal error.
PyGILState_STATE PyGILState_Ensure()
# Release any resources previously acquired. After this call, Python's
# state will be the same as it was prior to the corresponding
# PyGILState_Ensure() call (but generally this state will be unknown to
# the caller, hence the use of the GILState API.)
# Every call to PyGILState_Ensure must be matched by a call to
# PyGILState_Release on the same thread.
void PyGILState_Release(PyGILState_STATE)
# Routines for advanced debuggers, requested by David Beazley.
# Don't use unless you know what you are doing!
PyInterpreterState * PyInterpreterState_Head()
PyInterpreterState * PyInterpreterState_Next(PyInterpreterState *)
PyThreadState * PyInterpreterState_ThreadHead(PyInterpreterState *)
PyThreadState * PyThreadState_Next(PyThreadState *)
......@@ -81,17 +81,17 @@ cdef extern from "numpy/arrayobject.h":
NPY_COMPLEX256
NPY_COMPLEX512
enum NPY_ORDER:
ctypedef enum NPY_ORDER:
NPY_ANYORDER
NPY_CORDER
NPY_FORTRANORDER
enum NPY_CLIPMODE:
ctypedef enum NPY_CLIPMODE:
NPY_CLIP
NPY_WRAP
NPY_RAISE
enum NPY_SCALARKIND:
ctypedef enum NPY_SCALARKIND:
NPY_NOSCALAR,
NPY_BOOL_SCALAR,
NPY_INTPOS_SCALAR,
......@@ -101,12 +101,12 @@ cdef extern from "numpy/arrayobject.h":
NPY_OBJECT_SCALAR
enum NPY_SORTKIND:
ctypedef enum NPY_SORTKIND:
NPY_QUICKSORT
NPY_HEAPSORT
NPY_MERGESORT
cdef enum requirements:
enum:
NPY_C_CONTIGUOUS
NPY_F_CONTIGUOUS
NPY_CONTIGUOUS
......
cdef extern from "omp.h":
ctypedef struct omp_lock_t
ctypedef struct omp_nest_lock_t
ctypedef enum omp_sched_t:
omp_sched_static = 1,
omp_sched_dynamic = 2,
omp_sched_guided = 3,
omp_sched_auto = 4
extern void omp_set_num_threads(int)
extern int omp_get_num_threads()
extern int omp_get_max_threads()
extern int omp_get_thread_num()
extern int omp_get_num_procs()
extern int omp_in_parallel()
extern void omp_set_dynamic(int)
extern int omp_get_dynamic()
extern void omp_set_nested(int)
extern int omp_get_nested()
extern void omp_init_lock(omp_lock_t *)
extern void omp_destroy_lock(omp_lock_t *)
extern void omp_set_lock(omp_lock_t *)
extern void omp_unset_lock(omp_lock_t *)
extern int omp_test_lock(omp_lock_t *)
extern void omp_init_nest_lock(omp_nest_lock_t *)
extern void omp_destroy_nest_lock(omp_nest_lock_t *)
extern void omp_set_nest_lock(omp_nest_lock_t *)
extern void omp_unset_nest_lock(omp_nest_lock_t *)
extern int omp_test_nest_lock(omp_nest_lock_t *)
extern double omp_get_wtime()
extern double omp_get_wtick()
void omp_set_schedule(omp_sched_t, int)
void omp_get_schedule(omp_sched_t *, int *)
int omp_get_thread_limit()
void omp_set_max_active_levels(int)
int omp_get_max_active_levels()
int omp_get_level()
int omp_get_ancestor_thread_num(int)
int omp_get_team_size(int)
int omp_get_active_level()
from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF, Py_XDECREF
from cpython.exc cimport PyErr_Fetch, PyErr_Restore
from cpython.pystate cimport PyThreadState_Get
loglevel = 0
......@@ -80,6 +81,7 @@ cdef PyObject* SetupContext(char* funcname, int lineno, char* filename) except N
return NULL
cdef PyObject* type = NULL, *value = NULL, *tb = NULL
cdef PyObject* result = NULL
PyThreadState_Get()
PyErr_Fetch(&type, &value, &tb)
try:
ctx = Context(funcname, lineno, filename)
......@@ -131,16 +133,19 @@ cdef void GIVEREF(PyObject* ctx, PyObject* p_obj, int lineno):
cdef void INCREF(PyObject* ctx, PyObject* obj, int lineno):
if obj is not NULL: Py_INCREF(<object>obj)
PyThreadState_Get()
GOTREF(ctx, obj, lineno)
cdef void DECREF(PyObject* ctx, PyObject* obj, int lineno):
if GIVEREF_and_report(ctx, obj, lineno):
if obj is not NULL: Py_DECREF(<object>obj)
PyThreadState_Get()
cdef void FinishContext(PyObject** ctx):
if ctx == NULL or ctx[0] == NULL: return
cdef PyObject* type = NULL, *value = NULL, *tb = NULL
cdef object errors = None
PyThreadState_Get()
PyErr_Fetch(&type, &value, &tb)
try:
try:
......
......@@ -26,6 +26,8 @@ class _EmptyDecoratorAndManager(object):
def __exit__(self, exc_type, exc_value, traceback):
pass
cclass = ccall = cfunc = _EmptyDecoratorAndManager()
def inline(f, *args, **kwds):
if isinstance(f, basestring):
from Cython.Build.Inline import cython_inline
......@@ -88,6 +90,7 @@ class _nogil(object):
return exc_class is None
nogil = _nogil()
gil = _nogil()
del _nogil
# Emulated types
......@@ -326,3 +329,28 @@ NULL = p_void(0)
integral = floating = numeric = _FusedType()
type_ordering = [py_int, py_long, py_float, py_complex]
class CythonDotParallel(object):
"""
The cython.parallel module.
"""
__all__ = ['parallel', 'prange', 'threadid']
parallel = nogil
def prange(self, start=0, stop=None, step=1, schedule=None, nogil=False):
if stop is None:
stop = start
start = 0
return range(start, stop, step)
def threadid(self):
return 0
# def threadsavailable(self):
# return 1
import sys
sys.modules['cython.parallel'] = CythonDotParallel()
del sys
cimport cython
@cython.locals(x=Py_ssize_t)
cdef combinations(list l)
@cython.locals(x1=double, x2=double, y1=double, y2=double, z1=double, z2=double,
m1=double, m2=double, vx=double, vy=double, vz=double, i=long)
cdef advance(double dt, long n, list bodies=*, list pairs=*)
@cython.locals(x1=double, x2=double, y1=double, y2=double, z1=double, z2=double,
m=double, m1=double, m2=double, vx=double, vy=double, vz=double)
cdef report_energy(list bodies=*, list pairs=*, double e=*)
@cython.locals(vx=double, vy=double, vz=double, m=double)
cdef offset_momentum(tuple ref, list bodies=*, double px=*, double py=*, double pz=*)
cpdef test_nbody(long iterations)
#!/usr/bin/env python
"""N-body benchmark from the Computer Language Benchmarks Game.
This is intended to support Unladen Swallow's perf.py. Accordingly, it has been
modified from the Shootout version:
- Accept standard Unladen Swallow benchmark options.
- Run report_energy()/advance() in a loop.
- Reimplement itertools.combinations() to work with older Python versions.
"""
# Pulled from http://shootout.alioth.debian.org/u64q/benchmark.php?test=nbody&lang=python&id=4
# Contributed by Kevin Carson.
# Modified by Tupteq, Fredrik Johansson, and Daniel Nanz.
__contact__ = "collinwinter@google.com (Collin Winter)"
# Python imports
import optparse
import sys
from time import time
# Local imports
import util
def combinations(l):
"""Pure-Python implementation of itertools.combinations(l, 2)."""
result = []
for x in range(len(l) - 1):
ls = l[x+1:]
for y in ls:
result.append((l[x],y))
return result
PI = 3.14159265358979323
SOLAR_MASS = 4 * PI * PI
DAYS_PER_YEAR = 365.24
BODIES = {
'sun': ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], SOLAR_MASS),
'jupiter': ([4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01],
[1.66007664274403694e-03 * DAYS_PER_YEAR,
7.69901118419740425e-03 * DAYS_PER_YEAR,
-6.90460016972063023e-05 * DAYS_PER_YEAR],
9.54791938424326609e-04 * SOLAR_MASS),
'saturn': ([8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01],
[-2.76742510726862411e-03 * DAYS_PER_YEAR,
4.99852801234917238e-03 * DAYS_PER_YEAR,
2.30417297573763929e-05 * DAYS_PER_YEAR],
2.85885980666130812e-04 * SOLAR_MASS),
'uranus': ([1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01],
[2.96460137564761618e-03 * DAYS_PER_YEAR,
2.37847173959480950e-03 * DAYS_PER_YEAR,
-2.96589568540237556e-05 * DAYS_PER_YEAR],
4.36624404335156298e-05 * SOLAR_MASS),
'neptune': ([1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01],
[2.68067772490389322e-03 * DAYS_PER_YEAR,
1.62824170038242295e-03 * DAYS_PER_YEAR,
-9.51592254519715870e-05 * DAYS_PER_YEAR],
5.15138902046611451e-05 * SOLAR_MASS) }
SYSTEM = list(BODIES.values())
PAIRS = combinations(SYSTEM)
def advance(dt, n, bodies=SYSTEM, pairs=PAIRS):
for i in range(n):
for (([x1, y1, z1], v1, m1),
([x2, y2, z2], v2, m2)) in pairs:
dx = x1 - x2
dy = y1 - y2
dz = z1 - z2
mag = dt * ((dx * dx + dy * dy + dz * dz) ** (-1.5))
b1m = m1 * mag
b2m = m2 * mag
v1[0] -= dx * b2m
v1[1] -= dy * b2m
v1[2] -= dz * b2m
v2[0] += dx * b1m
v2[1] += dy * b1m
v2[2] += dz * b1m
for (r, [vx, vy, vz], m) in bodies:
r[0] += dt * vx
r[1] += dt * vy
r[2] += dt * vz
def report_energy(bodies=SYSTEM, pairs=PAIRS, e=0.0):
for (((x1, y1, z1), v1, m1),
((x2, y2, z2), v2, m2)) in pairs:
dx = x1 - x2
dy = y1 - y2
dz = z1 - z2
e -= (m1 * m2) / ((dx * dx + dy * dy + dz * dz) ** 0.5)
for (r, [vx, vy, vz], m) in bodies:
e += m * (vx * vx + vy * vy + vz * vz) / 2.
return e
def offset_momentum(ref, bodies=SYSTEM, px=0.0, py=0.0, pz=0.0):
for (r, [vx, vy, vz], m) in bodies:
px -= vx * m
py -= vy * m
pz -= vz * m
(r, v, m) = ref
v[0] = px / m
v[1] = py / m
v[2] = pz / m
def test_nbody(iterations):
# Warm-up runs.
report_energy()
advance(0.01, 20000)
report_energy()
times = []
for _ in range(iterations):
t0 = time()
report_energy()
advance(0.01, 20000)
report_energy()
t1 = time()
times.append(t1 - t0)
return times
if __name__ == '__main__':
parser = optparse.OptionParser(
usage="%prog [options]",
description=("Run the n-body benchmark."))
util.add_standard_options_to(parser)
options, args = parser.parse_args()
offset_momentum(BODIES['sun']) # Set up global state
util.run_benchmark(options, options.num_runs, test_nbody)
#!/usr/bin/env python
"""Simple, brute-force N-Queens solver."""
__author__ = "collinwinter@google.com (Collin Winter)"
# Python imports
import optparse
import re
import string
from time import time
# Local imports
import util
import cython
try:
from builtins import range as _xrange
except ImportError:
from __builtin__ import xrange as _xrange
# Pure-Python implementation of itertools.permutations().
@cython.locals(n=int, i=int, j=int)
def permutations(iterable):
"""permutations(range(3), 2) --> (0,1) (0,2) (1,0) (1,2) (2,0) (2,1)"""
pool = tuple(iterable)
n = len(pool)
indices = list(range(n))
cycles = list(range(1, n+1))[::-1]
yield [ pool[i] for i in indices ]
while n:
for i in reversed(range(n)):
j = cycles[i] - 1
if j == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
cycles[i] = j
indices[i], indices[-j] = indices[-j], indices[i]
yield [ pool[i] for i in indices ]
break
else:
return
# From http://code.activestate.com/recipes/576647/
@cython.locals(queen_count=int, i=int, vec=list)
def n_queens(queen_count):
"""N-Queens solver.
Args:
queen_count: the number of queens to solve for. This is also the
board size.
Yields:
Solutions to the problem. Each yielded value is looks like
(3, 8, 2, 1, 4, ..., 6) where each number is the column position for the
queen, and the index into the tuple indicates the row.
"""
cols = list(range(queen_count))
for vec in permutations(cols):
if (queen_count == len({ vec[i]+i for i in cols })
== len({ vec[i]-i for i in cols })):
yield vec
def test_n_queens(iterations):
# Warm-up runs.
list(n_queens(8))
list(n_queens(8))
times = []
for _ in _xrange(iterations):
t0 = time()
list(n_queens(8))
t1 = time()
times.append(t1 - t0)
return times
if __name__ == "__main__":
parser = optparse.OptionParser(
usage="%prog [options]",
description=("Test the performance of an N-Queens solvers."))
util.add_standard_options_to(parser)
options, args = parser.parse_args()
util.run_benchmark(options, options.num_runs, test_n_queens)
cimport cython
cdef class Packet:
cdef public object link
cdef public object ident
cdef public object kind
cdef public Py_ssize_t datum
cdef public list data
cpdef append_to(self,lst)
cdef class TaskRec:
pass
cdef class DeviceTaskRec(TaskRec):
cdef public object pending
cdef class IdleTaskRec(TaskRec):
cdef public long control
cdef public Py_ssize_t count
cdef class HandlerTaskRec(TaskRec):
cdef public object work_in # = None
cdef public object device_in # = None
cpdef workInAdd(self,p)
cpdef deviceInAdd(self,p)
cdef class WorkerTaskRec(TaskRec):
cdef public object destination # = I_HANDLERA
cdef public Py_ssize_t count
cdef class TaskState:
cdef public bint packet_pending # = True
cdef public bint task_waiting # = False
cdef public bint task_holding # = False
cpdef packetPending(self)
cpdef waiting(self)
cpdef running(self)
cpdef waitingWithPacket(self)
cpdef bint isPacketPending(self)
cpdef bint isTaskWaiting(self)
cpdef bint isTaskHolding(self)
cpdef bint isTaskHoldingOrWaiting(self)
cpdef bint isWaitingWithPacket(self)
cdef class TaskWorkArea:
cdef public list taskTab # = [None] * TASKTABSIZE
cdef public object taskList # = None
cdef public Py_ssize_t holdCount # = 0
cdef public Py_ssize_t qpktCount # = 0
cdef class Task(TaskState):
cdef public Task link # = taskWorkArea.taskList
cdef public object ident # = i
cdef public object priority # = p
cdef public object input # = w
cdef public object handle # = r
cpdef addPacket(self,Packet p,old)
cpdef runTask(self)
cpdef waitTask(self)
cpdef hold(self)
cpdef release(self,i)
cpdef qpkt(self,Packet pkt)
cpdef findtcb(self,id)
cdef class DeviceTask(Task):
@cython.locals(d=DeviceTaskRec)
cpdef fn(self,Packet pkt,r)
cdef class HandlerTask(Task):
@cython.locals(h=HandlerTaskRec)
cpdef fn(self,Packet pkt,r)
cdef class IdleTask(Task):
@cython.locals(i=IdleTaskRec)
cpdef fn(self,Packet pkt,r)
cdef class WorkTask(Task):
@cython.locals(w=WorkerTaskRec)
cpdef fn(self,Packet pkt,r)
@cython.locals(t=Task)
cpdef schedule()
cdef class Richards:
cpdef run(self, iterations)
# based on a Java version:
# Based on original version written in BCPL by Dr Martin Richards
# in 1981 at Cambridge University Computer Laboratory, England
# and a C++ version derived from a Smalltalk version written by
# L Peter Deutsch.
# Java version: Copyright (C) 1995 Sun Microsystems, Inc.
# Translation from C++, Mario Wolczko
# Outer loop added by Alex Jacoby
# Task IDs
I_IDLE = 1
I_WORK = 2
I_HANDLERA = 3
I_HANDLERB = 4
I_DEVA = 5
I_DEVB = 6
# Packet types
K_DEV = 1000
K_WORK = 1001
# Packet
BUFSIZE = 4
BUFSIZE_RANGE = range(BUFSIZE)
class Packet(object):
def __init__(self,l,i,k):
self.link = l
self.ident = i
self.kind = k
self.datum = 0
self.data = [0] * BUFSIZE
def append_to(self,lst):
self.link = None
if lst is None:
return self
else:
p = lst
next = p.link
while next is not None:
p = next
next = p.link
p.link = self
return lst
# Task Records
class TaskRec(object):
pass
class DeviceTaskRec(TaskRec):
def __init__(self):
self.pending = None
class IdleTaskRec(TaskRec):
def __init__(self):
self.control = 1
self.count = 10000
class HandlerTaskRec(TaskRec):
def __init__(self):
self.work_in = None
self.device_in = None
def workInAdd(self,p):
self.work_in = p.append_to(self.work_in)
return self.work_in
def deviceInAdd(self,p):
self.device_in = p.append_to(self.device_in)
return self.device_in
class WorkerTaskRec(TaskRec):
def __init__(self):
self.destination = I_HANDLERA
self.count = 0
# Task
class TaskState(object):
def __init__(self):
self.packet_pending = True
self.task_waiting = False
self.task_holding = False
def packetPending(self):
self.packet_pending = True
self.task_waiting = False
self.task_holding = False
return self
def waiting(self):
self.packet_pending = False
self.task_waiting = True
self.task_holding = False
return self
def running(self):
self.packet_pending = False
self.task_waiting = False
self.task_holding = False
return self
def waitingWithPacket(self):
self.packet_pending = True
self.task_waiting = True
self.task_holding = False
return self
def isPacketPending(self):
return self.packet_pending
def isTaskWaiting(self):
return self.task_waiting
def isTaskHolding(self):
return self.task_holding
def isTaskHoldingOrWaiting(self):
return self.task_holding or (not self.packet_pending and self.task_waiting)
def isWaitingWithPacket(self):
return self.packet_pending and self.task_waiting and not self.task_holding
tracing = False
layout = 0
def trace(a):
global layout
layout -= 1
if layout <= 0:
print()
layout = 50
print(a, end='')
TASKTABSIZE = 10
class TaskWorkArea(object):
def __init__(self):
self.taskTab = [None] * TASKTABSIZE
self.taskList = None
self.holdCount = 0
self.qpktCount = 0
taskWorkArea = TaskWorkArea()
class Task(TaskState):
def __init__(self,i,p,w,initialState,r):
self.link = taskWorkArea.taskList
self.ident = i
self.priority = p
self.input = w
self.packet_pending = initialState.isPacketPending()
self.task_waiting = initialState.isTaskWaiting()
self.task_holding = initialState.isTaskHolding()
self.handle = r
taskWorkArea.taskList = self
taskWorkArea.taskTab[i] = self
def fn(self,pkt,r):
raise NotImplementedError
def addPacket(self,p,old):
if self.input is None:
self.input = p
self.packet_pending = True
if self.priority > old.priority:
return self
else:
p.append_to(self.input)
return old
def runTask(self):
if self.isWaitingWithPacket():
msg = self.input
self.input = msg.link
if self.input is None:
self.running()
else:
self.packetPending()
else:
msg = None
return self.fn(msg,self.handle)
def waitTask(self):
self.task_waiting = True
return self
def hold(self):
taskWorkArea.holdCount += 1
self.task_holding = True
return self.link
def release(self,i):
t = self.findtcb(i)
t.task_holding = False
if t.priority > self.priority:
return t
else:
return self
def qpkt(self,pkt):
t = self.findtcb(pkt.ident)
taskWorkArea.qpktCount += 1
pkt.link = None
pkt.ident = self.ident
return t.addPacket(pkt,self)
def findtcb(self,id):
t = taskWorkArea.taskTab[id]
if t is None:
raise Exception("Bad task id %d" % id)
return t
# DeviceTask
class DeviceTask(Task):
def __init__(self,i,p,w,s,r):
Task.__init__(self,i,p,w,s,r)
def fn(self,pkt,r):
d = r
assert isinstance(d, DeviceTaskRec)
if pkt is None:
pkt = d.pending
if pkt is None:
return self.waitTask()
else:
d.pending = None
return self.qpkt(pkt)
else:
d.pending = pkt
if tracing: trace(pkt.datum)
return self.hold()
class HandlerTask(Task):
def __init__(self,i,p,w,s,r):
Task.__init__(self,i,p,w,s,r)
def fn(self,pkt,r):
h = r
assert isinstance(h, HandlerTaskRec)
if pkt is not None:
if pkt.kind == K_WORK:
h.workInAdd(pkt)
else:
h.deviceInAdd(pkt)
work = h.work_in
if work is None:
return self.waitTask()
count = work.datum
if count >= BUFSIZE:
h.work_in = work.link
return self.qpkt(work)
dev = h.device_in
if dev is None:
return self.waitTask()
h.device_in = dev.link
dev.datum = work.data[count]
work.datum = count + 1
return self.qpkt(dev)
# IdleTask
class IdleTask(Task):
def __init__(self,i,p,w,s,r):
Task.__init__(self,i,0,None,s,r)
def fn(self,pkt,r):
i = r
assert isinstance(i, IdleTaskRec)
i.count -= 1
if i.count == 0:
return self.hold()
elif i.control & 1 == 0:
i.control //= 2
return self.release(I_DEVA)
else:
i.control = i.control//2 ^ 0xd008
return self.release(I_DEVB)
# WorkTask
A = ord('A')
class WorkTask(Task):
def __init__(self,i,p,w,s,r):
Task.__init__(self,i,p,w,s,r)
def fn(self,pkt,r):
w = r
assert isinstance(w, WorkerTaskRec)
if pkt is None:
return self.waitTask()
if w.destination == I_HANDLERA:
dest = I_HANDLERB
else:
dest = I_HANDLERA
w.destination = dest
pkt.ident = dest
pkt.datum = 0
for i in BUFSIZE_RANGE: # range(BUFSIZE)
w.count += 1
if w.count > 26:
w.count = 1
pkt.data[i] = A + w.count - 1
return self.qpkt(pkt)
import time
def schedule():
t = taskWorkArea.taskList
while t is not None:
pkt = None
if tracing:
print("tcb =", t.ident)
if t.isTaskHoldingOrWaiting():
t = t.link
else:
if tracing: trace(chr(ord("0")+t.ident))
t = t.runTask()
class Richards(object):
def run(self, iterations):
for i in range(iterations):
taskWorkArea.holdCount = 0
taskWorkArea.qpktCount = 0
IdleTask(I_IDLE, 1, 10000, TaskState().running(), IdleTaskRec())
wkq = Packet(None, 0, K_WORK)
wkq = Packet(wkq , 0, K_WORK)
WorkTask(I_WORK, 1000, wkq, TaskState().waitingWithPacket(), WorkerTaskRec())
wkq = Packet(None, I_DEVA, K_DEV)
wkq = Packet(wkq , I_DEVA, K_DEV)
wkq = Packet(wkq , I_DEVA, K_DEV)
HandlerTask(I_HANDLERA, 2000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())
wkq = Packet(None, I_DEVB, K_DEV)
wkq = Packet(wkq , I_DEVB, K_DEV)
wkq = Packet(wkq , I_DEVB, K_DEV)
HandlerTask(I_HANDLERB, 3000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())
wkq = None;
DeviceTask(I_DEVA, 4000, wkq, TaskState().waiting(), DeviceTaskRec());
DeviceTask(I_DEVB, 5000, wkq, TaskState().waiting(), DeviceTaskRec());
schedule()
if taskWorkArea.holdCount == 9297 and taskWorkArea.qpktCount == 23246:
pass
else:
return False
return True
def entry_point(iterations):
r = Richards()
startTime = time.time()
result = r.run(iterations)
endTime = time.time()
return result, startTime, endTime
def main(entry_point = entry_point, iterations = 10):
print("Richards benchmark (Python) starting... [%r]" % entry_point)
result, startTime, endTime = entry_point(iterations)
if not result:
print("Incorrect results!")
return -1
print("finished.")
total_s = endTime - startTime
print("Total time for %d iterations: %.2f secs" % (iterations, total_s))
print("Average time per iteration: %.2f ms" % (total_s*1000/iterations))
return 42
try:
import sys
if '-nojit' in sys.argv:
sys.argv.remove('-nojit')
raise ImportError
import pypyjit
except ImportError:
pass
else:
import types
for item in globals().values():
if isinstance(item, types.FunctionType):
pypyjit.enable(item.func_code)
elif isinstance(item, type):
for it in item.__dict__.values():
if isinstance(it, types.FunctionType):
pypyjit.enable(it.func_code)
if __name__ == '__main__':
import sys
if len(sys.argv) >= 2:
main(iterations = int(sys.argv[1]))
else:
main()
cimport cython
cdef inline double eval_A(double i, double j)
@cython.locals(i=long)
cdef list eval_A_times_u(list u)
@cython.locals(i=long)
cdef list eval_At_times_u(list u)
cdef list eval_AtA_times_u(list u)
@cython.locals(j=long, u_j=double, partial_sum=double)
cdef double part_A_times_u(double i, list u)
@cython.locals(j=long, u_j=double, partial_sum=double)
cdef double part_At_times_u(double i, list u)
# -*- coding: utf-8 -*-
# The Computer Language Benchmarks Game
# http://shootout.alioth.debian.org/
# Contributed by Sebastien Loisel
# Fixed by Isaac Gouy
# Sped up by Josh Goldfoot
# Dirtily sped up by Simon Descarpentries
# Concurrency by Jason Stitt
from time import time
import util
import optparse
def eval_A (i, j):
return 1.0 / ((i + j) * (i + j + 1) / 2 + i + 1)
def eval_A_times_u (u):
return [ part_A_times_u(i,u) for i in range(len(u)) ]
def eval_At_times_u (u):
return [ part_At_times_u(i,u) for i in range(len(u)) ]
def eval_AtA_times_u (u):
return eval_At_times_u (eval_A_times_u (u))
def part_A_times_u(i, u):
partial_sum = 0
for j, u_j in enumerate(u):
partial_sum += eval_A (i, j) * u_j
return partial_sum
def part_At_times_u(i, u):
partial_sum = 0
for j, u_j in enumerate(u):
partial_sum += eval_A (j, i) * u_j
return partial_sum
DEFAULT_N = 130
def main(n):
times = []
for i in range(n):
t0 = time()
u = [1] * DEFAULT_N
for dummy in range (10):
v = eval_AtA_times_u (u)
u = eval_AtA_times_u (v)
vBv = vv = 0
for ue, ve in zip (u, v):
vBv += ue * ve
vv += ve * ve
tk = time()
times.append(tk - t0)
return times
if __name__ == "__main__":
parser = optparse.OptionParser(
usage="%prog [options]",
description="Test the performance of the spectralnorm benchmark")
util.add_standard_options_to(parser)
options, args = parser.parse_args()
util.run_benchmark(options, options.num_runs, main)
#!/usr/bin/env python
"""Utility code for benchmark scripts."""
__author__ = "collinwinter@google.com (Collin Winter)"
import math
import operator
try:
reduce
except NameError:
from functools import reduce
def run_benchmark(options, num_runs, bench_func, *args):
"""Run the given benchmark, print results to stdout.
Args:
options: optparse.Values instance.
num_runs: number of times to run the benchmark
bench_func: benchmark function. `num_runs, *args` will be passed to this
function. This should return a list of floats (benchmark execution
times).
"""
if options.profile:
import cProfile
prof = cProfile.Profile()
prof.runcall(bench_func, num_runs, *args)
prof.print_stats(sort=options.profile_sort)
else:
data = bench_func(num_runs, *args)
if options.take_geo_mean:
product = reduce(operator.mul, data, 1)
print(math.pow(product, 1.0 / len(data)))
else:
for x in data:
print(x)
def add_standard_options_to(parser):
"""Add a bunch of common command-line flags to an existing OptionParser.
This function operates on `parser` in-place.
Args:
parser: optparse.OptionParser instance.
"""
parser.add_option("-n", action="store", type="int", default=100,
dest="num_runs", help="Number of times to run the test.")
parser.add_option("--profile", action="store_true",
help="Run the benchmark through cProfile.")
parser.add_option("--profile_sort", action="store", type="str",
default="time", help="Column to sort cProfile output by.")
parser.add_option("--take_geo_mean", action="store_true",
help="Return the geo mean, rather than individual data.")
......@@ -12,6 +12,7 @@ include Doc/*
include Demos/*.pyx
include Demos/*.py
include Demos/callback/*
include Demos/benchmarks/*
include Demos/embed/*
include Demos/freeze/*
include Demos/libraries/*
......
......@@ -18,6 +18,7 @@ Contents:
limitations
pyrex_differences
early_binding_for_speed
parallelism
debugging
Indices and tables
......
.. highlight:: cython
.. py:module:: cython.parallel
**********************************
Using Parallelism
**********************************
Cython supports native parallelism through the :py:mod:`cython.parallel`
module. To use this kind of parallelism, the GIL must be released. It
currently supports OpenMP, but later on more backends might be supported.
.. function:: prange([start,] stop[, step], nogil=False, schedule=None)
This function can be used for parallel loops. OpenMP automatically
starts a thread pool and distributes the work according to the schedule
used. ``step`` must not be 0. This function can only be used with the
GIL released. If ``nogil`` is true, the loop will be wrapped in a nogil
section.
Thread-locality and reductions are automatically inferred for variables.
If you assign to a variable, it becomes lastprivate, meaning that the
variable will contain the value from the last iteration. If you use an
inplace operator on a variable, it becomes a reduction, meaning that the
values from the thread-local copies of the variable will be reduced with
the operator and assigned to the original variable after the loop. The
index variable is always lastprivate.
The ``schedule`` is passed to OpenMP and can be one of the following:
+-----------------+------------------------------------------------------+
| Schedule | Description |
+=================+======================================================+
|static | The iteration space is divided into chunks that are |
| | approximately equal in size, and at most one chunk |
| | is distributed to each thread. |
+-----------------+------------------------------------------------------+
|dynamic | The iterations are distributed to threads in the team|
| | as the threads request them, with a chunk size of 1. |
+-----------------+------------------------------------------------------+
|guided | The iterations are distributed to threads in the team|
| | as the threads request them. The size of each chunk |
| | is proportional to the number of unassigned |
| | iterations divided by the number of threads in the |
| | team, decreasing to 1. |
+-----------------+------------------------------------------------------+
|auto | The decision regarding scheduling is delegated to the|
| | compiler and/or runtime system. The programmer gives |
| | the implementation the freedom to choose any possible|
| | mapping of iterations to threads in the team. |
+-----------------+------------------------------------------------------+
|runtime | The schedule and chunk size are taken from the |
| | runtime-scheduling-variable, which can be set through|
| | the ``omp_set_schedule`` function call, or the |
| | ``OMP_SCHEDULE`` environment variable. |
+-----------------+------------------------------------------------------+
The default schedule is implementation defined. For more information consult
the OpenMP specification: [#]_.
Example with a reduction::
from cython.parallel import prange, parallel, threadid
cdef int i
cdef int sum = 0
for i in prange(n, nogil=True):
sum += i
print sum
Example with a shared numpy array::
from cython.parallel import *
def func(np.ndarray[double] x, double alpha):
cdef Py_ssize_t i
for i in prange(x.shape[0]):
x[i] = alpha * x[i]
.. function:: parallel
This directive can be used as part of a ``with`` statement to execute code
sequences in parallel. This is currently useful to setup thread-local
buffers used by a prange. A contained prange will be a worksharing loop
that is not parallel, so any variable assigned to in the parallel section
is also private to the prange. Variables that are private in the parallel
construct are undefined after the parallel block.
Example with thread-local buffers::
from cython.parallel import *
from cython.stdlib cimport abort
cdef Py_ssize_t i, n = 100
cdef int * local_buf
cdef size_t size = 10
with nogil, parallel:
local_buf = malloc(sizeof(int) * size)
if local_buf == NULL:
abort()
# populate our local buffer in a sequential loop
for i in range(size):
local_buf[i] = i * 2
# share the work using the thread-local buffer(s)
for i in prange(n, schedule='guided'):
func(local_buf)
free(local_buf)
Later on sections might be supported in parallel blocks, to distribute
code sections of work among threads.
.. function:: threadid()
Returns the id of the thread. For n threads, the ids will range from 0 to
n.
Compiling
=========
To actually use the OpenMP support, you need to tell the C or C++ compiler to
enable OpenMP. For gcc this can be done as follows in a setup.py::
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_module = Extension(
"hello",
["hello.pyx"],
extra_compile_args=['-fopenmp'],
libraries=['gomp'],
)
setup(
name = 'Hello world app',
cmdclass = {'build_ext': build_ext},
ext_modules = [ext_module],
)
.. rubric:: References
.. [#] http://www.openmp.org/mp-documents/spec30.pdf
......@@ -205,7 +205,7 @@ class PyxImporter(object):
try:
fp, pathname, (ext,mode,ty) = imp.find_module(fullname,package_path)
if fp: fp.close() # Python should offer a Default-Loader to avoid this double find/open!
if pathname.endswith(self.extension):
if pathname and pathname.endswith(self.extension):
return PyxLoader(fullname, pathname,
pyxbuild_dir=self.pyxbuild_dir)
if ty != imp.C_EXTENSION: # only when an extension, check if we have a .pyx next!
......
......@@ -4,6 +4,7 @@ import os
import sys
import re
import gc
import locale
import codecs
import shutil
import time
......@@ -11,6 +12,7 @@ import unittest
import doctest
import operator
import tempfile
import warnings
import traceback
try:
from StringIO import StringIO
......@@ -54,6 +56,7 @@ CY3_DIR = None
from distutils.dist import Distribution
from distutils.core import Extension
from distutils.command.build_ext import build_ext as _build_ext
from distutils import sysconfig
distutils_distro = Distribution()
if sys.platform == 'win32':
......@@ -78,8 +81,77 @@ def update_numpy_extension(ext):
import numpy
ext.include_dirs.append(numpy.get_include())
def update_openmp_extension(ext):
language = ext.language
if language == 'cpp':
flags = OPENMP_CPP_COMPILER_FLAGS
else:
flags = OPENMP_C_COMPILER_FLAGS
if flags:
compile_flags, link_flags = flags
ext.extra_compile_args.extend(compile_flags.split())
ext.extra_link_args.extend(link_flags.split())
return ext
return EXCLUDE_EXT
def get_openmp_compiler_flags(language):
"""
As of gcc 4.2, it supports OpenMP 2.5. Gcc 4.4 implements 3.0. We don't
(currently) check for other compilers.
returns a two-tuple of (CFLAGS, LDFLAGS) to build the OpenMP extension
"""
if language == 'cpp':
cc = sysconfig.get_config_var('CXX')
else:
cc = sysconfig.get_config_var('CC')
# For some reason, cc can be e.g. 'gcc -pthread'
cc = cc.split()[0]
matcher = re.compile(r"gcc version (\d+\.\d+)").search
try:
import subprocess
except ImportError:
try:
in_, out_err = os.popen4([cc, "-v"])
except EnvironmentError:
warnings.warn("Unable to find the %s compiler: %s: %s" %
(language, os.strerror(sys.exc_info()[1].errno), cc))
return None
output = out_err.read()
else:
try:
p = subprocess.Popen([cc, "-v"], stderr=subprocess.PIPE)
except EnvironmentError:
# Be compatible with Python 3
warnings.warn("Unable to find the %s compiler: %s: %s" %
(language, os.strerror(sys.exc_info()[1].errno), cc))
return None
_, output = p.communicate()
output = output.decode(locale.getpreferredencoding() or 'ASCII', 'replace')
compiler_version = matcher(output).group(1)
if compiler_version and compiler_version.split('.') >= ['4', '2']:
return '-fopenmp', '-fopenmp'
locale.setlocale(locale.LC_ALL, '')
OPENMP_C_COMPILER_FLAGS = get_openmp_compiler_flags('c')
OPENMP_CPP_COMPILER_FLAGS = get_openmp_compiler_flags('cpp')
# Return this from the EXT_EXTRAS matcher callback to exclude the extension
EXCLUDE_EXT = object()
EXT_EXTRAS = {
'tag:numpy' : update_numpy_extension,
'tag:openmp': update_openmp_extension,
}
# TODO: use tags
......@@ -87,7 +159,8 @@ VER_DEP_MODULES = {
# tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
# (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
(2,4) : (operator.lt, lambda x: x in ['run.extern_builtins_T258',
'run.builtin_sorted'
'run.builtin_sorted',
'run.reversed_iteration',
]),
(2,5) : (operator.lt, lambda x: x in ['run.any',
'run.all',
......@@ -98,6 +171,7 @@ VER_DEP_MODULES = {
'run.cython3',
'run.generators_py', # generators, with statement
'run.pure_py', # decorators, with statement
'run.purecdef',
]),
(2,7) : (operator.lt, lambda x: x in ['run.withstat_py', # multi context with statement
]),
......@@ -519,13 +593,21 @@ class CythonCompileTestCase(unittest.TestCase):
extra_compile_args = ext_compile_flags,
**extra_extension_args
)
if self.language == 'cpp':
# Set the language now as the fixer might need it
extension.language = 'c++'
for matcher, fixer in EXT_EXTRAS.items():
if isinstance(matcher, str):
del EXT_EXTRAS[matcher]
matcher = string_selector(matcher)
EXT_EXTRAS[matcher] = fixer
if matcher(module, tags):
extension = fixer(extension) or extension
newext = fixer(extension)
if newext is EXCLUDE_EXT:
return
extension = newext or extension
if self.language == 'cpp':
extension.language = 'c++'
build_extension.extensions = [extension]
......@@ -647,6 +729,7 @@ def run_forked_test(result, run_func, test_name, fork=True):
try:
cid, result_code = os.waitpid(child_id, 0)
module_name = test_name.split()[-1]
# os.waitpid returns the child's result code in the
# upper byte of result_code, and the signal it was
# killed by in the lower byte
......
......@@ -18,9 +18,11 @@ for_from_pyvar_loop_T601
decorators_T593
temp_sideeffects_T654
class_scope_T671
slice2_T636
# CPython regression tests that don't current work:
pyregr.test_threadsignals
pyregr.test_signal
pyregr.test_capi
pyregr.test_socket
pyregr.test_threading
......@@ -30,6 +32,12 @@ pyregr.test_pep3131
# CPython regression tests that don't make sense
pyregr.test_gdb
pyregr.test_support
pyregr.test_peepholer
# CPython regression tests that take too long
pyregr.test_subprocess
pyregr.test_zipfile64
pyregr.test_tuple
# Inlined generators
all
......
PYTHON setup.py build_ext --inplace
PYTHON test.py
######## setup.py ########
from Cython.Build.Dependencies import cythonize
from distutils.core import setup
exts = cythonize("*.pyx")
for e in exts:
if e.name == "d":
e.sources.append("a.c")
setup(
ext_modules = exts,
)
######## a.pxd ########
ctypedef api float flt
cdef int int0
cdef float flt0
cdef api int int1
cdef api float flt1
cdef public api int int2
cdef public api flt flt2
######## a.pyx ########
cdef int int0 = 1, int1 = 1, int2 = 1
cdef float flt0 = 1, flt1 = 1, flt2 = 1
cdef api int int3 = 1
cdef api flt flt3 = 1
cdef public int int4 = 1
cdef public flt flt4 = 1
def get_int():
return (int0, int1, int2, int3, int4)
def get_flt():
return (flt0, flt1, flt2, flt3, flt4)
######## b.pyx ########
from a cimport *
int0 = int1 = int2 = 7
flt0 = flt1 = flt2 = 7
######## c.pyx ########
# distutils: language = c++
cdef extern from "a_api.h":
int import_a() except -1
ctypedef float flt
int int1, int2, int3
flt flt1, flt2, flt3
import_a()
int1 = int2 = int3 = 5
flt1 = flt2 = flt3 = 5
######## inita.h ########
#if PY_MAJOR_VERSION >= 3
void inita(void)
{
PyObject *sys_modules = NULL;
PyObject *mod = NULL;
sys_modules = PyImport_GetModuleDict();
if (!sys_modules) return;
mod = PyInit_a();
if (!mod) return;
PyDict_SetItemString(sys_modules, (char*)"a", mod);
}
#endif
######## d.pyx ########
cdef extern from "a.h":
pass
cdef extern from "inita.h":
pass
cdef extern from "a.h":
void inita() except *
ctypedef float flt
int int2, int4
flt flt2, flt4
inita()
int2 = int4 = 3
flt2 = flt4 = 3
######## test.py ########
import a
assert a.get_int() == (1,1,1,1,1)
assert a.get_flt() == (1,1,1,1,1)
import b
assert a.get_int() == (7,7,7,1,1)
assert a.get_flt() == (7,7,7,1,1)
import c
assert a.get_int() == (7,5,5,5,1)
assert a.get_flt() == (7,5,5,5,1)
import d
import a
assert a.get_int() == (1,1,3,1,3)
assert a.get_flt() == (1,1,3,1,3)
# mode: compile
from publicapi_pxd_mix cimport *
bar0()
bar1()
bar2()
bar3()
spam0(None)
spam1(None)
spam2(None)
spam3(None)
i0 = 0
i1 = 1
i2 = 2
i3 = 3
......@@ -49,20 +49,26 @@ cdef public api class Bar3 [type PyBar3_Type, object PyBar3_Object]: pass
# --
cdef inline void bar0(): pass
cdef extern from *:
void foo()
cdef inline void bar (): pass
cdef void bar0()
cdef public void bar1()
cdef api void bar2()
cdef public api void bar3()
cdef inline void* spam0(object o) except NULL: return NULL
cdef inline void* spam (object o) except NULL: return NULL
cdef void* spam0(object o) except NULL
cdef public void* spam1(object o) except NULL
cdef api void* spam2(object o) nogil except NULL
cdef public api void* spam3(object o) except NULL with gil
# --
#cdef public int i1
#cdef api int i2
#cdef public api int i3
cdef int i0 = 0 # XXX implement initialization!!!
cdef public int i1
cdef api int i2
cdef public api int i3
# --
......@@ -8,10 +8,17 @@ cdef class Bar1: pass
cdef class Bar2: pass
cdef class Bar3: pass
cdef void bar0(): pass
cdef public void bar1(): pass
cdef api void bar2(): pass
cdef public api void bar3(): pass
cdef void* spam0(object o) except NULL: return NULL
cdef public void* spam1(object o) except NULL: return NULL
cdef api void* spam2(object o) nogil except NULL: return NULL
cdef public api void* spam3(object o) except NULL with gil: return NULL
cdef int i0 = 0 # XXX This should not be required!
cdef public int i1 = 1
cdef api int i2 = 2
cdef public api int i3 = 3
# cython: remove_unreachable=False
# mode: error
break
......@@ -27,11 +28,11 @@ def bool_result():
_ERRORS = u'''
3:0: break statement not inside loop
6:4: break statement not inside loop
9:4: break statement not inside loop
12:4: break statement not inside loop
17:5: break statement not inside loop
21:4: break statement not inside loop
23:4: break statement not inside loop
4:0: break statement not inside loop
7:4: break statement not inside loop
10:4: break statement not inside loop
13:4: break statement not inside loop
18:5: break statement not inside loop
22:4: break statement not inside loop
24:4: break statement not inside loop
'''
# mode: error
class Pyclass(object):
cdef bad(self):
pass
_ERRORS = """
4:9: cdef statement not allowed here
"""
# mode: error
import cython
class Pyclass(object):
@cython.cfunc
def bad(self):
pass
_ERRORS = """
6:4: cfunc directive is not allowed here
"""
# cython: remove_unreachable=False
# mode: error
continue
......@@ -26,11 +27,11 @@ def bool_result():
return True
_ERRORS = u'''
3:0: continue statement not inside loop
6:4: continue statement not inside loop
9:4: continue statement not inside loop
12:4: continue statement not inside loop
17:5: continue statement not inside loop
21:4: continue statement not inside loop
23:4: continue statement not inside loop
4:0: continue statement not inside loop
7:4: continue statement not inside loop
10:4: continue statement not inside loop
13:4: continue statement not inside loop
18:5: continue statement not inside loop
22:4: continue statement not inside loop
24:4: continue statement not inside loop
'''
# mode: error
cimport cython.parallel.parallel as p
from cython.parallel cimport something
import cython.parallel.parallel as p
from cython.parallel import something
from cython.parallel cimport prange
import cython.parallel
prange(1, 2, 3, schedule='dynamic')
cdef int i
with nogil, cython.parallel.parallel():
for i in prange(10, schedule='invalid_schedule'):
pass
with cython.parallel.parallel():
print "hello world!"
cdef int *x = NULL
with nogil, cython.parallel.parallel():
for j in prange(10):
pass
for x[1] in prange(10):
pass
for x in prange(10):
pass
with cython.parallel.parallel():
pass
with nogil, cython.parallel.parallel:
pass
_ERRORS = u"""
e_cython_parallel.pyx:3:8: cython.parallel.parallel is not a module
e_cython_parallel.pyx:4:0: No such directive: cython.parallel.something
e_cython_parallel.pyx:6:7: cython.parallel.parallel is not a module
e_cython_parallel.pyx:7:0: No such directive: cython.parallel.something
e_cython_parallel.pyx:13:6: prange() can only be used as part of a for loop
e_cython_parallel.pyx:13:6: prange() can only be used without the GIL
e_cython_parallel.pyx:18:19: Invalid schedule argument to prange: invalid_schedule
c_cython_parallel.pyx:21:29: The parallel section may only be used without the GIL
e_cython_parallel.pyx:27:10: target may not be a Python object as we don't have the GIL
e_cython_parallel.pyx:30:9: Can only iterate over an iteration variable
e_cython_parallel.pyx:33:10: Must be of numeric type, not int *
e_cython_parallel.pyx:36:33: Closely nested 'with parallel:' blocks are disallowed
e_cython_parallel.pyx:39:12: The parallel directive must be called
"""
# cython: remove_unreachable=False
# mode: error
cdef void g():
......@@ -9,7 +10,7 @@ cdef int h():
return # error
return p # error
_ERRORS = u"""
5:17: Return with value in void function
9:1: Return value required
10:17: Cannot assign type 'int *' to 'int'
6:17: Return with value in void function
10:1: Return value required
11:17: Cannot assign type 'int *' to 'int'
"""
# cython: remove_unreachable=False
# mode: error
def f(a, b):
......@@ -5,6 +6,6 @@ def f(a, b):
break # error
continue # error
_ERRORS = u"""
5:1: break statement not inside loop
6:1: continue statement not inside loop
6:1: break statement not inside loop
7:1: continue statement not inside loop
"""
# mode: error
with gil:
pass
with nogil:
with nogil:
pass
cdef void without_gil() nogil:
# This is not an error, as 'func' *may* be called without the GIL, but it
# may also be held.
with nogil:
pass
cdef void with_gil() with gil:
# This is an error, as the GIL is acquired already
with gil:
pass
def func():
with gil:
pass
_ERRORS = u'''
3:5: Trying to acquire the GIL while it is already held.
7:9: Trying to release the GIL while it was previously released.
18:9: Trying to acquire the GIL while it is already held.
22:9: Trying to acquire the GIL while it is already held.
'''
# cython: remove_unreachable=False
# mode: error
cdef object f(object x) nogil:
......@@ -92,71 +93,71 @@ def bare_pyvar_name(object x):
# except these: 29, 34, 44, 56, 58, 60, 62-64
_ERRORS = u"""
3:5: Function with Python return type cannot be declared nogil
6:5: Function declared nogil has Python locals or temporaries
8:6: Assignment of Python object not allowed without gil
11:5: Discarding owned Python object not allowed without gil
13:5: Function with Python return type cannot be declared nogil
17:5: Calling gil-requiring function not allowed without gil
26:9: Calling gil-requiring function not allowed without gil
28:12: Assignment of Python object not allowed without gil
30:8: Discarding owned Python object not allowed without gil
30:16: Constructing complex number not allowed without gil
32:8: Backquote expression not allowed without gil
32:8: Discarding owned Python object not allowed without gil
32:9: Operation not allowed without gil
33:15: Assignment of Python object not allowed without gil
33:15: Operation not allowed without gil
33:15: Python import not allowed without gil
34:8: Operation not allowed without gil
34:13: Python import not allowed without gil
34:25: Constructing Python list not allowed without gil
34:25: Operation not allowed without gil
35:17: Iterating over Python object not allowed without gil
37:11: Discarding owned Python object not allowed without gil
37:11: Indexing Python object not allowed without gil
4:5: Function with Python return type cannot be declared nogil
7:5: Function declared nogil has Python locals or temporaries
9:6: Assignment of Python object not allowed without gil
12:5: Discarding owned Python object not allowed without gil
14:5: Function with Python return type cannot be declared nogil
18:5: Calling gil-requiring function not allowed without gil
27:9: Calling gil-requiring function not allowed without gil
29:12: Assignment of Python object not allowed without gil
31:8: Discarding owned Python object not allowed without gil
31:16: Constructing complex number not allowed without gil
33:8: Backquote expression not allowed without gil
33:8: Discarding owned Python object not allowed without gil
33:9: Operation not allowed without gil
34:15: Assignment of Python object not allowed without gil
34:15: Operation not allowed without gil
34:15: Python import not allowed without gil
35:8: Operation not allowed without gil
35:13: Python import not allowed without gil
35:25: Constructing Python list not allowed without gil
35:25: Operation not allowed without gil
36:17: Iterating over Python object not allowed without gil
38:11: Discarding owned Python object not allowed without gil
38:11: Slicing Python object not allowed without gil
39:11: Constructing Python slice object not allowed without gil
38:11: Indexing Python object not allowed without gil
39:11: Discarding owned Python object not allowed without gil
39:11: Indexing Python object not allowed without gil
39:13: Converting to Python object not allowed without gil
39:15: Converting to Python object not allowed without gil
39:17: Converting to Python object not allowed without gil
40:11: Accessing Python attribute not allowed without gil
39:11: Slicing Python object not allowed without gil
40:11: Constructing Python slice object not allowed without gil
40:11: Discarding owned Python object not allowed without gil
41:9: Constructing Python tuple not allowed without gil
41:9: Discarding owned Python object not allowed without gil
42:8: Constructing Python list not allowed without gil
42:8: Discarding owned Python object not allowed without gil
43:8: Constructing Python dict not allowed without gil
40:11: Indexing Python object not allowed without gil
40:13: Converting to Python object not allowed without gil
40:15: Converting to Python object not allowed without gil
40:17: Converting to Python object not allowed without gil
41:11: Accessing Python attribute not allowed without gil
41:11: Discarding owned Python object not allowed without gil
42:9: Constructing Python tuple not allowed without gil
42:9: Discarding owned Python object not allowed without gil
43:8: Constructing Python list not allowed without gil
43:8: Discarding owned Python object not allowed without gil
44:12: Discarding owned Python object not allowed without gil
44:12: Truth-testing Python object not allowed without gil
45:13: Python type test not allowed without gil
47:10: Discarding owned Python object not allowed without gil
47:10: Operation not allowed without gil
48:8: Discarding owned Python object not allowed without gil
48:8: Operation not allowed without gil
49:10: Assignment of Python object not allowed without gil
49:14: Assignment of Python object not allowed without gil
50:9: Assignment of Python object not allowed without gil
50:13: Assignment of Python object not allowed without gil
50:16: Creating temporary Python reference not allowed without gil
50:19: Creating temporary Python reference not allowed without gil
51:11: Assignment of Python object not allowed without gil
51:11: Indexing Python object not allowed without gil
52:11: Accessing Python attribute not allowed without gil
44:8: Constructing Python dict not allowed without gil
44:8: Discarding owned Python object not allowed without gil
45:12: Discarding owned Python object not allowed without gil
45:12: Truth-testing Python object not allowed without gil
46:13: Python type test not allowed without gil
48:10: Discarding owned Python object not allowed without gil
48:10: Operation not allowed without gil
49:8: Discarding owned Python object not allowed without gil
49:8: Operation not allowed without gil
50:10: Assignment of Python object not allowed without gil
50:14: Assignment of Python object not allowed without gil
51:9: Assignment of Python object not allowed without gil
51:13: Assignment of Python object not allowed without gil
51:16: Creating temporary Python reference not allowed without gil
51:19: Creating temporary Python reference not allowed without gil
52:11: Assignment of Python object not allowed without gil
53:8: Constructing Python tuple not allowed without gil
53:8: Python print statement not allowed without gil
54:8: Deleting Python object not allowed without gil
55:8: Returning Python object not allowed without gil
56:8: Raising exception not allowed without gil
57:14: Truth-testing Python object not allowed without gil
59:17: Truth-testing Python object not allowed without gil
61:8: For-loop using object bounds or target not allowed without gil
63:8: Try-except statement not allowed without gil
67:8: Try-finally statement not allowed without gil
84:8: For-loop using object bounds or target not allowed without gil
52:11: Indexing Python object not allowed without gil
53:11: Accessing Python attribute not allowed without gil
53:11: Assignment of Python object not allowed without gil
54:8: Constructing Python tuple not allowed without gil
54:8: Python print statement not allowed without gil
55:8: Deleting Python object not allowed without gil
56:8: Returning Python object not allowed without gil
57:8: Raising exception not allowed without gil
58:14: Truth-testing Python object not allowed without gil
60:17: Truth-testing Python object not allowed without gil
62:8: For-loop using object bounds or target not allowed without gil
64:8: Try-except statement not allowed without gil
68:8: Cannot use try/finally in nogil sections unless it contains a 'with gil' statement.
85:8: For-loop using object bounds or target not allowed without gil
"""
# cython: remove_unreachable=False
# ticket: 135
# mode: error
......@@ -31,13 +32,13 @@ else:
_ERRORS = u'''
7:0: Return not inside a function body
10:4: Return not inside a function body
13:4: Return not inside a function body
15:5: Return not inside a function body
18:5: Return not inside a function body
22:4: Return not inside a function body
25:4: Return not inside a function body
28:4: Return not inside a function body
30:4: Return not inside a function body
8:0: Return not inside a function body
11:4: Return not inside a function body
14:4: Return not inside a function body
16:5: Return not inside a function body
19:5: Return not inside a function body
23:4: Return not inside a function body
26:4: Return not inside a function body
29:4: Return not inside a function body
31:4: Return not inside a function body
'''
# mode: error
# tag: werror
def simple_return():
return
print 'Where am I?'
def simple_loops(*args):
for i in args:
continue
print 'Never be here'
while True:
break
print 'Never be here'
def conditional(a, b):
if a:
return 1
elif b:
return 2
else:
return 3
print 'oops'
_ERRORS = """
6:4: Unreachable code
11:8: Unreachable code
15:8: Unreachable code
24:4: Unreachable code
"""
......@@ -2,6 +2,10 @@ __doc__ = u"""
>>> import sys
>>> sys.getrefcount(Foo.__pyx_vtable__)
2
>>> sys.getrefcount(__pyx_capi__['bar'])
2
>>> sys.getrefcount(__pyx_capi__['spam'])
2
>>> sys.getrefcount(__pyx_capi__['ten'])
2
>>> sys.getrefcount(__pyx_capi__['pi'])
......@@ -10,6 +14,8 @@ __doc__ = u"""
2
>>> sys.getrefcount(__pyx_capi__['dct'])
2
>>> sys.getrefcount(__pyx_capi__['tpl'])
2
>>> sys.getrefcount(__pyx_capi__['one'])
2
>>> sys.getrefcount(__pyx_capi__['two'])
......@@ -22,6 +28,8 @@ cdef public api class Foo [type FooType, object FooObject]:
cdef void bar(self):
pass
cdef public api void bar():
pass
cdef api void spam():
pass
......@@ -30,6 +38,7 @@ cdef api double pi = 3.14
cdef api object obj = object()
cdef api dict dct = {}
cdef public api int one = 1
cdef public int two = 2
cdef public api tuple tpl = ()
cdef public api float one = 1
cdef public float two = 2
......@@ -13,19 +13,23 @@ def slice_charptr_end():
"""
return cstring[:1], cstring[:3], cstring[:9]
@cython.test_assert_path_exists("//ForFromStatNode",
"//ForFromStatNode//SliceIndexNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
def slice_charptr_for_loop_py():
"""
>>> slice_charptr_for_loop_py()
['a', 'b', 'c']
['b', 'c', 'A', 'B']
['B', 'C', 'q', 't', 'p']
"""
print str([ c for c in cstring[:3] ]).replace(" b'", " '").replace("[b'", "['")
print str([ c for c in cstring[1:5] ]).replace(" b'", " '").replace("[b'", "['")
print str([ c for c in cstring[4:9] ]).replace(" b'", " '").replace("[b'", "['")
#### BROKEN: this test assumes that the result of a char* iteration
#### becomes a bytes object, which is not the case when applying
#### carray iteration. Contradiction.
##
## @cython.test_assert_path_exists("//ForFromStatNode",
## "//ForFromStatNode//SliceIndexNode")
## @cython.test_fail_if_path_exists("//ForInStatNode")
## def slice_charptr_for_loop_py():
## """
## >>> slice_charptr_for_loop_py()
## ['a', 'b', 'c']
## ['b', 'c', 'A', 'B']
## ['B', 'C', 'q', 't', 'p']
## """
## print str([ c for c in cstring[:3] ]).replace(" b'", " '").replace("[b'", "['")
## print str([ c for c in cstring[1:5] ]).replace(" b'", " '").replace("[b'", "['")
## print str([ c for c in cstring[4:9] ]).replace(" b'", " '").replace("[b'", "['")
@cython.test_assert_path_exists("//ForFromStatNode",
"//ForFromStatNode//IndexNode")
......@@ -89,19 +93,23 @@ cdef return4(): return 4
cdef return5(): return 5
cdef return9(): return 9
@cython.test_assert_path_exists("//ForFromStatNode",
"//ForFromStatNode//SliceIndexNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
def slice_charptr_for_loop_py_enumerate():
"""
>>> slice_charptr_for_loop_py_enumerate()
[(0, 'a'), (1, 'b'), (2, 'c')]
[(0, 'b'), (1, 'c'), (2, 'A'), (3, 'B')]
[(0, 'B'), (1, 'C'), (2, 'q'), (3, 't'), (4, 'p')]
"""
print str([ (i,c) for i,c in enumerate(cstring[:3]) ]).replace(" b'", " '")
print str([ (i,c) for i,c in enumerate(cstring[1:5]) ]).replace(" b'", " '")
print str([ (i,c) for i,c in enumerate(cstring[4:9]) ]).replace(" b'", " '")
#### BROKEN: this test assumes that the result of a char* iteration
#### becomes a bytes object, which is not the case when applying
#### carray iteration. Contradiction.
##
## @cython.test_assert_path_exists("//ForFromStatNode",
## "//ForFromStatNode//SliceIndexNode")
## @cython.test_fail_if_path_exists("//ForInStatNode")
## def slice_charptr_for_loop_py_enumerate():
## """
## >>> slice_charptr_for_loop_py_enumerate()
## [(0, 'a'), (1, 'b'), (2, 'c')]
## [(0, 'b'), (1, 'c'), (2, 'A'), (3, 'B')]
## [(0, 'B'), (1, 'C'), (2, 'q'), (3, 't'), (4, 'p')]
## """
## print str([ (i,c) for i,c in enumerate(cstring[:3]) ]).replace(" b'", " '")
## print str([ (i,c) for i,c in enumerate(cstring[1:5]) ]).replace(" b'", " '")
## print str([ (i,c) for i,c in enumerate(cstring[4:9]) ]).replace(" b'", " '")
@cython.test_assert_path_exists("//ForFromStatNode",
"//ForFromStatNode//IndexNode")
......
cimport cython
cimport check_fused_types_pxd
import math
ctypedef char *string_t
ctypedef cython.fused_type(int, long, float, string_t) fused_t
......@@ -136,7 +138,7 @@ def test_if_then_else_float_int():
cdef composed_t composed(composed_t x, composed_t y):
if composed_t in base_t_p_p or composed_t is string_t:
if string_t == composed_t:
print x, y
print x.decode('ascii'), y.decode('ascii')
else:
print x[0][0], y[0][0]
......@@ -160,7 +162,7 @@ def test_composed_types():
>>> test_composed_types()
it is a complex number
0.5 0.6
(0.9+0.4j)
9 4
<BLANKLINE>
not a complex number
7 8
......@@ -177,7 +179,7 @@ def test_composed_types():
cdef string_t e = "spam", f = "eggs"
result = composed(a, b)
print result
print int(math.ceil(result.real * 10)), int(math.ceil(result.imag * 10))
print
print composed(c, d)
......@@ -186,5 +188,5 @@ def test_composed_types():
composed(&cp, &dp)
print
print composed(e, f)
print composed(e, f).decode('ascii')
......@@ -164,8 +164,7 @@ def dict_comp():
# in Python 3, d.keys/values/items() are the iteration methods
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
@cython.test_fail_if_path_exists(
"//ForInStatNode")
def dict_iter(dict d):
......@@ -178,6 +177,9 @@ def dict_iter(dict d):
[1, 2, 3]
>>> sorted(items)
[('a', 1), ('b', 2), ('c', 3)]
>>> dict_iter({})
([], [], [])
"""
keys = [ key for key in d.keys() ]
values = [ value for value in d.values() ]
......
# mode: run
# tag: forin
import sys
try:
from builtins import next
except ImportError:
def next(it):
return it.next()
def for_in_pyiter_pass(it):
"""
>>> it = Iterable(5)
>>> for_in_pyiter_pass(it)
>>> next(it)
Traceback (most recent call last):
StopIteration
"""
for item in it:
pass
def for_in_pyiter(it):
"""
>>> for_in_pyiter(Iterable(5))
[0, 1, 2, 3, 4]
"""
l = []
for item in it:
l.append(item)
return l
def for_in_list():
"""
>>> for_in_pyiter([1,2,3,4,5])
[1, 2, 3, 4, 5]
"""
class Iterable(object):
"""
>>> for_in_pyiter(Iterable(5))
[0, 1, 2, 3, 4]
"""
def __init__(self, N):
self.N = N
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i < self.N:
i = self.i
self.i += 1
return i
raise StopIteration
next = __next__
if sys.version_info[0] >= 3:
class NextReplacingIterable(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i > 5:
raise StopIteration
self.i += 1
self.__next__ = self.next2
return 1
def next2(self):
self.__next__ = self.next3
return 2
def next3(self):
del self.__next__
raise StopIteration
else:
class NextReplacingIterable(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def next(self):
if self.i > 5:
raise StopIteration
self.i += 1
self.next = self.next2
return 1
def next2(self):
self.next = self.next3
return 2
def next3(self):
del self.next
raise StopIteration
def for_in_next_replacing_iter():
"""
>>> for_in_pyiter(NextReplacingIterable())
[1, 1, 1, 1, 1, 1]
"""
def for_in_gen(N):
"""
>>> for_in_pyiter(for_in_gen(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
"""
for i in range(N):
yield i
......@@ -49,6 +49,47 @@ def for_char_in_bytes(bytes s):
else:
return 'X'
#### Py2 and Py3 behave differently here: Py2->bytes, Py3->integer
##
## @cython.test_assert_path_exists("//ForFromStatNode")
## @cython.test_fail_if_path_exists("//ForInStatNode")
## def for_obj_in_bytes_slice(bytes s):
## """
## >>> for_obj_in_bytes_slice(bytes_abc)
## 'X'
## >>> for_obj_in_bytes_slice(bytes_ABC)
## 'B'
## >>> for_obj_in_bytes_slice(bytes_abc_null)
## 'X'
## >>> for_obj_in_bytes_slice(bytes_ABC_null)
## 'B'
## """
## for c in s[1:-1]:
## if c == b'B':
## return 'B'
## else:
## return 'X'
@cython.test_assert_path_exists("//ForFromStatNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
def for_char_in_bytes_slice(bytes s):
"""
>>> for_char_in_bytes_slice(bytes_abc)
'X'
>>> for_char_in_bytes_slice(bytes_ABC)
'B'
>>> for_char_in_bytes_slice(bytes_abc_null)
'X'
>>> for_char_in_bytes_slice(bytes_ABC_null)
'B'
"""
cdef char c
for c in s[1:-1]:
if c == c'B':
return 'B'
else:
return 'X'
@cython.test_assert_path_exists("//ForFromStatNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
def for_char_in_enumerate_bytes(bytes s):
......@@ -70,20 +111,22 @@ def for_char_in_enumerate_bytes(bytes s):
else:
return 'X'
@cython.test_assert_path_exists("//ForFromStatNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
def for_pyvar_in_char_ptr(char* c_string):
"""
>>> for_pyvar_in_char_ptr( (bytes_abc+bytes_ABC) * 2 )
[True, True, True, False, False, False, True, True, True, False]
>>> for_pyvar_in_char_ptr( bytes_abc_null * 2 )
[True, False, True, False, True, True, False, True, False, True]
"""
in_test = []
cdef object c
for c in c_string[:10]:
in_test.append( c in b'abc' )
return in_test
#### Py2 and Py3 behave differently here: Py2->bytes, Py3->integer
##
## @cython.test_assert_path_exists("//ForFromStatNode")
## @cython.test_fail_if_path_exists("//ForInStatNode")
## def for_pyvar_in_char_ptr(char* c_string):
## """
## >>> for_pyvar_in_char_ptr( (bytes_abc+bytes_ABC) * 2 )
## [True, True, True, False, False, False, True, True, True, False]
## >>> for_pyvar_in_char_ptr( bytes_abc_null * 2 )
## [True, False, True, False, True, True, False, True, False, True]
## """
## in_test = []
## cdef object c
## for c in c_string[:10]:
## in_test.append( c in b'abc' )
## return in_test
@cython.test_assert_path_exists("//ForFromStatNode")
@cython.test_fail_if_path_exists("//ForInStatNode")
......
......@@ -28,7 +28,11 @@ def test_pure():
cdef cdef_func_with_fused_args(fused_type1 x, fused_type1 y, fused_type2 z):
print x, y, z
if fused_type1 is string_t:
print x.decode('ascii'), y.decode('ascii'), z.decode('ascii')
else:
print x, y, z.decode('ascii')
return x + y
def test_cdef_func_with_fused_args():
......@@ -41,13 +45,16 @@ def test_cdef_func_with_fused_args():
4.2 8.6 bunny
12.8
"""
print cdef_func_with_fused_args('spam', 'ham', 'eggs')
print cdef_func_with_fused_args('spam', 'ham', 'eggs').decode('ascii')
print cdef_func_with_fused_args(10, 20, 'butter')
print cdef_func_with_fused_args(4.2, 8.6, 'bunny')
cdef fused_type1 fused_with_pointer(fused_type1 *array):
for i in range(5):
print array[i]
if fused_type1 is string_t:
print array[i].decode('ascii')
else:
print array[i]
obj = array[0] + array[1] + array[2] + array[3] + array[4]
# if cython.typeof(fused_type1) is string_t:
......@@ -90,9 +97,9 @@ def test_fused_with_pointer():
cdef float float_array[5]
cdef string_t string_array[5]
cdef char *s1 = "humpty", *s2 = "dumpty", *s3 = "fall", *s4 = "splatch", *s5 = "breakfast"
cdef char *s
strings = ["humpty", "dumpty", "fall", "splatch", "breakfast"]
strings = [b"humpty", b"dumpty", b"fall", b"splatch", b"breakfast"]
for i in range(5):
int_array[i] = i
......@@ -107,7 +114,7 @@ def test_fused_with_pointer():
print
print fused_with_pointer(float_array)
print
print fused_with_pointer(string_array)
print fused_with_pointer(string_array).decode('ascii')
cdef test_specialize(fused_type1 x, fused_type1 *y, composed_t z, other_t *a):
......
......@@ -19,12 +19,13 @@ def items(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iteritems(dict d):
"""
>>> iteritems(d)
[(10, 0), (11, 1), (12, 2), (13, 3)]
>>> iteritems({})
[]
"""
l = []
for k,v in d.iteritems():
......@@ -34,12 +35,13 @@ def iteritems(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iteritems_int(dict d):
"""
>>> iteritems_int(d)
[(10, 0), (11, 1), (12, 2), (13, 3)]
>>> iteritems_int({})
[]
"""
cdef int k,v
l = []
......@@ -50,12 +52,13 @@ def iteritems_int(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iteritems_tuple(dict d):
"""
>>> iteritems_tuple(d)
[(10, 0), (11, 1), (12, 2), (13, 3)]
>>> iteritems_tuple({})
[]
"""
l = []
for t in d.iteritems():
......@@ -65,8 +68,7 @@ def iteritems_tuple(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iteritems_listcomp(dict d):
cdef list l = [(k,v) for k,v in d.iteritems()]
l.sort()
......@@ -74,12 +76,13 @@ def iteritems_listcomp(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterkeys(dict d):
"""
>>> iterkeys(d)
[10, 11, 12, 13]
>>> iterkeys({})
[]
"""
l = []
for k in d.iterkeys():
......@@ -89,12 +92,13 @@ def iterkeys(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterkeys_int(dict d):
"""
>>> iterkeys_int(d)
[10, 11, 12, 13]
>>> iterkeys_int({})
[]
"""
cdef int k
l = []
......@@ -105,12 +109,13 @@ def iterkeys_int(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterdict(dict d):
"""
>>> iterdict(d)
[10, 11, 12, 13]
>>> iterdict({})
[]
"""
l = []
for k in d:
......@@ -120,12 +125,13 @@ def iterdict(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterdict_int(dict d):
"""
>>> iterdict_int(d)
[10, 11, 12, 13]
>>> iterdict_int({})
[]
"""
cdef int k
l = []
......@@ -136,12 +142,13 @@ def iterdict_int(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterdict_reassign(dict d):
"""
>>> iterdict_reassign(d)
[10, 11, 12, 13]
>>> iterdict_reassign({})
[]
"""
cdef dict d_new = {}
l = []
......@@ -153,12 +160,13 @@ def iterdict_reassign(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def iterdict_listcomp(dict d):
"""
>>> iterdict_listcomp(d)
[10, 11, 12, 13]
>>> iterdict_listcomp({})
[]
"""
cdef list l = [k for k in d]
l.sort()
......@@ -166,12 +174,13 @@ def iterdict_listcomp(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def itervalues(dict d):
"""
>>> itervalues(d)
[0, 1, 2, 3]
>>> itervalues({})
[]
"""
l = []
for v in d.itervalues():
......@@ -181,12 +190,13 @@ def itervalues(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def itervalues_int(dict d):
"""
>>> itervalues_int(d)
[0, 1, 2, 3]
>>> itervalues_int({})
[]
"""
cdef int v
l = []
......@@ -197,12 +207,13 @@ def itervalues_int(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def itervalues_listcomp(dict d):
"""
>>> itervalues_listcomp(d)
[0, 1, 2, 3]
>>> itervalues_listcomp({})
[]
"""
cdef list l = [v for v in d.itervalues()]
l.sort()
......@@ -210,13 +221,44 @@ def itervalues_listcomp(dict d):
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode/SimpleCallNode",
"//WhileStatNode/SimpleCallNode/NameNode")
"//WhileStatNode//DictIterationNextNode")
def itervalues_kwargs(**d):
"""
>>> itervalues_kwargs(a=1, b=2, c=3, d=4)
[1, 2, 3, 4]
>>> itervalues_kwargs()
[]
"""
cdef list l = [v for v in d.itervalues()]
l.sort()
return l
@cython.test_assert_path_exists(
"//WhileStatNode",
"//WhileStatNode//DictIterationNextNode")
def iterdict_change_size(dict d):
"""
>>> count, i = 0, -1
>>> d = {1:2, 10:20}
>>> for i in d:
... d[i+1] = 5
... count += 1
... if count > 5:
... break # safety
Traceback (most recent call last):
RuntimeError: dictionary changed size during iteration
>>> iterdict_change_size({1:2, 10:20})
Traceback (most recent call last):
RuntimeError: dictionary changed size during iteration
>>> print( iterdict_change_size({}) )
DONE
"""
cdef int count = 0
i = -1
for i in d:
d[i+1] = 5
count += 1
if count > 5:
break # safety
return "DONE"
......@@ -3,13 +3,22 @@
def get_locals(x, *args, **kwds):
"""
>>> sorted( get_locals(1,2,3, k=5) .items())
>>> sorted( get_locals(1,2,3, k=5).items() )
[('args', (2, 3)), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)]
"""
cdef int z = 5
y = "hi"
return locals()
def get_vars(x, *args, **kwds):
"""
>>> sorted( get_vars(1,2,3, k=5).items() )
[('args', (2, 3)), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)]
"""
cdef int z = 5
y = "hi"
return vars()
def get_dir(x, *args, **kwds):
"""
>>> sorted( get_dir(1,2,3, k=5) )
......@@ -45,6 +54,19 @@ def in_dir(x, *args, **kwds):
y = "hi"
return x in dir()
def in_vars(x, *args, **kwds):
"""
>>> in_vars('z')
True
>>> in_vars('args')
True
>>> in_vars('X')
False
"""
cdef int z = 5
y = "hi"
return x in vars()
def sorted(it):
l = list(it)
l.sort()
......
# tag: numpy
# tag: openmp
cimport cython
from cython.parallel import prange
cimport numpy as np
@cython.boundscheck(False)
def test_parallel_numpy_arrays():
"""
>>> test_parallel_numpy_arrays()
-5
-4
-3
-2
-1
0
1
2
3
4
"""
cdef Py_ssize_t i
cdef np.ndarray[np.int_t] x
try:
import numpy
except ImportError:
for i in range(-5, 5):
print i
return
x = numpy.zeros(10, dtype=numpy.int)
for i in prange(x.shape[0], nogil=True):
x[i] = i - 5
for i in x:
print i
# tag: run
# tag: openmp
cimport cython.parallel
from cython.parallel import prange, threadid
cimport openmp
from libc.stdlib cimport malloc, free
def test_parallel():
"""
>>> test_parallel()
"""
cdef int maxthreads = openmp.omp_get_max_threads()
cdef int *buf = <int *> malloc(sizeof(int) * maxthreads)
if buf == NULL:
raise MemoryError
with nogil, cython.parallel.parallel():
buf[threadid()] = threadid()
for i in range(maxthreads):
assert buf[i] == i
free(buf)
include "sequential_parallel.pyx"
......@@ -200,5 +200,5 @@ ae(myobj.cpdef_method[cy.int, cy.float](10, 10.0), (10, 10.0))
"""
d = {'obj': obj, 'myobj': myobj, 'ae': ae}
exec s in d, d
#exec s in d
import cython
from cython import cfunc, cclass, ccall
@cython.test_assert_path_exists('//CFuncDefNode')
@cython.cfunc
def ftang():
x = 0
@cython.test_assert_path_exists('//CFuncDefNode')
@cfunc
def fpure(a):
return a*2
def test():
"""
>>> test()
4
"""
ftang()
return fpure(2)
with cfunc:
@cython.test_assert_path_exists('//CFuncDefNode')
def fwith1(a):
return a*3
@cython.test_assert_path_exists('//CFuncDefNode')
def fwith2(a):
return a*4
with cclass:
@cython.test_assert_path_exists('//CClassDefNode')
class Egg(object):
pass
@cython.test_assert_path_exists('//CClassDefNode')
class BigEgg(object):
@cython.test_assert_path_exists('//CFuncDefNode')
@cython.cfunc
def f(self, a):
return a*10
def test_with():
"""
>>> test_with()
(3, 4, 50)
"""
return fwith1(1), fwith2(1), BigEgg().f(5)
@cython.test_assert_path_exists('//CClassDefNode')
@cython.cclass
class PureFoo(object):
a = cython.declare(cython.double)
def __init__(self, a):
self.a = a
def __call__(self):
return self.a
@cython.test_assert_path_exists('//CFuncDefNode')
@cython.cfunc
def puremeth(self, a):
return a*2
def test_method():
"""
>>> test_method()
4
True
"""
x = PureFoo(2)
print(x.puremeth(2))
if cython.compiled:
print(isinstance(x(), float))
else:
print(True)
return
@cython.ccall
def ccall_sqr(x):
return x*x
@cclass
class Overidable(object):
@ccall
def meth(self):
return 0
def test_ccall():
"""
>>> test_ccall()
25
>>> ccall_sqr(5)
25
"""
return ccall_sqr(5)
def test_ccall_method(x):
"""
>>> test_ccall_method(Overidable())
0
>>> Overidable().meth()
0
>>> class Foo(Overidable):
... def meth(self):
... return 1
>>> test_ccall_method(Foo())
1
>>> Foo().meth()
1
"""
return x.meth()
# mode: run
# tag: forin, builtins, reversed, enumerate
cimport cython
import sys
IS_PY3 = sys.version_info[0] >= 3
def _reversed(it):
return list(it)[::-1]
@cython.test_assert_path_exists('//ForInStatNode',
'//ForInStatNode/IteratorNode',
'//ForInStatNode/IteratorNode[@reversed = True]',
)
@cython.test_fail_if_path_exists('//ForInStatNode/IteratorNode//SimpleCallNode')
def reversed_list(list l):
"""
>>> [ i for i in _reversed([1,2,3,4]) ]
[4, 3, 2, 1]
>>> reversed_list([1,2,3,4])
[4, 3, 2, 1]
>>> reversed_list([])
[]
>>> reversed_list(None)
Traceback (most recent call last):
TypeError: 'NoneType' object is not iterable
"""
result = []
for item in reversed(l):
result.append(item)
return result
@cython.test_assert_path_exists('//ForInStatNode',
'//ForInStatNode/IteratorNode',
'//ForInStatNode/IteratorNode[@reversed = True]',
)
@cython.test_fail_if_path_exists('//ForInStatNode/IteratorNode//SimpleCallNode')
def reversed_tuple(tuple t):
"""
>>> [ i for i in _reversed((1,2,3,4)) ]
[4, 3, 2, 1]
>>> reversed_tuple((1,2,3,4))
[4, 3, 2, 1]
>>> reversed_tuple(())
[]
>>> reversed_tuple(None)
Traceback (most recent call last):
TypeError: 'NoneType' object is not iterable
"""
result = []
for item in reversed(t):
result.append(item)
return result
@cython.test_assert_path_exists('//ForInStatNode',
'//ForInStatNode/IteratorNode',
'//ForInStatNode/IteratorNode[@reversed = True]',
)
@cython.test_fail_if_path_exists('//ForInStatNode/IteratorNode//SimpleCallNode')
def enumerate_reversed_list(list l):
"""
>>> list(enumerate(_reversed([1,2,3])))
[(0, 3), (1, 2), (2, 1)]
>>> enumerate_reversed_list([1,2,3])
[(0, 3), (1, 2), (2, 1)]
>>> enumerate_reversed_list([])
[]
>>> enumerate_reversed_list(None)
Traceback (most recent call last):
TypeError: 'NoneType' object is not iterable
"""
result = []
cdef Py_ssize_t i
for i, item in enumerate(reversed(l)):
result.append((i, item))
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_range(int N):
"""
>>> [ i for i in _reversed(range(5)) ]
[4, 3, 2, 1, 0]
>>> reversed_range(5)
([4, 3, 2, 1, 0], 0)
>>> [ i for i in _reversed(range(0)) ]
[]
>>> reversed_range(0)
([], 99)
"""
cdef int i = 99
result = []
for i in reversed(range(N)):
result.append(i)
return result, i
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_range_step_pos(int a, int b):
"""
>>> [ i for i in _reversed(range(0, 5, 1)) ]
[4, 3, 2, 1, 0]
>>> reversed_range_step_pos(0, 5)
([4, 3, 2, 1, 0], 0)
>>> [ i for i in _reversed(range(5, 0, 1)) ]
[]
>>> reversed_range_step_pos(5, 0)
([], 99)
"""
cdef int i = 99
result = []
for i in reversed(range(a, b, 1)):
result.append(i)
return result, i
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_range_step_neg(int a, int b):
"""
>>> [ i for i in _reversed(range(5, -1, -1)) ]
[0, 1, 2, 3, 4, 5]
>>> reversed_range_step_neg(5, -1)
([0, 1, 2, 3, 4, 5], 5)
>>> [ i for i in _reversed(range(0, 5, -1)) ]
[]
>>> reversed_range_step_neg(0, 5)
([], 99)
"""
cdef int i = 99
result = []
for i in reversed(range(a, b, -1)):
result.append(i)
return result, i
unicode_string = u"abcDEF"
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_unicode(unicode u):
"""
>>> print(''.join(_reversed(unicode_string)))
FEDcba
>>> print(''.join(reversed_unicode(unicode_string)))
FEDcba
"""
result = []
for c in reversed(u):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_unicode_slice(unicode u):
"""
>>> print(''.join(_reversed(unicode_string[1:-2])))
Dcb
>>> print(''.join(reversed_unicode_slice(unicode_string)))
Dcb
"""
result = []
for c in reversed(u[1:-2]):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_unicode_slice_step(unicode u):
"""
>>> print(''.join(_reversed(unicode_string[-2:1:-1])))
cDE
>>> print(''.join(reversed_unicode_slice_step(unicode_string)))
cDE
"""
result = []
for c in reversed(u[-2:1:-1]):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_unicode_slice_step_only(unicode u):
"""
>>> print(''.join(_reversed(unicode_string[::-1])))
abcDEF
>>> print(''.join(reversed_unicode_slice_step_only(unicode_string)))
abcDEF
"""
result = []
for c in reversed(u[::-1]):
result.append(c)
return result
bytes_string = b'abcDEF'
join_bytes = b''.join
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_bytes(bytes s):
"""
>>> b = IS_PY3 and bytes_string or map(ord, bytes_string)
>>> list(_reversed(b))
[70, 69, 68, 99, 98, 97]
>>> reversed_bytes(bytes_string)
[70, 69, 68, 99, 98, 97]
"""
cdef char c
result = []
for c in reversed(s):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_bytes_slice(bytes s):
"""
>>> b = IS_PY3 and bytes_string or map(ord, bytes_string)
>>> list(_reversed(b[1:-2]))
[68, 99, 98]
>>> reversed_bytes_slice(bytes_string)
[68, 99, 98]
"""
cdef char c
result = []
for c in reversed(s[1:-2]):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_bytes_slice_step(bytes s):
"""
>>> b = IS_PY3 and bytes_string or map(ord, bytes_string)
>>> list(_reversed(b[-2:1:-1]))
[99, 68, 69]
>>> reversed_bytes_slice_step(bytes_string)
[99, 68, 69]
"""
cdef char c
result = []
for c in reversed(s[-2:1:-1]):
result.append(c)
return result
@cython.test_assert_path_exists('//ForFromStatNode')
def reversed_bytes_slice_step_only(bytes s):
"""
>>> b = IS_PY3 and bytes_string or map(ord, bytes_string)
>>> list(_reversed(b[::-1]))
[97, 98, 99, 68, 69, 70]
>>> reversed_bytes_slice_step_only(bytes_string)
[97, 98, 99, 68, 69, 70]
"""
cdef char c
result = []
for c in reversed(s[::-1]):
result.append(c)
return result
# tag: run
cimport cython.parallel
from cython.parallel import prange, threadid
from libc.stdlib cimport malloc, free, abort
from libc.stdio cimport puts
import sys
try:
from builtins import next # Py3k
except ImportError:
def next(it):
return it.next()
#@cython.test_assert_path_exists(
# "//ParallelWithBlockNode//ParallelRangeNode[@schedule = 'dynamic']",
# "//GILStatNode[@state = 'nogil]//ParallelRangeNode")
def test_prange():
"""
>>> test_prange()
(9, 9, 45, 45)
"""
cdef Py_ssize_t i, j, sum1 = 0, sum2 = 0
with nogil, cython.parallel.parallel():
for i in prange(10, schedule='dynamic'):
sum1 += i
for j in prange(10, nogil=True):
sum2 += j
return i, j, sum1, sum2
def test_descending_prange():
"""
>>> test_descending_prange()
5
"""
cdef int i, start = 5, stop = -5, step = -2
cdef int sum = 0
for i in prange(start, stop, step, nogil=True):
sum += i
return sum
def test_propagation():
"""
>>> test_propagation()
(9, 9, 9, 9, 450, 450)
"""
cdef int i = 0, j = 0, x = 0, y = 0
cdef int sum1 = 0, sum2 = 0
for i in prange(10, nogil=True):
for j in prange(10):
sum1 += i
with nogil, cython.parallel.parallel():
for x in prange(10):
with cython.parallel.parallel():
for y in prange(10):
sum2 += y
return i, j, x, y, sum1, sum2
def test_unsigned_operands():
"""
>>> test_unsigned_operands()
10
"""
cdef int i
cdef int start = -5
cdef unsigned int stop = 5
cdef int step = 1
cdef int steps_taken = 0
for i in prange(start, stop, step, nogil=True):
steps_taken += 1
if steps_taken > 10:
abort()
return steps_taken
def test_reassign_start_stop_step():
"""
>>> test_reassign_start_stop_step()
20
"""
cdef int start = 0, stop = 10, step = 2
cdef int i
cdef int sum = 0
for i in prange(start, stop, step, nogil=True):
start = -2
stop = 2
step = 0
sum += i
return sum
def test_closure_parallel_privates():
"""
>>> test_closure_parallel_privates()
9 9
45 45
0 0 9 9
"""
cdef int x
def test_target():
nonlocal x
for x in prange(10, nogil=True):
pass
return x
print test_target(), x
def test_reduction():
nonlocal x
cdef int i
x = 0
for i in prange(10, nogil=True):
x += i
return x
print test_reduction(), x
def test_generator():
nonlocal x
cdef int i
x = 0
yield x
x = 2
for i in prange(10, nogil=True):
x = i
yield x
g = test_generator()
print next(g), x, next(g), x
def test_pure_mode():
"""
>>> test_pure_mode()
0
1
2
3
4
4
3
2
1
0
0
"""
import Cython.Shadow
pure_parallel = sys.modules['cython.parallel']
for i in pure_parallel.prange(5):
print i
for i in pure_parallel.prange(4, -1, -1, schedule='dynamic', nogil=True):
print i
with pure_parallel.parallel:
print pure_parallel.threadid()
# mode: run
# ticket 636
# tag: slicing, getitem
class Sliceable(object):
"""
>>> sl = Sliceable()
>>> sl[1:2]
(1, 2, None)
>>> py_slice2(sl, 1, 2)
(1, 2, None)
>>> sl[1:None]
(1, None, None)
>>> py_slice2(sl, 1, None)
(1, None, None)
>>> sl[None:2]
(None, 2, None)
>>> py_slice2(sl, None, 2)
(None, 2, None)
>>> sl[None:None]
(None, None, None)
>>> py_slice2(sl, None, None)
(None, None, None)
"""
def __getitem__(self, sl):
return (sl.start, sl.stop, sl.step)
def py_slice2(obj,a,b):
"""
>>> [1,2,3][1:2]
[2]
>>> py_slice2([1,2,3], 1, 2)
[2]
>>> [1,2,3][None:2]
[1, 2]
>>> py_slice2([1,2,3], None, 2)
[1, 2]
>>> [1,2,3][None:None]
[1, 2, 3]
>>> py_slice2([1,2,3], None, None)
[1, 2, 3]
"""
return obj[a:b]
......@@ -2,7 +2,9 @@
#cython: autotestdict=True
cdef class Foo:
pass
cdef int i
def __cinit__(self):
self.i = 1
cdef class SubFoo(Foo):
pass
......@@ -55,6 +57,25 @@ def foo3(arg):
"""
cdef val = <Foo?>arg
def attribute_access(arg):
"""
>>> attribute_access(Foo())
>>> attribute_access(SubFoo())
>>> attribute_access(None)
Traceback (most recent call last):
...
TypeError: Cannot convert NoneType to typetest_T417.Foo
>>> attribute_access(123)
Traceback (most recent call last):
...
TypeError: Cannot convert int to typetest_T417.Foo
>>> attribute_access(Bar())
Traceback (most recent call last):
...
TypeError: Cannot convert typetest_T417.Bar to typetest_T417.Foo
"""
cdef val = (<Foo?>arg).i
cdef int count = 0
......
def f(obj1, obj2, obj3, obj4, obj5):
# mode: run
# tag: sequence_unpacking
_set = set
def _it(N):
for i in range(N):
yield i
cdef class ItCount(object):
cdef object values
cdef readonly count
def __init__(self, values):
self.values = iter(values)
self.count = 0
def __iter__(self):
return self
def __next__(self):
self.count += 1
return next(self.values)
def kunterbunt(obj1, obj2, obj3, obj4, obj5):
"""
>>> f(1, (2,), (3,4,5), (6,(7,(8,9))), 0)
>>> kunterbunt(1, (2,), (3,4,5), (6,(7,(8,9))), 0)
(8, 9, (8, 9), (6, (7, (8, 9))), 0)
"""
obj1, = obj2
......@@ -9,3 +30,292 @@ def f(obj1, obj2, obj3, obj4, obj5):
obj1, (obj2, obj3) = obj4
[obj1, obj2] = obj3
return obj1, obj2, obj3, obj4, obj5
def unpack_tuple(tuple it):
"""
>>> unpack_tuple((1,2,3))
(1, 2, 3)
>>> a,b,c = None # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: ...
>>> unpack_tuple(None) # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: ...
"""
a,b,c = it
return a,b,c
def unpack_list(list it):
"""
>>> unpack_list([1,2,3])
(1, 2, 3)
>>> a,b,c = None # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: ...
>>> unpack_list(None) # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: ...
"""
a,b,c = it
return a,b,c
def unpack_to_itself(it):
"""
>>> it = _it(2)
>>> it, it = it
>>> it
1
>>> unpack_to_itself([1,2])
2
>>> unpack_to_itself((1,2))
2
>>> unpack_to_itself(_it(2))
1
>>> unpack_to_itself((1,2,3))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 2)
>>> unpack_to_itself(_it(3))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 2)
"""
it, it = it
return it
def unpack_partial(it):
"""
>>> it = _it(2)
>>> a = b = c = 0
>>> a,b,c = it
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> a, b, c
(0, 0, 0)
>>> unpack_partial([1,2])
(0, 0, 0)
>>> unpack_partial((1,2))
(0, 0, 0)
>>> unpack_partial(_it(2))
(0, 0, 0)
>>> it = ItCount([1,2])
>>> a = b = c = 0
>>> a,b,c = it
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> a, b, c
(0, 0, 0)
>>> it.count
3
>>> it = ItCount([1,2])
>>> unpack_partial(it)
(0, 0, 0)
>>> it.count
3
"""
a = b = c = 0
try:
a, b, c = it
except ValueError:
pass
return a, b, c
def unpack_fail_assignment(it):
"""
>>> it = ItCount([1, 2, 3])
>>> a = b = c = 0
>>> try: a, b[0], c = it
... except TypeError: pass
>>> a,b,c
(1, 0, 0)
>>> it.count
4
>>> it = ItCount([1, 2, 3])
>>> unpack_fail_assignment(it)
(1, 0, 0)
>>> it.count
4
"""
cdef object a,b,c
a = b = c = 0
try:
a, b[0], c = it
except TypeError:
pass
return a, b, c
def unpack_partial_typed(it):
"""
>>> unpack_partial_typed([1, 2, 'abc'])
(0, 0, 0)
>>> unpack_partial_typed((1, 'abc', 3))
(0, 0, 0)
>>> unpack_partial_typed(_set([1, 'abc', 3]))
(0, 0, 0)
>>> it = ItCount([1, 'abc', 3])
>>> unpack_partial_typed(it)
(0, 0, 0)
>>> it.count
4
"""
cdef int a,b,c
a = b = c = 0
try:
a, b, c = it
except TypeError:
pass
return a, b, c
def unpack_typed(it):
"""
>>> unpack_typed((1, 2.0, [1]))
(1, 2.0, [1])
>>> unpack_typed([1, 2.0, [1]])
(1, 2.0, [1])
>>> it = ItCount([1, 2.0, [1]])
>>> unpack_typed(it)
(1, 2.0, [1])
>>> it.count
4
>>> unpack_typed((1, None, [1]))
Traceback (most recent call last):
TypeError: a float is required
>>> unpack_typed([1, None, [1]])
Traceback (most recent call last):
TypeError: a float is required
>>> it = ItCount([1, None, [1]])
>>> unpack_typed(it)
Traceback (most recent call last):
TypeError: a float is required
>>> it.count
4
>>> unpack_typed((1, 2.0, (1,)))
Traceback (most recent call last):
TypeError: Expected list, got tuple
>>> it = ItCount([1, 2.0, (1,)])
>>> unpack_typed(it)
Traceback (most recent call last):
TypeError: Expected list, got tuple
>>> it.count
4
"""
cdef int a
cdef float b
cdef list c
a,b,c = it
return a,b,c
def failure_too_many(it):
"""
>>> a,b,c = [1,2,3,4] # doctest: +ELLIPSIS
Traceback (most recent call last):
ValueError: too many values to unpack...
>>> failure_too_many([1,2,3,4])
Traceback (most recent call last):
ValueError: too many values to unpack (expected 3)
>>> a,b,c = [1,2,3,4] # doctest: +ELLIPSIS
Traceback (most recent call last):
ValueError: too many values to unpack...
>>> failure_too_many((1,2,3,4))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 3)
>>> a,b,c = _set([1,2,3,4]) # doctest: +ELLIPSIS
Traceback (most recent call last):
ValueError: too many values to unpack...
>>> failure_too_many(_set([1,2,3,4]))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 3)
>>> a,b,c = _it(4) # doctest: +ELLIPSIS
Traceback (most recent call last):
ValueError: too many values to unpack...
>>> failure_too_many(_it(4))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 3)
"""
a,b,c = it
return a,b,c
def failure_too_few(it):
"""
>>> a,b,c = [1,2]
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> failure_too_few([1,2])
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> a,b,c = (1,2)
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> failure_too_few((1,2))
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> a,b,c = _set([1,2])
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> failure_too_few(_set([1,2]))
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> a,b,c = _it(2)
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
>>> failure_too_few(_it(2))
Traceback (most recent call last):
ValueError: need more than 2 values to unpack
"""
a,b,c = it
return a,b,c
def _it_failure(N):
for i in range(N):
yield i
raise ValueError("huhu")
def failure_while_unpacking(it):
"""
>>> a,b,c = _it_failure(0)
Traceback (most recent call last):
ValueError: huhu
>>> failure_while_unpacking(_it_failure(0))
Traceback (most recent call last):
ValueError: huhu
>>> a,b,c = _it_failure(1)
Traceback (most recent call last):
ValueError: huhu
>>> failure_while_unpacking(_it_failure(1))
Traceback (most recent call last):
ValueError: huhu
>>> a,b,c = _it_failure(2)
Traceback (most recent call last):
ValueError: huhu
>>> failure_while_unpacking(_it_failure(2))
Traceback (most recent call last):
ValueError: huhu
>>> a,b,c = _it_failure(3)
Traceback (most recent call last):
ValueError: huhu
>>> failure_while_unpacking(_it_failure(3))
Traceback (most recent call last):
ValueError: huhu
>>> a,b,c = _it_failure(4) # doctest: +ELLIPSIS
Traceback (most recent call last):
ValueError: too many values to unpack...
>>> failure_while_unpacking(_it_failure(4))
Traceback (most recent call last):
ValueError: too many values to unpack (expected 3)
"""
a,b,c = it
return a,b,c
# mode: run
# tag: generators unreachable
def with_yield_removed():
"""
>>> o = with_yield_removed()
>>> list(o)
[]
"""
return
yield
"""
Test the 'with gil:' statement.
"""
cimport cython
#from libc.stdio cimport printf, puts
from cpython cimport PyObject, Py_INCREF
import sys
def redirect_stderr(func, *args, **kwargs):
"""
Helper function that redirects stderr to stdout for doctest.
"""
stderr, sys.stderr = sys.stderr, sys.stdout
func(*args, **kwargs)
sys.stderr = stderr
cdef void puts(char *string) with gil:
"""
We need this for doctest, used from nogil sections.
"""
print string.decode('ascii')
# Start with some normal Python functions
def test_simple():
"""
>>> test_simple()
['spam', 'ham']
"""
with nogil:
with gil:
print ['spam', 'ham']
def test_nested_gil_blocks():
"""
>>> test_nested_gil_blocks()
entered outer nogil section
entered outer gil section
entered inner nogil section
entered inner gil section
leaving inner gil section
leaving inner nogil section
leaving outer gil section
leaving outer nogil section
"""
with nogil:
puts("entered outer nogil section")
with gil:
print 'entered outer gil section'
with nogil:
puts("entered inner nogil section")
with gil:
print 'entered inner gil section'
print 'leaving inner gil section'
puts("leaving inner nogil section")
print "leaving outer gil section"
puts("leaving outer nogil section")
def test_propagate_exception():
"""
>>> test_propagate_exception()
Traceback (most recent call last):
...
Exception: This exception propagates!
"""
# Note, doctest doesn't support both output and exceptions
with nogil:
with gil:
raise Exception("This exception propagates!")
def test_catch_exception():
"""
>>> test_catch_exception()
This is executed
Exception value
This is also executed
"""
try:
with nogil:
with gil:
print "This is executed"
raise Exception("Exception value")
print "This is not executed"
puts("This is also not executed")
except Exception, e:
print e
print "This is also executed"
def test_try_finally_and_outer_except():
"""
>>> test_try_finally_and_outer_except()
First finally clause
Second finally clause
Caught: Some Exception
End of function
"""
try:
with nogil:
with gil:
try:
with nogil:
with gil:
try:
raise Exception("Some Exception")
finally:
puts("First finally clause")
finally:
puts("Second finally clause")
puts("This is not executed")
except Exception, e:
print "Caught:", e
print "End of function"
def test_restore_exception():
"""
>>> test_restore_exception()
Traceback (most recent call last):
...
Exception: Override the raised exception
"""
with nogil:
with gil:
try:
with nogil:
with gil:
raise Exception("Override this please")
finally:
raise Exception("Override the raised exception")
def test_declared_variables():
"""
>>> test_declared_variables()
None
None
['s', 'p', 'a', 'm']
['s', 'p', 'a', 'm']
"""
cdef object somevar
print somevar
with nogil:
with gil:
print somevar
somevar = list("spam")
print somevar
print somevar
def test_undeclared_variables():
"""
>>> test_undeclared_variables()
None
None
['s', 'p', 'a', 'm']
['s', 'p', 'a', 'm']
"""
print somevar
with nogil:
with gil:
print somevar
somevar = list("spam")
print somevar
print somevar
def test_loops_and_boxing():
"""
>>> test_loops_and_boxing()
spamham
h
a
m
done looping
"""
cdef char c, *string = "spamham"
with nogil:
with gil:
print string.decode('ascii')
for c in string[4:]:
print "%c" % c
else:
print "done looping"
cdef class SomeExtClass(object):
cdef int some_attribute
@cython.infer_types(True)
def test_infer_types():
"""
>>> test_infer_types()
10
"""
with nogil:
with gil:
obj = SomeExtClass()
obj.some_attribute = 10
print obj.some_attribute
def test_closure():
"""
>>> test_closure()
Traceback (most recent call last):
...
Exception: {'twinkle': 'little star'}
"""
a = dict(twinkle='little star')
def inner_function():
with nogil:
with gil:
raise Exception(a)
with nogil:
with gil:
inner_function()
raise Exception("This should not be raised!")
cpdef test_cpdef():
"""
>>> test_cpdef()
Seems to work!
Or does it?
"""
with nogil:
with gil:
print "Seems to work!"
puts("Or does it?")
# Now test some cdef functions with different return types
cdef void void_nogil_ignore_exception() nogil:
with gil:
raise Exception("This is swallowed")
puts("unreachable")
with gil:
print "unreachable"
cdef void void_nogil_nested_gil() nogil:
with gil:
with nogil:
with gil:
print 'Inner gil section'
puts("nogil section")
raise Exception("Swallow this")
puts("Don't print this")
def test_nogil_void_funcs_with_gil():
"""
>>> redirect_stderr(test_nogil_void_funcs_with_gil)
Exception Exception: Exception('This is swallowed',) in 'with_gil.void_nogil_ignore_exception' ignored
Inner gil section
nogil section
Exception Exception: Exception('Swallow this',) in 'with_gil.void_nogil_nested_gil' ignored
"""
void_nogil_ignore_exception()
void_nogil_nested_gil()
def test_nogil_void_funcs_with_nogil():
"""
>>> redirect_stderr(test_nogil_void_funcs_with_nogil)
Exception Exception: Exception('This is swallowed',) in 'with_gil.void_nogil_ignore_exception' ignored
Inner gil section
nogil section
Exception Exception: Exception('Swallow this',) in 'with_gil.void_nogil_nested_gil' ignored
"""
with nogil:
void_nogil_ignore_exception()
void_nogil_nested_gil()
cdef PyObject *nogil_propagate_exception() nogil except NULL:
with nogil:
with gil:
raise Exception("This exception propagates!")
return <PyObject *> 1
def test_nogil_propagate_exception():
"""
>>> test_nogil_propagate_exception()
Traceback (most recent call last):
...
Exception: This exception propagates!
"""
nogil_propagate_exception()
cdef with_gil_raise() with gil:
raise Exception("This exception propagates!")
def test_release_gil_call_gil_func():
"""
>>> test_release_gil_call_gil_func()
Traceback (most recent call last):
...
Exception: This exception propagates!
"""
with nogil:
with gil:
with_gil_raise()
# Test try/finally in nogil blocks
def test_try_finally_in_nogil():
"""
>>> test_try_finally_in_nogil()
Traceback (most recent call last):
...
Exception: Override exception!
"""
with nogil:
try:
with gil:
raise Exception("This will be overridden")
finally:
with gil:
raise Exception("Override exception!")
with gil:
raise Exception("This code should not be executed!")
def test_nogil_try_finally_no_exception():
"""
>>> test_nogil_try_finally_no_exception()
first nogil try
nogil try gil
second nogil try
nogil finally
------
First with gil block
Second with gil block
finally block
"""
with nogil:
try:
puts("first nogil try")
with gil:
print "nogil try gil"
puts("second nogil try")
finally:
puts("nogil finally")
print '------'
with nogil:
try:
with gil:
print "First with gil block"
with gil:
print "Second with gil block"
finally:
puts("finally block")
def test_nogil_try_finally_propagate_exception():
"""
>>> test_nogil_try_finally_propagate_exception()
Execute finally clause
Propagate this!
"""
try:
with nogil:
try:
with gil:
raise Exception("Propagate this!")
with gil:
raise Exception("Don't reach this section!")
finally:
puts("Execute finally clause")
except Exception, e:
print e
def test_nogil_try_finally_return_in_with_gil(x):
"""
>>> test_nogil_try_finally_return_in_with_gil(10)
print me
10
"""
with nogil:
try:
with gil:
raise Exception("Swallow me!")
finally:
with gil:
print "print me"
return x
print "I am not executed"
cdef void nogil_try_finally_return() nogil:
try:
with gil:
raise Exception("I am swallowed in nogil code... right?")
finally:
with gil:
print "print me first"
return
with gil:
print "I am not executed"
def test_nogil_try_finally_return():
"""
>>> test_nogil_try_finally_return()
print me first
"""
with nogil:
nogil_try_finally_return()
#ifndef CPP_OVERLOAD_WRAPPER_LIB_H
#define CPP_OVERLOAD_WRAPPER_LIB_H
void voidfunc(void);
double doublefunc (double a, double b, double c);
......@@ -20,3 +21,4 @@ public:
};
double transmogrify_from_cpp (DoubleKeeper const *obj, double value);
#endif
cdef extern from "cpp_overload_wrapper_lib.cpp":
pass
cdef extern from "cpp_overload_wrapper_lib.h":
void voidfunc()
double doublefunc(double a, double b, double c)
......
#ifndef CPPWRAP_LIB_H
#define CPPWRAP_LIB_H
void voidfunc(void);
double doublefunc (double a, double b, double c);
......@@ -18,3 +19,4 @@ public:
};
double transmogrify_from_cpp (DoubleKeeper const *obj, double value);
#endif
cdef extern from "cppwrap_lib.cpp":
pass
cdef extern from "cppwrap_lib.h":
void voidfunc()
double doublefunc(double a, double b, double c)
......
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