Repository: numba/numba

Issue:
numba 0.46, useless(?) code in parfor init block
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [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).
- [ ] 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).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

We are porting hpat to numba 0.46 and found discrepancies in parfor pass output.

pre parfor pass input IR is identical.

In previous version init block after parfor pass was empty.

In 0.46 init block contains seemingly useless code.
```
init block:
    arr.83 = $arr.83.139                     ['$arr.83.139', 'arr.83']
    del arr.83                               []
```

We didn't manage to reduce code to pure numba yet but here are IR before preparfor pass  and after parfor pass.

Any ideas why this is happens?

prev numba
```
=========== before preparfor ===========
label 0:
    N = arg(0, name=N)                       ['N']
    $0.1 = global(np: <module 'numpy' from '/home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numpy/__init__.py'>) ['$0.1']
    $0.2 = getattr(value=$0.1, attr=arange)  ['$0.1', '$0.2']
    $0.4 = call $0.2(N, func=$0.2, args=[Var(N, /home/ibutygin/src/hpat/hpat/hpat/tests/test_basic.py (303))], kws=(), vararg=None) ['$0.2', '$0.4', 'N']
    A = $0.4                                 ['$0.4', 'A']
    _dist_arr.36 = A                         ['A', '_dist_arr.36']
    $0.1.37 = global(hpat: <module 'hpat' from '/home/ibutygin/src/hpat/hpat/hpat/__init__.py'>) ['$0.1.37']
    $0.2.38 = getattr(value=$0.1.37, attr=distributed_api) ['$0.1.37', '$0.2.38']
    $0.3.39 = getattr(value=$0.2.38, attr=dist_return) ['$0.2.38', '$0.3.39']
    $0.5.40 = call $0.3.39(_dist_arr.36, func=$0.3.39, args=[Var(_dist_arr.36, /home/ibutygin/src/hpat/hpat/hpat/hiframes/hiframes_untyped.py (1735))], kws=(), vararg=None) ['$0.3.39', '$0.5.40', '_dist_arr.36']
    _d_arr.41 = $0.5.40                      ['$0.5.40', '_d_arr.41']
    distributed_return.44 = cast(value=_d_arr.41) ['_d_arr.41', 'distributed_return.44']
    return distributed_return.44             ['distributed_return.44']
========================================
============= after parfor =============
label 0:
    N = arg(0, name=N)                       ['N']
    $start.54.115 = const(int, 0)            ['$start.54.115']
    $step.56.116 = const(int, 1)             ['$step.56.116']
    $dtype.57.117 = const(dtype, int64)      ['$dtype.57.117']
    $0.5.62 = global(math: <module 'math' from '/home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/lib-dynload/math.cpython-37m-x86_64-linux-gnu.so'>) ['$0.5.62']
    $0.6.63 = getattr(value=$0.5.62, attr=ceil) ['$0.5.62', '$0.6.63']
    del $0.5.62                              []
    $0.9.66 = N - $start.54.115              ['$0.9.66', '$start.54.115', 'N']
    del N                                    []
    $0.11.68 = $0.9.66 / $step.56.116        ['$0.11.68', '$0.9.66', '$step.56.116']
    del $0.9.66                              []
    $nitems_r.70.118 = call $0.6.63($0.11.68, func=$0.6.63, args=[Var($0.11.68, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (354))], kws=(), vararg=None) ['$0.11.68', '$0.6.63', '$nitems_r.70.118']
    del $0.6.63                              []
    del $0.11.68                             []
    $0.13.71 = global(int: <class 'int'>)    ['$0.13.71']
    $0.14.72 = global(max: <built-in function max>) ['$0.14.72']
    $const0.16.74 = const(int, 0)            ['$const0.16.74']
    $0.17.75 = call $0.14.72($nitems_r.70.118, $const0.16.74, func=$0.14.72, args=[Var($nitems_r.70.118, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (354)), Var($const0.16.74, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (355))], kws=(), vararg=None) ['$0.14.72', '$0.17.75', '$const0.16.74', '$nitems_r.70.118']
    del $nitems_r.70.118                     []
    del $const0.16.74                        []
    del $0.14.72                             []
    $nitems.77.119 = call $0.13.71($0.17.75, func=$0.13.71, args=[Var($0.17.75, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (355))], kws=(), vararg=None) ['$0.13.71', '$0.17.75', '$nitems.77.119']
    del $0.17.75                             []
    del $0.13.71                             []
    $0.19.78 = global(np: <module 'numpy' from '/home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numpy/__init__.py'>) ['$0.19.78']
    $0.20.79 = getattr(value=$0.19.78, attr=empty) ['$0.19.78', '$0.20.79']
    del $0.19.78                             []
    $arr.83.120 = call $0.20.79($nitems.77.119, $dtype.57.117, func=$0.20.79, args=[Var($nitems.77.119, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (355)), Var($dtype.57.117, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (332))], kws=(), vararg=None) ['$0.20.79', '$arr.83.120', '$dtype.57.117', '$nitems.77.119']
    del $dtype.57.117                        []
    del $0.20.79                             []
    $0.1.37 = global(hpat: <module 'hpat' from '/home/ibutygin/src/hpat/hpat/hpat/__init__.py'>) ['$0.1.37']
    $0.2.38 = getattr(value=$0.1.37, attr=distributed_api) ['$0.1.37', '$0.2.38']
    del $0.1.37                              []
    $0.3.39 = getattr(value=$0.2.38, attr=dist_return) ['$0.2.38', '$0.3.39']
    del $0.2.38                              []
---begin parfor 0---
index_var =  parfor_index.112
LoopNest(index_variable = parfor_index.112, range = (0, $nitems.77.119, 1))
init block:
label 78:
    i.99 = parfor_index.112                  ['i.99', 'parfor_index.112']
    $74.5.103 = i.99 * $step.56.116          ['$74.5.103', '$step.56.116', 'i.99']
    $74.6.104 = $start.54.115 + $74.5.103    ['$74.5.103', '$74.6.104', '$start.54.115']
    $arr.83.120[parfor_index.112] = $74.6.104 ['$74.6.104', '$arr.83.120', 'parfor_index.112']
----end parfor 0----
    del $step.56.116                         []
    del $start.54.115                        []
    $_d_arr.41.121 = call $0.3.39($arr.83.120, func=$0.3.39, args=[Var($arr.83.120, /home/ibutygin/miniconda3/envs/HPAT/lib/python3.7/site-packages/numba/parfor.py (356))], kws=(), vararg=None) ['$0.3.39', '$_d_arr.41.121', '$arr.83.120']
    del $arr.83.120                          []
    del $0.3.39                              []
    distributed_return.44 = cast(value=$_d_arr.41.121) ['$_d_arr.41.121', 'distributed_return.44']
    del $_d_arr.41.121                       []
    return distributed_return.44             ['distributed_return.44']
========================================
```

