Repository: numba/numba

Issue:
The fastmath SVML option has been broken since somewhere between 0.43.1 and 0.44
<!--

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).
- [x] 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).

Numba 0.43.1 was the last version of Numba with a working SVML option accessible via the standard JIT decorators. The patch 310b3cbb1 broke SVML working, but it's not especially the fault of that patch. The issue seems to be caused by decorating a function with `njit` ahead of setting the vector library option to LLVM via the llvmlite bindings.

This is broken:
```python
from llvmlite import binding

# import before set
from numba import njit
binding.set_option('SVML', '-vector-library=SVML')

import numpy as np
import math

@njit(fastmath={'fast'})
def foo(n):
    x   = np.empty(n*8, dtype=np.float64)
    ret = np.empty_like(x)
    for i in range(ret.size):
            ret[i] += math.cosh(x[i])
    return ret

foo(1000)

if 'intel_svmlcc' in foo.inspect_llvm(foo.signatures[0]):
    print("found")
    exit(0)
else:
    print("not found")
    exit(1)
```
whereas this works as expected:
```python
from llvmlite import binding

# set before import
binding.set_option('SVML', '-vector-library=SVML')
from numba import njit

import numpy as np
import math

@njit(fastmath={'fast'})
def foo(n):
    x   = np.empty(n*8, dtype=np.float64)
    ret = np.empty_like(x)
    for i in range(ret.size):
            ret[i] += math.cosh(x[i])
    return ret

foo(1000)

if 'intel_svmlcc' in foo.inspect_llvm(foo.signatures[0]):
    print("found")
    exit(0)
else:
    print("not found")
    exit(1)
```

The issue manifests in the Numba code base during init as there's now all this:
https://github.com/numba/numba/blob/bdd8e312a24c5b76ee4fe480abe553cff349aa51/numba/__init__.py#L46-L52
which are imports of modules that somewhere in their import tree import `njit` and then decorate one or more functions with it, this prior to setting the vector-library option in the llvmlite DSO to SVML.

This is most likely an issue with internal state of the llvmlite DSO. Fixing options:
1. Trace the use of `njit` on the `__init__` import path and replace with `register_jitable`.
2. Move anything that uses `njit` on the `__init__` import path to occur logically after setting the vector-library option on the DSO.
3. Work out what the actual problem is and then fix that opposed to applying sequence based ducktape :)

This bug does not impact the use of `compile_isolated` as the SVML tests in the test suite continue to pass.


Discussion:

Resolving patch:
diff --git a/numba/__init__.py b/numba/__init__.py
--- a/numba/__init__.py
+++ b/numba/__init__.py
@@ -43,9 +43,6 @@
 import numba.withcontexts
 from numba.withcontexts import objmode_context as objmode
 
-# Initialize typed containers
-import numba.typed
-
 # Enable bytes/unicode array support (Python 3.x only)
 from .utils import IS_PY3
 if IS_PY3:
@@ -186,3 +183,15 @@ def _try_enable_svml():
 Is set to True if Intel SVML is in use.
 """
 config.USING_SVML = _try_enable_svml()
+
+
+# ---------------------- WARNING WARNING WARNING ----------------------------
+# The following imports occur below here (SVML init) because somewhere in their
+# import sequence they have a `@njit` wrapped function. This triggers too early
+# a bind to the underlying LLVM libraries which then irretrievably sets the LLVM
+# SVML state to "no SVML". See https://github.com/numba/numba/issues/4689 for
+# context.
+# ---------------------- WARNING WARNING WARNING ----------------------------
+
+# Initialize typed containers
+import numba.typed
diff --git a/numba/charseq.py b/numba/charseq.py
--- a/numba/charseq.py
+++ b/numba/charseq.py
@@ -3,8 +3,9 @@
 import numpy as np
 from llvmlite import ir
 
-from numba import njit, types, cgutils, unicode
-from numba.extending import overload, intrinsic, overload_method, lower_cast
+from numba import types, cgutils, unicode
+from numba.extending import (overload, intrinsic, overload_method, lower_cast,
+                             register_jitable)
 
 # bytes and str arrays items are of type CharSeq and UnicodeCharSeq,
 # respectively.  See numpy/types/npytypes.py for CharSeq,
@@ -58,14 +59,14 @@ def deref_uint32(typingctx, data, offset):
     return sig, make_deref_codegen(32)
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def charseq_get_code(a, i):
     """Access i-th item of CharSeq object via code value
     """
     return deref_uint8(a, i)
 
 
-@njit
+@register_jitable
 def charseq_get_value(a, i):
     """Access i-th item of CharSeq object via code value.
 
@@ -77,7 +78,7 @@ def charseq_get_value(a, i):
     return code
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def unicode_charseq_get_code(a, i):
     """Access i-th item of UnicodeCharSeq object via code value
     """
@@ -92,14 +93,14 @@ def unicode_charseq_get_code(a, i):
             'unicode_charseq_get_code: unicode_byte_width not in [1, 2, 4]')
 
 
-@njit
+@register_jitable
 def unicode_get_code(a, i):
     """Access i-th item of UnicodeType object.
     """
     return unicode._get_code_point(a, i)
 
 
-@njit
+@register_jitable
 def bytes_get_code(a, i):
     """Access i-th item of Bytes object.
         """
@@ -129,7 +130,7 @@ def _is_bytes(a):
     return isinstance(a, (types.CharSeq, types.Bytes))
 
 
