Skip to content

Commit

Permalink
Simple closure defnode call inlining
Browse files Browse the repository at this point in the history
  • Loading branch information
vitek committed Jan 12, 2012
1 parent c1c378a commit 1e94a83
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
100 changes: 100 additions & 0 deletions Cython/Compiler/ExprNodes.py
Expand Up @@ -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):
Expand Down
22 changes: 22 additions & 0 deletions Cython/Compiler/Optimize.py
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Cython/Compiler/Options.py
Expand Up @@ -106,6 +106,9 @@
'warn.unused_arg': False,
'warn.unused_result': False,

# optimizations
'optimize.inline_defnode_calls': False,

# remove unreachable code
'remove_unreachable': True,

Expand Down
2 changes: 2 additions & 0 deletions Cython/Compiler/Pipeline.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions 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.