Repository: numba/numba

Issue:
Bug in object mode IR optimization
The following code:

```
@jit(forceobj=True)
def chain1(x, y):
    a = (b, c) = (x, y)
    return b

chain1(1, 0)
```

will result in the following exception:

```
  File "crash1.py", line 21, in <module>
    chain1(1, 0)
  File "/home/antoine/numba/numba/dispatcher.py", line 142, in _compile_and_call
    return self(*args, **kws)
SystemError: null argument to internal routine
```

As far as I can tell, this is because of an invalid optimization in RemoveRedundantAssign:

```
label 0:
   x.1 = x
   y.1 = y
   $0.1 = build_tuple(items=[Var(x.1, crash1.py (17)), Var(y.1, crash1.py (17))])
   a = $0.1
   $0.4 = exhaust_iter(count=2, value=$0.1)
   $0.2 = static_getitem(index=0, value=$0.4)
   $0.3 = static_getitem(index=1, value=$0.4)
   b = $0.2
   c = $0.3
   return b
----------------------------------ir optimize-----------------------------------
label 0:
   x.1 = x
   y.1 = y
   a = build_tuple(items=[Var(x.1, crash1.py (17)), Var(y.1, crash1.py (17))])
   $0.4 = exhaust_iter(count=2, value=$0.1)
   $0.2 = static_getitem(index=0, value=$0.4)
   $0.3 = static_getitem(index=1, value=$0.4)
   b = $0.2
   c = $0.3
   return b
```

In the optimized IR, the `build_tuple` result is only stored in `a`, but `exhaust_iter` is called on `$0.1`, which is then NULL. This results in the SystemError being raised in PySequence_Tuple.



Discussion:

Resolving patch:
diff --git a/numba/dataflow.py b/numba/dataflow.py
--- a/numba/dataflow.py
+++ b/numba/dataflow.py
@@ -38,28 +38,28 @@ def dispatch(self, info, inst):
         fn = getattr(self, fname)
         fn(info, inst)
 
-    def dup_topx(self, info, count):
-        stack = [info.pop() for _ in range(count)]
-        for val in reversed(stack):
+    def dup_topx(self, info, inst, count):
+        orig = [info.pop() for _ in range(count)]
+        orig.reverse()
+        # We need to actually create new temporaries if we want the
+        # IR optimization pass to work correctly (see issue #580)
+        duped = [info.make_temp() for _ in range(count)]
+        info.append(inst, orig=orig, duped=duped)
+        for val in orig:
             info.push(val)
-        for val in reversed(stack):
+        for val in duped:
             info.push(val)
 
-    def op_DUP_TOP(self, info, inst):
-        tos = info.pop()
-        info.push(tos)
-        info.push(tos)
-
     def op_DUP_TOPX(self, info, inst):
         count = inst.arg
         assert 1 <= count <= 5, "Invalid DUP_TOPX count"
-        self.dup_topx(info, count)
+        self.dup_topx(info, inst, count)
 
     def op_DUP_TOP(self, info, inst):
-        self.dup_topx(info, count=1)
+        self.dup_topx(info, inst, count=1)
 
     def op_DUP_TOP_TWO(self, info, inst):
-        self.dup_topx(info, count=2)
+        self.dup_topx(info, inst, count=2)
 
     def op_ROT_TWO(self, info, inst):
         first = info.pop()
diff --git a/numba/interpreter.py b/numba/interpreter.py
--- a/numba/interpreter.py
+++ b/numba/interpreter.py
@@ -411,6 +411,13 @@ def op_STORE_FAST(self, inst, value):
         value = self.get(value)
         self.store(value=value, name=dstname)
 
