Repository: numba/numba

Issue:
 @numba.njit(parallel=True) is NoOP for recarray view.
Adding `@numba.njit(parallel=True)`
makes my recarray 2000x faster. 

But also a No-OP.


```
numba.__version__ == '0.43.0'
```

```python
import numba
import numpy as np

# Numpy array laid out like a C-datastructure:
#   struct {
#       float x;
#       int y;
#   };
x = np.zeros(1000000, dtype=[
    ('x', float),
    ('y', int),
])

v = x.view(np.recarray)

@numba.njit(parallel=False)
def f(a, val):
    for j in range(len(a)):
        a.x[j] = val
c = 91.0
f(v, c)  # Ensure code is compiled
%timeit f(v,c)  # 1.17ms /loop
assert (v.x[:] == np.full(len(x), c)).all()   # Success!!

# All the same, except for parallel=True
@numba.njit(parallel=True)
def f(a, val):
    for j in range(len(a)):
        a.x[j] = val
c = 94.0
f(v, c)  # Ensure code is compiled
%timeit f(v,c)  # 562 ns per loop! WOW!
# But....
assert (v.x[:] == np.full(len(x), c)).all() #  doesn't do anything...
```

If not supported, it should produce an exception.


Discussion:
The problem seems to manifest with the original (non-view array also)
```
def f(a, val):
    for aa in a:
        aa.x = val  # OK

def f(a, val):
    for i in range(len(a)):
        a[i].x = val  # OK

def f(a, val):
    for i in range(len(a)):
        a.x[i] = val  # No-OP if parallel=True
```
... when passed to the following tests.
```
c = 91.0
f1 = numba.njit(parallel=False)(f)
f1(x, c)  # Ensure code is compiled
%timeit f1(x,c)  # 1.17ms /loop
assert (x['x'][:] == np.full(len(x), c)).all()

c = 92.0
f1 = numba.njit(parallel=True)(f)
f1(x, c)  # Ensure code is compiled
%timeit f1(x,c)  # 1.17ms /loop
assert (x['x'][:] == np.full(len(x), c)).all()


# Working on x.view
x = x.view(np.recarray)

c = 93.0
f1 = numba.njit(parallel=False)(f)
f1(x, c)  # Ensure code is compiled
%timeit f1(x,c)  # 1.17ms /loop
assert (x['x'][:] == np.full(len(x), c)).all()

c = 94.0
f1 = numba.njit(parallel=True)(f)
f1(x, c)  # Ensure code is compiled
%timeit f1(x,c)  # 1.17ms /loop
assert (x['x'][:] == np.full(len(x), c)).all()
```


So, I'm guessing the problems is:
```
   a.x[i] = val
```
has no effect when a is a recarray. Is it expected?

Curiously, parallel=True also provides no speedup.
Thanks for the report, I can reproduce. I think that the issue is that the record array syntax is supported but probably interpreted incorrectly when `parallel=True` is set. This is a small reproducer:
```python
from numba import njit
import numpy as np

def gen_a():
    return np.zeros(5, dtype=[('x', float), ('y', int),])

def f(a, val):
    for i in range(len(a)):
        a.x[i] = val

def test(dec):
    a = gen_a()
    dec(f)(a, 10.)
    print(a)

for d in (njit, njit(parallel=True)):
    test(d)
```

As to:
> If not supported, it should produce an exception.

This is not the behaviour of this flag... what `parallel=True` should do is parallelize parts of the code it can recognise as having parallel semantics, and then leave the rest of the code as-is. You can get a diagnostic report from the jitted function using the `.parallel_diagnostics(level=integer)` method. e.g.:
```
from numba import njit
import numpy as np

def gen_a():
    return np.zeros(5, dtype=[('x', float), ('y', int),])

@njit(parallel=True)
def f(a, val):
    for i in range(len(a)):
        a.x[i] = val


a = gen_a()
f(a, 10.)
f.parallel_diagnostics(level=4)
```
which shows this:
```
================================================================================
======= Parallel Accelerator Optimizing:  Function f, issue4007.py (7)  ========
================================================================================


Parallel loop listing for  Function f, issue4007.py (7) 
-------------------------------|loop #ID
@njit(parallel=True)           | 
def f(a, val):                 | 
    for i in range(len(a)):    | 
        a.x[i] = val           | 
--------------------------------- Fusing loops ---------------------------------
Attempting fusion of parallel loops (combines loops with similar properties)...
----------------------------- Before Optimisation ------------------------------
--------------------------------------------------------------------------------
------------------------------ After Optimisation ------------------------------
Parallel structure is already optimal.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
 
---------------------------Loop invariant code motion---------------------------

Instruction hoisting:
No instruction hoisting found
--------------------------------------------------------------------------------
```
suggesting nothing can be parallelized, so it seems like there are probably a couple of things that are not-quite-right. Ping @DrTodd13.



Resolving patch:
diff --git a/numba/ir_utils.py b/numba/ir_utils.py
--- a/numba/ir_utils.py
+++ b/numba/ir_utils.py
@@ -701,6 +701,10 @@ def find_potential_aliases(blocks, args, typemap, func_ir, alias_map=None,
                 if (isinstance(expr, ir.Expr) and expr.op == 'getattr'
                         and expr.attr in ['T', 'ctypes', 'flat']):
                     _add_alias(lhs, expr.value.name, alias_map, arg_aliases)
+                # a = b.c.  a should alias b
+                if (isinstance(expr, ir.Expr) and expr.op == 'getattr'
+                        and expr.value.name in arg_aliases):
+                    _add_alias(lhs, expr.value.name, alias_map, arg_aliases)
                 # calls that can create aliases such as B = A.ravel()
                 if isinstance(expr, ir.Expr) and expr.op == 'call':
                     fdef = guard(find_callname, func_ir, expr, typemap)


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
@@ -2085,6 +2085,29 @@ def test_impl(n):
             return B
         self.prange_tester(test_impl, 1.0)
 
+    @skip_unsupported
+    def test_argument_alias_recarray_field(self):
+        # Test for issue4007.
+        def test_impl(n):
+            for i in range(len(n)):
+                n.x[i] = 7.0
+            return n
+        X1 = np.zeros(10, dtype=[('x', float), ('y', int), ])
+        X2 = np.zeros(10, dtype=[('x', float), ('y', int), ])
+        X3 = np.zeros(10, dtype=[('x', float), ('y', int), ])
+        v1 = X1.view(np.recarray)
+        v2 = X2.view(np.recarray)
+        v3 = X3.view(np.recarray)
+
+        # Numpy doesn't seem to support almost equal on recarray.
+        # So, we convert to list and use assertEqual instead.
+        python_res = list(test_impl(v1))
+        njit_res = list(njit(test_impl)(v2))
+        pa_func = njit(test_impl, parallel=True)
+        pa_res = list(pa_func(v3))
+        self.assertEqual(python_res, njit_res)
+        self.assertEqual(python_res, pa_res)
+
     @skip_unsupported
     def test_mutable_list_param(self):
         """ issue3699: test that mutable variable to call in loop