Repository: numba/numba

Issue:
Lowering Error when using @njit(parallel = True)
## Minimal Reproducer
I have pruned my code tocould get the minimal reproducer that keeps throwing the error.

````
import pandas as pd
import numpy as np
from numba import njit, prange

@njit
def play_level(pid, t_lvl, t_plr, plr, mx_lvl):

    current_lvl = plr[pid, 2]

    aban_time = np.random.geometric(1 / t_lvl[current_lvl])
    player_time = np.random.geometric(1 / t_plr[current_lvl])

    if (aban_time <= player_time): #abandon gamee

        plr = abandon(pid, plr)

    else:  # pass level

        plr = progress(pid, plr, mx_lvl)

    return plr


@njit
def keep_playing(pid, plr, mx_lvl):
    """
     player = [player_id, churner, level]

    """

    condition = (plr[pid, 1] != 1) & (plr[pid, 2] != mx_lvl)

    return condition


@njit
def progress(pid, p, last_level):
    """
     player = [player_id, churner, level]

    """

    o = p[pid, 2]

    if o == last_level-1:

        p[pid, 1] = 1

    else:

        p[pid, 2] += 1

    return p


@njit
def abandon(pid, p):
    """
     player = [player_id, churner, level]

    """
    o = p[pid, 2]

    p[pid, 1] = 1

    return p


@njit
def simulate_players(num_players, t_lvl, t_plr, plr, mx_lvl ):

    for p in range(num_players):

        while keep_playing(p, plr, mx_lvl):

            plr = play_level(p, t_lvl, t_plr, plr, mx_lvl)

    return plr



@njit(parallel = True)
def run_simulations(n_p, n_s, t_lvl, t_plr, plr,mx_lvl):
    for s in prange(n_s):
        plr = simulate_players(n_p, t_lvl, t_plr, plr, mx_lvl)
    return plr



# Simulation

max_level = 1000
n_players = 2000000

# Define times
t_levels = 10*(1+np.random.random(max_level))
t_player = 10*(1+np.random.random(max_level))

# Define Array
#player = [player_id, churner, level]
player = np.zeros(shape=(n_players,3), dtype = np.uint32)
player[:, 0] = range(n_players)


n_sims = 100
level_data_simulations = run_simulations(n_players, n_sims, t_levels, t_player, player, max_level)
````