numba 0.46
```
=========== before preparfor ===========
label 0:
    N = arg(0, name=N)                       ['N']
    $0.1 = global(np: <module 'numpy' from '/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/site-packages/numpy/__init__.py'>) ['$0.1']
    $0.2 = getattr(value=$0.1, attr=arange)  ['$0.1', '$0.2']
    $0.4 = call $0.2(N, func=$0.2, args=[Var(N, /home/ibutygin/src/numba/hpat/hpat/tests/test_basic.py (303))], kws=(), vararg=None) ['$0.2', '$0.4', 'N']
    A = $0.4                                 ['$0.4', 'A']
    _dist_arr.36 = A                         ['A', '_dist_arr.36']
    $0.1.37 = global(hpat: <module 'hpat' from '/home/ibutygin/src/numba/hpat/hpat/__init__.py'>) ['$0.1.37']
    $0.2.38 = getattr(value=$0.1.37, attr=distributed_api) ['$0.1.37', '$0.2.38']
    $0.3.39 = getattr(value=$0.2.38, attr=dist_return) ['$0.2.38', '$0.3.39']
    $0.5.40 = call $0.3.39(_dist_arr.36, func=$0.3.39, args=[Var(_dist_arr.36, /home/ibutygin/src/numba/hpat/hpat/hiframes/hiframes_untyped.py (1753))], kws=(), vararg=None) ['$0.3.39', '$0.5.40', '_dist_arr.36']
    _d_arr.41 = $0.5.40                      ['$0.5.40', '_d_arr.41']
    distributed_return.44 = cast(value=_d_arr.41) ['_d_arr.41', 'distributed_return.44']
    return distributed_return.44             ['distributed_return.44']
========================================
============= after parfor =============
label 0:
    N = arg(0, name=N)                       ['N']
    $start.54.115 = const(int, 0)            ['$start.54.115']
    $step.56.116 = const(int, 1)             ['$step.56.116']
    $dtype.57.117 = const(dtype, int64)      ['$dtype.57.117']
    $0.5.62 = global(math: <module 'math' from '/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/lib-dynload/math.cpython-37m-x86_64-linux-gnu.so'>) ['$0.5.62']
    $0.6.63 = getattr(value=$0.5.62, attr=ceil) ['$0.5.62', '$0.6.63']
    del $0.5.62                              []
    $0.9.66 = N - $start.54.115              ['$0.9.66', '$start.54.115', 'N']
    del N                                    []
    $0.11.68 = $0.9.66 / $step.56.116        ['$0.11.68', '$0.9.66', '$step.56.116']
    del $0.9.66                              []
    $nitems_r.70.118 = call $0.6.63($0.11.68, func=$0.6.63, args=[Var($0.11.68, /home/ibutygin/src/numba/numba/numba/parfor.py (354))], kws=(), vararg=None) ['$0.11.68', '$0.6.63', '$nitems_r.70.118']
    del $0.6.63                              []
    del $0.11.68                             []
    $0.13.71 = global(int: <class 'int'>)    ['$0.13.71']
    $0.14.72 = global(max: <built-in function max>) ['$0.14.72']
    $const0.16.74 = const(int, 0)            ['$const0.16.74']
    $0.17.75 = call $0.14.72($nitems_r.70.118, $const0.16.74, func=$0.14.72, args=[Var($nitems_r.70.118, /home/ibutygin/src/numba/numba/numba/parfor.py (354)), Var($const0.16.74, /home/ibutygin/src/numba/numba/numba/parfor.py (355))], kws=(), vararg=None) ['$0.14.72', '$0.17.75', '$const0.16.74', '$nitems_r.70.118']
    del $nitems_r.70.118                     []
    del $const0.16.74                        []
    del $0.14.72                             []
    $nitems.77.119 = call $0.13.71($0.17.75, func=$0.13.71, args=[Var($0.17.75, /home/ibutygin/src/numba/numba/numba/parfor.py (355))], kws=(), vararg=None) ['$0.13.71', '$0.17.75', '$nitems.77.119']
    del $0.17.75                             []
    del $0.13.71                             []
    $0.19.78 = global(np: <module 'numpy' from '/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/site-packages/numpy/__init__.py'>) ['$0.19.78']
    $0.20.79 = getattr(value=$0.19.78, attr=empty) ['$0.19.78', '$0.20.79']
    del $0.19.78                             []
    $arr.83.139 = call $0.20.79($nitems.77.119, $dtype.57.117, func=$0.20.79, args=[Var($nitems.77.119, /home/ibutygin/src/numba/numba/numba/parfor.py (355)), Var($dtype.57.117, /home/ibutygin/src/numba/numba/numba/parfor.py (332))], kws=(), vararg=None) ['$0.20.79', '$arr.83.139', '$dtype.57.117', '$nitems.77.119']
    del $dtype.57.117                        []
    del $0.20.79                             []
    $0.1.37 = global(hpat: <module 'hpat' from '/home/ibutygin/src/numba/hpat/hpat/__init__.py'>) ['$0.1.37']
    $0.2.38 = getattr(value=$0.1.37, attr=distributed_api) ['$0.1.37', '$0.2.38']
    del $0.1.37                              []
    $0.3.39 = getattr(value=$0.2.38, attr=dist_return) ['$0.2.38', '$0.3.39']
    del $0.2.38                              []
---begin parfor 0---
index_var =  parfor_index.112
LoopNest(index_variable = parfor_index.112, range = (0, $nitems.77.119, 1))
init block:
    arr.83 = $arr.83.139                     ['$arr.83.139', 'arr.83']
    del arr.83                               []
label 78:
    i.99 = parfor_index.112                  ['i.99', 'parfor_index.112']
    $74.5.103 = i.99 * $step.56.116          ['$74.5.103', '$step.56.116', 'i.99']
    $74.6.104 = $start.54.115 + $74.5.103    ['$74.5.103', '$74.6.104', '$start.54.115']
    $arr.83.139[parfor_index.112] = $74.6.104 ['$74.6.104', '$arr.83.139', 'parfor_index.112']
----end parfor 0----
    del $step.56.116                         []
    del $start.54.115                        []
    $_d_arr.41.121 = call $0.3.39($arr.83.139, func=$0.3.39, args=[Var($arr.83.139, /home/ibutygin/src/numba/numba/numba/parfor.py (356))], kws=(), vararg=None) ['$0.3.39', '$_d_arr.41.121', '$arr.83.139']
    del $arr.83.139                          []
    del $0.3.39                              []
    distributed_return.44 = cast(value=$_d_arr.41.121) ['$_d_arr.41.121', 'distributed_return.44']
    del $_d_arr.41.121                       []
    return distributed_return.44             ['distributed_return.44']
========================================
```


