Repository: numba/numba

Issue:
0.53 regression in array analysis for tuple globals
<!--

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).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released (0.53.0rc1) 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'.

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->
I'm guessing this regression is due to the newly added global support in array analysis:
```python
import numba

gval = (1.2,)

@numba.njit(parallel=True)
def f():
    d = gval[0]

f()
```
```
Traceback (most recent call last):
  File "../arr_global_tuple.py", line 10, in <module>
    f()
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 439, in _compile_for_args
    raise e
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 372, in _compile_for_args
    return_val = self.compile(tuple(argtypes))
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 909, in compile
    cres = self._compiler.compile(args, return_type)
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 79, in compile
    status, retval = self._compile_cached(args, return_type)
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 93, in _compile_cached
    retval = self._compile_core(args, return_type)
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 106, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
  File "/Users/ehsan/tmp/numba/numba/core/compiler.py", line 606, in compile_extra
    return pipeline.compile_extra(func)
  File "/Users/ehsan/tmp/numba/numba/core/compiler.py", line 353, in compile_extra
    return self._compile_bytecode()
  File "/Users/ehsan/tmp/numba/numba/core/compiler.py", line 415, in _compile_bytecode
    return self._compile_core()
  File "/Users/ehsan/tmp/numba/numba/core/compiler.py", line 395, in _compile_core
    raise e
  File "/Users/ehsan/tmp/numba/numba/core/compiler.py", line 386, in _compile_core
    pm.run(self.state)
  File "/Users/ehsan/tmp/numba/numba/core/compiler_machinery.py", line 339, in run
    raise patched_exception
  File "/Users/ehsan/tmp/numba/numba/core/compiler_machinery.py", line 330, in run
    self._runPass(idx, pass_inst, state)
  File "/Users/ehsan/tmp/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/Users/ehsan/tmp/numba/numba/core/compiler_machinery.py", line 289, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "/Users/ehsan/tmp/numba/numba/core/compiler_machinery.py", line 262, in check
    mangled = func(compiler_state)
  File "/Users/ehsan/tmp/numba/numba/core/typed_passes.py", line 303, in run_pass
    parfor_pass.run()
  File "/Users/ehsan/tmp/numba/numba/parfors/parfor.py", line 2777, in run
    self._pre_run()
  File "/Users/ehsan/tmp/numba/numba/parfors/parfor.py", line 2769, in _pre_run
    self.array_analysis.run(self.func_ir.blocks)
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 1155, in run
    self._run_on_blocks(topo_order, blocks, cfg, init_equiv_set)
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 1175, in _run_on_blocks
    pending_transforms = self._determine_transform(
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 1256, in _determine_transform
    pre, post = self._analyze_inst(
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 1384, in _analyze_inst
    needs_define = equiv_set.insert_equiv(lhs, shape)
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 524, in insert_equiv
    obj_names = [self._get_names(x) for x in objs]
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 524, in <listcomp>
    obj_names = [self._get_names(x) for x in objs]
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 433, in _get_names
    return tuple(self._get_names(x)[0] for x in obj)
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 433, in <genexpr>
    return tuple(self._get_names(x)[0] for x in obj)
  File "/Users/ehsan/tmp/numba/numba/parfors/array_analysis.py", line 437, in _get_names
    raise NotImplementedError(
NotImplementedError: Failed in nopython mode pipeline (step: convert to parfors)
ShapeEquivSet does not support [1, 2]
```



Discussion:

Resolving patch:
diff --git a/numba/parfors/array_analysis.py b/numba/parfors/array_analysis.py
--- a/numba/parfors/array_analysis.py
+++ b/numba/parfors/array_analysis.py
@@ -1306,7 +1306,11 @@ def _analyze_inst(self, label, scope, equiv_set, inst, redefined):
                 shape = inst.value
             elif isinstance(inst.value, ir.Global):
                 gvalue = inst.value.value
-                if isinstance(gvalue, tuple):
+                # only integer values can be part of shape
+                # TODO: support cases with some but not all integer values or
+                # nested tuples
+                if (isinstance(gvalue, tuple)
+                        and all(isinstance(v, int) for v in gvalue)):
                     shape = gvalue
                 elif isinstance(gvalue, int):
                     shape = (gvalue,)


Test patch:
diff --git a/numba/tests/test_array_analysis.py b/numba/tests/test_array_analysis.py
--- a/numba/tests/test_array_analysis.py
+++ b/numba/tests/test_array_analysis.py
@@ -44,6 +44,11 @@ def __init__(self, n):
         self.T = n + 1
 
 
+# test value for test_global_tuple
+GVAL = (1.2,)
+GVAL2 = (3, 4)
+
+
 class TestEquivSet(TestCase):
 
     """
@@ -930,6 +935,16 @@ def test_broadcast(m, n):
                                equivs=[self.with_equiv('a', 'c', 'e')],
                                asserts=None)
 
+        # make sure shape of a global tuple of ints is handled properly
+        def test_global_tuple():
+            a = np.ones(GVAL2)
+            b = np.ones(GVAL2)
+
+        self._compile_and_test(test_global_tuple, (),
+                               equivs=[self.with_equiv('a', 'b')],
+                               asserts=None)
+
+
 class TestArrayAnalysisParallelRequired(TestCase):
     """This is to just split out tests that need the parallel backend and
     therefore serialised execution.
@@ -1017,6 +1032,18 @@ def test_impl(data):
         data = np.arange(10.)
         np.testing.assert_array_equal(test_impl(data), test_impl.py_func(data))
 
+    @skip_unsupported
+    def test_global_tuple(self):
+        """make sure a global tuple with non-integer values does not cause errors
+        (test for #6726).
+        """
+
+        def test_impl():
+            d = GVAL[0]
+            return d
+
+        self.assertEqual(njit(test_impl, parallel=True)(), test_impl())
+
 
 class TestArrayAnalysisInterface(TestCase):
     def test_analyze_op_call_interface(self):