general optimisation support for calls to builtin types and their methods
authorStefan Behnel <scoder@users.berlios.de>
Sun, 29 Mar 2009 11:27:55 +0000 (13:27 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Sun, 29 Mar 2009 11:27:55 +0000 (13:27 +0200)
currently providing optimisations for
- getattr(o,a)
- getattr(o,a,d)
- X.append(o)
- L.append(o)
- list.append(L,x)

Cython/Compiler/Builtin.py
Cython/Compiler/ExprNodes.py
Cython/Compiler/Main.py
Cython/Compiler/Optimize.py
Cython/Compiler/PyrexTypes.py
tests/run/getattr3call.pyx

index c7b1be6f42e93fe2810de7c0b0f510633cb0d1be..25374457059337b3528ed6649052a09aa7508c53 100644 (file)
@@ -21,7 +21,7 @@ builtin_function_table = [
     #('eval',      "",     "",      ""),
     #('execfile',  "",     "",      ""),
     #('filter',    "",     "",      ""),
-    ('getattr',    "OO",   "O",     "PyObject_GetAttr"),
+    #('getattr',    "OO",   "O",     "PyObject_GetAttr"),   # optimised later on
     ('getattr3',   "OOO",  "O",     "__Pyx_GetAttr3",       "getattr"),
     ('hasattr',    "OO",   "b",     "PyObject_HasAttr"),
     ('hash',       "O",    "l",     "PyObject_Hash"),
index 65714aee6e29269df9807347ef0b69e35469eaef..1bc9affd7e22c25df6b9acfdbd305ea1d71612cf 100644 (file)
@@ -2315,15 +2315,6 @@ class SimpleCallNode(CallNode):
         function = self.function
         function.is_called = 1
         self.function.analyse_types(env)
-        if function.is_attribute and function.is_py_attr and \
-           function.attribute == "append" and len(self.args) == 1:
-            # L.append(x) is almost always applied to a list
-            self.py_func = self.function
-            self.function = NameNode(pos=self.function.pos, name="__Pyx_PyObject_Append")
-            self.function.analyse_types(env)
-            self.self = self.py_func.obj
-            function.obj = CloneNode(self.self)
-            env.use_utility_code(append_utility_code)
         if function.is_attribute and function.entry and function.entry.is_cmethod:
             # Take ownership of the object from which the attribute
             # was obtained, because we need to pass it as 'self'.
@@ -2516,7 +2507,37 @@ class SimpleCallNode(CallNode):
                     code.putln("%s%s; %s" % (lhs, rhs, goto_error))
                 if self.type.is_pyobject and self.result():
                     code.put_gotref(self.py_result())
-                    
+
+
+class PythonCapiFunctionNode(ExprNode):
+    subexprs = []
+    def __init__(self, pos, name, func_type, utility_code = None):
+        self.pos = pos
+        self.name = name
+        self.type = func_type
+        self.utility_code = utility_code
+
+    def generate_result_code(self, code):
+        if self.utility_code:
+            code.globalstate.use_utility_code(self.utility_code)
+
+    def calculate_result_code(self):
+        return self.name
+
+class PythonCapiCallNode(SimpleCallNode):
+    # Python C-API Function call (only created in transforms)
+
+    def __init__(self, pos, function_name, func_type,
+                 utility_code = None, **kwargs):
+        self.type = func_type.return_type
+        self.result_ctype = self.type
+        self.function = PythonCapiFunctionNode(
+            pos, function_name, func_type,
+            utility_code = utility_code)
+        # call this last so that we can override the constructed
+        # attributes above with explicit keyword arguments if required
+        SimpleCallNode.__init__(self, pos, **kwargs)
+
 
 class GeneralCallNode(CallNode):
     #  General Python function call, including keyword,
@@ -5419,29 +5440,6 @@ impl = ""
 
 #------------------------------------------------------------------------------------
 
-append_utility_code = UtilityCode(
-proto = """
-static INLINE PyObject* __Pyx_PyObject_Append(PyObject* L, PyObject* x) {
-    if (likely(PyList_CheckExact(L))) {
-        if (PyList_Append(L, x) < 0) return NULL;
-        Py_INCREF(Py_None);
-        return Py_None; /* this is just to have an accurate signature */
-    }
-    else {
-        PyObject *r, *m;
-        m = __Pyx_GetAttrString(L, "append");
-        if (!m) return NULL;
-        r = PyObject_CallFunctionObjArgs(m, x, NULL);
-        Py_DECREF(m);
-        return r;
-    }
-}
-""",
-impl = ""
-)
-
-#------------------------------------------------------------------------------------
-
 # If the is_unsigned flag is set, we need to do some extra work to make 
 # sure the index doesn't become negative. 
 
index 1dfed5999458c35f09bcd58896c63e7a25aba5ab..1749b4c263d4fa46cdd87c98381929ffe0e4abe2 100644 (file)
@@ -84,7 +84,7 @@ class Context(object):
         from ParseTreeTransforms import GilCheck
         from AutoDocTransforms import EmbedSignature
         from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
-        from Optimize import FlattenBuiltinTypeCreation, ConstantFolding, FinalOptimizePhase
+        from Optimize import OptimiseBuiltinCalls, ConstantFolding, FinalOptimizePhase
         from Buffer import IntroduceBufferAuxiliaryVars
         from ModuleNode import check_c_declarations
 
@@ -125,7 +125,7 @@ class Context(object):
             IntroduceBufferAuxiliaryVars(self),
             _check_c_declarations,
             AnalyseExpressionsTransform(self),
-            FlattenBuiltinTypeCreation(),
+            OptimiseBuiltinCalls(),
 #            ComprehensionTransform(),
             IterationTransform(),
             SwitchTransform(),
index e483494f8ac92bc10ebcc0273644057549226a79..88d3e998e4c0ec9703fd43e9991b397cfda24287 100644 (file)
@@ -7,8 +7,10 @@ import UtilNodes
 import TypeSlots
 import Symtab
 import Options
-from StringEncoding import EncodedString
 
+from Cython.Utils import UtilityCode
+from StringEncoding import EncodedString
+from Errors import error
 from ParseTreeTransforms import SkipDeclarations
 
 #def unwrap_node(node):
@@ -414,18 +416,12 @@ class FlattenInListTransform(Visitor.VisitorTransform, SkipDeclarations):
     visit_Node = Visitor.VisitorTransform.recurse_to_children
 
 
-class FlattenBuiltinTypeCreation(Visitor.VisitorTransform):
-    """Optimise some common instantiation patterns for builtin types.
+class OptimiseBuiltinCalls(Visitor.VisitorTransform):
+    """Optimise some common methods calls and instantiation patterns
+    for builtin types.
     """
-    PyList_AsTuple_func_type = PyrexTypes.CFuncType(
-        PyrexTypes.py_object_type, [
-            PyrexTypes.CFuncTypeArg("list",  Builtin.list_type, None)
-            ])
-
-    PyList_AsTuple_name = EncodedString("PyList_AsTuple")
-
-    PyList_AsTuple_entry = Symtab.Entry(
-        PyList_AsTuple_name, PyList_AsTuple_name, PyList_AsTuple_func_type)
+    # only intercept on call nodes
+    visit_Node = Visitor.VisitorTransform.recurse_to_children
 
     def visit_GeneralCallNode(self, node):
         self.visitchildren(node)
@@ -441,16 +437,38 @@ class FlattenBuiltinTypeCreation(Visitor.VisitorTransform):
             node = handler(node, node.arg_tuple)
         return node
 
+    def visit_PyTypeTestNode(self, node):
+        """Flatten redundant type checks after tree changes.
+        """
+        old_arg = node.arg
+        self.visitchildren(node)
+        if old_arg is node.arg or node.arg.type != node.type:
+            return node
+        return node.arg
+
     def _find_handler(self, call_type, function):
-        if not function.type.is_builtin_type:
+        if not function.type.is_pyobject:
             return None
-        if not isinstance(function, ExprNodes.NameNode):
+        if function.is_name:
+            if not function.type.is_builtin_type and '_' in function.name:
+                # not interesting anyway, so let's play safe here
+                return None
+            match_name = function.name
+        elif isinstance(function, ExprNodes.AttributeNode):
+            if not function.obj.type.is_builtin_type:
+                type_name = "object" # safety measure
+            else:
+                type_name = function.obj.type.name
+            match_name = "%s_%s" % (type_name, function.attribute)
+        else:
             return None
-        handler = getattr(self, '_handle_%s_%s' % (call_type, function.name), None)
+        handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None)
         if handler is None:
-            handler = getattr(self, '_handle_any_%s' % function.name, None)
+            handler = getattr(self, '_handle_any_%s' % match_name, None)
         return handler
 
+    ### builtin types
+
     def _handle_general_dict(self, node, pos_args, kwargs):
         """Replace dict(a=b,c=d,...) by the underlying keyword dict
         construction which is done anyway.
@@ -491,6 +509,11 @@ class FlattenBuiltinTypeCreation(Visitor.VisitorTransform):
         else:
             return node
 
+    PyList_AsTuple_func_type = PyrexTypes.CFuncType(
+        Builtin.tuple_type, [
+            PyrexTypes.CFuncTypeArg("list", Builtin.list_type, None)
+            ])
+
     def _handle_simple_tuple(self, node, pos_args):
         """Replace tuple([...]) by a call to PyList_AsTuple.
         """
@@ -506,27 +529,139 @@ class FlattenBuiltinTypeCreation(Visitor.VisitorTransform):
             # everything else may be None => take the safe path
             return node
 
-        node.args = pos_args.args
-        node.arg_tuple = None
-        node.type = Builtin.tuple_type
-        node.result_ctype = Builtin.tuple_type
-        node.function = ExprNodes.NameNode(
-            pos = node.pos,
-            name = self.PyList_AsTuple_name,
-            type = self.PyList_AsTuple_func_type,
-            entry = self.PyList_AsTuple_entry)
+        return ExprNodes.PythonCapiCallNode(
+            node.pos, "PyList_AsTuple", self.PyList_AsTuple_func_type,
+            args = pos_args.args,
+            is_temp = node.is_temp
+            )
+
+    ### builtin functions
+
+    PyObject_GetAttr2_func_type = PyrexTypes.CFuncType(
+        PyrexTypes.py_object_type, [
+            PyrexTypes.CFuncTypeArg("object", PyrexTypes.py_object_type, None),
+            PyrexTypes.CFuncTypeArg("attr_name", PyrexTypes.py_object_type, None),
+            ])
+
+    PyObject_GetAttr3_func_type = PyrexTypes.CFuncType(
+        PyrexTypes.py_object_type, [
+            PyrexTypes.CFuncTypeArg("object", PyrexTypes.py_object_type, None),
+            PyrexTypes.CFuncTypeArg("attr_name", PyrexTypes.py_object_type, None),
+            PyrexTypes.CFuncTypeArg("default", PyrexTypes.py_object_type, None),
+            ])
+
+    def _handle_simple_getattr(self, node, pos_args):
+        # not really a builtin *type*, but worth optimising anyway
+        if not isinstance(pos_args, ExprNodes.TupleNode):
+            return node
+        args = pos_args.args
+        if len(args) == 2:
+            node = ExprNodes.PythonCapiCallNode(
+                node.pos, "PyObject_GetAttr", self.PyObject_GetAttr2_func_type,
+                args = args,
+                is_temp = node.is_temp
+                )
+        elif len(args) == 3:
+            node = ExprNodes.PythonCapiCallNode(
+                node.pos, "__Pyx_GetAttr3", self.PyObject_GetAttr3_func_type,
+                utility_code = Builtin.getattr3_utility_code,
+                args = args,
+                is_temp = node.is_temp
+                )
+        else:
+            error(node.pos, "getattr() called with wrong number of args, "
+                  "expected 2 or 3, found %d" %
+                  len(pos_args.args))
         return node
 
-    def visit_PyTypeTestNode(self, node):
-        """Flatten redundant type checks after tree changes.
-        """
-        old_arg = node.arg
-        self.visitchildren(node)
-        if old_arg is node.arg or node.arg.type != node.type:
+    ### methods of builtin types
+
+    PyObject_Append_func_type = PyrexTypes.CFuncType(
+        PyrexTypes.py_object_type, [
+            PyrexTypes.CFuncTypeArg("list", PyrexTypes.py_object_type, None),
+            PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None),
+            ])
+
+    def _handle_simple_object_append(self, node, pos_args):
+        # X.append() is almost always referring to a list
+        if not isinstance(pos_args, ExprNodes.TupleNode):
+            return node
+        if len(pos_args.args) != 1:
             return node
-        return node.arg
 
-    visit_Node = Visitor.VisitorTransform.recurse_to_children
+        args = [node.function.obj] + pos_args.args
+        return ExprNodes.PythonCapiCallNode(
+            node.pos, "__Pyx_PyObject_Append", self.PyObject_Append_func_type,
+            args = args,
+            is_temp = node.is_temp,
+            utility_code = append_utility_code # FIXME: move to Builtin.py
+            )
+
+    PyList_Append_func_type = PyrexTypes.CFuncType(
+        PyrexTypes.c_int_type, [
+            PyrexTypes.CFuncTypeArg("list", PyrexTypes.py_object_type, None),
+            PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None),
+            ],
+        exception_value = "-1")
+
+    def _handle_simple_list_append(self, node, pos_args):
+        if not isinstance(pos_args, ExprNodes.TupleNode):
+            return node
+        if len(pos_args.args) != 1:
+            error(node.pos, "list.append(x) called with wrong number of args, found %d" %
+                  len(pos_args.args))
+            return node
+
+        obj = node.function.obj
+        # FIXME: obj may need a None check (ticket #166)
+        args = [obj] + pos_args.args
+        return ExprNodes.PythonCapiCallNode(
+            node.pos, "PyList_Append", self.PyList_Append_func_type,
+            args = args,
+            is_temp = node.is_temp
+            )
+
+    def _handle_simple_type_append(self, node, pos_args):
+        # unbound method call to list.append(L, x) ?
+        if node.function.obj.name != 'list':
+            return node
+        if not isinstance(pos_args, ExprNodes.TupleNode):
+            return node
+
+        args = pos_args.args
+        if len(args) != 2:
+            error(node.pos, "list.append(x) called with wrong number of args, found %d" %
+                  len(pos_args.args))
+            return node
+
+        # FIXME: this may need a type check on the first operand
+        return ExprNodes.PythonCapiCallNode(
+            node.pos, "PyList_Append", self.PyList_Append_func_type,
+            args = args,
+            is_temp = node.is_temp
+            )
+
+
+append_utility_code = UtilityCode(
+proto = """
+static INLINE PyObject* __Pyx_PyObject_Append(PyObject* L, PyObject* x) {
+    if (likely(PyList_CheckExact(L))) {
+        if (PyList_Append(L, x) < 0) return NULL;
+        Py_INCREF(Py_None);
+        return Py_None; /* this is just to have an accurate signature */
+    }
+    else {
+        PyObject *r, *m;
+        m = __Pyx_GetAttrString(L, "append");
+        if (!m) return NULL;
+        r = PyObject_CallFunctionObjArgs(m, x, NULL);
+        Py_DECREF(m);
+        return r;
+    }
+}
+""",
+impl = ""
+)
 
 
 class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
index a2b79438dba2e06ea4935db5c910e63cc1b07fa5..2cafcfc4ecc708d1c8300ca90fcd81ad39260d1d 100644 (file)
@@ -234,7 +234,8 @@ class PyObjectType(PyrexType):
     #  Base class for all Python object types (reference-counted).
     #
     #  buffer_defaults  dict or None     Default options for bu
-    
+
+    name = "object"
     is_pyobject = 1
     default_value = "0"
     pymemberdef_typecode = "T_OBJECT"
index 6f629f12b42ff2fc3fd20d0f5cd0fb1f445654f4..11f793250c01b3f828521c44b521a017eae7192b 100644 (file)
@@ -12,9 +12,7 @@ __doc__ = u"""
     1
     >>> g(t, 'b', 2)
     2
-"""
 
-BROKEN = """
     >>> h(t, 'a', 2)
     1
     >>> h(t, 'b', 2)
@@ -27,5 +25,5 @@ def f(a, b):
 def g(a, b, c):
     return getattr3(a, b, c)
 
-#def h(a, b, c):
-#    return getattr(a, b, c)
+def h(a, b, c):
+    return getattr(a, b, c)