Discussion:
Thanks for the report.

> We are porting hpat to numba 0.46 and found discrepancies in parfor pass output.

From which version are you porting it from? 0.44.0, 0.44.1, 0.45.0, 0.45.1?
0.45.1
I don't think a huge amount was added specifically to parfors between 0.45.1 and 0.46.0:
```
git diff 0.45.1 -- numba/npyufunc/parfor.py numba/parfor.py
```
looks pretty much like just this patch https://github.com/numba/numba/pull/4305 which I don't think would necessarily mutate the parfor init block.

An IR legalizer was added in https://github.com/numba/numba/pull/4162 to ensure the IR is valid prior to lowering, I'm wondering if that's now preventing some cleanup happening in parfors? Also, given the above, could you just run DCE over the init block?
> could you just run DCE over the init block?

I tried to add `DeadCodeElimination` pass right after parfor but it had no effect.

And how can I run DCE over a single block? There is `dead_code_elimination` function in ir_utils, but it need entire function not a single block.
> > could you just run DCE over the init block?
> 
> I tried to add `DeadCodeElimination` pass right after parfor but it had no effect.
> 
> And how can I run DCE over a single block? There is `dead_code_elimination` function in ir_utils, but it need entire function not a single block.

I don't think there's a already existing function to do that, you'd probably have to customise something.