+    def op_DUP_TOPX(self, inst, orig, duped):
+        for src, dst in zip(orig, duped):
+            self.store(value=self.get(src), name=dst)
+
+    op_DUP_TOP = op_DUP_TOPX
+    op_DUP_TOP_TWO = op_DUP_TOPX
+
     def op_STORE_ATTR(self, inst, target, value):
         attr = self.code_names[inst.arg]
         sa = ir.SetAttr(target=self.get(target), value=self.get(value),


Test patch:
diff --git a/numba/tests/test_unpack_sequence.py b/numba/tests/test_unpack_sequence.py
--- a/numba/tests/test_unpack_sequence.py
+++ b/numba/tests/test_unpack_sequence.py
@@ -66,8 +66,20 @@ def unpack_nested_heterogenous_tuple():
     return a + b + c
 
 
+def chained_unpack_assign1(x, y):
+    # Used to fail in object mode (issue #580)
+    a = (b, c) = (x, y)
+    (d, e) = a
+    return d + e + b + c
+
+
 class TestUnpack(TestCase):
 
+    def run_nullary_func(self, pyfunc, flags):
+        cr = compile_isolated(pyfunc, (), flags=flags)
+        cfunc = cr.entry_point
+        self.assertPreciseEqual(cfunc(), pyfunc())
+
     def test_unpack_list(self):
         pyfunc = unpack_list
         cr = compile_isolated(pyfunc, (), flags=enable_pyobj_flags)
@@ -89,41 +101,40 @@ def test_unpack_shape_npm(self):
         self.test_unpack_shape(flags=no_pyobj_flags)
 
     def test_unpack_range(self, flags=force_pyobj_flags):
-        pyfunc = unpack_range
-        cr = compile_isolated(pyfunc, (), flags=flags)
-        cfunc = cr.entry_point
-        self.assertPreciseEqual(cfunc(), pyfunc())
+        self.run_nullary_func(unpack_range, flags)
 
     def test_unpack_range_npm(self):
         self.test_unpack_range(flags=no_pyobj_flags)
 
     def test_unpack_tuple(self, flags=force_pyobj_flags):
-        pyfunc = unpack_tuple
-        cr = compile_isolated(pyfunc, (), flags=flags)
-        cfunc = cr.entry_point
-        self.assertPreciseEqual(cfunc(), pyfunc())
+        self.run_nullary_func(unpack_tuple, flags)
 
     def test_unpack_tuple_npm(self):
         self.test_unpack_tuple(flags=no_pyobj_flags)
 
     def test_unpack_heterogenous_tuple(self, flags=force_pyobj_flags):
-        pyfunc = unpack_heterogenous_tuple
-        cr = compile_isolated(pyfunc, (), flags=flags)
-        cfunc = cr.entry_point
-        self.assertPreciseEqual(cfunc(), pyfunc())
+        self.run_nullary_func(unpack_heterogenous_tuple, flags)
 
     def test_unpack_heterogenous_tuple_npm(self):
         self.test_unpack_heterogenous_tuple(flags=no_pyobj_flags)
 
     def test_unpack_nested_heterogenous_tuple(self, flags=force_pyobj_flags):
-        pyfunc = unpack_nested_heterogenous_tuple
-        cr = compile_isolated(pyfunc, (), flags=flags)
-        cfunc = cr.entry_point
-        self.assertPreciseEqual(cfunc(), pyfunc())
+        self.run_nullary_func(unpack_nested_heterogenous_tuple, flags)
 
     def test_unpack_nested_heterogenous_tuple_npm(self):
         self.test_unpack_nested_heterogenous_tuple(flags=no_pyobj_flags)
 
+    def test_chained_unpack_assign(self, flags=force_pyobj_flags):
+        pyfunc = chained_unpack_assign1
+        cr = compile_isolated(pyfunc, [types.int32, types.int32],
+                              flags=flags)
+        cfunc = cr.entry_point
+        args = (4, 5)
+        self.assertPreciseEqual(cfunc(*args), pyfunc(*args))
+
+    def test_chained_unpack_assign_npm(self):
+        self.test_chained_unpack_assign(flags=no_pyobj_flags)
+
     def check_unpack_error(self, pyfunc, flags=force_pyobj_flags):
         cr = compile_isolated(pyfunc, (), flags=flags)
         cfunc = cr.entry_point