Repository: numba/numba

Issue:
Parfor reductions not working when overload uses inline='always' and parallel=True flags
- [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 a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

Hi,

I've run into another regression in numba=0.53.1 that breaks reductions in parfors in overloads that get inlined.
The script that works on numba=0.52 and fails on 0.53 is following:
```
import numpy as np
from numba import njit, prange
from numba.extending import overload

def foo(self):
    pass

''' Problem manifest only if foo overload is both inline='always' and
parallel=True, so switching one off is kind of workaround '''
@overload(foo, inline='always', jit_options={'parallel': True})
def foo_ovld(self):
    def impl(self):
        num_bytes = 0
        arr_len = len(self)

        for i in prange(arr_len):
            item = self[i]
            num_bytes += len(str(item))

        return num_bytes

    return impl

@njit(parallel=True)
def test_impl(self):
    return foo(self)

data = np.array([2, 3, 0])
result = test_impl(data)
assert result == 3
print(f"result={result}")
```

The warning: 
> NumbaParallelSafetyWarning: Variable num_bytes.2.115.1 used in parallel loop may be written to simultaneously by multiple workers and may result in non-deterministic or unintended results.

points out a problem in the IR at the time of parfor lowering:

```
>>> func_ir.dump()
label 0:
    self = arg(0, name=self)                 ['self']
    self_shape.116 = getattr(value=self, attr=shape) ['self', 'self_shape.116']
    self_size0.117 = static_getitem(value=self_shape.116, index=0, index_var=None, fn=<built-in function getitem>) ['self_shape.116', 'self_size0.117']
    num_bytes.86 = const(int, 0)             ['num_bytes.86']
    num_bytes.2.115 = num_bytes.86           ['num_bytes.2.115', 'num_bytes.86']
    num_bytes.2.115.1 = num_bytes.2.115      ['num_bytes.2.115', 'num_bytes.2.115.1']
---begin parfor 1---
index_var =  parfor_index.118
params =  {'num_bytes.2.115', 'num_bytes.2.115.1', 'self'}
races =  {'num_bytes.2.115.1'}
LoopNest(index_variable = parfor_index.118, range = (0, self_size0.117, 1))
init block:
label 106:
    item.104 = getitem(value=self, index=parfor_index.118, fn=<built-in function getitem>) ['item.104', 'parfor_index.118', 'self']
    $38load_global.6.106 = global(len: <built-in function len>) ['$38load_global.6.106']
    $40load_global.7.107 = global(str: <class 'str'>) ['$40load_global.7.107']
    $44call_function.9.109 = call $40load_global.7.107(item.104, func=$40load_global.7.107, args=[Var(item.104, test_parfor_reduction.1.py:18)], kws=(), vararg=None) ['$40load_global.7.107', '$44call_function.9.109', 'item.104']
    $46call_function.10.110 = call $38load_global.6.106($44call_function.9.109, func=$38load_global.6.106, args=[Var($44call_function.9.109, test_parfor_reduction.1.py:19)], kws=(), vararg=None) ['$38load_global.6.106', '$44call_function.9.109', '$46call_function.10.110']
    $num_bytes.2.115.131 = inplace_binop(fn=<built-in function iadd>, immutable_fn=<built-in function add>, lhs=num_bytes.2.115, rhs=$46call_function.10.110, static_lhs=Undefined, static_rhs=Undefined) ['$46call_function.10.110', '$num_bytes.2.115.131', 'num_bytes.2.115']
    num_bytes.2.115 = $num_bytes.2.115.131   ['$num_bytes.2.115.131', 'num_bytes.2.115']        # <------ note two different variables 
    num_bytes.2.115.1 = $num_bytes.2.115.131 ['$num_bytes.2.115.131', 'num_bytes.2.115.1']     # that are assigned binop result
----end parfor 1----
    $8return_value.3 = cast(value=num_bytes.2.115.1) ['$8return_value.3', 'num_bytes.2.115.1']
    return $8return_value.3                  ['$8return_value.3']
```

Where only `num_bytes.2.115` would be recognized as reduction node, as `num_bytes.2.115.1` is assigned variable that depends on `num_bytes.2.115` but not on `num_bytes.2.115.1`.

It seems that additional variable num_bytes.2.115.1 is added during:
https://github.com/numba/numba/blob/432588b0dc3c3bf9059f0310cb417366b9875b26/numba/core/inline_closurecall.py#L524

Git bisect also points on this commit as a first commit with the issue (associated PR is #6741):
7267ffa407189eedc05fe88ed9ec869b2e91a421 is the first bad commit

BR,
Alexey.


Discussion:
Thanks for the report. CC @DrTodd13 as code owner and @ehsantn as patch author of #6741.

Resolving patch:
diff --git a/numba/core/ssa.py b/numba/core/ssa.py
--- a/numba/core/ssa.py
+++ b/numba/core/ssa.py
@@ -254,6 +254,10 @@ def on_assign(self, states, assign):
             if len(defmap) == 0:
                 newtarget = assign.target
                 _logger.debug("first assign: %s", newtarget)
+
+                # add this variable if it's not in scope
+                if newtarget.name not in scope.localvars:
+                    scope.define(newtarget.name, assign.loc)
             else:
                 newtarget = scope.redefine(assign.target.name, loc=assign.loc)
             assign = ir.Assign(


Test patch: