From 405c14c26d2ebe55a23102c21a1fcd42018e384c Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 5 Jun 2008 12:20:27 +0200 Subject: [PATCH] Pyrex merge: nogil declaration checking --- Cython/Compiler/ExprNodes.py | 143 ++++++++++++++++++++++++++------- Cython/Compiler/Nodes.py | 113 +++++++++++++++----------- Cython/Compiler/PyrexTypes.py | 19 +++-- Cython/Compiler/Symtab.py | 7 +- runtests.py | 7 +- tests/compile/nogil.h | 2 + tests/compile/nogil.pyx | 19 ++++- tests/errors/nogil.pyx | 136 +++++++++++++++++++++++++++++++ tests/errors/nogilfunctype.pyx | 9 +++ tests/run/withnogil.pyx | 2 +- 10 files changed, 364 insertions(+), 93 deletions(-) create mode 100644 tests/compile/nogil.h create mode 100644 tests/errors/nogil.pyx create mode 100644 tests/errors/nogilfunctype.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 5446b29e..13166a88 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -297,7 +297,11 @@ class ExprNode(Node): def analyse_target_types(self, env): self.analyse_types(env) - + + def gil_assignment_check(self, env): + if env.nogil and self.type.is_pyobject: + error(self.pos, "Assignment of Python object not allowed without gil") + def check_const(self): self.not_const() @@ -309,7 +313,11 @@ class ExprNode(Node): def addr_not_const(self): error(self.pos, "Address is not constant") - + + def gil_check(self, env): + if env.nogil and self.type.is_pyobject: + self.gil_error() + # ----------------- Result Allocation ----------------- def result_in_temp(self): @@ -755,11 +763,16 @@ class LongNode(AtomicExprNode): def compile_time_value(self, denv): return long(self.value) + + gil_message = "Constructing Python long int" def analyse_types(self, env): self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing Python long int" + def generate_evaluation_code(self, code): code.putln( '%s = PyLong_FromString("%s", 0, 0); %s' % ( @@ -778,8 +791,11 @@ class ImagNode(AtomicExprNode): def analyse_types(self, env): self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing complex number" + def generate_evaluation_code(self, code): code.putln( "%s = PyComplex_FromDoubles(0.0, %s); %s" % ( @@ -880,7 +896,10 @@ class NameNode(AtomicExprNode): else: self.is_temp = 1 env.use_utility_code(get_name_interned_utility_code) - + self.gil_check(env) + + gil_message = "Accessing Python global or builtin" + def analyse_entry(self, env): #print "NameNode.analyse_entry:", self.name ### self.check_identifier_kind() @@ -1057,8 +1076,11 @@ class BackquoteNode(ExprNode): self.arg.analyse_types(env) self.arg = self.arg.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Backquote expression" + def generate_result_code(self, code): code.putln( "%s = PyObject_Repr(%s); %s" % ( @@ -1083,9 +1105,12 @@ class ImportNode(ExprNode): if self.name_list: self.name_list.analyse_types(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 env.use_utility_code(import_utility_code) - + + gil_message = "Python import" + def generate_result_code(self, code): if self.name_list: name_list_code = self.name_list.py_result() @@ -1111,11 +1136,14 @@ class IteratorNode(ExprNode): self.sequence.analyse_types(env) self.sequence = self.sequence.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 self.counter = TempNode(self.pos, PyrexTypes.c_py_ssize_t_type, env) self.counter.allocate_temp(env) - + + gil_message = "Iterating over Python object" + def release_temp(self, env): env.release_temp(self.result_code) self.counter.release_temp(env) @@ -1262,6 +1290,7 @@ class IndexNode(ExprNode): else: self.index = self.index.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 else: if self.base.type.is_ptr or self.base.type.is_array: @@ -1278,7 +1307,9 @@ class IndexNode(ExprNode): error(self.pos, "Invalid index type '%s'" % self.index.type) - + + gil_message = "Indexing Python object" + def check_const_addr(self): self.base.check_const_addr() self.index.check_const() @@ -1405,8 +1436,11 @@ class SliceIndexNode(ExprNode): if self.stop: self.stop = self.stop.coerce_to(c_int, env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Slicing Python object" + def generate_result_code(self, code): code.putln( "%s = PySequence_GetSlice(%s, %s, %s); %s" % ( @@ -1479,8 +1513,11 @@ class SliceNode(ExprNode): self.stop = self.stop.coerce_to_pyobject(env) self.step = self.step.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing Python slice object" + def generate_result_code(self, code): code.putln( "%s = PySlice_New(%s, %s, %s); %s" % ( @@ -1490,7 +1527,15 @@ class SliceNode(ExprNode): self.step.py_result(), code.error_goto_if_null(self.result_code, self.pos))) -class SimpleCallNode(ExprNode): + +class CallNode(ExprNode): + def gil_check(self, env): + # Make sure we're not in a nogil environment + if env.nogil: + error(self.pos, "Calling gil-requiring function without gil") + + +class SimpleCallNode(CallNode): # Function call without keyword, * or ** args. # # function ExprNode @@ -1539,6 +1584,7 @@ class SimpleCallNode(ExprNode): self.arg_tuple.analyse_types(env) self.args = None self.type = py_object_type + self.gil_check(env) self.is_temp = 1 else: for arg in self.args: @@ -1611,6 +1657,9 @@ class SimpleCallNode(ExprNode): if func_type.exception_check == '+': if func_type.exception_value is None: env.use_utility_code(cpp_exception_utility_code) + # Check gil + if not func_type.nogil: + self.gil_check(env) def calculate_result_code(self): return self.c_call_code() @@ -1705,7 +1754,7 @@ class SimpleCallNode(ExprNode): rhs, code.error_goto_if(" && ".join(exc_checks), self.pos))) -class GeneralCallNode(ExprNode): +class GeneralCallNode(CallNode): # General Python function call, including keyword, # * and ** arguments. # @@ -1741,6 +1790,7 @@ class GeneralCallNode(ExprNode): self.starstar_arg = \ self.starstar_arg.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 def generate_result_code(self, code): @@ -1791,8 +1841,11 @@ class AsTupleNode(ExprNode): self.arg.analyse_types(env) self.arg = self.arg.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing Python tuple" + def generate_result_code(self, code): code.putln( "%s = PySequence_Tuple(%s); %s" % ( @@ -1997,12 +2050,15 @@ class AttributeNode(ExprNode): self.type = py_object_type self.is_py_attr = 1 self.interned_attr_cname = env.intern_identifier(self.attribute) + self.gil_check(env) else: if not obj_type.is_error: error(self.pos, "Object of type '%s' has no attribute '%s'" % (obj_type, self.attribute)) - + + gil_message = "Accessing Python attribute" + def is_simple(self): if self.obj: return self.result_in_temp() or self.obj.is_simple() @@ -2119,8 +2175,9 @@ class SequenceNode(ExprNode): arg.analyse_types(env) self.args[i] = arg.coerce_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + def analyse_target_types(self, env): self.iterator = PyTempNode(self.pos, env) self.unpacked_items = [] @@ -2217,7 +2274,9 @@ class SequenceNode(ExprNode): class TupleNode(SequenceNode): # Tuple constructor. - + + gil_message = "Constructing Python tuple" + def analyse_types(self, env): if len(self.args) == 0: self.is_temp = 0 @@ -2268,7 +2327,9 @@ class TupleNode(SequenceNode): class ListNode(SequenceNode): # List constructor. - + + gil_message = "Constructing Python list" + def analyse_types(self, env): SequenceNode.analyse_types(self, env) self.type = list_type @@ -2365,8 +2426,11 @@ class DictNode(ExprNode): for item in self.key_value_pairs: item.analyse_types(env) self.type = dict_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing Python dict" + def allocate_temps(self, env, result = None): # Custom method used here because key-value # pairs are evaluated and used one at a time. @@ -2441,9 +2505,12 @@ class ClassNode(ExprNode): self.doc = self.doc.coerce_to_pyobject(env) self.module_name = env.global_scope().qualified_name self.type = py_object_type + self.gil_check(env) self.is_temp = 1 env.use_utility_code(create_class_utility_code); + gil_message = "Constructing Python class" + def generate_result_code(self, code): if self.doc: code.put_error_if_neg(self.pos, @@ -2473,8 +2540,11 @@ class UnboundMethodNode(ExprNode): def analyse_types(self, env): self.function.analyse_types(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing an unbound method" + def generate_result_code(self, code): code.putln( "%s = PyMethod_New(%s, 0, %s); %s" % ( @@ -2493,8 +2563,11 @@ class PyCFunctionNode(AtomicExprNode): def analyse_types(self, env): self.type = py_object_type + self.gil_check(env) self.is_temp = 1 - + + gil_message = "Constructing Python function" + def generate_result_code(self, code): code.putln( "%s = PyCFunction_New(&%s, 0); %s" % ( @@ -2546,6 +2619,7 @@ class UnopNode(ExprNode): if self.is_py_operation(): self.coerce_operand_to_pyobject(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 else: self.analyse_c_operation(env) @@ -2895,6 +2969,7 @@ class BinopNode(ExprNode): if self.is_py_operation(): self.coerce_operands_to_pyobjects(env) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 if Options.incref_local_binop and self.operand1.type.is_pyobject: self.operand1 = self.operand1.coerce_to_temp(env) @@ -3126,6 +3201,7 @@ class BoolBinopNode(ExprNode): self.operand2 = self.operand2.coerce_to_pyobject(env) self.temp_bool = TempNode(self.pos, PyrexTypes.c_bint_type, env) self.type = py_object_type + self.gil_check(env) else: self.operand1 = self.operand1.coerce_to_boolean(env) self.operand2 = self.operand2.coerce_to_boolean(env) @@ -3134,11 +3210,10 @@ class BoolBinopNode(ExprNode): # both operands be temp nodes. self.operand1 = self.operand1.coerce_to_temp(env) #CTT self.operand2 = self.operand2.coerce_to_temp(env) - # coerce_to_simple does not seem to be sufficient - #self.operand1 = self.operand1.coerce_to_simple(env) - #self.operand2 = self.operand2.coerce_to_simple(env) self.is_temp = 1 - + + gil_message = "Truth-testing Python object" + def allocate_temps(self, env, result_code = None): # We don't need both operands at the same time, and # one of the operands will also be our result. So we @@ -3435,7 +3510,6 @@ class PrimaryCmpNode(ExprNode, CmpNode): if self.has_int_operands(): self.coerce_chars_to_ints(env) if self.cascade: - #self.operand2 = self.operand2.coerce_to_temp(env) #CTT self.operand2 = self.operand2.coerce_to_simple(env) self.cascade.coerce_cascaded_operands_to_temp(env) self.check_operand_types(env) @@ -3685,8 +3759,11 @@ class PyTypeTestNode(CoercionNode): assert dst_type.is_extension_type or dst_type.is_builtin_type, "PyTypeTest on non extension type" CoercionNode.__init__(self, arg) self.type = dst_type + self.gil_check(env) self.result_ctype = arg.ctype() env.use_utility_code(type_test_utility_code) + + gil_message = "Python type test" def analyse_types(self, env): pass @@ -3721,11 +3798,14 @@ class CoerceToPyTypeNode(CoercionNode): def __init__(self, arg, env): CoercionNode.__init__(self, arg) self.type = py_object_type + self.gil_check(env) self.is_temp = 1 if not arg.type.to_py_function: error(arg.pos, "Cannot convert '%s' to Python object" % arg.type) - + + gil_message = "Converting to Python object" + def generate_result_code(self, code): function = self.arg.type.to_py_function code.putln('%s = %s(%s); %s' % ( @@ -3770,7 +3850,11 @@ class CoerceToBooleanNode(CoercionNode): CoercionNode.__init__(self, arg) self.type = PyrexTypes.c_bint_type if arg.type.is_pyobject: + if env.nogil: + self.gil_error() self.is_temp = 1 + + gil_message = "Truth-testing Python object" def check_const(self): if self.is_temp: @@ -3799,8 +3883,11 @@ class CoerceToTempNode(CoercionNode): self.type = self.arg.type self.is_temp = 1 if self.type.is_pyobject: + self.gil_check(env) self.result_ctype = py_object_type - + + gil_message = "Creating temporary Python reference" + def generate_result_code(self, code): #self.arg.generate_evaluation_code(code) # Already done # by generic generate_subexpr_evaluation_code! diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index e8ac2f84..58831036 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -106,7 +106,16 @@ class Node(object): def __init__(self, pos, **kw): self.pos = pos self.__dict__.update(kw) - + + gil_message = "Operation" + + def gil_check(self, env): + if env.nogil: + self.gil_error() + + def gil_error(self): + error(self.pos, "%s not allowed without gil" % self.gil_message) + def get_child_accessors(self): """Returns an iterator over the children of the Node. Each member in the iterated list is an object with get(), set(value), and name() methods, @@ -488,9 +497,9 @@ class CFuncDeclaratorNode(CDeclaratorNode): # Catch attempted C-style func(void) decl if type.is_void: error(arg_node.pos, "Use spam() rather than spam(void) to declare a function with no arguments.") - if type.is_pyobject and self.nogil: - error(self.pos, - "Function with Python argument cannot be declared nogil") +# if type.is_pyobject and self.nogil: +# error(self.pos, +# "Function with Python argument cannot be declared nogil") func_type_args.append( PyrexTypes.CFuncTypeArg(name, type, arg_node.pos)) if arg_node.default: @@ -537,9 +546,6 @@ class CFuncDeclaratorNode(CDeclaratorNode): error(self.exception_value.pos, "Exception value incompatible with function return type") exc_check = self.exception_check - if return_type.is_pyobject and self.nogil: - error(self.pos, - "Function with Python return type cannot be declared nogil") if return_type.is_array: error(self.pos, "Function cannot return an array") @@ -867,6 +873,9 @@ class FuncDefNode(StatNode, BlockNode): genv = env.global_scope() lenv = LocalScope(name = self.entry.name, outer_scope = genv) lenv.return_type = self.return_type + type = self.entry.type + if type.is_cfunction: + lenv.nogil = type.nogil and not type.with_gil code.init_labels() self.declare_arguments(lenv) transforms.run('before_analyse_function', self, env=env, lenv=lenv, genv=genv) @@ -1113,8 +1122,12 @@ class CFuncDefNode(FuncDefNode): self.declare_argument(env, arg) def need_gil_acquisition(self, lenv): + type = self.type with_gil = self.type.with_gil - if self.type.nogil and not with_gil: + if type.nogil and not with_gil: + if type.return_type.is_pyobject: + error(self.pos, + "Function with Python return type cannot be declared nogil") for entry in lenv.var_entries + lenv.temp_entries: if entry.type.is_pyobject: error(self.pos, "Function declared nogil has Python locals or temporaries") @@ -2216,6 +2229,7 @@ class SingleAssignmentNode(AssignmentNode): def analyse_types(self, env, use_temp = 0): self.rhs.analyse_types(env) self.lhs.analyse_target_types(env) + self.lhs.gil_assignment_check(env) self.rhs = self.rhs.coerce_to(self.lhs.type, env) if use_temp: self.rhs = self.rhs.coerce_to_temp(env) @@ -2280,6 +2294,7 @@ class CascadedAssignmentNode(AssignmentNode): self.coerced_rhs_list = [] for lhs in self.lhs_list: lhs.analyse_target_types(env) + lhs.gil_assignment_check(env) rhs = CloneNode(self.rhs) rhs = rhs.coerce_to(lhs.type, env) self.coerced_rhs_list.append(rhs) @@ -2522,15 +2537,9 @@ class PrintStatNode(StatNode): self.arg_tuple = self.arg_tuple.coerce_to_pyobject(env) self.arg_tuple.release_temp(env) env.use_utility_code(printing_utility_code) - return - for i in range(len(self.args)): - arg = self.args[i] - arg.analyse_types(env) - arg = arg.coerce_to_pyobject(env) - arg.allocate_temps(env) - arg.release_temp(env) - self.args[i] = arg - #env.recycle_pending_temps() # TEMPORARY + self.gil_check(env) + + gil_message = "Python print statement" def generate_execution_code(self, code): self.arg_tuple.generate_evaluation_code(code) @@ -2559,10 +2568,14 @@ class DelStatNode(StatNode): def analyse_expressions(self, env): for arg in self.args: arg.analyse_target_expression(env, None) - if not arg.type.is_pyobject: + if arg.type.is_pyobject: + self.gil_check(env) + else: error(arg.pos, "Deletion of non-Python object") #arg.release_target_temp(env) - + + gil_message = "Deleting Python object" + def generate_execution_code(self, code): for arg in self.args: if arg.type.is_pyobject: @@ -2649,7 +2662,11 @@ class ReturnStatNode(StatNode): and not return_type.is_pyobject and not return_type.is_returncode): error(self.pos, "Return value required") - + if return_type.is_pyobject: + self.gil_check(env) + + gil_message = "Returning Python object" + def generate_execution_code(self, code): code.mark_pos(self.pos) if not self.return_type: @@ -2711,11 +2728,11 @@ class RaiseStatNode(StatNode): self.exc_value.release_temp(env) if self.exc_tb: self.exc_tb.release_temp(env) -# if not (self.exc_type or self.exc_value or self.exc_tb): -# env.use_utility_code(reraise_utility_code) -# else: env.use_utility_code(raise_utility_code) - + self.gil_check(env) + + gil_message = "Raising exception" + def generate_execution_code(self, code): if self.exc_type: self.exc_type.generate_evaluation_code(code) @@ -2764,8 +2781,11 @@ class ReraiseStatNode(StatNode): child_attrs = [] def analyse_expressions(self, env): + self.gil_check(env) env.use_utility_code(raise_utility_code) + gil_message = "Raising exception" + def generate_execution_code(self, code): vars = code.exc_vars if vars: @@ -2792,7 +2812,10 @@ class AssertStatNode(StatNode): self.cond.release_temp(env) if self.value: self.value.release_temp(env) + self.gil_check(env) #env.recycle_pending_temps() # TEMPORARY + + gil_message = "Raising exception" def generate_execution_code(self, code): code.putln("#ifndef PYREX_WITHOUT_ASSERTIONS") @@ -2888,7 +2911,6 @@ class IfClauseNode(Node): self.condition = \ self.condition.analyse_temp_boolean_expression(env) self.condition.release_temp(env) - #env.recycle_pending_temps() # TEMPORARY self.body.analyse_expressions(env) def generate_execution_code(self, code, end_label): @@ -3250,6 +3272,7 @@ class TryExceptStatNode(StatNode): except_clause.analyse_declarations(env) if self.else_clause: self.else_clause.analyse_declarations(env) + self.gil_check(env) def analyse_expressions(self, env): self.body.analyse_expressions(env) @@ -3258,7 +3281,10 @@ class TryExceptStatNode(StatNode): except_clause.analyse_expressions(env) if self.else_clause: self.else_clause.analyse_expressions(env) - + self.gil_check(env) + + gil_message = "Try-except statement" + def generate_execution_code(self, code): old_error_label = code.new_error_label() our_error_label = code.error_label @@ -3418,16 +3444,11 @@ class TryFinallyStatNode(StatNode): def analyse_expressions(self, env): self.body.analyse_expressions(env) self.cleanup_list = env.free_temp_entries[:] - #self.exc_vars = ( - # env.allocate_temp(PyrexTypes.py_object_type), - # env.allocate_temp(PyrexTypes.py_object_type), - # env.allocate_temp(PyrexTypes.py_object_type)) - #self.lineno_var = \ - # env.allocate_temp(PyrexTypes.c_int_type) self.finally_clause.analyse_expressions(env) - #for var in self.exc_vars: - # env.release_temp(var) - + self.gil_check(env) + + gil_message = "Try-finally statement" + def generate_execution_code(self, code): old_error_label = code.error_label old_labels = code.all_new_labels() @@ -3572,6 +3593,15 @@ class GILStatNode(TryFinallyStatNode): body = body, finally_clause = GILExitNode(pos, state = state)) + def analyse_expressions(self, env): + was_nogil = env.nogil + env.nogil = 1 + TryFinallyStatNode.analyse_expressions(self, env) + env.nogil = was_nogil + + def gil_check(self, env): + pass + def generate_execution_code(self, code): code.putln("/*with %s:*/ {" % self.state) if self.state == 'gil': @@ -3582,19 +3612,6 @@ class GILStatNode(TryFinallyStatNode): TryFinallyStatNode.generate_execution_code(self, code) code.putln("}") -#class GILEntryNode(StatNode): -# # state string 'gil' or 'nogil' -# -# def analyse_expressions(self, env): -# pass -# -# def generate_execution_code(self, code): -# if self.state == 'gil': -# code.putln("PyGILState_STATE _save = PyGILState_Ensure();") -# else: -# code.putln("PyThreadState *_save;") -# code.putln("Py_UNBLOCK_THREADS") - class GILExitNode(StatNode): # Used as the 'finally' block in a GILStatNode diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index cb71d828..a9183fc7 100644 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -695,9 +695,10 @@ class CFuncType(CType): return 0 if not self.same_calling_convention_as(other_type): return 0 + if self.nogil != other_type.nogil: + return 0 return 1 - - + def compatible_signature_with(self, other_type, as_cmethod = 0): return self.compatible_signature_with_resolved_type(other_type.resolve(), as_cmethod) @@ -789,22 +790,24 @@ class CFuncType(CType): arg_decl_code = ", ".join(arg_decl_list) if not arg_decl_code and not pyrex: arg_decl_code = "void" - exc_clause = "" + trailer = "" if (pyrex or for_display) and not self.return_type.is_pyobject: if self.exception_value and self.exception_check: - exc_clause = " except? %s" % self.exception_value + trailer = " except? %s" % self.exception_value elif self.exception_value: - exc_clause = " except %s" % self.exception_value + trailer = " except %s" % self.exception_value elif self.exception_check == '+': - exc_clause = " except +" + trailer = " except +" else: - " except *" + " except *" # ignored + if self.nogil: + trailer += " nogil" cc = self.calling_convention_prefix() if (not entity_code and cc) or entity_code.startswith("*"): entity_code = "(%s%s)" % (cc, entity_code) cc = "" return self.return_type.declaration_code( - "%s%s(%s)%s" % (cc, entity_code, arg_decl_code, exc_clause), + "%s%s(%s)%s" % (cc, entity_code, arg_decl_code, trailer), for_display, dll_linkage, pyrex) def function_header_code(self, func_name, arg_code): diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 3f8a8f4b..11d0a73d 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -150,12 +150,14 @@ class Scope: # pystring_entries [Entry] String const entries newly used as # Python strings in this scope # control_flow ControlFlow Used for keeping track of environment state + # nogil boolean In a nogil section is_py_class_scope = 0 is_c_class_scope = 0 is_module_scope = 0 scope_prefix = "" in_cinclude = 0 + nogil = 0 def __init__(self, name, outer_scope, parent_scope): # The outer_scope is the next scope in the lookup chain. @@ -1324,6 +1326,7 @@ class CClassScope(ClassScope): # entry.type = type else: error(pos, "Signature not compatible with previous declaration") + error(entry.pos, "Previous declaration is here") else: if self.defined: error(pos, @@ -1363,8 +1366,8 @@ class CClassScope(ClassScope): entry.is_variable = 1 self.inherited_var_entries.append(entry) for base_entry in base_scope.cfunc_entries: - entry = self.add_cfunction(base_entry.name, base_entry.type, None, - adapt(base_entry.cname), base_entry.visibility) + entry = self.add_cfunction(base_entry.name, base_entry.type, + base_entry.pos, adapt(base_entry.cname), base_entry.visibility) entry.is_inherited = 1 def allocate_temp(self, type): diff --git a/runtests.py b/runtests.py index 1d86bd0e..ba98c8c9 100644 --- a/runtests.py +++ b/runtests.py @@ -17,7 +17,7 @@ CFLAGS = os.getenv('CFLAGS', '').split() class ErrorWriter(object): - match_error = re.compile('(warning:)?(?:.*:)?([-0-9]+):([-0-9]+):(.*)').match + match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match def __init__(self): self.output = [] self.write = self.output.append @@ -31,8 +31,9 @@ class ErrorWriter(object): is_warning, line, column, message = match.groups() if (is_warning and collect_warnings) or \ (not is_warning and collect_errors): - result.append( "%d:%d:%s" % (int(line), int(column), message.strip()) ) - return result + result.append( (int(line), int(column), message.strip()) ) + result.sort() + return [ "%d:%d:%s" % values for values in result ] def geterrors(self): return self._collect(True, False) diff --git a/tests/compile/nogil.h b/tests/compile/nogil.h new file mode 100644 index 00000000..fb2e4d4e --- /dev/null +++ b/tests/compile/nogil.h @@ -0,0 +1,2 @@ +void e1(void); +void *e2(void); diff --git a/tests/compile/nogil.pyx b/tests/compile/nogil.pyx index 8f747ad0..ba6f856a 100644 --- a/tests/compile/nogil.pyx +++ b/tests/compile/nogil.pyx @@ -1,5 +1,18 @@ -cdef extern void g(int x) nogil +cdef extern object g(object x) nogil +cdef extern void g2(object x) nogil + +cdef extern from "nogil.h": + void e1() nogil + int *e2() nogil cdef void f(int x) nogil: - cdef int y - y = 42 + cdef int y + y = 42 + +cdef void h(object x) nogil: + cdef void *p + g2(x) + g2(p) + p = x + e1() + e2() diff --git a/tests/errors/nogil.pyx b/tests/errors/nogil.pyx new file mode 100644 index 00000000..17e2e434 --- /dev/null +++ b/tests/errors/nogil.pyx @@ -0,0 +1,136 @@ +cdef object f(object x) nogil: + pass + +cdef void g(int x) nogil: + cdef object z + z = None + +cdef void h(int x) nogil: + p() + +cdef object p() nogil: + pass + +cdef void r() nogil: + q() + +cdef object m(): + cdef object x, y, obj + cdef int i, j, k + global fred + q() + with nogil: + r() + q() + i = 42 + obj = None + 17L + 7j + help + `"Hello"` + import fred + from fred import obj + for x in obj: + pass + obj[i] + obj[i:j] + obj[i:j:k] + obj.fred + (x, y) + [x, y] + {x: y} + obj and x + t(obj) +# f(42) # Cython handles this internally + x + obj + -obj + x = y = obj + x, y = y, x + obj[i] = x + obj.fred = x + print obj + del fred + return obj + raise obj + if obj: + pass + while obj: + pass + for x <= obj <= y: + pass + try: + pass + except: + pass + try: + pass + finally: + pass + +cdef void q(): + pass + +cdef class C: + pass + +cdef void t(C c) nogil: + pass + + +_ERRORS = u""" + 1: 5: Function with Python return type cannot be declared nogil + 6: 6: Assignment of Python object not allowed without gil + 4: 5: Function declared nogil has Python locals or temporaries + 8: 5: Function declared nogil has Python locals or temporaries +11: 5: Function with Python return type cannot be declared nogil +15: 5: Calling gil-requiring function without gil +24: 9: Calling gil-requiring function without gil +26:12: Assignment of Python object not allowed without gil +27: 8: Constructing Python long int not allowed without gil +28: 8: Constructing complex number not allowed without gil +29:12: Accessing Python global or builtin not allowed without gil +30: 8: Backquote expression not allowed without gil +31:15: Python import not allowed without gil +31:15: Assignment of Python object not allowed without gil +32:13: Python import not allowed without gil +32:25: Constructing Python list not allowed without gil +33:17: Iterating over Python object not allowed without gil +35:11: Indexing Python object not allowed without gil +36:11: Slicing Python object not allowed without gil +37:11: Constructing Python slice object not allowed without gil +37:11: Indexing Python object not allowed without gil +37:13: Converting to Python object not allowed without gil +37:15: Converting to Python object not allowed without gil +37:17: Converting to Python object not allowed without gil +38:11: Accessing Python attribute not allowed without gil +39: 9: Constructing Python tuple not allowed without gil +40: 8: Constructing Python list not allowed without gil +41: 8: Constructing Python dict not allowed without gil +42:12: Creating temporary Python reference not allowed without gil +42:12: Truth-testing Python object not allowed without gil +42:17: Creating temporary Python reference not allowed without gil +43:13: Python type test not allowed without gil +#44: 4: Converting to Python object not allowed without gil +45:10: Operation not allowed without gil +46: 8: Operation not allowed without gil +47:10: Assignment of Python object not allowed without gil +47:14: Assignment of Python object not allowed without gil +48: 9: Assignment of Python object not allowed without gil +48:13: Assignment of Python object not allowed without gil +48:16: Creating temporary Python reference not allowed without gil +48:19: Creating temporary Python reference not allowed without gil +49:11: Indexing Python object not allowed without gil +49:11: Assignment of Python object not allowed without gil +50:11: Accessing Python attribute not allowed without gil +50:11: Assignment of Python object not allowed without gil +51: 8: Constructing Python tuple not allowed without gil +51: 8: Python print statement not allowed without gil +52: 8: Deleting Python object not allowed without gil +53: 8: Returning Python object not allowed without gil +54: 8: Raising exception not allowed without gil +55:14: Truth-testing Python object not allowed without gil +57:17: Truth-testing Python object not allowed without gil +59: 8: Converting to Python object not allowed without gil +61: 8: Try-except statement not allowed without gil +65: 8: Try-finally statement not allowed without gil +""" diff --git a/tests/errors/nogilfunctype.pyx b/tests/errors/nogilfunctype.pyx new file mode 100644 index 00000000..617ed829 --- /dev/null +++ b/tests/errors/nogilfunctype.pyx @@ -0,0 +1,9 @@ +cdef extern from *: + cdef void f() nogil + cdef void (*fp)() + +fp = f + +_ERRORS = u""" +5:6: Cannot assign type 'void (void) nogil' to 'void (*)(void)' +""" diff --git a/tests/run/withnogil.pyx b/tests/run/withnogil.pyx index 02a42b74..8d1f8d36 100644 --- a/tests/run/withnogil.pyx +++ b/tests/run/withnogil.pyx @@ -18,5 +18,5 @@ def g(): h() return 1 -cdef int h() except -1: +cdef int h() nogil except -1: pass -- 2.26.2