## Error and Traceback
```
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    661     try:
--> 662         yield
    663     except NumbaError as e:

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    257                                    loc=self.loc, errcls_=defaulterrcls):
--> 258                 self.lower_inst(inst)
    259 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower_inst(self, inst)
    409                 if isinstance(inst, _class):
--> 410                     func(self, inst)
    411                     return

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/npyufunc/parfor.py in _lower_parfor_parallel(lowerer, parfor)
     97     parfor_redvars, parfor_reddict = numba.parfor.get_parfor_reductions(
---> 98         parfor, parfor.params, lowerer.fndesc.calltypes)
     99 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/parfor.py in get_parfor_reductions(parfor, parfor_params, calltypes, reductions, reduce_varnames, param_uses, param_nodes, var_to_param)
   2968             param_nodes[param].reverse()
-> 2969             reduce_nodes = get_reduce_nodes(param, param_nodes[param])
   2970             init_val = guard(get_reduction_init, reduce_nodes)

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/parfor.py in get_reduce_nodes(name, nodes)
   3017                 non_red_args = [ x for (x, y) in args if y.name != name ]
-> 3018                 assert len(non_red_args) == 1
   3019                 args = [ (x, y) for (x, y) in args if x != y.name ]

AssertionError: 

During handling of the above exception, another exception occurred:

LoweringError                             Traceback (most recent call last)
<ipython-input-79-3ff90f149e7e> in <module>
      1 n_sims = 100
----> 2 level_data_simulations = run_simulations(n_players, n_sims, t_levels, t_player, player, max_level)

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    393                     e.patch_message(''.join(e.args) + help_msg)
    394             # ignore the FULL_TRACEBACKS config, this needs reporting!
--> 395             raise e
    396 
    397     def inspect_llvm(self, signature=None):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    350                 argtypes.append(self.typeof_pyval(a))
    351         try:
--> 352             return self.compile(tuple(argtypes))
    353         except errors.TypingError as e:
    354             # Intercept typing error that may be due to an argument

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, sig)
    691 
    692             self._cache_misses[sig] += 1
--> 693             cres = self._compiler.compile(args, return_type)
    694             self.add_overload(cres)
    695             self._cache.save_overload(sig, cres)

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, args, return_type)
     74 
     75     def compile(self, args, return_type):
---> 76         status, retval = self._compile_cached(args, return_type)
     77         if status:
     78             return retval

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in _compile_cached(self, args, return_type)
     88 
     89         try:
---> 90             retval = self._compile_core(args, return_type)
     91         except errors.TypingError as e:
     92             self._failed_cache[key] = e

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in _compile_core(self, args, return_type)
    106                                       args=args, return_type=return_type,
    107                                       flags=flags, locals=self.locals,
--> 108                                       pipeline_class=self.pipeline_class)
    109         # Check typing error if object mode is used
    110         if cres.typing_error is not None and not flags.enable_pyobject:

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    970     pipeline = pipeline_class(typingctx, targetctx, library,
    971                               args, return_type, flags, locals)
--> 972     return pipeline.compile_extra(func)
    973 
    974 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in compile_extra(self, func)
    388         self.lifted = ()
    389         self.lifted_from = None
--> 390         return self._compile_bytecode()
    391 
    392     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in _compile_bytecode(self)
    901         """
    902         assert self.func_ir is None
--> 903         return self._compile_core()
    904 
    905     def _compile_ir(self):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in _compile_core(self)
    888         self.define_pipelines(pm)
    889         pm.finalize()
--> 890         res = pm.run(self.status)
    891         if res is not None:
    892             # Early pipeline completion

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    264                     # No more fallback pipelines?
    265                     if is_final_pipeline:
--> 266                         raise patched_exception
    267                     # Go to next fallback pipeline
    268                     else:

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    255                 try:
    256                     event("-- %s" % stage_name)
--> 257                     stage()
    258                 except _EarlyPipelineCompletion as e:
    259                     return e.result

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in stage_nopython_backend(self)
    762         """
    763         lowerfn = self.backend_nopython_mode
--> 764         self._backend(lowerfn, objectmode=False)
    765 
    766     def stage_compile_interp_mode(self):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in _backend(self, lowerfn, objectmode)
    701             self.library.enable_object_caching()
    702 
--> 703         lowered = lowerfn()
    704         signature = typing.signature(self.return_type, *self.args)
    705         self.cr = compile_result(

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in backend_nopython_mode(self)
    688                 self.calltypes,
    689                 self.flags,
--> 690                 self.metadata)
    691 
    692     def _backend(self, lowerfn, objectmode):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/compiler.py in native_lowering_stage(targetctx, library, interp, typemap, restype, calltypes, flags, metadata)
   1141         lower = lowering.Lower(targetctx, library, fndesc, interp,
   1142                                metadata=metadata)
-> 1143         lower.lower()
   1144         if not flags.no_cpython_wrapper:
   1145             lower.create_cpython_wrapper(flags.release_gil)

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower(self)
    175         if self.generator_info is None:
    176             self.genlower = None
--> 177             self.lower_normal_function(self.fndesc)
    178         else:
    179             self.genlower = self.GeneratorLower(self)

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower_normal_function(self, fndesc)
    216         # Init argument values
    217         self.extract_function_arguments()
--> 218         entry_block_tail = self.lower_function_body()
    219 
    220         # Close tail of entry block

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower_function_body(self)
    241             bb = self.blkmap[offset]
    242             self.builder.position_at_end(bb)
--> 243             self.lower_block(block)
    244 
    245         self.post_lower()

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    256             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    257                                    loc=self.loc, errcls_=defaulterrcls):
--> 258                 self.lower_inst(inst)
    259 
    260     def create_cpython_wrapper(self, release_gil=False):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/contextlib.py in __exit__(self, type, value, traceback)
    128                 value = type()
    129             try:
--> 130                 self.gen.throw(type, value, traceback)
    131             except StopIteration as exc:
    132                 # Suppress StopIteration *unless* it's the same exception that

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    668         from numba import config
    669         tb = sys.exc_info()[2] if config.FULL_TRACEBACKS else None
--> 670         six.reraise(type(newerr), newerr, tb)
    671 
    672 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/six.py in reraise(tp, value, tb)
    657         if value.__traceback__ is not tb:
    658             raise value.with_traceback(tb)
--> 659         raise value
    660 
    661 else:

LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)


File "<ipython-input-78-9c8bcba8955a>", line 4:
def run_simulations(n_p, n_s, t_lvl, t_plr, plr,mx_lvl):
    <source elided>
#    lvl_sims = np.empty(shape=(np.shape(lvl)[0], np.shape(lvl)[1], n_s))
    for s in prange(n_s):
    ^

[1] During: lowering "id=0[LoopNest(index_variable = parfor_index.10, range = (0, n_s, 1))]{12: <ir.Block at <ipython-input-78-9c8bcba8955a> (4)>}Var(parfor_index.10, <ipython-input-78-9c8bcba8955a> (4))" at <ipython-input-78-9c8bcba8955a> (4)
-------------------------------------------------------------------------------
This should not have happened, a problem has occurred in Numba's internals.
You are currently using Numba version 0.45.1.

Please report the error message and traceback, along with a minimal reproducer
at: https://github.com/numba/numba/issues/new

If more help is needed please feel free to speak to the Numba core developers
directly at: https://gitter.im/numba/numba

Thanks in advance for your help in improving Numba!
```