-@njit
+@register_jitable
 def unicode_charseq_get_value(a, i):
     """Access i-th item of UnicodeCharSeq object via unicode value
 
@@ -733,7 +734,7 @@ def impl(a, b):
             return impl
 
 
-@njit
+@register_jitable
 def _map_bytes(seq):
     return [s._to_bytes() for s in seq]
 
diff --git a/numba/unicode.py b/numba/unicode.py
--- a/numba/unicode.py
+++ b/numba/unicode.py
@@ -20,7 +20,6 @@
 from numba.datamodel import register_default, StructModel
 from numba import cgutils
 from numba import types
-from numba import njit
 from numba.pythonapi import (
     PY_UNICODE_1BYTE_KIND,
     PY_UNICODE_2BYTE_KIND,
@@ -240,7 +239,7 @@ def details(context, builder, signature, args):
     return sig, details
 
 
-@njit
+@register_jitable
 def _empty_string(kind, length, is_ascii=0):
     char_width = _kind_to_byte_width(kind)
     s = _malloc_string(kind, char_width, length, is_ascii)
@@ -249,7 +248,7 @@ def _empty_string(kind, length, is_ascii=0):
 
 
 # Disable RefCt for performance.
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def _get_code_point(a, i):
     if a._kind == PY_UNICODE_1BYTE_KIND:
         return deref_uint8(a._data, i)
@@ -295,7 +294,7 @@ def set_uint32(typingctx, data, idx, ch):
     return sig, make_set_codegen(32)
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def _set_code_point(a, i, ch):
     # WARNING: This method is very dangerous:
     #   * Assumes that data contents can be changed (only allowed for new
@@ -314,7 +313,7 @@ def _set_code_point(a, i, ch):
             "Unexpected unicode representation in _set_code_point")
 
 
-@njit
+@register_jitable
 def _pick_kind(kind1, kind2):
     if kind1 == PY_UNICODE_WCHAR_KIND or kind2 == PY_UNICODE_WCHAR_KIND:
         raise AssertionError("PY_UNICODE_WCHAR_KIND unsupported")
@@ -332,14 +331,14 @@ def _pick_kind(kind1, kind2):
         raise AssertionError("Unexpected unicode representation in _pick_kind")
 
 
-@njit
+@register_jitable
 def _pick_ascii(is_ascii1, is_ascii2):
     if is_ascii1 == 1 and is_ascii2 == 1:
         return types.uint32(1)
     return types.uint32(0)
 
 
-@njit
+@register_jitable
 def _kind_to_byte_width(kind):
     if kind == PY_UNICODE_1BYTE_KIND:
         return 1
@@ -353,7 +352,7 @@ def _kind_to_byte_width(kind):
         raise AssertionError("Unexpected unicode encoding encountered")
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def _cmp_region(a, a_offset, b, b_offset, n):
     if n == 0:
         return 0
@@ -373,7 +372,7 @@ def _cmp_region(a, a_offset, b, b_offset, n):
     return 0
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def _find(substr, s):
     # Naive, slow string matching for now
     for i in range(len(s) - len(substr) + 1):
@@ -382,7 +381,7 @@ def _find(substr, s):
     return -1
 
 
-@njit
+@register_jitable
 def _is_whitespace(code_point):
     # list copied from
     # https://github.com/python/cpython/blob/master/Objects/unicodetype_db.h
@@ -791,7 +790,7 @@ def rjust_impl(string, width, fillchar=' '):
     return rjust_impl
 
 
-@njit
+@register_jitable
 def join_list(sep, parts):
     parts_len = len(parts)
     if parts_len == 0:
@@ -966,7 +965,7 @@ def strip_impl(string, chars=None):
 
 # String creation
 
-@njit
+@register_jitable
 def normalize_str_idx(idx, length, is_start=True):
     """
     Parameters
@@ -1051,7 +1050,7 @@ def codegen(context, builder, sig, args):
     return sig, codegen
 
 
-@njit(_nrt=False)
+@register_jitable(_nrt=False)
 def _strncpy(dst, dst_offset, src, src_offset, n):
     if src._kind == dst._kind:
         byte_width = _kind_to_byte_width(src._kind)


Test patch:
diff --git a/numba/tests/test_svml.py b/numba/tests/test_svml.py
--- a/numba/tests/test_svml.py
+++ b/numba/tests/test_svml.py
@@ -10,7 +10,7 @@
 from itertools import chain, combinations
 
 import numba
-from numba import prange, unittest_support as unittest
+from numba import prange, njit, unittest_support as unittest
 from numba.targets import cpu
 from numba.compiler import compile_isolated, Flags
 from numba.six import exec_
@@ -403,6 +403,17 @@ def check_no_svml():
                 "process failed with code %s: stderr follows\n%s\n" %
                 (popen.returncode, err.decode()))
 
+    def test_svml_working_in_non_isolated_context(self):
+        @njit(fastmath={'fast'}, error_model="numpy")
+        def impl(n):
+            x   = np.empty(n * 8, dtype=np.float64)
+            ret = np.empty_like(x)
+            for i in range(ret.size):
+                    ret[i] += math.cosh(x[i])
+            return ret
+        impl(1)
+        self.assertTrue('intel_svmlcc' in impl.inspect_llvm(impl.signatures[0]))
+
 
 if __name__ == '__main__':
     unittest.main()