I do have to ask what's the actual issue here? Why are some provably dead statements causing a problem?
> I do have to ask what's the actual issue here? Why are some provably dead statements causing a problem?

We have our custom passes which transform parfors into mpi-distibuted code.

They will generate additional code for such 'dead' code (comparing to previous numba version) and we see it in our reports.
I see, it might be beneficial to write a DCE pass that can actually operate on the Parfors class if it is not working already, I imagine that may be generally useful?

@DrTodd13 any ideas why the above is now happening. I don't think it's anything necessarily related to patches to parfors since 0.45.1.
@Hardcode84 @stuartarchibald I think we need a minimal reproducer.  If Ivan doesn't want to post it publically then he can send it to me directly.
I was unable to make pure (without our custom passes in pipeline) reproducer yet. We only have HPAT-based for now (it is one of the HPAT tests actually).

I will probably try to reduce test case again on next week.

We will also probably post our update-to-46-numba HPAT PR (WIP) on next week or so.
If it is hard to decouple from HPAT, i have HPAT installed and can run HPAT tests.  Just post what branch and test commands and I can help diagnosing the issue.
@sklam 

branch from https://github.com/IntelPython/hpat/pull/224

But it is probably broken now due to changes #4687

quick workaround to enable it will be to change in numba `inline_closure_call`
```
    - if isinstance(callee, pytypes.FunctionType):
    -     from numba import compiler
    -     callee_ir = compiler.run_frontend(callee, inline_closures=True)
    - else:
    -     callee_ir = get_ir_of_code(glbls, callee_code)
    + callee_ir = get_ir_of_code(glbls, callee_code)
```

