Repository: numba/numba

Issue:
doing " += 1"  in arrays does not add one in prange loop
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug
```python
import numpy as np
import numba as nb

d = 10
k = 2

@nb.njit(parallel = True)
def test_parallel(d, k):

    gg      = np.zeros((d, k), dtype = nb.int32) 
    gg_next = np.zeros((d, k), dtype = nb.int32)   
        
          
    for i_gg in nb.prange(d): #this part can be parallelized.

        # set value for gg[i_gg]
        for n in range(k):
            gg[i_gg, n] = i_gg

        
        gg_next[i_gg, :]  = gg[i_gg, :] # initialize gg_next[i_gg]
        gg_next[i_gg, 0] += 1

    # print errors (without prange to print results nicely)
    for i_gg in range(d):
        print('i_gg = ', i_gg , ', gg = ', gg[i_gg], ', gg_next = ', gg_next[i_gg])

```

then I got
```python
test_parallel(d, k)
```

```
i_gg =  0 , gg =  [0 0] , gg_next =  [1 0]
i_gg =  1 , gg =  [1 1] , gg_next =  [1 1]
i_gg =  2 , gg =  [2 2] , gg_next =  [1 2]
i_gg =  3 , gg =  [3 3] , gg_next =  [1 3]
i_gg =  4 , gg =  [4 4] , gg_next =  [1 4]
i_gg =  5 , gg =  [5 5] , gg_next =  [1 5]
i_gg =  6 , gg =  [6 6] , gg_next =  [1 6]
i_gg =  7 , gg =  [7 7] , gg_next =  [1 7]
i_gg =  8 , gg =  [8 8] , gg_next =  [1 8]
i_gg =  9 , gg =  [9 9] , gg_next =  [1 9]
```

If we slightly modify code in this way, or simply drop 'parallel = True'

```python
@nb.njit(parallel = True)
def test_parallel_nocolon(d, k):

    gg      = np.zeros((d, k), dtype = nb.int32)
    gg_next = np.zeros((d, k), dtype = nb.int32)   
        
          
    for i_gg in nb.prange(d): #this part can be parallelized.

        # set value for gg[i_gg]
        for n in range(k):
            gg[i_gg, n] = i_gg

        
        gg_next[i_gg]  = gg[i_gg] # <--- only diff
        gg_next[i_gg, 0] += 1

    # print errors (without prange to print results nicely)
    for i_gg in range(d):
        print('i_gg = ', i_gg , ', gg = ', gg[i_gg], ', gg_next = ', gg_next[i_gg])

```

```python
test_parallel_nocolon(d, k)
```
then I get 

```
i_gg =  0 , gg =  [0 0] , gg_next =  [1 0]
i_gg =  1 , gg =  [1 1] , gg_next =  [2 1]
i_gg =  2 , gg =  [2 2] , gg_next =  [3 2]
i_gg =  3 , gg =  [3 3] , gg_next =  [4 3]
i_gg =  4 , gg =  [4 4] , gg_next =  [5 4]
i_gg =  5 , gg =  [5 5] , gg_next =  [6 5]
i_gg =  6 , gg =  [6 6] , gg_next =  [7 6]
i_gg =  7 , gg =  [7 7] , gg_next =  [8 7]
i_gg =  8 , gg =  [8 8] , gg_next =  [9 8]
i_gg =  9 , gg =  [9 9] , gg_next =  [10  9]
```

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->



Discussion:
@yaoyuki thanks for reporting this. I can reproduce this and it looks like a bug in the parfors component of Numba. My guess would be that something is going wrong during array analysis.
cc @DrTodd13 

Resolving patch:
diff --git a/numba/parfors/parfor.py b/numba/parfors/parfor.py
--- a/numba/parfors/parfor.py
+++ b/numba/parfors/parfor.py
@@ -2627,6 +2627,7 @@ def run(self):
                                                             up_direction=False)
             dprint_func_ir(self.func_ir, "after maximize fusion down")
             self.fuse_parfors(self.array_analysis, self.func_ir.blocks)
+            dprint_func_ir(self.func_ir, "after first fuse")
             # push non-parfors up
             maximize_fusion(self.func_ir, self.func_ir.blocks, self.typemap)
             dprint_func_ir(self.func_ir, "after maximize fusion up")
@@ -2763,6 +2764,7 @@ def fuse_parfors(self, array_analysis, blocks):
     def fuse_recursive_parfor(self, parfor, equiv_set):
         blocks = wrap_parfor_blocks(parfor)
         maximize_fusion(self.func_ir, blocks, self.typemap)
+        dprint_func_ir(self.func_ir, "after recursive maximize fusion down", blocks)
         arr_analysis = array_analysis.ArrayAnalysis(self.typingctx, self.func_ir,
                                                 self.typemap, self.calltypes)
         arr_analysis.run(blocks, equiv_set)
