Commit b22011f8 authored by Vitja Makarov's avatar Vitja Makarov

Merge pull request #80 from vitek/_defnode_refactor

DefNode refactoring and initial closure function call inlining
parents 262bc9fb d8fa1dba
......@@ -3864,6 +3864,103 @@ class SimpleCallNode(CallNode):
code.funcstate.release_temp(self.opt_arg_struct)
class InlinedDefNodeCallNode(CallNode):
# Inline call to defnode
#
# function PyCFunctionNode
# function_name NameNode
# args [ExprNode]
subexprs = ['args', 'function_name']
is_temp = 1
type = py_object_type
function = None
function_name = None
def can_be_inlined(self):
func_type= self.function.def_node
if func_type.star_arg or func_type.starstar_arg:
return False
if len(func_type.args) != len(self.args):
return False
return True
def analyse_types(self, env):
self.function_name.analyse_types(env)
for arg in self.args:
arg.analyse_types(env)
func_type = self.function.def_node
actual_nargs = len(self.args)
# Coerce arguments
some_args_in_temps = False
for i in xrange(actual_nargs):
formal_type = func_type.args[i].type
arg = self.args[i].coerce_to(formal_type, env)
if arg.is_temp:
if i > 0:
# first argument in temp doesn't impact subsequent arguments
some_args_in_temps = True
elif arg.type.is_pyobject and not env.nogil:
if arg.nonlocally_immutable():
# plain local variables are ok
pass
else:
# we do not safely own the argument's reference,
# but we must make sure it cannot be collected
# before we return from the function, so we create
# an owned temp reference to it
if i > 0: # first argument doesn't matter
some_args_in_temps = True
arg = arg.coerce_to_temp(env)
self.args[i] = arg
if some_args_in_temps:
# if some args are temps and others are not, they may get
# constructed in the wrong order (temps first) => make
# sure they are either all temps or all not temps (except
# for the last argument, which is evaluated last in any
# case)
for i in xrange(actual_nargs-1):
arg = self.args[i]
if arg.nonlocally_immutable():
# locals, C functions, unassignable types are safe.
pass
elif arg.type.is_cpp_class:
# Assignment has side effects, avoid.
pass
elif env.nogil and arg.type.is_pyobject:
# can't copy a Python reference into a temp in nogil
# env (this is safe: a construction would fail in
# nogil anyway)
pass
else:
#self.args[i] = arg.coerce_to_temp(env)
# instead: issue a warning
if i > 0:
warning(arg.pos, "Argument evaluation order in C function call is undefined and may not be as expected", 0)
break
def generate_result_code(self, code):
arg_code = [self.function_name.py_result()]
func_type = self.function.def_node
for arg, proto_arg in zip(self.args, func_type.args):
if arg.type.is_pyobject:
arg_code.append(arg.result_as(proto_arg.type))
else:
arg_code.append(arg.result())
arg_code = ', '.join(arg_code)
code.putln(
"%s = %s(%s); %s" % (
self.result(),
self.function.def_node.entry.pyfunc_cname,
arg_code,
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
class PythonCapiFunctionNode(ExprNode):
subexprs = []
def __init__(self, pos, py_name, cname, func_type, utility_code = None):
......@@ -6252,15 +6349,16 @@ class GeneratorExpressionNode(LambdaNode):
super(GeneratorExpressionNode, self).analyse_declarations(env)
# No pymethdef required
self.def_node.pymethdef_required = False
self.def_node.py_wrapper_required = False
self.def_node.is_cyfunction = False
# Force genexpr signature
self.def_node.entry.signature = TypeSlots.pyfunction_noargs
def generate_result_code(self, code):
code.putln(
'%s = %s(%s, NULL); %s' % (
'%s = %s(%s); %s' % (
self.result(),
self.def_node.entry.func_cname,
self.def_node.entry.pyfunc_cname,
self.self_result_code(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
......
......@@ -327,6 +327,32 @@ class NameReference(object):
return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
class ControlFlowState(list):
# Keeps track of Node's entry assignments
#
# cf_is_null [boolean] It is uninitialized
# cf_maybe_null [boolean] May be uninitialized
# is_single [boolean] Has only one assignment at this point
cf_maybe_null = False
cf_is_null = False
is_single = False
def __init__(self, state):
if Uninitialized in state:
state.discard(Uninitialized)
self.cf_maybe_null = True
if not state:
self.cf_is_null = True
else:
if len(state) == 1:
self.is_single = True
super(ControlFlowState, self).__init__(state)
def one(self):
return self[0]
class GVContext(object):
"""Graphviz subgraph object."""
......@@ -530,11 +556,10 @@ def check_definitions(flow, compiler_directives):
messages.report()
# Remove Uninitialized from cf_state
for node in assmt_nodes:
node.cf_state.discard(Uninitialized)
node.cf_state = ControlFlowState(node.cf_state)
for node in references:
node.cf_state.discard(Uninitialized)
node.cf_state = ControlFlowState(node.cf_state)
class AssignmentCollector(TreeVisitor):
......@@ -632,13 +657,7 @@ class ControlFlowAnalysis(CythonTransform):
return node
def visit_DefNode(self, node):
## XXX: no target name node here
node.used = True
entry = node.entry
if entry.is_anonymous:
entry = self.env.lookup(node.name)
if entry:
self.flow.mark_assignment(node, object_expr_not_none, entry)
return self.visit_FuncDefNode(node)
def visit_GeneratorBodyDefNode(self, node):
......
......@@ -19,6 +19,7 @@ funcdoc_prefix = pyrex_prefix + "doc_"
enum_prefix = pyrex_prefix + "e_"
func_prefix = pyrex_prefix + "f_"
pyfunc_prefix = pyrex_prefix + "pf_"
pywrap_prefix = pyrex_prefix + "pw_"
genbody_prefix = pyrex_prefix + "gb_"
gstab_prefix = pyrex_prefix + "getsets_"
prop_get_prefix = pyrex_prefix + "getprop_"
......
......@@ -1308,7 +1308,6 @@ class FuncDefNode(StatNode, BlockNode):
# with fused argument types with a FusedCFuncDefNode
py_func = None
assmt = None
needs_closure = False
needs_outer_scope = False
pymethdef_required = False
......@@ -1405,14 +1404,7 @@ class FuncDefNode(StatNode, BlockNode):
if 'cython_unused' not in self.modifiers:
self.modifiers = self.modifiers + ['cython_unused']
preprocessor_guard = None
if self.entry.is_special and not is_buffer_slot:
slot = TypeSlots.method_name_to_slot.get(self.entry.name)
if slot:
preprocessor_guard = slot.preprocessor_guard_code()
if (self.entry.name == '__long__' and
not self.entry.scope.lookup_here('__int__')):
preprocessor_guard = None
preprocessor_guard = self.get_preprocessor_guard()
profile = code.globalstate.directives['profile']
if profile and lenv.nogil:
......@@ -1459,7 +1451,7 @@ class FuncDefNode(StatNode, BlockNode):
self.generate_argument_declarations(lenv, code)
for entry in lenv.var_entries:
if not entry.in_closure:
if not (entry.in_closure or entry.is_arg):
code.put_var_declaration(entry)
# Initialize the return variable __pyx_r
......@@ -1555,7 +1547,8 @@ class FuncDefNode(StatNode, BlockNode):
is_cdef = isinstance(self, CFuncDefNode)
for entry in lenv.arg_entries:
if entry.type.is_pyobject:
if (acquire_gil or entry.assignments) and not entry.in_closure:
if ((acquire_gil or len(entry.cf_assignments) > 1) and
not entry.in_closure):
code.put_var_incref(entry)
# Note: defaults are always increffed. For def functions, we
......@@ -1565,6 +1558,9 @@ class FuncDefNode(StatNode, BlockNode):
if is_cdef and entry.type.is_memoryviewslice:
code.put_incref_memoryviewslice(entry.cname,
have_gil=not lenv.nogil)
for entry in lenv.var_entries:
if entry.is_arg and len(entry.cf_assignments) > 1:
code.put_var_incref(entry)
# ----- Initialise local buffer auxiliary variables
for entry in lenv.var_entries + lenv.arg_entries:
......@@ -1709,14 +1705,15 @@ class FuncDefNode(StatNode, BlockNode):
if entry.type.is_memoryviewslice:
code.put_xdecref_memoryviewslice(entry.cname,
have_gil=not lenv.nogil)
elif entry.type.is_pyobject:
code.put_var_decref(entry)
if not entry.is_arg or len(entry.cf_assignments) > 1:
code.put_var_decref(entry)
# Decref any increfed args
for entry in lenv.arg_entries:
if entry.type.is_pyobject:
if (acquire_gil or entry.assignments) and not entry.in_closure:
if ((acquire_gil or len(entry.cf_assignments) > 1) and
not entry.in_closure):
code.put_var_decref(entry)
if entry.type.is_memoryviewslice:
code.put_xdecref_memoryviewslice(entry.cname,
......@@ -1820,9 +1817,6 @@ class FuncDefNode(StatNode, BlockNode):
for arg in self.args:
if not arg.is_dynamic:
arg.generate_assignment_code(code)
# For Python class methods, create and store function object
if self.assmt:
self.assmt.generate_execution_code(code)
#
# Special code for the __getbuffer__ function
......@@ -1853,6 +1847,21 @@ class FuncDefNode(StatNode, BlockNode):
code.putln("__Pyx_DECREF(Py_None); %s->obj = NULL;" % info)
code.putln("}")
def get_preprocessor_guard(self):
is_buffer_slot = ((self.entry.name == "__getbuffer__" and
self.entry.scope.is_c_class_scope) or
(self.entry.name == "__releasebuffer__" and
self.entry.scope.is_c_class_scope))
if self.entry.is_special and not is_buffer_slot:
slot = TypeSlots.method_name_to_slot.get(self.entry.name)
if slot:
preprocessor_guard = slot.preprocessor_guard_code()
if (self.entry.name == '__long__' and
not self.entry.scope.lookup_here('__int__')):
preprocessor_guard = None
return preprocessor_guard
class CFuncDefNode(FuncDefNode):
# C function definition.
#
......@@ -2548,7 +2557,9 @@ def __pyx_fused_cpdef(signatures, args, kwargs):
py_func.analyse_declarations(env)
# ... and its body
py_func.scope = env
ParseTreeTransforms.AnalyseDeclarationsTransform(None)(py_func)
# Will be analysed later by underlying AnalyseDeclarationsTransform
#ParseTreeTransforms.AnalyseDeclarationsTransform(None)(py_func)
e, orig_e = py_func.entry, orig_py_func.entry
......@@ -2607,6 +2618,8 @@ class PyArgDeclNode(Node):
# entry Symtab.Entry
# annotation ExprNode or None Py3 argument annotation
child_attrs = []
is_self_arg = False
is_type_arg = False
def generate_function_definitions(self, env, code):
self.entry.generate_function_definitions(env, code)
......@@ -2633,8 +2646,6 @@ class DefNode(FuncDefNode):
# The following subnode is constructed internally
# when the def statement is inside a Python class definition.
#
# assmt AssignmentNode Function construction/assignment
#
# fused_py_func DefNode The original fused cpdef DefNode
# (in case this is a specialization)
# specialized_cpdefs [DefNode] list of specialized cpdef DefNodes
......@@ -2645,9 +2656,6 @@ class DefNode(FuncDefNode):
child_attrs = ["args", "star_arg", "starstar_arg", "body", "decorators"]
lambda_name = None
assmt = None
num_kwonly_args = 0
num_required_kw_args = 0
reqd_kw_flags_cname = "0"
is_wrapper = 0
no_assignment_synthesis = 0
......@@ -2663,6 +2671,9 @@ class DefNode(FuncDefNode):
fused_py_func = False
specialized_cpdefs = None
py_wrapper = None
py_wrapper_required = True
func_cname = None
def __init__(self, pos, **kwds):
FuncDefNode.__init__(self, pos, **kwds)
......@@ -2781,6 +2792,16 @@ class DefNode(FuncDefNode):
self.return_type = self.entry.signature.return_type()
self.create_local_scope(env)
self.py_wrapper = DefNodeWrapper(
self.pos,
target=self,
name=self.entry.name,
args=self.args,
star_arg=self.star_arg,
starstar_arg=self.starstar_arg,
return_type=self.return_type)
self.py_wrapper.analyse_declarations(env)
def analyse_argument_types(self, env):
directive_locals = self.directive_locals = env.directives['locals']
allow_none_for_extension_args = env.directives['allow_none_for_extension_args']
......@@ -2926,17 +2947,6 @@ class DefNode(FuncDefNode):
"(%d declared, %s expected)" % (
desc, self.name, len(self.args), expected_str))
def signature_has_nongeneric_args(self):
argcount = len(self.args)
if argcount == 0 or (
argcount == 1 and (self.args[0].is_self_arg or
self.args[0].is_type_arg)):
return 0
return 1
def signature_has_generic_args(self):
return self.entry.signature.has_generic_args
def declare_pyfunction(self, env):
#print "DefNode.declare_pyfunction:", self.name, "in", env ###
name = self.name
......@@ -2950,8 +2960,7 @@ class DefNode(FuncDefNode):
entry = env.declare_pyfunction(name, self.pos, allow_redefine=not self.is_wrapper)
self.entry = entry
prefix = env.next_id(env.scope_prefix)
entry.func_cname = Naming.pyfunc_prefix + prefix + name
entry.pymethdef_cname = Naming.pymethdef_prefix + prefix + name
self.entry.pyfunc_cname = Naming.pyfunc_prefix + prefix + name
if Options.docstrings:
entry.doc = embed_position(self.pos, self.doc)
entry.doc_cname = Naming.funcdoc_prefix + prefix + name
......@@ -2967,6 +2976,7 @@ class DefNode(FuncDefNode):
entry = env.declare_lambda_function(self.lambda_name, self.pos)
entry.doc = None
self.entry = entry
self.entry.pyfunc_cname = entry.cname
def declare_arguments(self, env):
for arg in self.args:
......@@ -2981,10 +2991,6 @@ class DefNode(FuncDefNode):
arg.entry.is_arg = 1
arg.entry.used = 1
arg.entry.is_self_arg = arg.is_self_arg
if arg.hdr_type:
if arg.is_self_arg or arg.is_type_arg or \
(arg.type.is_extension_type and not arg.hdr_type.is_extension_type):
arg.entry.is_declared_generic = 1
self.declare_python_arg(env, self.star_arg)
self.declare_python_arg(env, self.starstar_arg)
......@@ -3005,14 +3011,13 @@ class DefNode(FuncDefNode):
self.local_scope.directives = env.directives
self.analyse_default_values(env)
if self.needs_assignment_synthesis(env):
# Shouldn't we be doing this at the module level too?
self.synthesize_assignment_node(env)
elif self.decorators:
if not self.needs_assignment_synthesis(env) and self.decorators:
for decorator in self.decorators[::-1]:
decorator.decorator.analyse_expressions(env)
def needs_assignment_synthesis(self, env, code=None):
if self.is_wrapper:
return False
if self.specialized_cpdefs or self.is_staticmethod:
return True
if self.no_assignment_synthesis:
......@@ -3027,122 +3032,272 @@ class DefNode(FuncDefNode):
return code.globalstate.directives['binding']
return env.is_py_class_scope or env.is_closure_scope
def synthesize_assignment_node(self, env):
import ExprNodes
def error_value(self):
return self.entry.signature.error_value
def caller_will_check_exceptions(self):
return 1
def generate_function_definitions(self, env, code):
# Before closure cnames are mangled
if self.py_wrapper_required:
# func_cname might be modified by @cname
self.py_wrapper.func_cname = self.entry.func_cname
self.py_wrapper.generate_function_definitions(env, code)
FuncDefNode.generate_function_definitions(self, env, code)
if self.fused_py_func:
def generate_function_header(self, code, with_pymethdef, proto_only=0):
if proto_only:
if self.py_wrapper_required:
self.py_wrapper.generate_function_header(
code, with_pymethdef, True)
return
arg_code_list = []
if self.entry.signature.has_dummy_arg:
if self.needs_outer_scope or self.defaults_struct:
self_arg = 'PyObject *%s' % Naming.self_cname
else:
self_arg = 'CYTHON_UNUSED PyObject *%s' % Naming.self_cname
arg_code_list.append(self_arg)
genv = env
while genv.is_py_class_scope or genv.is_c_class_scope:
genv = genv.outer_scope
def arg_decl_code(arg):
entry = arg.entry
if entry.in_closure:
cname = entry.original_cname
else:
cname = entry.cname
decl = entry.type.declaration_code(cname)
if entry.cf_used:
return decl
return 'CYTHON_UNUSED ' + decl
for arg in self.args:
arg_code_list.append(arg_decl_code(arg))
if self.star_arg:
arg_code_list.append(arg_decl_code(self.star_arg))
if self.starstar_arg:
arg_code_list.append(arg_decl_code(self.starstar_arg))
arg_code = ', '.join(arg_code_list)
dc = self.return_type.declaration_code(self.entry.pyfunc_cname)
decls_code = code.globalstate['decls']
preprocessor_guard = self.get_preprocessor_guard()
if preprocessor_guard:
decls_code.putln(preprocessor_guard)
decls_code.putln(
"static %s(%s); /* proto */" % (dc, arg_code))
if preprocessor_guard:
decls_code.putln("#endif")
code.putln("static %s(%s) {" % (dc, arg_code))
def generate_argument_declarations(self, env, code):
pass
def generate_keyword_list(self, code):
pass
def generate_argument_parsing_code(self, env, code):
# Move arguments into closure if required
def put_into_closure(entry):
if entry.in_closure:
code.putln('%s = %s;' % (entry.cname, entry.original_cname))
code.put_var_incref(entry)
code.put_var_giveref(entry)
for arg in self.args:
put_into_closure(arg.entry)
for arg in self.star_arg, self.starstar_arg:
if arg:
put_into_closure(arg.entry)
def generate_argument_type_tests(self, code):
pass
class DefNodeWrapper(FuncDefNode):
# DefNode python wrapper code generator
defnode = None
target = None # Target DefNode
def __init__(self, *args, **kwargs):
FuncDefNode.__init__(self, *args, **kwargs)
self.num_kwonly_args = self.target.num_kwonly_args
self.num_required_kw_args = self.target.num_required_kw_args
self.num_required_args = self.target.num_required_args
self.self_in_stararg = self.target.self_in_stararg
self.signature = None
def analyse_declarations(self, env):
target_entry = self.target.entry
name = self.name
prefix = env.next_id(env.scope_prefix)
target_entry.func_cname = Naming.pywrap_prefix + prefix + name
target_entry.pymethdef_cname = Naming.pymethdef_prefix + prefix + name
if genv.is_closure_scope:
rhs = self.py_cfunc_node = ExprNodes.InnerFunctionNode(
self.pos, def_node=self,
pymethdef_cname=self.entry.pymethdef_cname,
code_object=ExprNodes.CodeObjectNode(self))
self.signature = target_entry.signature
def signature_has_nongeneric_args(self):
argcount = len(self.args)
if argcount == 0 or (
argcount == 1 and (self.args[0].is_self_arg or
self.args[0].is_type_arg)):
return 0
return 1
def signature_has_generic_args(self):
return self.signature.has_generic_args
def generate_function_body(self, code):
args = []
if self.signature.has_dummy_arg:
args.append(Naming.self_cname)
for arg in self.args:
if arg.hdr_type:
args.append(arg.type.cast_code(arg.entry.cname))
else:
args.append(arg.entry.cname)
if self.star_arg:
args.append(self.star_arg.entry.cname)
if self.starstar_arg:
args.append(self.starstar_arg.entry.cname)
args = ', '.join(args)
if not self.return_type.is_void:
code.put('%s = ' % Naming.retval_cname)
code.putln('%s(%s);' % (
self.target.entry.pyfunc_cname, args))
def generate_function_definitions(self, env, code):
lenv = self.target.local_scope
# Generate C code for header and body of function
code.putln("")
code.putln("/* Python wrapper */")
preprocessor_guard = self.target.get_preprocessor_guard()
if preprocessor_guard:
code.putln(preprocessor_guard)
code.enter_cfunc_scope()
code.return_from_error_cleanup_label = code.new_label()
with_pymethdef = (self.target.needs_assignment_synthesis(lenv, code) or
self.target.pymethdef_required)
self.generate_function_header(code, with_pymethdef)
self.generate_argument_declarations(lenv, code)
self.generate_keyword_list(code)
tempvardecl_code = code.insertion_point()
if self.return_type.is_pyobject:
retval_init = ' = 0'
else:
rhs = ExprNodes.PyCFunctionNode(
self.pos,
def_node=self,
pymethdef_cname=self.entry.pymethdef_cname,
binding=env.directives['binding'],
specialized_cpdefs=self.specialized_cpdefs,
code_object=ExprNodes.CodeObjectNode(self))
retval_init = ''
if not self.return_type.is_void:
code.putln('%s%s;' % (
self.return_type.declaration_code(Naming.retval_cname),
retval_init))
code.put_declare_refcount_context()
code.put_setup_refcount_context('%s (wrapper)' % self.name)
if env.is_py_class_scope:
rhs.binding = True
self.generate_argument_parsing_code(lenv, code)
self.generate_argument_type_tests(code)
self.generate_function_body(code)
self.is_cyfunction = rhs.binding
# ----- Go back and insert temp variable declarations
tempvardecl_code.put_temp_declarations(code.funcstate)
if self.decorators:
for decorator in self.decorators[::-1]:
rhs = ExprNodes.SimpleCallNode(
decorator.pos,
function = decorator.decorator,
args = [rhs])
# ----- Error cleanup
if code.error_label in code.labels_used:
code.put_goto(code.return_label)
code.put_label(code.error_label)
for cname, type in code.funcstate.all_managed_temps():
code.put_xdecref(cname, type)
self.assmt = SingleAssignmentNode(self.pos,
lhs = ExprNodes.NameNode(self.pos, name = self.name),
rhs = rhs)
self.assmt.analyse_declarations(env)
self.assmt.analyse_expressions(env)
# ----- Non-error return cleanup
code.put_label(code.return_label)
for entry in lenv.var_entries:
if entry.is_arg and entry.type.is_pyobject:
code.put_var_decref(entry)
code.put_finish_refcount_context()
if not self.return_type.is_void:
code.putln("return %s;" % Naming.retval_cname)
code.putln('}')
code.exit_cfunc_scope()
if preprocessor_guard:
code.putln("#endif /*!(%s)*/" % preprocessor_guard)
def generate_function_header(self, code, with_pymethdef, proto_only=0):
arg_code_list = []
sig = self.entry.signature
sig = self.signature
if sig.has_dummy_arg or self.self_in_stararg:
arg_code_list.append(
"PyObject *%s" % Naming.self_cname)
for arg in self.args:
if not arg.is_generic:
if arg.is_self_arg or arg.is_type_arg:
arg_code_list.append("PyObject *%s" % arg.hdr_cname)
else:
decl = arg.hdr_type.declaration_code(arg.hdr_cname)
entry = self.local_scope.lookup(arg.name)
if not entry.cf_used:
arg_code_list.append('CYTHON_UNUSED ' + decl)
else:
arg_code_list.append(decl)
if not self.entry.is_special and sig.method_flags() == [TypeSlots.method_noargs]:
arg_code_list.append(
arg.hdr_type.declaration_code(arg.hdr_cname))
entry = self.target.entry
if not entry.is_special and sig.method_flags() == [TypeSlots.method_noargs]:
arg_code_list.append("CYTHON_UNUSED PyObject *unused")
if (self.entry.scope.is_c_class_scope and self.entry.name == "__ipow__"):
if entry.scope.is_c_class_scope and entry.name == "__ipow__":
arg_code_list.append("CYTHON_UNUSED PyObject *unused")
if sig.has_generic_args:
arg_code_list.append(
"PyObject *%s, PyObject *%s"
% (Naming.args_cname, Naming.kwds_cname))
arg_code = ", ".join(arg_code_list)
dc = self.return_type.declaration_code(self.entry.func_cname)
mf = " ".join(self.modifiers).upper()
if mf: mf += " "
header = "static %s%s(%s)" % (mf, dc, arg_code)
dc = self.return_type.declaration_code(entry.func_cname)
header = "static %s(%s)" % (dc, arg_code)
code.putln("%s; /*proto*/" % header)
if proto_only:
if self.fused_py_func:
if self.target.fused_py_func:
# If we are the specialized version of the cpdef, we still
# want the prototype for the "fused cpdef", in case we're
# checking to see if our method was overridden in Python
self.fused_py_func.generate_function_header(
self.target.fused_py_func.generate_function_header(
code, with_pymethdef, proto_only=True)
return
if (Options.docstrings and self.entry.doc and
not self.fused_py_func and
not self.entry.scope.is_property_scope and
(not self.entry.is_special or self.entry.wrapperbase_cname)):
if (Options.docstrings and entry.doc and
not self.target.fused_py_func and
not entry.scope.is_property_scope and
(not entry.is_special or entry.wrapperbase_cname)):
# h_code = code.globalstate['h_code']
docstr = self.entry.doc
docstr = entry.doc
if docstr.is_unicode:
docstr = docstr.utf8encode()
code.putln(
'static char %s[] = "%s";' % (
self.entry.doc_cname,
entry.doc_cname,
split_string_literal(escape_byte_string(docstr))))
if self.entry.is_special:
if entry.is_special:
code.putln(
"struct wrapperbase %s;" % self.entry.wrapperbase_cname)
"struct wrapperbase %s;" % entry.wrapperbase_cname)
if with_pymethdef or self.fused_py_func:
if with_pymethdef or self.target.fused_py_func:
code.put(
"static PyMethodDef %s = " %
self.entry.pymethdef_cname)
code.put_pymethoddef(self.entry, ";", allow_skip=False)
entry.pymethdef_cname)
code.put_pymethoddef(self.target.entry, ";", allow_skip=False)
code.putln("%s {" % header)
def generate_argument_declarations(self, env, code):
for arg in self.args:
if arg.is_generic: # or arg.needs_conversion:
if arg.is_generic:
if arg.needs_conversion:
code.putln("PyObject *%s = 0;" % arg.hdr_cname)
elif not arg.entry.in_closure:
else:
code.put_var_declaration(arg.entry)
for entry in env.var_entries:
if entry.is_arg:
code.put_var_declaration(entry)
def generate_keyword_list(self, code):
if self.signature_has_generic_args() and \
......@@ -3159,7 +3314,7 @@ class DefNode(FuncDefNode):
def generate_argument_parsing_code(self, env, code):
# Generate fast equivalent of PyArg_ParseTuple call for
# generic arguments, if any, including args/kwargs
if self.entry.signature.has_dummy_arg and not self.self_in_stararg:
if self.signature.has_dummy_arg and not self.self_in_stararg:
# get rid of unused argument warning
code.putln("%s = %s;" % (Naming.self_cname, Naming.self_cname))
......@@ -3175,9 +3330,6 @@ class DefNode(FuncDefNode):
if not arg.type.is_pyobject:
if not arg.type.create_from_py_utility_code(env):
pass # will fail later
elif arg.is_self_arg and arg.entry.in_closure:
# must store 'self' in the closure explicitly for extension types
self.generate_arg_assignment(arg, arg.hdr_cname, code)
if not self.signature_has_generic_args():
if has_star_or_kw_args:
......@@ -3220,41 +3372,17 @@ class DefNode(FuncDefNode):
code.put_var_xdecref_clear(self.starstar_arg.entry)
else:
code.put_var_decref_clear(self.starstar_arg.entry)
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
# facilitate generic closure instance deallocation. In
# the case of an argument type error, it's best to just
# DECREF+clear the already handled references, as this
# frees their references as early as possible.
for arg in self.args:
if arg.type.is_pyobject and arg.entry.in_closure:
code.put_var_xdecref_clear(arg.entry)
if self.needs_closure:
code.put_decref(Naming.cur_scope_cname, self.local_scope.scope_class.type)
code.put_add_traceback(self.target.entry.qualified_name)
code.put_finish_refcount_context()
code.putln("return %s;" % self.error_value())
if code.label_used(end_label):
code.put_label(end_label)
# fix refnanny view on closure variables here, instead of
# doing it separately for each arg parsing special case
if self.star_arg and self.star_arg.entry.in_closure:
code.put_var_giveref(self.star_arg.entry)
if self.starstar_arg and self.starstar_arg.entry.in_closure:
code.put_var_giveref(self.starstar_arg.entry)
for arg in self.args:
if arg.type.is_pyobject and arg.entry.in_closure:
code.put_var_giveref(arg.entry)
def generate_arg_assignment(self, arg, item, code, incref_closure=True):
if arg.type.is_pyobject:
if arg.is_generic:
item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item)
entry = arg.entry
if incref_closure and entry.in_closure:
code.put_incref(item, PyrexTypes.py_object_type)
code.putln("%s = %s;" % (entry.cname, item))
else:
func = arg.type.from_py_function
......@@ -3509,8 +3637,6 @@ class DefNode(FuncDefNode):
code.putln("if (unlikely(!%s)) {" % self.star_arg.entry.cname)
if self.starstar_arg:
code.put_decref_clear(self.starstar_arg.entry.cname, py_object_type)
if self.needs_closure:
code.put_decref(Naming.cur_scope_cname, self.local_scope.scope_class.type)
code.put_finish_refcount_context()
code.putln('return %s;' % self.error_value())
code.putln('}')
......@@ -3527,10 +3653,10 @@ class DefNode(FuncDefNode):
code.putln("PyObject* values[%d] = {%s};" % (
max_args, ','.join('0'*max_args)))
if self.defaults_struct:
if self.target.defaults_struct:
code.putln('%s *%s = __Pyx_CyFunction_Defaults(%s, %s);' % (
self.defaults_struct, Naming.dynamic_args_cname,
self.defaults_struct, Naming.self_cname))
self.target.defaults_struct, Naming.dynamic_args_cname,
self.target.defaults_struct, Naming.self_cname))
# assign borrowed Python default values to the values array,
# so that they can be overwritten by received arguments below
......@@ -3697,10 +3823,6 @@ class DefNode(FuncDefNode):
for arg in self.args:
if arg.needs_conversion:
self.generate_arg_conversion(arg, code)
elif not arg.is_self_arg and arg.entry.in_closure:
if arg.type.is_pyobject:
code.put_incref(arg.hdr_cname, py_object_type)
code.putln('%s = %s;' % (arg.entry.cname, arg.hdr_cname))
def generate_arg_conversion(self, arg, code):
# Generate conversion code for one argument.
......@@ -3768,10 +3890,7 @@ class DefNode(FuncDefNode):
self.generate_arg_none_check(arg, code)
def error_value(self):
return self.entry.signature.error_value
def caller_will_check_exceptions(self):
return 1
return self.signature.error_value
class GeneratorDefNode(DefNode):
......@@ -7837,6 +7956,8 @@ class CnameDecoratorNode(StatNode):
e.cname = self.cname
e.func_cname = self.cname
e.used = True
if e.pyfunc_cname and '.' in e.pyfunc_cname:
e.pyfunc_cname = self.mangle(e.pyfunc_cname)
elif is_struct_or_enum:
e.cname = e.type.cname = self.cname
else:
......@@ -7853,12 +7974,16 @@ class CnameDecoratorNode(StatNode):
for name, entry in scope.entries.iteritems():
if entry.func_cname:
cname = entry.cname
if '.' in cname:
# remove __pyx_base from func_cname
cname = cname.split('.')[-1]
entry.func_cname = '%s_%s' % (self.cname, cname)
entry.func_cname = self.mangle(entry.cname)
if entry.pyfunc_cname:
old = entry.pyfunc_cname
entry.pyfunc_cname = self.mangle(entry.pyfunc_cname)
def mangle(self, cname):
if '.' in cname:
# remove __pyx_base from func_cname
cname = cname.split('.')[-1]
return '%s_%s' % (self.cname, cname)
def analyse_expressions(self, env):
self.node.analyse_expressions(env)
......
......@@ -1643,6 +1643,28 @@ class EarlyReplaceBuiltinCalls(Visitor.EnvTransform):
return node
return kwargs
class InlineDefNodeCalls(Visitor.CythonTransform):
visit_Node = Visitor.VisitorTransform.recurse_to_children
def visit_SimpleCallNode(self, node):
self.visitchildren(node)
if not self.current_directives.get('optimize.inline_defnode_calls'):
return node
function_name = node.function
if not function_name.is_name:
return node
if not function_name.cf_state.is_single:
return node
function = function_name.cf_state.one().rhs
if not isinstance(function, ExprNodes.PyCFunctionNode):
return node
inlined = ExprNodes.InlinedDefNodeCallNode(
node.pos, function_name=function_name,
function=function, args=node.args)
if inlined.can_be_inlined():
return inlined
return node
class OptimizeBuiltinCalls(Visitor.EnvTransform):
"""Optimize some common methods calls and instantiation patterns
......
......@@ -106,6 +106,9 @@ directive_defaults = {
'warn.unused_arg': False,
'warn.unused_result': False,
# optimizations
'optimize.inline_defnode_calls': False,
# remove unreachable code
'remove_unreachable': True,
......
......@@ -1493,6 +1493,9 @@ if VALUE is not None:
if node.py_func:
node.stats.insert(0, node.py_func)
self.visit(node.py_func)
if node.py_func.needs_assignment_synthesis(env):
node = [node, self._synthesize_assignment(node.py_func, env)]
else:
node.body.analyse_declarations(lenv)
......@@ -1514,6 +1517,54 @@ if VALUE is not None:
self.seen_vars_stack.pop()
return node
def visit_DefNode(self, node):
node = self.visit_FuncDefNode(node)
env = self.env_stack[-1]
if (not isinstance(node, Nodes.DefNode) or
node.fused_py_func or node.is_generator_body or
not node.needs_assignment_synthesis(env)):
return node
return [node, self._synthesize_assignment(node, env)]
def _synthesize_assignment(self, node, env):
# Synthesize assignment node and put it right after defnode
genv = env
while genv.is_py_class_scope or genv.is_c_class_scope:
genv = genv.outer_scope
if genv.is_closure_scope:
rhs = node.py_cfunc_node = ExprNodes.InnerFunctionNode(
node.pos, def_node=node,
pymethdef_cname=node.entry.pymethdef_cname,
code_object=ExprNodes.CodeObjectNode(node))
else:
rhs = ExprNodes.PyCFunctionNode(
node.pos,
def_node=node,
pymethdef_cname=node.entry.pymethdef_cname,
binding=self.current_directives.get('binding'),
specialized_cpdefs=node.specialized_cpdefs,
code_object=ExprNodes.CodeObjectNode(node))
if env.is_py_class_scope:
rhs.binding = True
node.is_cyfunction = rhs.binding
if node.decorators:
for decorator in node.decorators[::-1]:
rhs = ExprNodes.SimpleCallNode(
decorator.pos,
function = decorator.decorator,
args = [rhs])
assmt = Nodes.SingleAssignmentNode(
node.pos,
lhs=ExprNodes.NameNode(node.pos,name=node.name),
rhs=rhs)
assmt.analyse_declarations(env)
return assmt
def visit_ScopedExprNode(self, node):
env = self.env_stack[-1]
node.analyse_declarations(env)
......@@ -1645,11 +1696,13 @@ if VALUE is not None:
return None
def visit_CnameDecoratorNode(self, node):
self.visitchildren(node)
if not node.node:
child_node = self.visit(node.node)
if not child_node:
return None
if type(child_node) is list: # Assignment synthesized
node.child_node = child_node[0]
return [node] + child_node[1:]
node.node = child_node
return node
def create_Property(self, entry):
......@@ -2065,9 +2118,6 @@ class CreateClosureClasses(CythonTransform):
return from_closure, in_closure
def create_class_from_scope(self, node, target_module_scope, inner_node=None):
# skip generator body
if node.is_generator_body:
return
# move local variables into closure
if node.is_generator:
for entry in node.local_scope.entries.values():
......@@ -2160,6 +2210,10 @@ class CreateClosureClasses(CythonTransform):
self.path.pop()
return node
def visit_GeneratorBodyDefNode(self, node):
self.visitchildren(node)
return node
def visit_CFuncDefNode(self, node):
self.visitchildren(node)
return node
......
......@@ -137,6 +137,7 @@ def create_pipeline(context, mode, exclude_classes=()):
from AutoDocTransforms import EmbedSignature
from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
from Optimize import InlineDefNodeCalls
from Optimize import ConstantFolding, FinalOptimizePhase
from Optimize import DropRefcountingTransform
from Buffer import IntroduceBufferAuxiliaryVars
......@@ -185,6 +186,7 @@ def create_pipeline(context, mode, exclude_classes=()):
MarkOverflowingArithmetic(context),
IntroduceBufferAuxiliaryVars(context),
_check_c_declarations,
InlineDefNodeCalls(context),
AnalyseExpressionsTransform(context),
FindInvalidUseOfFusedTypes(context),
CreateClosureClasses(context), ## After all lookups and type inference
......
......@@ -156,6 +156,7 @@ class Entry(object):
from_closure = 0
is_declared_generic = 0
is_readonly = 0
pyfunc_cname = None
func_cname = None
func_modifiers = []
final_func_cname = None
......
# cython: optimize.inline_defnode_calls=True
# mode: run
cimport cython
@cython.test_fail_if_path_exists('//SimpleCallNode')
@cython.test_assert_path_exists('//InlinedDefNodeCallNode')
def simple_noargs():
"""
>>> simple_noargs()
123
"""
def inner():
return 123
return inner()
@cython.test_fail_if_path_exists('//SimpleCallNode')
@cython.test_assert_path_exists('//InlinedDefNodeCallNode')
def test_coerce(a, int b):
"""
>>> test_coerce(2, 2)
4
"""
def inner(int a, b):
return a * b
return inner(a, b)
cdef class Foo(object):
def __repr__(self):
return '<Foo>'
@cython.test_fail_if_path_exists('//SimpleCallNode')
@cython.test_assert_path_exists('//InlinedDefNodeCallNode')
def test_func_signature(a):
"""
>>> test_func_signature(Foo())
<Foo>
"""
def inner(Foo a):
return a
return inner(a)
@cython.test_fail_if_path_exists('//SimpleCallNode')
@cython.test_assert_path_exists('//InlinedDefNodeCallNode')
def test_func_signature2(a, b):
"""
>>> test_func_signature2(Foo(), 123)
(<Foo>, 123)
"""
def inner(Foo a, b):
return a, b
return inner(a, b)
# Starred args and default values are not yet supported for inlining
@cython.test_assert_path_exists('//SimpleCallNode')
def test_defaults(a, b):
"""
>>> test_defaults(1, 2)
(1, 2, 123)
"""
def inner(a, b=b, c=123):
return a, b, c
return inner(a)
@cython.test_assert_path_exists('//SimpleCallNode')
def test_starred(a):
"""
>>> test_starred(123)
(123, (), {})
"""
def inner(a, *args, **kwargs):
return a, args, kwargs
return inner(a)
......@@ -406,10 +406,10 @@ cdef object some_float_value():
return 2.0
@cython.test_fail_if_path_exists('//NameNode[@type.is_pyobject = True]')
@cython.test_assert_path_exists('//NameNode[@type.is_pyobject]',
'//NameNode[@type.is_pyobject = False]')
@infer_types(None)
@cython.test_fail_if_path_exists('//DefNode//NameNode[@type.is_pyobject = True]')
@cython.test_assert_path_exists('//DefNode//NameNode[@type.is_pyobject]',
'//DefNode//NameNode[@type.is_pyobject = False]')
def double_loop():
"""
>>> double_loop() == 1.0 * 10
......
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