Repository: numba/numba

Issue:
LLVM IR parsing error when `np.bool_` used inside a closure
## Reporting a bug

- [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'.

As above.
Thanks to the help of @stuartarchibald I managed to find what was causing the bug.
Probably the reproducer can be reduced a lot.

The bug happens because the variable `nonzero_diagonal` is a `np.bool_`. If you change it to a standard bool (True/False) the error goes away. This is triggered only when this variable is used in a closure, and not when passed in as an argument to the jitted function.

```python
import numpy as np
import numba
from numba import types
from numba import njit, jit

#mock data, but will lead to segfaults if executed
x = np.ones((16,4), dtype=np.float64) #op.hilbert.all_states()
sections = np.empty(x.shape[0], dtype=np.intp)
local_states = np.ones((8,2,2), dtype=np.float64) # op._local_states
basis = np.ones((8,2), dtype=np.int64) # op._basis
constant = 0.0 # op._constant
diag_mels = np.ones((8,4), dtype=np.float64) # op._diag_mels
n_conns = np.ones((8,4), dtype=np.int64) # op._n_conns
all_mels = np.ones((8,4,3), dtype=np.float64) # op._mels
all_x_prime = np.ones((8,4,3, 2), dtype=np.float64) # op._x_prime
acting_on = np.ones((8,2), dtype=np.int64) # op._acting_on
acting_size = np.ones(8, dtype=np.int64) # op._acting_size
nonzero_diagonal = np.bool_(True) # op._nonzero_diagonal
pad = False

arguments_np = [x, sections, local_states,basis,constant,diag_mels,n_conns,all_mels,all_x_prime,acting_on,acting_size, nonzero_diagonal, pad]
signature_np = tuple([numba.typeof(arg) for arg in arguments_np])

signature = signature_np

def _get_conn_flattened_kernel(
    x,
    sections,
    local_states,
    basis,
    constant,
    diag_mels,
    n_conns,
    all_mels,
    all_x_prime,
    acting_on,
    acting_size,
    nonzero_diagonal,
    pad=False,
):
    batch_size = x.shape[0]
    n_sites = x.shape[1]
    dtype = all_mels.dtype

    assert sections.shape[0] == batch_size

    n_operators = n_conns.shape[0]
    xs_n = np.empty((batch_size, n_operators), dtype=np.intp)

    tot_conn = 0
    max_conn = 0

    for b in range(batch_size):
        # diagonal element
        conn_b = 1 if nonzero_diagonal else 0

        # counting the off-diagonal elements
        for i in range(n_operators):
            acting_size_i = acting_size[i]

            xs_n[b, i] = 0
            x_b = x[b]
            x_i = x_b[acting_on[i, :acting_size_i]]
            for k in range(acting_size_i):
                xs_n[b, i] += (
                    np.searchsorted(
                        local_states[i, acting_size_i - k - 1],
                        x_i[acting_size_i - k - 1],
                    )
                    * basis[i, k]
                )

            conn_b += n_conns[i, xs_n[b, i]]

        tot_conn += conn_b
        sections[b] = tot_conn

        if pad:
            max_conn = max(conn_b, max_conn)

    if pad:
        tot_conn = batch_size * max_conn

    x_prime = np.empty((tot_conn, n_sites), dtype=x.dtype)
    mels = np.empty(tot_conn, dtype=dtype)

    c = 0
    for b in range(batch_size):
        c_diag = c
        x_batch = x[b]

        if nonzero_diagonal:
            mels[c_diag] = constant
            x_prime[c_diag] = np.copy(x_batch)
            c += 1

        for i in range(n_operators):

            # Diagonal part
            #  If nonzero_diagonal, this goes to c_diag = 0 ....
            # if zero_diagonal this just sets the last element to 0
            # so it's not worth it skipping it
            mels[c_diag] += diag_mels[i, xs_n[b, i]]
            n_conn_i = n_conns[i, xs_n[b, i]]

            if n_conn_i > 0:
                sites = acting_on[i]
                acting_size_i = acting_size[i]

                for cc in range(n_conn_i):
                    mels[c + cc] = all_mels[i, xs_n[b, i], cc]
                    x_prime[c + cc] = np.copy(x_batch)

                    for k in range(acting_size_i):
                        x_prime[c + cc, sites[k]] = all_x_prime[
                            i, xs_n[b, i], cc, k
                        ]
                c += n_conn_i

        if pad:
            delta_conn = max_conn - (c - c_diag)
            mels[c : c + delta_conn].fill(0)
            x_prime[c : c + delta_conn, :] = np.copy(x_batch)
            c += delta_conn
            sections[b] = c

    return x_prime, mels

print("compiling")

compiled_fun = njit(signature)(_get_conn_flattened_kernel)
compiled_fun2 = njit(_get_conn_flattened_kernel)

print("done compiling")

def gccf_fun(x, sections):
    return compiled_fun2(
        x,
        sections,
        local_states,
        basis,
        constant,
        diag_mels,
        n_conns,
        all_mels,
        all_x_prime,
        acting_on,
        acting_size,
        nonzero_diagonal,
    )

signature_closure = (numba.typeof(x), numba.typeof(sections))

closure_fun = njit(signature_closure)(gccf_fun)
```

Leads to the error

```python
RuntimeError: Failed in nopython mode pipeline (step: nopython mode backend)
LLVM IR parsing error
<string>:547:12: error: expected value token
  store i1 True, i1* %"$26load_global.12"
           ^
```


Discussion:
@PhilipVinc Many thanks for patiently tracking this difficult bug down, much appreciated. Debug log is here for reference: https://gitter.im/numba/numba?at=60892c060dd2e30b30f20611

MWR:
```python
from numba import njit
import numpy as np

np_bool_global = np.bool_(True)

@njit
def inner():
    np_bool_global

inner()
```

Resolving patch:
diff --git a/numba/cpython/numbers.py b/numba/cpython/numbers.py
--- a/numba/cpython/numbers.py
+++ b/numba/cpython/numbers.py
@@ -1333,6 +1333,12 @@ def constant_complex(context, builder, ty, pyval):
 @lower_constant(types.Float)
 @lower_constant(types.Boolean)
 def constant_integer(context, builder, ty, pyval):
+    # See https://github.com/numba/numba/issues/6979
+    # llvmlite ir.IntType specialises the formatting of the constant for a
+    # cpython bool. A NumPy np.bool_ is not a cpython bool so force it to be one
+    # so that the constant renders correctly!
+    if isinstance(pyval, np.bool_):
+        pyval = bool(pyval)
     lty = context.get_value_type(ty)
     return lty(pyval)
 


Test patch:
diff --git a/numba/tests/test_globals.py b/numba/tests/test_globals.py
--- a/numba/tests/test_globals.py
+++ b/numba/tests/test_globals.py
@@ -1,5 +1,6 @@
 import numpy as np
 from numba import jit, njit
+from numba.extending import register_jitable
 from numba.tests import usecases
 import unittest
 
@@ -100,6 +101,19 @@ def global_npy_int_tuple():
     return tup_npy_ints[0] + tup_npy_ints[1]
 
 
+_glbl_np_bool_T = np.bool_(True)
+_glbl_np_bool_F = np.bool_(False)
+
+
+@register_jitable # consumer function
+def _sink(*args):
+    pass
+
+def global_npy_bool():
+    _sink(_glbl_np_bool_T, _glbl_np_bool_F)
+    return _glbl_np_bool_T, _glbl_np_bool_F
+
+
 class TestGlobals(unittest.TestCase):
 
     def check_global_ndarray(self, **jitargs):
@@ -219,6 +233,13 @@ def test_global_npy_int_tuple(self):
         jitfunc = njit(pyfunc)
         self.assertEqual(pyfunc(), jitfunc())
 
+    def test_global_npy_bool(self):
+        # Test global NumPy bool
+        # See issue https://github.com/numba/numba/issues/6979
+        pyfunc = global_npy_bool
+        jitfunc = njit(pyfunc)
+        self.assertEqual(pyfunc(), jitfunc())
+
 
 if __name__ == '__main__':
     unittest.main()