@@ -3587,14 +3589,26 @@ def maximize_fusion(func_ir, blocks, typemap, up_direction=True):
     so they are adjacent.
     """
     call_table, _ = get_call_table(blocks)
-    alias_map, arg_aliases = find_potential_aliases(blocks, func_ir.arg_names, typemap, func_ir)
+    alias_map, arg_aliases = find_potential_aliases(
+                                 blocks,
+                                 func_ir.arg_names,
+                                 typemap,
+                                 func_ir
+                             )
     for block in blocks.values():
         order_changed = True
         while order_changed:
-            order_changed = maximize_fusion_inner(func_ir, block, call_table,
-                                                  alias_map, up_direction)
+            order_changed = maximize_fusion_inner(
+                                func_ir,
+                                block,
+                                call_table,
+                                alias_map,
+                                arg_aliases,
+                                up_direction
+                            )
 
-def maximize_fusion_inner(func_ir, block, call_table, alias_map, up_direction=True):
+def maximize_fusion_inner(func_ir, block, call_table, alias_map,
+                          arg_aliases, up_direction=True):
     order_changed = False
     i = 0
     # i goes to body[-3] (i+1 to body[-2]) since body[-1] is terminator and
@@ -3602,9 +3616,10 @@ def maximize_fusion_inner(func_ir, block, call_table, alias_map, up_direction=Tr
     while i < len(block.body) - 2:
         stmt = block.body[i]
         next_stmt = block.body[i+1]
-        can_reorder = (_can_reorder_stmts(stmt, next_stmt, func_ir, call_table, alias_map)
+        can_reorder = (_can_reorder_stmts(stmt, next_stmt, func_ir,
+                                          call_table, alias_map, arg_aliases)
                         if up_direction else _can_reorder_stmts(next_stmt, stmt,
-                        func_ir, call_table, alias_map))
+                        func_ir, call_table, alias_map, arg_aliases))
         if can_reorder:
             block.body[i] = next_stmt
             block.body[i+1] = stmt
@@ -3612,16 +3627,18 @@ def maximize_fusion_inner(func_ir, block, call_table, alias_map, up_direction=Tr
         i += 1
     return order_changed
 
-def expand_aliases(the_set, alias_map):
+def expand_aliases(the_set, alias_map, arg_aliases):
     ret = set()
     for i in the_set:
         if i in alias_map:
             ret = ret.union(alias_map[i])
-        else:
-            ret.add(i)
+        if i in arg_aliases:
+            ret = ret.union(arg_aliases)
+        ret.add(i)
     return ret
 
-def _can_reorder_stmts(stmt, next_stmt, func_ir, call_table, alias_map):
+def _can_reorder_stmts(stmt, next_stmt, func_ir, call_table,
+                       alias_map, arg_aliases):
     """
     Check dependencies to determine if a parfor can be reordered in the IR block
     with a non-parfor statement.
@@ -3630,18 +3647,20 @@ def _can_reorder_stmts(stmt, next_stmt, func_ir, call_table, alias_map):
     # don't reorder calls with side effects (e.g. file close)
     # only read-read dependencies are OK
     # make sure there is no write-write, write-read dependencies
-    if (isinstance(
-            stmt, Parfor) and not isinstance(
-            next_stmt, Parfor) and not isinstance(
-            next_stmt, ir.Print)
-            and (not isinstance(next_stmt, ir.Assign)
-                 or has_no_side_effect(
-                next_stmt.value, set(), call_table)
+    if (isinstance(stmt, Parfor)
+        and not isinstance(next_stmt, Parfor)
+        and not isinstance(next_stmt, ir.Print)
+        and (not isinstance(next_stmt, ir.Assign)
+            or has_no_side_effect(next_stmt.value, set(), call_table)
             or guard(is_assert_equiv, func_ir, next_stmt.value))):
-        stmt_accesses = expand_aliases({v.name for v in stmt.list_vars()}, alias_map)
-        stmt_writes = expand_aliases(get_parfor_writes(stmt), alias_map)
-        next_accesses = expand_aliases({v.name for v in next_stmt.list_vars()}, alias_map)
-        next_writes = expand_aliases(get_stmt_writes(next_stmt), alias_map)
+        stmt_accesses = expand_aliases({v.name for v in stmt.list_vars()},
+                                       alias_map, arg_aliases)
+        stmt_writes = expand_aliases(get_parfor_writes(stmt),
+                                     alias_map, arg_aliases)
+        next_accesses = expand_aliases({v.name for v in next_stmt.list_vars()},
+                                       alias_map, arg_aliases)
+        next_writes = expand_aliases(get_stmt_writes(next_stmt),
+                                     alias_map, arg_aliases)
         if len((stmt_writes & next_accesses)
                 | (next_writes & stmt_accesses)) == 0:
             return True


Test patch:
diff --git a/numba/tests/test_parfors.py b/numba/tests/test_parfors.py
--- a/numba/tests/test_parfors.py
+++ b/numba/tests/test_parfors.py
@@ -3050,6 +3050,46 @@ def test_impl(a):
 
         self.check(test_impl, np.arange(3))
 
+    @skip_parfors_unsupported
+    def test_issue5942_1(self):
+        # issue5942: tests statement reordering of
+        # aliased arguments.
+        def test_impl(gg, gg_next):
+            gs = gg.shape
+            d = gs[0]
+            for i_gg in prange(d):
+                gg_next[i_gg, :]  = gg[i_gg, :]
+                gg_next[i_gg, 0] += 1
+
+            return gg_next
+
+        d = 4
+        k = 2
+
+        gg      = np.zeros((d, k), dtype = np.int32)
+        gg_next = np.zeros((d, k), dtype = np.int32)
+        self.check(test_impl, gg, gg_next)
+
+    @skip_parfors_unsupported
+    def test_issue5942_2(self):
+        # issue5942: tests statement reordering
+        def test_impl(d, k):
+            gg      = np.zeros((d, k), dtype = np.int32)
+            gg_next = np.zeros((d, k), dtype = np.int32)
+
+            for i_gg in prange(d):
+                for n in range(k):
+                    gg[i_gg, n] = i_gg
+                gg_next[i_gg, :]  = gg[i_gg, :]
+                gg_next[i_gg, 0] += 1
+
+            return gg_next
+
+        d = 4
+        k = 2
+
+        self.check(test_impl, d, k)
+
 
 class TestParforsOptions(TestParforsBase):