Discussion:
@miquel-olivenbaumhaus From a first glance this may be a bug in the parallel accelerator but will need some triaging to confirm. Thank you for taking the time to report this!
Thanks for the report. MWE:
```python
import numpy as np
from numba import njit, prange

@njit
def foo(x):
    return x

@njit(parallel=True)
def call_foo(n, pass_thru):
    for _ in prange(n):
        pass_thru = foo(pass_thru)

call_foo(1, np.ones(3))
```
suspect the issue is that the loop body has a loop carried dependency and `pass_thru` is also effectively reducing into itself (which makes it invalid for use in prange). The assertion error comes from the reduction check.

This has a similar problem:
```python
def run_simulations(n_p, n_s, t_lvl, t_plr, plr,mx_lvl):
    for s in prange(n_s):
        plr = simulate_players(n_p, t_lvl, t_plr, plr, mx_lvl)
    return plr
```
in the variable `plr`.

A case like this:
```python
@njit
def foo(x):
    return x

@njit(parallel=True)
def call_bar(n):
    for _ in prange(n):
        x = foo(5)
    return x

call_bar(1)
```
is actually identifiable as a race condition on `x`.

I think the thing to do here it to improve the error message.
I think there is a bug in the same location @DrTodd13, this fails to compile `bar`, looking for the same named variable across the reduction is causing problems. I'm pretty sure the `bar` use case is valid and ought to be supported.
```python
from numba import njit, prange
import numpy as np

@njit(parallel=True)
def foo(n):
    acc = np.zeros((5, 12))
    for i in prange(n):
        acc += np.ones((5, 12))
    return acc


print(foo(5))

@njit(parallel=True)
def bar(n):
    acc = np.zeros((5, 12))
    for i in prange(n):
        acc += np.ones_like(acc)
    return acc

print(bar(5))
```
@stuartarchibald I think your implicit knowledge there is that ones_like doesn't change acc.  Imagine that you were calling a numpy function that could change acc.  Then it is clearly a reduction pattern that we wouldn't support.  Let's say you have a function that took the reduction variable and didn't modify it but just read it.  Even then, this pattern could not be supported.  So, even if we used something like "has_no_side_effects" and "is_pure" to look at ones_like then we may know that acc isn't changed but we need to go further and say that it isn't even read in any meaningful sense.  This is doable but is new infrastructure.  What do you think?  Personally, maybe I can argue that we can somehow improve the error message but I don't think we should put in the effort to support this case right now.
@stuartarchibald I have implemented a check for more of these kinds of conditions and a better error message but how do I test this?  Do we have any other tests that check for certain exceptions raised by the compiler itself?  Please advise.

> @stuartarchibald I have implemented a check for more of these kinds of conditions and a better error message but how do I test this? Do we have any other tests that check for certain exceptions raised by the compiler itself? Please advise.

