Repository: numba/numba

Issue:
function with parallel forloops + njit raises error (inconsistently) 
## Reporting a bug

- [x] I am 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 the following function : 
``` 
@njit(parallel=True)
def cosine_similarity_loop_numba(out, x_lens, x, xx, y, yy):
    for x_idx in prange(x.shape[2]):
        for x_t in range(x_lens[x_idx]):
            for y_t in range(y.shape[1]):
                out[x_t, y_t, x_idx] = (x[:, x_t, x_idx] * y[:, y_t]).sum() / ((xx[x_t, x_idx] ** .5) * (yy[y_t] ** .5))
    return out
```
[reproduce_parfor_bug.zip](https://github.com/numba/numba/files/4528927/reproduce_parfor_bug.zip)
I have placed an example of inputs that raise the issue. It's in numpy savez format - so use 
```
obj = np.load(path_to_file)
cosine_similarity_loop_numba(obj["out"], obj["x_lens"], obj["x"], obj["xx"], obj["y"], obj["yy"])
```

The error I get is: 
```
numba.errors.UnsupportedError: Failed in nopython mode pipeline (step: nopython mode backend)
Failed in nopython mode pipeline (step: nopython frontend)
Unsupported op-code encountered: arrayexpr(expr=(<built-in function mul>, [Var($58.10, d blah.py:66), Var($58.18,  blah.py:66)]), ty=array(float64, 1d, F))
File " /blah/blah..py", line 66:
def cosine_similarity_loop_numba(out, x_lens, x, xx, y, yy):
    <source elided>
            for y_t in range(y.shape[1]):
                out[x_t, y_t, x_idx] = (x[:, x_t, x_idx] * y[:, y_t]).sum() / ((xx[x_t, x_idx] ** .5) * (yy[y_t] ** .5))
                ^
[1] During: lowering "id=1[LoopNest(index_variable = parfor_index.65, range = (0, $4.5, 1))]{36: <ir.Block at  /blah/blah..py (64)>, 38: <ir.Block at  /blah/blah..py (64)>, 20: <ir.Block at  /blah/blah..py (63)>, 183: <ir.Block at  /blah/blah..py (66)>, 56: <ir.Block at  /blah/blah.py (65)>, 185: <ir.Block at  /blah/blah.py (66)>, 58: <ir.Block at  /blah/blah..py (65)>}Var(parfor_index.65,  blah.py:63)" at /blah/blah.py (63)

```
( I've replaced the actual paths and file names with /blah/blah...





Discussion:
Thanks for the report. The Mystery Object contains:
```
[('out', array(float64, 3d, C)),
 ('x_lens', array(int64, 1d, C)),
 ('x', array(float64, 3d, C)),
 ('xx', array(float64, 2d, C)),
 ('y', array(float32, 2d, F)),
 ('yy', array(float32, 1d, C))]
```
Self contained reproducer:
```python
from numba import njit, prange, types
import numpy as np

tystr = 'f8[:,:,::1](f8[:,:,::1], i8[::1], f8[:,:,::1], f8[:,::1], f8[::1,:],f8[::1])'
sig = eval(tystr, types.__dict__)
print(sig)

@njit(sig, parallel=True)
def cosine_similarity_loop_numba(out, x_lens, x, xx, y, yy):
    for x_idx in prange(x.shape[2]):
        for x_t in range(x_lens[x_idx]):
            for y_t in range(y.shape[1]):
                out[x_t, y_t, x_idx] = (x[:, x_t, x_idx] * y[:, y_t]).sum() / ((xx[x_t, x_idx] ** .5) * (yy[y_t] ** .5))
    return out
```
this is clearly a bug, also likely due to parfors not having a huge amount of support for `F` ordering IIRC.

CC @DrTodd13
I wrapped the code with a try, except clause so that if there's an issue it falls back to a non-parallelalized version. While it doesn't always fall. Even when it doesn't fail it says :
"The keyword argument 'parallel=True' was specified but no transformation for parallel execution was possible."
and then runs on just one thread...

Resolving patch:
diff --git a/numba/core/ir_utils.py b/numba/core/ir_utils.py
--- a/numba/core/ir_utils.py
+++ b/numba/core/ir_utils.py
@@ -57,7 +57,7 @@ def next_label():
     return _max_label
 
 
-def mk_alloc(typemap, calltypes, lhs, size_var, dtype, scope, loc):
+def mk_alloc(typemap, calltypes, lhs, size_var, dtype, scope, loc, lhs_typ):
     """generate an array allocation with np.empty() and return list of nodes.
     size_var can be an int variable or tuple of int variables.
     """
@@ -113,9 +113,34 @@ def mk_alloc(typemap, calltypes, lhs, size_var, dtype, scope, loc):
     # signature(
     #    types.npytypes.Array(dtype, ndims, 'C'), size_typ,
     #    types.functions.NumberClass(dtype))
-    alloc_assign = ir.Assign(alloc_call, lhs, loc)
 
-    out.extend([g_np_assign, attr_assign, typ_var_assign, alloc_assign])
+    if lhs_typ.layout == 'F':
+        empty_c_typ = lhs_typ.copy(layout='C')
+        empty_c_var = ir.Var(scope, mk_unique_var("$empty_c_var"), loc)
+        if typemap:
+            typemap[empty_c_var.name] = lhs_typ.copy(layout='C')
+        empty_c_assign = ir.Assign(alloc_call, empty_c_var, loc)
+
+        # attr call: asfortranarray = getattr(g_np_var, asfortranarray)
+        asfortranarray_attr_call = ir.Expr.getattr(g_np_var, "asfortranarray", loc)
+        afa_attr_var = ir.Var(scope, mk_unique_var("$asfortran_array_attr"), loc)
+        if typemap:
+            typemap[afa_attr_var.name] = get_np_ufunc_typ(numpy.asfortranarray)
+        afa_attr_assign = ir.Assign(asfortranarray_attr_call, afa_attr_var, loc)
+        # call asfortranarray
+        asfortranarray_call = ir.Expr.call(afa_attr_var, [empty_c_var], (), loc)
+        if calltypes:
+            calltypes[asfortranarray_call] = typemap[afa_attr_var.name].get_call_type(
+                typing.Context(), [empty_c_typ], {})
+
+        asfortranarray_assign = ir.Assign(asfortranarray_call, lhs, loc)
+
+        out.extend([g_np_assign, attr_assign, typ_var_assign, empty_c_assign,
+                    afa_attr_assign, asfortranarray_assign])
+    else:
+        alloc_assign = ir.Assign(alloc_call, lhs, loc)
+        out.extend([g_np_assign, attr_assign, typ_var_assign, alloc_assign])
+
     return out
 
 
diff --git a/numba/parfors/parfor.py b/numba/parfors/parfor.py
--- a/numba/parfors/parfor.py
+++ b/numba/parfors/parfor.py
@@ -1763,7 +1763,8 @@ def run(self, blocks):
                 if isinstance(instr, ir.Assign):
                     expr = instr.value
                     lhs = instr.target
-                    if self._is_C_order(lhs.name):
+                    lhs_typ = self.pass_states.typemap[lhs.name]
+                    if self._is_C_or_F_order(lhs_typ):
                         # only translate C order since we can't allocate F
                         if guard(self._is_supported_npycall, expr):
                             new_instr = self._numpy_to_parfor(equiv_set, lhs, expr)
@@ -1787,8 +1788,26 @@ def run(self, blocks):
             block.body = new_body
 
     def _is_C_order(self, arr_name):
-        typ = self.pass_states.typemap[arr_name]
-        return isinstance(typ, types.npytypes.Array) and typ.layout == 'C' and typ.ndim > 0
+        if isinstance(arr_name, types.npytypes.Array):
+            return arr_name.layout == 'C' and arr_name.ndim > 0
+        elif arr_name is str:
+            typ = self.pass_states.typemap[arr_name]
+            return (isinstance(typ, types.npytypes.Array) and
+                    typ.layout == 'C' and
+                    typ.ndim > 0)
+        else:
+            return False
+
+    def _is_C_or_F_order(self, arr_name):
+        if isinstance(arr_name, types.npytypes.Array):
+            return (arr_name.layout == 'C' or arr_name.layout == 'F') and arr_name.ndim > 0
+        elif arr_name is str:
+            typ = self.pass_states.typemap[arr_name]
+            return (isinstance(typ, types.npytypes.Array) and
+                    (typ.layout == 'C' or typ.layout == 'F') and
+                    typ.ndim > 0)
+        else:
+            return False
 
     def _arrayexpr_to_parfor(self, equiv_set, lhs, arrayexpr, avail_vars):
         """generate parfor from arrayexpr node, which is essentially a
@@ -1808,7 +1827,8 @@ def _arrayexpr_to_parfor(self, equiv_set, lhs, arrayexpr, avail_vars):
         # generate init block and body
         init_block = ir.Block(scope, loc)
         init_block.body = mk_alloc(pass_states.typemap, pass_states.calltypes, lhs,
-                                   tuple(size_vars), el_typ, scope, loc)
+                                   tuple(size_vars), el_typ, scope, loc,
+                                   pass_states.typemap[lhs.name])
         body_label = next_label()
         body_block = ir.Block(scope, loc)
         expr_out_var = ir.Var(scope, mk_unique_var("$expr_out_var"), loc)
@@ -1886,7 +1906,8 @@ def _numpy_map_to_parfor(self, equiv_set, call_name, lhs, args, kws, expr):
         # generate init block and body
         init_block = ir.Block(scope, loc)
         init_block.body = mk_alloc(pass_states.typemap, pass_states.calltypes, lhs,
-                                   tuple(size_vars), el_typ, scope, loc)
+                                   tuple(size_vars), el_typ, scope, loc,
+                                   pass_states.typemap[lhs.name])
         body_label = next_label()
         body_block = ir.Block(scope, loc)
         expr_out_var = ir.Var(scope, mk_unique_var("$expr_out_var"), loc)


Test patch: