Repository: numba/numba

Issue:
parallel function compiles with v0.50.1 and fails with v0.51.0rc1
## Reporting a bug

- [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).

I have a bit of parallelized code that compiled fine with numba v0.50.1, but fails with a LoweringError on v0.51.0rc1. Here is a chopped down bit of code that produces the error:

```python
import numpy as np
from numba import njit, prange


@njit(parallel=True)
def f(r):
    for ir in prange(r.shape[0]):
        dist = np.inf
        tr = np.array([0, 0, 0], dtype=np.float32)
        for i in [1, 0, -1]:
            dist_t = np.linalg.norm(r[ir, :] + i)
            if dist_t < dist:
                dist = dist_t
                tr = np.array([i, i, i], dtype=np.float32)
        r[ir, :] += tr
    return r


r = np.array([[0., 0., 0.], [0., 0., 1.]])
f(r)
```

I've tried to chop the code down further. Setting tr to a float instead of an array allows the code to compile. Removing the inner for loop and replacing it with `i = 0` allows the code to compile. Removing the if statement allows the code to compile. Replacing `prange` with `range` allows the code to compile.

Here are the outputs of `numba -s` for each of the environments (pass = v0.50.1, fail = v0.51.0rc1):
[fail.log](https://github.com/numba/numba/files/5044487/fail.log)
[pass.log](https://github.com/numba/numba/files/5044488/pass.log)

Lastly, here is the traceback that is produced on my machine:
[error.log](https://github.com/numba/numba/files/5044495/error.log)

parallel function compiles with v0.50.1 and fails with v0.51.0rc1
## Reporting a bug

- [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).

I have a bit of parallelized code that compiled fine with numba v0.50.1, but fails with a LoweringError on v0.51.0rc1. Here is a chopped down bit of code that produces the error:

```python
import numpy as np
from numba import njit, prange


@njit(parallel=True)
def f(r):
    for ir in prange(r.shape[0]):
        dist = np.inf
        tr = np.array([0, 0, 0], dtype=np.float32)
        for i in [1, 0, -1]:
            dist_t = np.linalg.norm(r[ir, :] + i)
            if dist_t < dist:
                dist = dist_t
                tr = np.array([i, i, i], dtype=np.float32)
        r[ir, :] += tr
    return r


r = np.array([[0., 0., 0.], [0., 0., 1.]])
f(r)
```

I've tried to chop the code down further. Setting tr to a float instead of an array allows the code to compile. Removing the inner for loop and replacing it with `i = 0` allows the code to compile. Removing the if statement allows the code to compile. Replacing `prange` with `range` allows the code to compile.

Here are the outputs of `numba -s` for each of the environments (pass = v0.50.1, fail = v0.51.0rc1):
[fail.log](https://github.com/numba/numba/files/5044487/fail.log)
[pass.log](https://github.com/numba/numba/files/5044488/pass.log)

Lastly, here is the traceback that is produced on my machine:
[error.log](https://github.com/numba/numba/files/5044495/error.log)



Discussion:
Thanks for the report, I can reproduce. Note this is Python 3.8 only. `git bisect` points to:
```
d8a272eeeb0420c68d9594b8b1b3e633ea4ac098 is the first bad commit
commit d8a272eeeb0420c68d9594b8b1b3e633ea4ac098
Date:   Fri Jun 12 16:14:50 2020 -0700

    Move the conversion of inplace binops after the prange pass so that reductions in prange are not interpreted as inplace binops.  Unify the calltypes of the binop creates in the inplace_binop parfor body.

 numba/parfors/parfor.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
bisect run success
```
CC @DrTodd13 seems like this is a regression for 0.51.0. I'm not 100% convinced it's a parfors issue as it's the CFG reconstruction from blocks that's falling over. Only reproduces on Python 3.8. Backtrace:
```
  File "<numba_path>/numba/parfors/parfor.py", line 2748, in run
    self.fuse_parfors(self.array_analysis, self.func_ir.blocks)
  File "<numba_path>/numba/parfors/parfor.py", line 2864, in fuse_parfors
    fused_node, fuse_report = try_fuse(equiv_set, stmt, next_stmt)
  File "<numba_path>/numba/parfors/parfor.py", line 3856, in try_fuse
    return fuse_parfors_inner(parfor1, parfor2)
  File "<numba_path>/numba/parfors/parfor.py", line 3885, in fuse_parfors_inner
    blocks = rename_labels(blocks)
  File "<numba_path>/numba/core/ir_utils.py", line 1201, in rename_labels
    topo_order = find_topo_order(blocks)
  File "<numba_path>/numba/core/ir_utils.py", line 1083, in find_topo_order
    cfg = compute_cfg_from_blocks(blocks)
  File "<numba_path>/numba/core/analysis.py", line 243, in compute_cfg_from_blocks
    cfg.add_edge(k, target)
  File "<numba_path>/numba/core/controlflow.py", line 87, in add_edge
    raise ValueError("Cannot add edge as dest node %s not in nodes %s" %
ValueError: Failed in nopython mode pipeline (step: convert to parfors)
Cannot add edge as dest node 189 not in nodes {0}
```
I found a fix. It's a problem of non-unique label.
Thanks for the report, I can reproduce. Note this is Python 3.8 only. `git bisect` points to:
```
d8a272eeeb0420c68d9594b8b1b3e633ea4ac098 is the first bad commit
commit d8a272eeeb0420c68d9594b8b1b3e633ea4ac098
Date:   Fri Jun 12 16:14:50 2020 -0700

    Move the conversion of inplace binops after the prange pass so that reductions in prange are not interpreted as inplace binops.  Unify the calltypes of the binop creates in the inplace_binop parfor body.

 numba/parfors/parfor.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
bisect run success
```
CC @DrTodd13 seems like this is a regression for 0.51.0. I'm not 100% convinced it's a parfors issue as it's the CFG reconstruction from blocks that's falling over. Only reproduces on Python 3.8. Backtrace:
```
  File "<numba_path>/numba/parfors/parfor.py", line 2748, in run
    self.fuse_parfors(self.array_analysis, self.func_ir.blocks)
  File "<numba_path>/numba/parfors/parfor.py", line 2864, in fuse_parfors
    fused_node, fuse_report = try_fuse(equiv_set, stmt, next_stmt)
  File "<numba_path>/numba/parfors/parfor.py", line 3856, in try_fuse
    return fuse_parfors_inner(parfor1, parfor2)
  File "<numba_path>/numba/parfors/parfor.py", line 3885, in fuse_parfors_inner
    blocks = rename_labels(blocks)
  File "<numba_path>/numba/core/ir_utils.py", line 1201, in rename_labels
    topo_order = find_topo_order(blocks)
  File "<numba_path>/numba/core/ir_utils.py", line 1083, in find_topo_order
    cfg = compute_cfg_from_blocks(blocks)
  File "<numba_path>/numba/core/analysis.py", line 243, in compute_cfg_from_blocks
    cfg.add_edge(k, target)
  File "<numba_path>/numba/core/controlflow.py", line 87, in add_edge
    raise ValueError("Cannot add edge as dest node %s not in nodes %s" %
ValueError: Failed in nopython mode pipeline (step: convert to parfors)
Cannot add edge as dest node 189 not in nodes {0}
```
I found a fix. It's a problem of non-unique label.

Resolving patch:
diff --git a/numba/parfors/parfor.py b/numba/parfors/parfor.py
--- a/numba/parfors/parfor.py
+++ b/numba/parfors/parfor.py
@@ -2700,6 +2700,9 @@ class ParforPass(ParforPassStates):
     def _pre_run(self):
         # run array analysis, a pre-requisite for parfor translation
         self.array_analysis.run(self.func_ir.blocks)
+        # NOTE: Prepare _max_label. See #6102
+        ir_utils._max_label = max(ir_utils._max_label,
+                                  ir_utils.find_max_label(self.func_ir.blocks))
 
     def run(self):
         """run parfor conversion pass: replace Numpy calls


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
@@ -38,7 +38,7 @@
 from numba.tests.support import (TestCase, captured_stdout, MemoryLeakMixin,
                       override_env_config, linux_only, tag,
                       skip_parfors_unsupported, _32bit, needs_blas,
-                      needs_lapack, disabled_test)
+                      needs_lapack, disabled_test, skip_unless_scipy)
 import cmath
 import unittest
 
@@ -3102,6 +3102,27 @@ def test_impl(d, k):
 
         self.check(test_impl, d, k)
 
+    @skip_parfors_unsupported
+    @skip_unless_scipy
+    def test_issue6102(self):
+        # The problem is originally observed on Python3.8 because of the
+        # changes in how loops are represented in 3.8 bytecode.
+        @njit(parallel=True)
+        def f(r):
+            for ir in prange(r.shape[0]):
+                dist = np.inf
+                tr = np.array([0, 0, 0], dtype=np.float32)
+                for i in [1, 0, -1]:
+                    dist_t = np.linalg.norm(r[ir, :] + i)
+                    if dist_t < dist:
+                        dist = dist_t
+                        tr = np.array([i, i, i], dtype=np.float32)
+                r[ir, :] += tr
+            return r
+
+        r = np.array([[0., 0., 0.], [0., 0., 1.]])
+        self.assertPreciseEqual(f(r), f.py_func(r))
+
 
 class TestParforsOptions(TestParforsBase):