Great, thanks! We usually just try and compile something within a `with self.assertRaises()` context managed region. e.g.
https://github.com/numba/numba/blob/3e1d89cb500e84c26fec3810164fbaf07003f991/numba/tests/test_withlifting.py#L566-L580
is that the sort of thing you were thinking of?
> @stuartarchibald I think your implicit knowledge there is that ones_like doesn't change acc. Imagine that you were calling a numpy function that could change acc. Then it is clearly a reduction pattern that we wouldn't support. Let's say you have a function that took the reduction variable and didn't modify it but just read it. Even then, this pattern could not be supported. So, even if we used something like "has_no_side_effects" and "is_pure" to look at ones_like then we may know that acc isn't changed but we need to go further and say that it isn't even read in any meaningful sense. This is doable but is new infrastructure. What do you think? Personally, maybe I can argue that we can somehow improve the error message but I don't think we should put in the effort to support this case right now.

Ah yes, agree, it's new behaviour required, I think improving the error message would suffice. Also as `np.ones_like(acc)` in this case is loop invariant there's a work around. Thanks.

Resolving patch:
diff --git a/numba/npyufunc/parfor.py b/numba/npyufunc/parfor.py
--- a/numba/npyufunc/parfor.py
+++ b/numba/npyufunc/parfor.py
@@ -96,7 +96,7 @@ def _lower_parfor_parallel(lowerer, parfor):
     parfor_output_arrays = numba.parfor.get_parfor_outputs(
         parfor, parfor.params)
     parfor_redvars, parfor_reddict = numba.parfor.get_parfor_reductions(
-        parfor, parfor.params, lowerer.fndesc.calltypes)
+        lowerer.func_ir, parfor, parfor.params, lowerer.fndesc.calltypes)
 
     # init reduction array allocation here.
     nredvars = len(parfor_redvars)
