Skip to content
Browse files

Simple closure defnode call inlining

  • Loading branch information...
1 parent c1c378a commit 1e94a8348cb239314c7950d1b4de2593174fdd4a @vitek vitek committed
View
100 Cython/Compiler/ExprNodes.py
@@ -3864,6 +3864,106 @@ def generate_result_code(self, code):
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:
+ if proto_arg.hdr_type:
+ arg_code.append(arg.result_as(proto_arg.hdr_type))
+ else:
+ 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):
View
22 Cython/Compiler/Optimize.py
@@ -1643,6 +1643,28 @@ def _handle_general_function_dict(self, node, pos_args, kwargs):
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 len(function_name.cf_state) != 1:
+ return node
+ function = list(function_name.cf_state)[0].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
View
3 Cython/Compiler/Options.py
@@ -106,6 +106,9 @@
'warn.unused_arg': False,
'warn.unused_result': False,
+# optimizations
+ 'optimize.inline_defnode_calls': False,
+
# remove unreachable code
'remove_unreachable': True,
View
2 Cython/Compiler/Pipeline.py
@@ -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
View
77 tests/run/closure_inlining.pyx
@@ -0,0 +1,77 @@
+# 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)

0 comments on commit 1e94a83

Please sign in to comment.
Something went wrong with that request. Please try again.