Test `python -W ignore -u -m unittest -v hpat.tests.test_basic.TestBasic.test_dist_return`

Output should be:
```
FAIL: test_dist_return (hpat.tests.test_basic.TestBasic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/ibutygin/src/numba/hpat/hpat/tests/test_basic.py", line 313, in test_dist_return
    self.assertEqual(count_array_OneDs(), 1)
AssertionError: 2 != 1
```

Thanks
Here is on pure numba:
```
import numba;

@numba.njit(parallel=True)
def foo():
    res = 0
    arr = [1,2,3,4,5]
    numba.parfor.init_prange()
    dummy = arr
    for i in numba.prange(5):
        res += arr[i]
    return res + dummy[2]

print(foo())
```

0.46 ouput:
```
-----------------------------nopython: parfor_pass------------------------------
label 0:
    $const0.1 = const(int, 0)                ['$const0.1']
    res = $const0.1                          ['$const0.1', 'res']
    del $const0.1                            []
    $const0.2 = const(int, 1)                ['$const0.2']
    $const0.3 = const(int, 2)                ['$const0.3']
    $const0.4 = const(int, 3)                ['$const0.4']
    $const0.5 = const(int, 4)                ['$const0.5']
    $const0.6 = const(int, 5)                ['$const0.6']
    $dummy.21 = build_list(items=[Var($const0.2, test.py (6)), Var($const0.3, test.py (6)), Var($const0.4, test.py (6)), Var($const0.5, test.py (6)), Var($const0.6, test.py (6))]) ['$const0.2', '$const0.3', '$const0.4', '$const0.5', '$const0.6', '$dummy.21']
    del $const0.6                            []
    del $const0.5                            []
    del $const0.4                            []
    del $const0.3                            []
    del $const0.2                            []
    $const34.3 = const(int, 5)               ['$const34.3']
    $const64.3 = const(int, 2)               ['$const64.3']
---begin parfor 0---
index_var =  parfor_index.0
LoopNest(index_variable = parfor_index.0, range = (0, $const34.3, 1))
init block:
    dummy = $dummy.21                        ['$dummy.21', 'dummy']
    del dummy                                []
label 46:
    $46.5 = getitem(value=$dummy.21, index=parfor_index.0) ['$46.5', '$dummy.21', 'parfor_index.0']
    $res.22 = inplace_binop(fn=<built-in function iadd>, immutable_fn=<built-in function add>, lhs=res, rhs=$46.5, static_lhs=Undefined, static_rhs=Undefined) ['$46.5', '$res.22', 'res']
    res = $res.22                            ['$res.22', 'res']
----end parfor 0----
    $64.4 = static_getitem(value=$dummy.21, index=2, index_var=$const64.3) ['$64.4', '$const64.3', '$dummy.21']
    del $dummy.21                            []
    del $const64.3                           []
    $64.5 = res + $64.4                      ['$64.4', '$64.5', 'res']
    del res                                  []
    del $64.4                                []
    $64.6 = cast(value=$64.5)                ['$64.5', '$64.6']
    del $64.5                                []
    return $64.6                             ['$64.6']
```

