cython-devel

changeset 2194:b9d8cecc8975

general optimisation support for calls to builtin types and their methods

currently providing optimisations for
- getattr(o,a)
- getattr(o,a,d)
- X.append(o)
- L.append(o)
- list.append(L,x)
author Stefan Behnel <scoder@users.berlios.de>
date Sun Mar 29 13:27:55 2009 +0200 (2 years ago)
parents ceb0a35ea490
children c3ab9cc6856f
files Cython/Compiler/Builtin.py Cython/Compiler/ExprNodes.py Cython/Compiler/Main.py Cython/Compiler/Optimize.py Cython/Compiler/PyrexTypes.py tests/run/getattr3call.pyx
line diff
1.1 --- a/Cython/Compiler/Builtin.py Sun Mar 29 12:24:01 2009 +0200 1.2 +++ b/Cython/Compiler/Builtin.py Sun Mar 29 13:27:55 2009 +0200 1.3 @@ -21,7 +21,7 @@ 1.4 #('eval', "", "", ""), 1.5 #('execfile', "", "", ""), 1.6 #('filter', "", "", ""), 1.7 - ('getattr', "OO", "O", "PyObject_GetAttr"), 1.8 + #('getattr', "OO", "O", "PyObject_GetAttr"), # optimised later on 1.9 ('getattr3', "OOO", "O", "__Pyx_GetAttr3", "getattr"), 1.10 ('hasattr', "OO", "b", "PyObject_HasAttr"), 1.11 ('hash', "O", "l", "PyObject_Hash"),
2.1 --- a/Cython/Compiler/ExprNodes.py Sun Mar 29 12:24:01 2009 +0200 2.2 +++ b/Cython/Compiler/ExprNodes.py Sun Mar 29 13:27:55 2009 +0200 2.3 @@ -2315,15 +2315,6 @@ 2.4 function = self.function 2.5 function.is_called = 1 2.6 self.function.analyse_types(env) 2.7 - if function.is_attribute and function.is_py_attr and \ 2.8 - function.attribute == "append" and len(self.args) == 1: 2.9 - # L.append(x) is almost always applied to a list 2.10 - self.py_func = self.function 2.11 - self.function = NameNode(pos=self.function.pos, name="__Pyx_PyObject_Append") 2.12 - self.function.analyse_types(env) 2.13 - self.self = self.py_func.obj 2.14 - function.obj = CloneNode(self.self) 2.15 - env.use_utility_code(append_utility_code) 2.16 if function.is_attribute and function.entry and function.entry.is_cmethod: 2.17 # Take ownership of the object from which the attribute 2.18 # was obtained, because we need to pass it as 'self'. 2.19 @@ -2516,7 +2507,37 @@ 2.20 code.putln("%s%s; %s" % (lhs, rhs, goto_error)) 2.21 if self.type.is_pyobject and self.result(): 2.22 code.put_gotref(self.py_result()) 2.23 - 2.24 + 2.25 + 2.26 +class PythonCapiFunctionNode(ExprNode): 2.27 + subexprs = [] 2.28 + def __init__(self, pos, name, func_type, utility_code = None): 2.29 + self.pos = pos 2.30 + self.name = name 2.31 + self.type = func_type 2.32 + self.utility_code = utility_code 2.33 + 2.34 + def generate_result_code(self, code): 2.35 + if self.utility_code: 2.36 + code.globalstate.use_utility_code(self.utility_code) 2.37 + 2.38 + def calculate_result_code(self): 2.39 + return self.name 2.40 + 2.41 +class PythonCapiCallNode(SimpleCallNode): 2.42 + # Python C-API Function call (only created in transforms) 2.43 + 2.44 + def __init__(self, pos, function_name, func_type, 2.45 + utility_code = None, **kwargs): 2.46 + self.type = func_type.return_type 2.47 + self.result_ctype = self.type 2.48 + self.function = PythonCapiFunctionNode( 2.49 + pos, function_name, func_type, 2.50 + utility_code = utility_code) 2.51 + # call this last so that we can override the constructed 2.52 + # attributes above with explicit keyword arguments if required 2.53 + SimpleCallNode.__init__(self, pos, **kwargs) 2.54 + 2.55 2.56 class GeneralCallNode(CallNode): 2.57 # General Python function call, including keyword, 2.58 @@ -5419,29 +5440,6 @@ 2.59 2.60 #------------------------------------------------------------------------------------ 2.61 2.62 -append_utility_code = UtilityCode( 2.63 -proto = """ 2.64 -static INLINE PyObject* __Pyx_PyObject_Append(PyObject* L, PyObject* x) { 2.65 - if (likely(PyList_CheckExact(L))) { 2.66 - if (PyList_Append(L, x) < 0) return NULL; 2.67 - Py_INCREF(Py_None); 2.68 - return Py_None; /* this is just to have an accurate signature */ 2.69 - } 2.70 - else { 2.71 - PyObject *r, *m; 2.72 - m = __Pyx_GetAttrString(L, "append"); 2.73 - if (!m) return NULL; 2.74 - r = PyObject_CallFunctionObjArgs(m, x, NULL); 2.75 - Py_DECREF(m); 2.76 - return r; 2.77 - } 2.78 -} 2.79 -""", 2.80 -impl = "" 2.81 -) 2.82 - 2.83 -#------------------------------------------------------------------------------------ 2.84 - 2.85 # If the is_unsigned flag is set, we need to do some extra work to make 2.86 # sure the index doesn't become negative. 2.87
3.1 --- a/Cython/Compiler/Main.py Sun Mar 29 12:24:01 2009 +0200 3.2 +++ b/Cython/Compiler/Main.py Sun Mar 29 13:27:55 2009 +0200 3.3 @@ -84,7 +84,7 @@ 3.4 from ParseTreeTransforms import GilCheck 3.5 from AutoDocTransforms import EmbedSignature 3.6 from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform 3.7 - from Optimize import FlattenBuiltinTypeCreation, ConstantFolding, FinalOptimizePhase 3.8 + from Optimize import OptimiseBuiltinCalls, ConstantFolding, FinalOptimizePhase 3.9 from Buffer import IntroduceBufferAuxiliaryVars 3.10 from ModuleNode import check_c_declarations 3.11 3.12 @@ -125,7 +125,7 @@ 3.13 IntroduceBufferAuxiliaryVars(self), 3.14 _check_c_declarations, 3.15 AnalyseExpressionsTransform(self), 3.16 - FlattenBuiltinTypeCreation(), 3.17 + OptimiseBuiltinCalls(), 3.18 # ComprehensionTransform(), 3.19 IterationTransform(), 3.20 SwitchTransform(),
4.1 --- a/Cython/Compiler/Optimize.py Sun Mar 29 12:24:01 2009 +0200 4.2 +++ b/Cython/Compiler/Optimize.py Sun Mar 29 13:27:55 2009 +0200 4.3 @@ -7,8 +7,10 @@ 4.4 import TypeSlots 4.5 import Symtab 4.6 import Options 4.7 + 4.8 +from Cython.Utils import UtilityCode 4.9 from StringEncoding import EncodedString 4.10 - 4.11 +from Errors import error 4.12 from ParseTreeTransforms import SkipDeclarations 4.13 4.14 #def unwrap_node(node): 4.15 @@ -414,18 +416,12 @@ 4.16 visit_Node = Visitor.VisitorTransform.recurse_to_children 4.17 4.18 4.19 -class FlattenBuiltinTypeCreation(Visitor.VisitorTransform): 4.20 - """Optimise some common instantiation patterns for builtin types. 4.21 +class OptimiseBuiltinCalls(Visitor.VisitorTransform): 4.22 + """Optimise some common methods calls and instantiation patterns 4.23 + for builtin types. 4.24 """ 4.25 - PyList_AsTuple_func_type = PyrexTypes.CFuncType( 4.26 - PyrexTypes.py_object_type, [ 4.27 - PyrexTypes.CFuncTypeArg("list", Builtin.list_type, None) 4.28 - ]) 4.29 - 4.30 - PyList_AsTuple_name = EncodedString("PyList_AsTuple") 4.31 - 4.32 - PyList_AsTuple_entry = Symtab.Entry( 4.33 - PyList_AsTuple_name, PyList_AsTuple_name, PyList_AsTuple_func_type) 4.34 + # only intercept on call nodes 4.35 + visit_Node = Visitor.VisitorTransform.recurse_to_children 4.36 4.37 def visit_GeneralCallNode(self, node): 4.38 self.visitchildren(node) 4.39 @@ -441,16 +437,38 @@ 4.40 node = handler(node, node.arg_tuple) 4.41 return node 4.42 4.43 + def visit_PyTypeTestNode(self, node): 4.44 + """Flatten redundant type checks after tree changes. 4.45 + """ 4.46 + old_arg = node.arg 4.47 + self.visitchildren(node) 4.48 + if old_arg is node.arg or node.arg.type != node.type: 4.49 + return node 4.50 + return node.arg 4.51 + 4.52 def _find_handler(self, call_type, function): 4.53 - if not function.type.is_builtin_type: 4.54 + if not function.type.is_pyobject: 4.55 return None 4.56 - if not isinstance(function, ExprNodes.NameNode): 4.57 + if function.is_name: 4.58 + if not function.type.is_builtin_type and '_' in function.name: 4.59 + # not interesting anyway, so let's play safe here 4.60 + return None 4.61 + match_name = function.name 4.62 + elif isinstance(function, ExprNodes.AttributeNode): 4.63 + if not function.obj.type.is_builtin_type: 4.64 + type_name = "object" # safety measure 4.65 + else: 4.66 + type_name = function.obj.type.name 4.67 + match_name = "%s_%s" % (type_name, function.attribute) 4.68 + else: 4.69 return None 4.70 - handler = getattr(self, '_handle_%s_%s' % (call_type, function.name), None) 4.71 + handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None) 4.72 if handler is None: 4.73 - handler = getattr(self, '_handle_any_%s' % function.name, None) 4.74 + handler = getattr(self, '_handle_any_%s' % match_name, None) 4.75 return handler 4.76 4.77 + ### builtin types 4.78 + 4.79 def _handle_general_dict(self, node, pos_args, kwargs): 4.80 """Replace dict(a=b,c=d,...) by the underlying keyword dict 4.81 construction which is done anyway. 4.82 @@ -491,6 +509,11 @@ 4.83 else: 4.84 return node 4.85 4.86 + PyList_AsTuple_func_type = PyrexTypes.CFuncType( 4.87 + Builtin.tuple_type, [ 4.88 + PyrexTypes.CFuncTypeArg("list", Builtin.list_type, None) 4.89 + ]) 4.90 + 4.91 def _handle_simple_tuple(self, node, pos_args): 4.92 """Replace tuple([...]) by a call to PyList_AsTuple. 4.93 """ 4.94 @@ -506,27 +529,139 @@ 4.95 # everything else may be None => take the safe path 4.96 return node 4.97 4.98 - node.args = pos_args.args 4.99 - node.arg_tuple = None 4.100 - node.type = Builtin.tuple_type 4.101 - node.result_ctype = Builtin.tuple_type 4.102 - node.function = ExprNodes.NameNode( 4.103 - pos = node.pos, 4.104 - name = self.PyList_AsTuple_name, 4.105 - type = self.PyList_AsTuple_func_type, 4.106 - entry = self.PyList_AsTuple_entry) 4.107 + return ExprNodes.PythonCapiCallNode( 4.108 + node.pos, "PyList_AsTuple", self.PyList_AsTuple_func_type, 4.109 + args = pos_args.args, 4.110 + is_temp = node.is_temp 4.111 + ) 4.112 + 4.113 + ### builtin functions 4.114 + 4.115 + PyObject_GetAttr2_func_type = PyrexTypes.CFuncType( 4.116 + PyrexTypes.py_object_type, [ 4.117 + PyrexTypes.CFuncTypeArg("object", PyrexTypes.py_object_type, None), 4.118 + PyrexTypes.CFuncTypeArg("attr_name", PyrexTypes.py_object_type, None), 4.119 + ]) 4.120 + 4.121 + PyObject_GetAttr3_func_type = PyrexTypes.CFuncType( 4.122 + PyrexTypes.py_object_type, [ 4.123 + PyrexTypes.CFuncTypeArg("object", PyrexTypes.py_object_type, None), 4.124 + PyrexTypes.CFuncTypeArg("attr_name", PyrexTypes.py_object_type, None), 4.125 + PyrexTypes.CFuncTypeArg("default", PyrexTypes.py_object_type, None), 4.126 + ]) 4.127 + 4.128 + def _handle_simple_getattr(self, node, pos_args): 4.129 + # not really a builtin *type*, but worth optimising anyway 4.130 + if not isinstance(pos_args, ExprNodes.TupleNode): 4.131 + return node 4.132 + args = pos_args.args 4.133 + if len(args) == 2: 4.134 + node = ExprNodes.PythonCapiCallNode( 4.135 + node.pos, "PyObject_GetAttr", self.PyObject_GetAttr2_func_type, 4.136 + args = args, 4.137 + is_temp = node.is_temp 4.138 + ) 4.139 + elif len(args) == 3: 4.140 + node = ExprNodes.PythonCapiCallNode( 4.141 + node.pos, "__Pyx_GetAttr3", self.PyObject_GetAttr3_func_type, 4.142 + utility_code = Builtin.getattr3_utility_code, 4.143 + args = args, 4.144 + is_temp = node.is_temp 4.145 + ) 4.146 + else: 4.147 + error(node.pos, "getattr() called with wrong number of args, " 4.148 + "expected 2 or 3, found %d" % 4.149 + len(pos_args.args)) 4.150 return node 4.151 4.152 - def visit_PyTypeTestNode(self, node): 4.153 - """Flatten redundant type checks after tree changes. 4.154 - """ 4.155 - old_arg = node.arg 4.156 - self.visitchildren(node) 4.157 - if old_arg is node.arg or node.arg.type != node.type: 4.158 + ### methods of builtin types 4.159 + 4.160 + PyObject_Append_func_type = PyrexTypes.CFuncType( 4.161 + PyrexTypes.py_object_type, [ 4.162 + PyrexTypes.CFuncTypeArg("list", PyrexTypes.py_object_type, None), 4.163 + PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None), 4.164 + ]) 4.165 + 4.166 + def _handle_simple_object_append(self, node, pos_args): 4.167 + # X.append() is almost always referring to a list 4.168 + if not isinstance(pos_args, ExprNodes.TupleNode): 4.169 return node 4.170 - return node.arg 4.171 + if len(pos_args.args) != 1: 4.172 + return node 4.173 4.174 - visit_Node = Visitor.VisitorTransform.recurse_to_children 4.175 + args = [node.function.obj] + pos_args.args 4.176 + return ExprNodes.PythonCapiCallNode( 4.177 + node.pos, "__Pyx_PyObject_Append", self.PyObject_Append_func_type, 4.178 + args = args, 4.179 + is_temp = node.is_temp, 4.180 + utility_code = append_utility_code # FIXME: move to Builtin.py 4.181 + ) 4.182 + 4.183 + PyList_Append_func_type = PyrexTypes.CFuncType( 4.184 + PyrexTypes.c_int_type, [ 4.185 + PyrexTypes.CFuncTypeArg("list", PyrexTypes.py_object_type, None), 4.186 + PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None), 4.187 + ], 4.188 + exception_value = "-1") 4.189 + 4.190 + def _handle_simple_list_append(self, node, pos_args): 4.191 + if not isinstance(pos_args, ExprNodes.TupleNode): 4.192 + return node 4.193 + if len(pos_args.args) != 1: 4.194 + error(node.pos, "list.append(x) called with wrong number of args, found %d" % 4.195 + len(pos_args.args)) 4.196 + return node 4.197 + 4.198 + obj = node.function.obj 4.199 + # FIXME: obj may need a None check (ticket #166) 4.200 + args = [obj] + pos_args.args 4.201 + return ExprNodes.PythonCapiCallNode( 4.202 + node.pos, "PyList_Append", self.PyList_Append_func_type, 4.203 + args = args, 4.204 + is_temp = node.is_temp 4.205 + ) 4.206 + 4.207 + def _handle_simple_type_append(self, node, pos_args): 4.208 + # unbound method call to list.append(L, x) ? 4.209 + if node.function.obj.name != 'list': 4.210 + return node 4.211 + if not isinstance(pos_args, ExprNodes.TupleNode): 4.212 + return node 4.213 + 4.214 + args = pos_args.args 4.215 + if len(args) != 2: 4.216 + error(node.pos, "list.append(x) called with wrong number of args, found %d" % 4.217 + len(pos_args.args)) 4.218 + return node 4.219 + 4.220 + # FIXME: this may need a type check on the first operand 4.221 + return ExprNodes.PythonCapiCallNode( 4.222 + node.pos, "PyList_Append", self.PyList_Append_func_type, 4.223 + args = args, 4.224 + is_temp = node.is_temp 4.225 + ) 4.226 + 4.227 + 4.228 +append_utility_code = UtilityCode( 4.229 +proto = """ 4.230 +static INLINE PyObject* __Pyx_PyObject_Append(PyObject* L, PyObject* x) { 4.231 + if (likely(PyList_CheckExact(L))) { 4.232 + if (PyList_Append(L, x) < 0) return NULL; 4.233 + Py_INCREF(Py_None); 4.234 + return Py_None; /* this is just to have an accurate signature */ 4.235 + } 4.236 + else { 4.237 + PyObject *r, *m; 4.238 + m = __Pyx_GetAttrString(L, "append"); 4.239 + if (!m) return NULL; 4.240 + r = PyObject_CallFunctionObjArgs(m, x, NULL); 4.241 + Py_DECREF(m); 4.242 + return r; 4.243 + } 4.244 +} 4.245 +""", 4.246 +impl = "" 4.247 +) 4.248 4.249 4.250 class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
5.1 --- a/Cython/Compiler/PyrexTypes.py Sun Mar 29 12:24:01 2009 +0200 5.2 +++ b/Cython/Compiler/PyrexTypes.py Sun Mar 29 13:27:55 2009 +0200 5.3 @@ -234,7 +234,8 @@ 5.4 # Base class for all Python object types (reference-counted). 5.5 # 5.6 # buffer_defaults dict or None Default options for bu 5.7 - 5.8 + 5.9 + name = "object" 5.10 is_pyobject = 1 5.11 default_value = "0" 5.12 pymemberdef_typecode = "T_OBJECT"
6.1 --- a/tests/run/getattr3call.pyx Sun Mar 29 12:24:01 2009 +0200 6.2 +++ b/tests/run/getattr3call.pyx Sun Mar 29 13:27:55 2009 +0200 6.3 @@ -12,9 +12,7 @@ 6.4 1 6.5 >>> g(t, 'b', 2) 6.6 2 6.7 -""" 6.8 6.9 -BROKEN = """ 6.10 >>> h(t, 'a', 2) 6.11 1 6.12 >>> h(t, 'b', 2) 6.13 @@ -27,5 +25,5 @@ 6.14 def g(a, b, c): 6.15 return getattr3(a, b, c) 6.16 6.17 -#def h(a, b, c): 6.18 -# return getattr(a, b, c) 6.19 +def h(a, b, c): 6.20 + return getattr(a, b, c)