@@ -783,7 +783,7 @@ def _create_gufunc_for_parfor_body(
     # Get all parfor reduction vars, and operators.
     typemap = lowerer.fndesc.typemap
     parfor_redvars, parfor_reddict = numba.parfor.get_parfor_reductions(
-        parfor, parfor_params, lowerer.fndesc.calltypes)
+        lowerer.func_ir, parfor, parfor_params, lowerer.fndesc.calltypes)
     # Compute just the parfor inputs as a set difference.
     parfor_inputs = sorted(
         list(
diff --git a/numba/parfor.py b/numba/parfor.py
--- a/numba/parfor.py
+++ b/numba/parfor.py
@@ -1517,7 +1517,14 @@ def run(self):
             # prepare for parallel lowering
             # add parfor params to parfors here since lowering is destructive
             # changing the IR after this is not allowed
-            parfor_ids = get_parfor_params(self.func_ir.blocks, self.options.fusion, self.nested_fusion_info)
+            parfor_ids, parfors = get_parfor_params(self.func_ir.blocks,
+                                                    self.options.fusion,
+                                                    self.nested_fusion_info)
+            for p in parfors:
+                numba.parfor.get_parfor_reductions(self.func_ir,
+                                                   p,
+                                                   p.params,
+                                                   self.calltypes)
             if config.DEBUG_ARRAY_OPT_STATS:
                 name = self.func_ir.func_id.func_qualname
                 n_parfors = len(parfor_ids)
@@ -2843,6 +2850,7 @@ def get_parfor_params(blocks, options_fusion, fusion_info):
     # that could be undefined before. So we only consider variables that are
     # actually defined before the parfor body in the program.
     parfor_ids = set()
+    parfors = []
     pre_defs = set()
     _, all_defs = compute_use_defs(blocks)
     topo_order = find_topo_order(blocks)
@@ -2856,9 +2864,10 @@ def get_parfor_params(blocks, options_fusion, fusion_info):
             pre_defs |= before_defs
             parfor.params = get_parfor_params_inner(parfor, pre_defs, options_fusion, fusion_info) | parfor.races
             parfor_ids.add(parfor.id)
+            parfors.append(parfor)
 
         pre_defs |= all_defs[label]
-    return parfor_ids
+    return parfor_ids, parfors
 
 
 def get_parfor_params_inner(parfor, pre_defs, options_fusion, fusion_info):
@@ -2867,7 +2876,7 @@ def get_parfor_params_inner(parfor, pre_defs, options_fusion, fusion_info):
     cfg = compute_cfg_from_blocks(blocks)
     usedefs = compute_use_defs(blocks)
     live_map = compute_live_map(cfg, blocks, usedefs.usemap, usedefs.defmap)
-    parfor_ids = get_parfor_params(blocks, options_fusion, fusion_info)
+    parfor_ids, _ = get_parfor_params(blocks, options_fusion, fusion_info)
     n_parfors = len(parfor_ids)
     if n_parfors > 0:
         if config.DEBUG_ARRAY_OPT_STATS:
@@ -2911,7 +2920,7 @@ def get_parfor_outputs(parfor, parfor_params):
     outputs = list(set(outputs) & set(parfor_params))
     return sorted(outputs)
 
-def get_parfor_reductions(parfor, parfor_params, calltypes, reductions=None,
+def get_parfor_reductions(func_ir, parfor, parfor_params, calltypes, reductions=None,
         reduce_varnames=None, param_uses=None, param_nodes=None,
         var_to_param=None):
     """find variables that are updated using their previous values and an array
@@ -2959,7 +2968,7 @@ def get_parfor_reductions(parfor, parfor_params, calltypes, reductions=None,
                 param_nodes[cur_param].append(stmt_cp)
             if isinstance(stmt, Parfor):
                 # recursive parfors can have reductions like test_prange8
-                get_parfor_reductions(stmt, parfor_params, calltypes,
+                get_parfor_reductions(func_ir, stmt, parfor_params, calltypes,
                     reductions, reduce_varnames, param_uses, param_nodes, var_to_param)
     for param, used_vars in param_uses.items():
         # a parameter is a reduction variable if its value is used to update it
@@ -2968,7 +2977,7 @@ def get_parfor_reductions(parfor, parfor_params, calltypes, reductions=None,
         if param in used_vars and param not in reduce_varnames:
             reduce_varnames.append(param)
             param_nodes[param].reverse()
-            reduce_nodes = get_reduce_nodes(param, param_nodes[param])
+            reduce_nodes = get_reduce_nodes(param, param_nodes[param], func_ir)
             gri_out = guard(get_reduction_init, reduce_nodes)
             if gri_out is not None:
                 init_val, redop = gri_out
@@ -2997,7 +3006,17 @@ def get_reduction_init(nodes):
         return 1, acc_expr.fn
     return None, None
 
-def get_reduce_nodes(name, nodes):
+def supported_reduction(x, func_ir):
+    if x.op == 'inplace_binop' or x.op == 'binop':
+        return True
+    if x.op == 'call':
+        callname = guard(find_callname, func_ir, x)
+        callname = tuple(i if i != '__builtin__' else 'builtins' for i in callname)
+        if callname == ('max', 'builtins') or callname == ('min', 'builtins'):
+            return True
+    return False
+
+def get_reduce_nodes(name, nodes, func_ir):
     """
     Get nodes that combine the reduction variable with a sentinel variable.
     Recognizes the first node that combines the reduction variable with another
@@ -3022,6 +3041,15 @@ def lookup(var, varonly=True):
         if isinstance(rhs, ir.Expr):
             in_vars = set(lookup(v, True).name for v in rhs.list_vars())
             if name in in_vars:
+                next_node = nodes[i+1]
+                if not (isinstance(next_node, ir.Assign) and next_node.target.name == name):
+                    raise ValueError(("Use of reduction variable " + name +
+                                      " other than in a supported reduction"
+                                      " function is not permitted."))
+
+                if not supported_reduction(rhs, func_ir):
+                    raise ValueError(("Use of reduction variable " + name +
+                                      " in an unsupported reduction function."))
                 args = [ (x.name, lookup(x, True)) for x in get_expr_args(rhs) ]
                 non_red_args = [ x for (x, y) in args if y.name != name ]
                 assert len(non_red_args) == 1


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,6 +38,7 @@
 from .support import tag, override_env_config, skip_parfors_unsupported
 from .matmul_usecase import needs_blas
 from .test_linalg import needs_lapack
+import cmath
 
 
 
@@ -1037,6 +1038,20 @@ def test_impl2(A):
                 pcfunc.entry_point(np.array([], dtype=np.int64))
             self.assertIn(msg, str(e.exception))
 
+    @skip_unsupported
+    def test_use_of_reduction_var1(self):
+        def test_impl():
+            acc = 0
+            for i in prange(1):
+                acc = cmath.sqrt(acc)
+            return acc
+
+        # checks that invalid use of reduction variable is detected
+        msg = ("Use of reduction variable acc in an unsupported reduction function.")
+        with self.assertRaises(ValueError) as e:
+            pcfunc = self.compile_parallel(test_impl, ())
+        self.assertIn(msg, str(e.exception))
+
     @skip_unsupported
     def test_argmin(self):
         def test_impl1(A):