0.45.1 output is more different:
```
label 0:
    $const0.1 = const(int, 0)                ['$const0.1']
    res = $const0.1                          ['$const0.1', 'res']
    del $const0.1                            []
    $const0.2 = const(int, 1)                ['$const0.2']
    $const0.3 = const(int, 2)                ['$const0.3']
    $const0.4 = const(int, 3)                ['$const0.4']
    $const0.5 = const(int, 4)                ['$const0.5']
    $const0.6 = const(int, 5)                ['$const0.6']
    $arr.3 = build_list(items=[Var($const0.2, test.py (6)), Var($const0.3, test.py (6)), Var($const0.4, test.py (6)), Var($const0.5, test.py (6)), Var($const0.6, test.py (6))]) ['$arr.3', '$const0.2', '$const0.3', '$const0.4', '$const0.5', '$const0.6']
    del $const0.6                            []
    del $const0.5                            []
    del $const0.4                            []
    del $const0.3                            []
    del $const0.2                            []
    $const34.3 = const(int, 5)               ['$const34.3']
    $const64.3 = const(int, 2)               ['$const64.3']
    $64.4 = static_getitem(value=$arr.3, index=2, index_var=$const64.3) ['$64.4', '$arr.3', '$const64.3']
    del $const64.3                           []
---begin parfor 0---
index_var =  parfor_index.0
LoopNest(index_variable = parfor_index.0, range = (0, $const34.3, 1))
init block:
label 46:
    $46.5 = getitem(value=$arr.3, index=parfor_index.0) ['$46.5', '$arr.3', 'parfor_index.0']
    $res.19 = inplace_binop(fn=<built-in function iadd>, immutable_fn=<built-in function add>, lhs=res, rhs=$46.5, static_lhs=Undefined, static_rhs=Undefined) ['$46.5', '$res.19', 'res']
    res = $res.19                            ['$res.19', 'res']
----end parfor 0----
    del $arr.3                               []
    $64.5 = res + $64.4                      ['$64.4', '$64.5', 'res']
    del res                                  []
    del $64.4                                []
    $64.6 = cast(value=$64.5)                ['$64.5', '$64.6']
    del $64.5                                []
    return $64.6                             ['$64.6']
```

but this dead code
```
init block:
    dummy = $dummy.21                        ['$dummy.21', 'dummy']
    del dummy                                []
```

shouldn't be there in any case, right?
@ehsantn @Hardcode84 @stuartarchibald @sklam Note that this doesn't happen if init_prange is not explicitly called or if "dummy = arr" is moved above the init_prange call.  However, I did figure out why the dead code was in the init block.  When the dead code elimination is done for the parfor, the set of lives out of the parfor that is used to determine if something can be removed is calculated as the real lives plus all the aliases of all the lives.  On its face that seems reasonable because you don't want to remove something like "dummy[i] = 1" if dummy aliases something else.  Alias analysis sees the "dummy = arr" and then says that dummy is live post loop because arr is live post loop even though dummy is never directly accessed again.  I did a test where instead of passing the lives and all the aliases to the remove dead parfor extension that instead I just pass in lives and that eliminates the issue in the above test and works for test_parfors, test_ir_utils, and test_analysis.  Something a little less scary would be to have two sets of lives kept.  One applied to assignments (which would just be lives) and the other applied to everything else (which would be lives + aliases).  What does everyone think?

