Repository: numba/numba

Issue:
Regression in 0.53.0rc1 due to issue in temp variable assignment removal
This fails type inference on 0.53.0rc1, worked fine on 0.52.0:
```python
from numba import njit
import numpy as np

@njit
def foo():
    increment = 0.0
    foo_sum = 0.0
    i = 2

    while foo_sum < 100:
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        increment = np.sum(a)
        increment = np.abs(increment)
        foo_sum += increment
        i += 1

    return (foo_sum, i)

foo()
```
Debug log:
```
$ cat di58.py
from numba import njit
import numpy as np

@njit
def foo():
    increment = 0.0
    foo_sum = 0.0
    i = 2

    while foo_sum < 100:
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        increment = np.sum(a)
        increment = np.abs(increment)
        foo_sum += increment
        i += 1

    return (foo_sum, i)


$ HEAD=`git rev-parse HEAD`

$ git show-ref --tags -d|grep $HEAD
90e7cc74e1c3775727289162eade941c49a0db4f refs/tags/0.53.0rc1.post1^{}

$ python di58.py
Traceback (most recent call last):
  File "di58.py", line 20, in <module>
    foo()
  <snip>
  File "/numba/core/typeinfer.py", line 1174, in check_var
    raise TypingError(msg % (var, val, loc), loc)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Type of variable 'a' cannot be determined, operation: call $42load_method.9(a, func=$42load_method.9, args=[Var(a, di58.py:11)], kws=(), vararg=None), location: di58.py (12)

File "di58.py", line 12:
def foo():
    <source elided>
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        ^

$ git revert -m 1 d938288
[release0.53 9ddebc9] Revert "Merge pull request #6575 from ehsantn/ehsan/avoid_tmp_vars"
 11 files changed, 46 insertions(+), 179 deletions(-)

$ python di58.py # works fine
```

d938288 i.e. merge of https://github.com/numba/numba/pull/6575 seems to be the cause.

CC @ehsantn 
Regression in 0.53.0rc1 due to issue in temp variable assignment removal
This fails type inference on 0.53.0rc1, worked fine on 0.52.0:
```python
from numba import njit
import numpy as np

@njit
def foo():
    increment = 0.0
    foo_sum = 0.0
    i = 2

    while foo_sum < 100:
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        increment = np.sum(a)
        increment = np.abs(increment)
        foo_sum += increment
        i += 1

    return (foo_sum, i)

foo()
```
Debug log:
```
$ cat di58.py
from numba import njit
import numpy as np

@njit
def foo():
    increment = 0.0
    foo_sum = 0.0
    i = 2

    while foo_sum < 100:
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        increment = np.sum(a)
        increment = np.abs(increment)
        foo_sum += increment
        i += 1

    return (foo_sum, i)


$ HEAD=`git rev-parse HEAD`

$ git show-ref --tags -d|grep $HEAD
90e7cc74e1c3775727289162eade941c49a0db4f refs/tags/0.53.0rc1.post1^{}

$ python di58.py
Traceback (most recent call last):
  File "di58.py", line 20, in <module>
    foo()
  <snip>
  File "/numba/core/typeinfer.py", line 1174, in check_var
    raise TypingError(msg % (var, val, loc), loc)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Type of variable 'a' cannot be determined, operation: call $42load_method.9(a, func=$42load_method.9, args=[Var(a, di58.py:11)], kws=(), vararg=None), location: di58.py (12)

File "di58.py", line 12:
def foo():
    <source elided>
        a = [np.random.normal(1) for _ in range(i)]
        a = np.array(a)
        ^

$ git revert -m 1 d938288
[release0.53 9ddebc9] Revert "Merge pull request #6575 from ehsantn/ehsan/avoid_tmp_vars"
 11 files changed, 46 insertions(+), 179 deletions(-)

$ python di58.py # works fine
```

d938288 i.e. merge of https://github.com/numba/numba/pull/6575 seems to be the cause.

CC @ehsantn 


Discussion:
A smaller reproducer:

```python
from numba import njit
import numpy as np

@njit
def foo():
    for i in range(3):
        a = (lambda j: j)(i)
        a = np.array(a)

    return

foo()
```
The temp variable assignment pass is causing the [_inline_arraycall](https://github.com/numba/numba/blob/199798e2c849b5e63eeef36972566fda7b84625c/numba/core/inline_closurecall.py#L908) to misbehave. This can be checked by toggling https://github.com/numba/numba/blob/199798e2c849b5e63eeef36972566fda7b84625c/numba/core/inline_closurecall.py#L44.
There is probably an assumption about extra assignments somewhere. I can look at this if no one has started yet.
@ehsantn, it's all yours =)
A smaller reproducer:

```python
from numba import njit
import numpy as np

@njit
def foo():
    for i in range(3):
        a = (lambda j: j)(i)
        a = np.array(a)

    return

foo()
```
The temp variable assignment pass is causing the [_inline_arraycall](https://github.com/numba/numba/blob/199798e2c849b5e63eeef36972566fda7b84625c/numba/core/inline_closurecall.py#L908) to misbehave. This can be checked by toggling https://github.com/numba/numba/blob/199798e2c849b5e63eeef36972566fda7b84625c/numba/core/inline_closurecall.py#L44.
There is probably an assumption about extra assignments somewhere. I can look at this if no one has started yet.
@ehsantn, it's all yours =)

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
@@ -664,7 +664,13 @@ def remove_dead_block(block, lives, call_table, arg_aliases, alias_map,
         else:
             lives |= {v.name for v in stmt.list_vars()}
             if isinstance(stmt, ir.Assign):
-                lives.remove(lhs.name)
+                # make sure lhs is not used in rhs, e.g. a = g(a)
+                if isinstance(stmt.value, ir.Expr):
+                    rhs_vars = {v.name for v in stmt.value.list_vars()}
+                    if lhs.name not in rhs_vars:
+                        lives.remove(lhs.name)
+                else:
+                    lives.remove(lhs.name)
 
         new_body.append(stmt)
     new_body.reverse()


Test patch:
diff --git a/numba/tests/test_remove_dead.py b/numba/tests/test_remove_dead.py
--- a/numba/tests/test_remove_dead.py
+++ b/numba/tests/test_remove_dead.py
@@ -218,6 +218,18 @@ def func(A, i):
             # recover global state
             ir_utils.alias_func_extensions = old_ext_handlers
 
+    def test_rm_dead_rhs_vars(self):
+        """make sure lhs variable of assignment is considered live if used in
+        rhs (test for #6715).
+        """
+        def func():
+            for i in range(3):
+                a = (lambda j: j)(i)
+                a = np.array(a)
+            return a
+
+        self.assertEqual(func(), numba.njit(func)())
+
     @skip_parfors_unsupported
     def test_alias_parfor_extension(self):
         """Make sure aliases are considered in remove dead extension for