ticket #186: generate more efficient code when tuple-unpacking a typed tuple
authorStefan Behnel <scoder@users.berlios.de>
Fri, 13 Mar 2009 22:42:09 +0000 (23:42 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Fri, 13 Mar 2009 22:42:09 +0000 (23:42 +0100)
Cython/Compiler/ExprNodes.py
tests/run/tupleassign.pyx

index 38e518a727824e59a46205916cbaecc891d1bdba..c57b8e8f105bda63fc6977c34e3fd5cf0ddbfa0c 100644 (file)
@@ -3014,9 +3014,13 @@ class SequenceNode(NewTempExprNode):
         # allocates the temps in a rather hacky way -- the assignment
         # is evaluated twice, within each if-block.
 
+        if rhs.type is tuple_type:
+            tuple_check = "likely(%s != Py_None)"
+        else:
+            tuple_check = "PyTuple_CheckExact(%s)"
         code.putln(
-            "if (PyTuple_CheckExact(%s) && PyTuple_GET_SIZE(%s) == %s) {" % (
-                rhs.py_result(), 
+            "if (%s && likely(PyTuple_GET_SIZE(%s) == %s)) {" % (
+                tuple_check % rhs.py_result(), 
                 rhs.py_result(), 
                 len(self.args)))
         code.putln("PyObject* tuple = %s;" % rhs.py_result())
@@ -3037,37 +3041,55 @@ class SequenceNode(NewTempExprNode):
                  
         code.putln("} else {")
 
-        code.putln(
-            "%s = PyObject_GetIter(%s); %s" % (
-                self.iterator.result(),
-                rhs.py_result(),
-                code.error_goto_if_null(self.iterator.result(), self.pos)))
-        code.put_gotref(self.iterator.py_result())
-        rhs.generate_disposal_code(code)
-        for i in range(len(self.args)):
-            item = self.unpacked_items[i]
-            unpack_code = "__Pyx_UnpackItem(%s, %d)" % (
-                self.iterator.py_result(), i)
+        if rhs.type is tuple_type:
+            code.globalstate.use_utility_code(raise_none_iter_error_utility_code)
+            code.putln("if (%s == Py_None) {" %
+                       rhs.py_result())
+            code.putln("__Pyx_RaiseNoneNotIterableError();")
+            code.putln("} else if (PyTuple_GET_SIZE(%s) < %s) {" % (
+                rhs.py_result(), len(self.args)))
+            code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
+            code.putln("__Pyx_RaiseNeedMoreValuesError(PyTuple_GET_SIZE(%s));" %
+                       rhs.py_result());
+            code.putln("} else {")
+            code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
+            code.putln("__Pyx_RaiseTooManyValuesError();");
+            code.putln("}")
             code.putln(
-                "%s = %s; %s" % (
-                    item.result(),
-                    typecast(item.ctype(), py_object_type, unpack_code),
-                    code.error_goto_if_null(item.result(), self.pos)))
-            code.put_gotref(item.py_result())
-            value_node = self.coerced_unpacked_items[i]
-            value_node.generate_evaluation_code(code)
-        code.put_error_if_neg(self.pos, 
-            "__Pyx_EndUnpack(%s)" % (
-                self.iterator.py_result()))
-        if debug_disposal_code:
-            print("UnpackNode.generate_assignment_code:")
-            print("...generating disposal code for %s" % self.iterator)
-        self.iterator.generate_disposal_code(code)
-        self.iterator.free_temps(code)
+                code.error_goto(self.pos))
+        else:
+            code.putln(
+                "%s = PyObject_GetIter(%s); %s" % (
+                    self.iterator.result(),
+                    rhs.py_result(),
+                    code.error_goto_if_null(self.iterator.result(), self.pos)))
+            code.put_gotref(self.iterator.py_result())
+            rhs.generate_disposal_code(code)
+            for i in range(len(self.args)):
+                item = self.unpacked_items[i]
+                unpack_code = "__Pyx_UnpackItem(%s, %d)" % (
+                    self.iterator.py_result(), i)
+                code.putln(
+                    "%s = %s; %s" % (
+                        item.result(),
+                        typecast(item.ctype(), py_object_type, unpack_code),
+                        code.error_goto_if_null(item.result(), self.pos)))
+                code.put_gotref(item.py_result())
+                value_node = self.coerced_unpacked_items[i]
+                value_node.generate_evaluation_code(code)
+            code.put_error_if_neg(self.pos, 
+                "__Pyx_EndUnpack(%s)" % (
+                    self.iterator.py_result()))
+            if debug_disposal_code:
+                print("UnpackNode.generate_assignment_code:")
+                print("...generating disposal code for %s" % self.iterator)
+            self.iterator.generate_disposal_code(code)
+            self.iterator.free_temps(code)
+
+            for i in range(len(self.args)):
+                self.args[i].generate_assignment_code(
+                    self.coerced_unpacked_items[i], code)
 
-        for i in range(len(self.args)):
-            self.args[i].generate_assignment_code(
-                self.coerced_unpacked_items[i], code)
         code.putln("}")
         rhs.free_temps(code)
         
@@ -5257,43 +5279,6 @@ bad:
 
 #------------------------------------------------------------------------------------
 
-unpacking_utility_code = UtilityCode(
-proto = """
-static PyObject *__Pyx_UnpackItem(PyObject *, Py_ssize_t index); /*proto*/
-static int __Pyx_EndUnpack(PyObject *); /*proto*/
-""",
-impl = """
-static PyObject *__Pyx_UnpackItem(PyObject *iter, Py_ssize_t index) {
-    PyObject *item;
-    if (!(item = PyIter_Next(iter))) {
-        if (!PyErr_Occurred()) {
-            PyErr_Format(PyExc_ValueError,
-                #if PY_VERSION_HEX < 0x02050000
-                    "need more than %d values to unpack", (int)index);
-                #else
-                    "need more than %zd values to unpack", index);
-                #endif
-        }
-    }
-    return item;
-}
-
-static int __Pyx_EndUnpack(PyObject *iter) {
-    PyObject *item;
-    if ((item = PyIter_Next(iter))) {
-        Py_DECREF(item);
-        PyErr_SetString(PyExc_ValueError, "too many values to unpack");
-        return -1;
-    }
-    else if (!PyErr_Occurred())
-        return 0;
-    else
-        return -1;
-}
-""")
-
-#------------------------------------------------------------------------------------
-
 type_test_utility_code = UtilityCode(
 proto = """
 static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/
@@ -5531,18 +5516,89 @@ raise_noneattr_error_utility_code = UtilityCode(
 proto = """
 static INLINE void __Pyx_RaiseNoneAttributeError(const char* attrname);
 """,
-impl = """
+impl = '''
 static INLINE void __Pyx_RaiseNoneAttributeError(const char* attrname) {
     PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%s'", attrname);
 }
-""")
+''')
 
 raise_noneindex_error_utility_code = UtilityCode(
 proto = """
 static INLINE void __Pyx_RaiseNoneIndexingError(void);
 """,
-impl = """
+impl = '''
 static INLINE void __Pyx_RaiseNoneIndexingError(void) {
     PyErr_SetString(PyExc_TypeError, "'NoneType' object is unsubscriptable");
 }
-""")
+''')
+
+raise_none_iter_error_utility_code = UtilityCode(
+proto = """
+static INLINE void __Pyx_RaiseNoneNotIterableError(void);
+""",
+impl = '''
+static INLINE void __Pyx_RaiseNoneNotIterableError(void) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is iterable");
+}
+''')
+
+raise_too_many_values_to_unpack = UtilityCode(
+proto = """
+static INLINE void __Pyx_RaiseTooManyValuesError(void);
+""",
+impl = '''
+static INLINE void __Pyx_RaiseTooManyValuesError(void) {
+    PyErr_SetString(PyExc_ValueError, "too many values to unpack");
+}
+''')
+
+raise_need_more_values_to_unpack = UtilityCode(
+proto = """
+static INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index);
+""",
+impl = '''
+static INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
+    PyErr_Format(PyExc_ValueError,
+        #if PY_VERSION_HEX < 0x02050000
+                 "need more than %d value%s to unpack", (int)index,
+        #else
+                 "need more than %zd value%s to unpack", index,
+        #endif
+                 (index == 1) ? "" : "s");
+}
+''')
+
+#------------------------------------------------------------------------------------
+
+unpacking_utility_code = UtilityCode(
+proto = """
+static PyObject *__Pyx_UnpackItem(PyObject *, Py_ssize_t index); /*proto*/
+static int __Pyx_EndUnpack(PyObject *); /*proto*/
+""",
+impl = """
+static PyObject *__Pyx_UnpackItem(PyObject *iter, Py_ssize_t index) {
+    PyObject *item;
+    if (!(item = PyIter_Next(iter))) {
+        if (!PyErr_Occurred()) {
+            __Pyx_RaiseNeedMoreValuesError(index);
+        }
+    }
+    return item;
+}
+
+static int __Pyx_EndUnpack(PyObject *iter) {
+    PyObject *item;
+    if ((item = PyIter_Next(iter))) {
+        Py_DECREF(item);
+        __Pyx_RaiseTooManyValuesError();
+        return -1;
+    }
+    else if (!PyErr_Occurred())
+        return 0;
+    else
+        return -1;
+}
+""",
+requires = [raise_need_more_values_to_unpack,
+            raise_too_many_values_to_unpack]
+)
index 4339da1239d0f79fa1beacdb1ce84473f470a380..1a1a60521736d5c1ab572b7e486428cf5426fb0b 100644 (file)
@@ -3,6 +3,8 @@ __doc__ = u"""
 (1, 2, 3)
 >>> assign3(t)
 (1, 2, 3)
+>>> assign3_typed(t)
+(1, 2, 3)
 >>> assign3_int(l)
 (1, 2, 3)
 >>> assign3_mixed1(l)
@@ -12,6 +14,40 @@ __doc__ = u"""
 >>> assign3_mixed3(l)
 (1, 2, 3)
 
+>>> assign3_typed(l)
+Traceback (most recent call last):
+TypeError: Argument 't' has incorrect type (expected tuple, got list)
+
+>>> a,b,c = (1,) # doctest: +ELLIPSIS
+Traceback (most recent call last):
+ValueError: ...
+>>> assign3((1,))
+Traceback (most recent call last):
+ValueError: need more than 1 value to unpack
+>>> assign3_typed((1,))
+Traceback (most recent call last):
+ValueError: need more than 1 value to unpack
+
+>>> a,b,c = (1,2) # doctest: +ELLIPSIS
+Traceback (most recent call last):
+ValueError: ...
+>>> assign3((1,2))
+Traceback (most recent call last):
+ValueError: need more than 2 values to unpack
+>>> assign3_typed((1,2))
+Traceback (most recent call last):
+ValueError: need more than 2 values to unpack
+
+>>> a,b,c = (1,2,3,4)
+Traceback (most recent call last):
+ValueError: too many values to unpack
+>>> assign3((1,2,3,4))
+Traceback (most recent call last):
+ValueError: too many values to unpack
+>>> assign3_typed((1,2,3,4))
+Traceback (most recent call last):
+ValueError: too many values to unpack
+
 >>> a,b = 99,98
 >>> a,b = t
 Traceback (most recent call last):
@@ -47,6 +83,10 @@ def assign3(t):
     a,b,c = t
     return (a,b,c)
 
+def assign3_typed(tuple t):
+    a,b,c = t
+    return (a,b,c)
+
 def assign3_int(t):
     cdef int a,b,c
     a,b,c = t