Resolving patch:
diff --git a/numba/ir_utils.py b/numba/ir_utils.py
--- a/numba/ir_utils.py
+++ b/numba/ir_utils.py
@@ -536,6 +536,9 @@ def remove_dead(blocks, args, func_ir, typemap=None, alias_map=None, arg_aliases
                                                         func_ir)
     if config.DEBUG_ARRAY_OPT >= 1:
         print("remove_dead alias map:", alias_map)
+        print("live_map:", live_map)
+        print("usemap:", usedefs.usemap)
+        print("defmap:", usedefs.defmap)
     # keep set for easier search
     alias_set = set(alias_map.keys())
 
@@ -543,8 +546,12 @@ def remove_dead(blocks, args, func_ir, typemap=None, alias_map=None, arg_aliases
     for label, block in blocks.items():
         # find live variables at each statement to delete dead assignment
         lives = {v.name for v in block.terminator.list_vars()}
+        if config.DEBUG_ARRAY_OPT >= 2:
+            print("remove_dead processing block", label, lives)
         # find live variables at the end of block
         for out_blk, _data in cfg.successors(label):
+            if config.DEBUG_ARRAY_OPT >= 2:
+                print("succ live_map", out_blk, live_map[out_blk])
             lives |= live_map[out_blk]
         removed |= remove_dead_block(block, lives, call_table, arg_aliases,
                                      alias_map, alias_set, func_ir, typemap)
@@ -571,6 +578,8 @@ def remove_dead_block(block, lives, call_table, arg_aliases, alias_map,
     new_body = [block.terminator]
     # for each statement in reverse order, excluding terminator
     for stmt in reversed(block.body[:-1]):
+        if config.DEBUG_ARRAY_OPT >= 2:
+            print("remove_dead_block", stmt)
         # aliases of lives are also live
         alias_lives = set()
         init_alias_lives = lives & alias_set
@@ -581,7 +590,7 @@ def remove_dead_block(block, lives, call_table, arg_aliases, alias_map,
         # let external calls handle stmt if type matches
         if type(stmt) in remove_dead_extensions:
             f = remove_dead_extensions[type(stmt)]
-            stmt = f(stmt, lives_n_aliases, arg_aliases, alias_map, func_ir,
+            stmt = f(stmt, lives, lives_n_aliases, arg_aliases, alias_map, func_ir,
                      typemap)
             if stmt is None:
                 removed = True
@@ -1401,6 +1410,8 @@ def simplify(func_ir, typemap, calltypes):
         calltypes)
     restore_copy_var_names(func_ir.blocks, save_copies, typemap)
     # remove dead code to enable fusion
+    if config.DEBUG_ARRAY_OPT >= 1:
+        dprint_func_ir(func_ir, "after copy prop")
     remove_dead(func_ir.blocks, func_ir.arg_names, func_ir, typemap)
     func_ir.blocks = simplify_CFG(func_ir.blocks)
     if config.DEBUG_ARRAY_OPT >= 1:
diff --git a/numba/parfor.py b/numba/parfor.py
--- a/numba/parfor.py
+++ b/numba/parfor.py
@@ -3591,7 +3591,7 @@ def get_parfor_pattern_vars(parfor):
                     out.add(v.name)
     return out
 
-def remove_dead_parfor(parfor, lives, arg_aliases, alias_map, func_ir, typemap):
+def remove_dead_parfor(parfor, lives, lives_n_aliases, arg_aliases, alias_map, func_ir, typemap):
     """ remove dead code inside parfor including get/sets
     """
 
@@ -3608,7 +3608,7 @@ def remove_dead_parfor(parfor, lives, arg_aliases, alias_map, func_ir, typemap):
         parfor.loop_body[first_label].body,
         parfor.index_var, alias_map,
         first_block_saved_values,
-        lives
+        lives_n_aliases
         )
 
     # remove saved first block setitems if array potentially changed later
@@ -3635,13 +3635,13 @@ def remove_dead_parfor(parfor, lives, arg_aliases, alias_map, func_ir, typemap):
         block = parfor.loop_body[l]
         saved_values = first_block_saved_values.copy()
         _update_parfor_get_setitems(block.body, parfor.index_var, alias_map,
-                                        saved_values, lives)
+                                        saved_values, lives_n_aliases)
 
 
     # after getitem replacement, remove extra setitems
     blocks = parfor.loop_body.copy()  # shallow copy is enough
     last_label = max(blocks.keys())
-    return_label, tuple_var = _add_liveness_return_block(blocks, lives, typemap)
+    return_label, tuple_var = _add_liveness_return_block(blocks, lives_n_aliases, typemap)
     # jump to return label
     jump = ir.Jump(return_label, ir.Loc("parfors_dummy", -1))
     blocks[last_label].body.append(jump)
@@ -3674,8 +3674,14 @@ def remove_dead_parfor(parfor, lives, arg_aliases, alias_map, func_ir, typemap):
     typemap.pop(tuple_var.name)  # remove dummy tuple type
     blocks[last_label].body.pop()  # remove jump
 
-
-    # process parfor body recursively
+    """
+      Process parfor body recursively.
+      Note that this is the only place in this function that uses the
+      argument lives instead of lives_n_aliases.  The former does not
+      include the aliases of live variables but only the live variable
+      names themselves.  See a comment in this function for how that
+      is used. 
+    """
     remove_dead_parfor_recursive(
         parfor, lives, arg_aliases, alias_map, func_ir, typemap)
 
@@ -3731,6 +3737,17 @@ def remove_dead_parfor_recursive(parfor, lives, arg_aliases, alias_map,
     assert first_body_block > 0  # we are using 0 for init block here
     last_label = max(blocks.keys())
 
+    """
+      Previously, this statement used lives_n_aliases.  That had the effect of
+      keeping variables in the init_block alive if they aliased an array that
+      was later written to.  By using just lives to indicate which variables
+      names are live at exit of the parfor but then using alias_map for the
+      actual recursive dead code removal, we keep any writes to aliased arrays
+      alive but also allow aliasing assignments (i.e., a = b) to be eliminated
+      so long as 'b' is not written to through the variable 'a' later on.
+      This makes assignment handling of remove_dead_block work properly since
+      it allows distinguishing between live variables and their aliases.
+    """
     return_label, tuple_var = _add_liveness_return_block(blocks, lives, typemap)
 
     # branch back to first body label to simulate loop


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
@@ -374,6 +374,19 @@ def countArrays(test_func, args, **kws):
     test_ir, tp = get_optimized_numba_ir(test_func, args, **kws)
     return _count_arrays_inner(test_ir.blocks, tp.state.typemap)
 
+def get_init_block_size(test_func, args, **kws):
+    test_ir, tp = get_optimized_numba_ir(test_func, args, **kws)
+    blocks = test_ir.blocks
+
+    ret_count = 0
+
+    for label, block in blocks.items():
+        for i, inst in enumerate(block.body):
+            if isinstance(inst, numba.parfor.Parfor):
+                ret_count += len(inst.init_block.body)
+
+    return ret_count
+
 def _count_arrays_inner(blocks, typemap):
     ret_count = 0
     arr_set = set()
@@ -3056,6 +3069,20 @@ def test_impl(A):
             # recover global state
             numba.parfor.sequential_parfor_lowering = old_seq_flag
 
+    @skip_unsupported
+    def test_init_block_dce(self):
+        # issue4690
+        def test_impl():
+            res = 0
+            arr = [1,2,3,4,5]
+            numba.parfor.init_prange()
+            dummy = arr
+            for i in numba.prange(5):
+                res += arr[i]
+            return res + dummy[2]
+
+        self.assertTrue(get_init_block_size(test_impl, ()) == 0)
+
     @skip_unsupported
     def test_alias_analysis_for_parfor1(self):
         def test_impl():