Repository: numba/numba

Issue:
ipython kernel dies when using jit
I am using numba on Windows 10 (64-bit) with Anaconda3 (v 5.2), numba 0.38.0, and numpy 1.14.3.

When I run the code attached below for test, `ident_loops` works just fine, but the ipython kernel dies if I use `jit` (`jident_loops` in the code). I tested it on spyder and jupyter notebook, but the kernel dies in both cases.

I tested the identical code on my previous machine (Ubuntu 16.04) without any problem, but it kills the kernel now. (Currently I have only Windows so I cannot test it on Ubuntu again).

I deleted Anaconda and re-installed, but nothing changed.

```python
from numba import jit
import numpy as np

def ident_loops(x):
    r = np.empty_like(x)
    n = len(x)
    for i in range(n):
        r[i] = np.cos(x[i]) ** 2 + np.sin(x[i]) ** 2
    return r

@jit
def jident_loops(x):
    r = np.empty_like(x)
    n = len(x)
    for i in range(n):
        r[i] = np.cos(x[i]) ** 2 + np.sin(x[i]) ** 2
    return r

print(ident_loops(np.arange(1000)))
print(jident_loops(np.arange(1000)))
```

Any possible solution to this...?

Added: Some other examples, e.g., at [documentation](http://numba.pydata.org/numba-doc/0.38.0/user/examples.html) seem to work without problem. So now I guess my test code is seriously problematic...?


Discussion:
Thanks for the report. I can reproduce, will look into it.
@stuartarchibald Thanks. I also found that the same code works for `jident_loops(np.arange(n))` for n < 8, but it kills the kernel from n = 8.
Thanks, I believe that would be the case, this is a SVML related problem (SVML does vector math intrinsics, if you have n<8, chances are it is taking a different path depending on hardware etc). As a work around you can do:
```python
import os
os.environ['NUMBA_DISABLE_INTEL_SVML']  = '1'
import numba
```
so that it is disabled before Numba is initialised.

I've pinned it down to an illegal instruction being generated and executed, this is why the kernel dies.
@anton-malakhov any ideas? It's invariant of ipython/jupyter and reproduces in the standard CPython interpreter. The machine I'm reproducing on has a haswell chip and windows 7. Doesn't seem to matter which `icc_rt` is in use (defaults or Numba/Intel version). Conda setup line is:
```
conda create -n issue3016 numba jupyter numpy=1.14.3
```
running and attaching a debugger I can see:
```
Unhandled exception at <address provided if useful> svml_dispmd.dll in python.exe:
0xC000001D: Illegal Instruction
```
and I can provide the disassembly at that location if requested. Thanks.
Spoofing CPUs, seems like Westmere is the highest featureset that will not produce an illegal instruction for this machine.
Thanks for the report, we are on it
First result is that Numba/LLVM generates call to `__svml_cos8_ha` (and sin), which is AVX-512 version, then it fails on zmm-enabled instruction. The suspect is LLVM code generator which sees two `ymm` and thinks it can use sin8 & cos8 calls processing "two at once". Our tests don't see it because it is an effect of combined calls.. we could extend test_svml to make sure non-avx512 processor target "avoids" `zmm`
For unfortunately still not found reason LLVM on windows selects 8 as vectorization factor. I'm looking for the source of issue. So as temporary workaround I'd suggest the following to the `__init__.py`

```python
def _try_enable_svml():
    """
    Tries to enable SVML if configuration permits use and the library is found.
    """
    if not config.DISABLE_INTEL_SVML:
        try:
            if sys.platform.startswith('linux'):
                llvmlite.binding.load_library_permanently("libsvml.so")
            elif sys.platform.startswith('darwin'):
                llvmlite.binding.load_library_permanently("libsvml.dylib")
            elif sys.platform.startswith('win'):
                llvmlite.binding.load_library_permanently("svml_dispmd")
	        llvmlite.binding.set_option('SVML3016', '-force-vector-width=4')
            else:
                return False
            llvmlite.binding.set_option('SVML', '-vector-library=SVML')
            return True
        except:
            if platform.machine == 'x86_64' and config.DEBUG:
                warnings.warn("SVML was not found/could not be loaded.")
    return False
```
To select upper bound for vectorization factor LLVM divides widest register length by the size of the smallest type it found in the loop. So the difference between windows and linux in default type of array created by np.arange.  On windows this type is int32, in Linux this type is int64. 
It is the problem of SVML vectorizer itself and it wasn't touched in SVML patch.
I will look for the fix.
Thanks for looking into this @anton-malakhov and @dvnagorny, the explanations seem good. I anticipate that a patch can be applied to the llvmlite LLVM builds independently of upstream acceptance.  As suggested above we should also augment the unit tests for SVML to check for this occurrence. Thanks again :)

Resolving patch:
diff --git a/numba/dispatcher.py b/numba/dispatcher.py
old mode 100755
new mode 100644


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
@@ -18,7 +18,7 @@
 needs_svml = unittest.skipUnless(numba.config.USING_SVML,
                                  "SVML tests need SVML to be present")
 
-# a map of vector lenghs with corresponding CPU architecture
+# a map of float64 vector lenghs with corresponding CPU architecture
 vlen2cpu = {2: 'nehalem', 4: 'haswell', 8: 'skylake-avx512'}
 
 # K: SVML functions, V: python functions which are expected to be SIMD-vectorized
@@ -74,6 +74,8 @@
     "tanh":    [np.tanh, math.tanh],
     "trunc":      [],  # np.trunc, math.trunc],
 }
+# TODO: these functions are not vectorizable with complex types
+complex_funcs_exclude = ["sqrt", "tan", "log10", "expm1", "log1p", "tanh", "log"]
 
 # remove untested entries
 svml_funcs = {k: v for k, v in svml_funcs.items() if len(v) > 0}
@@ -95,7 +97,8 @@ def func_patterns(func, args, res, dtype, mode, vlen, flags, pad=' '*8):
         arg_list = ','.join([a+'[0]' for a in args])
         body = '%s%s[0] += math.%s(%s)\n' % (pad, res, func, arg_list)
     elif mode == "numpy":
-        body = '%s%s += np.%s(%s)\n' % (pad, res, func, ','.join(args))
+        body = '%s%s += np.%s(%s)' % (pad, res, func, ','.join(args))
+        body += '.astype(np.%s)\n' % dtype if dtype.startswith('int') else '\n'
     else:
         assert mode == "range" or mode == "prange"
         arg_list = ','.join([a+'[i]' for a in args])
@@ -107,19 +110,22 @@ def func_patterns(func, args, res, dtype, mode, vlen, flags, pad=' '*8):
     # TODO: it will enable mixed usecases like prange + numpy
 
     # type specialization
-    f = func+'f' if dtype == 'float32' else func
-    v = vlen*2 if dtype == 'float32' else vlen
+    is_f32 = dtype == 'float32' or dtype == 'complex64'
+    f = func+'f' if is_f32 else func
+    v = vlen*2 if is_f32 else vlen
     # general expectations
+    prec_suff = '' if getattr(flags, 'fastmath', False) else '_ha'
     scalar_func = '$_'+f if numba.config.IS_OSX else '$'+f
-    svml_func = '__svml_%s%d%s,' % (
-                f, v, '' if getattr(flags, 'fastmath', False) else '_ha')
+    svml_func = '__svml_%s%d%s,' % (f, v, prec_suff)
     if mode == "scalar":
         contains = [scalar_func]
-        avoids = [svml_func]
-    else:  # will vectorize
+        avoids = ['__svml_', svml_func]
+    else:            # will vectorize
         contains = [svml_func]
         avoids = []  # [scalar_func] - TODO: if possible, force LLVM to prevent
-                   #                       generating the failsafe scalar paths
+                     #                     generating the failsafe scalar paths
+        if vlen != 8 and (is_f32 or dtype == 'int32'):  # Issue #3016
+            avoids += ['%zmm', '__svml_%s%d%s,' % (f, v*2, prec_suff)]
     # special handling
     if func == 'sqrt':
         if mode == "scalar":
@@ -143,17 +149,19 @@ def combo_svml_usecase(dtype, mode, vlen, flags):
 
     name = usecase_name(dtype, mode, vlen, flags)
     body = """def {name}(n):
-        ret = np.empty(n*8, dtype=np.{dtype})
-        x   = np.empty(n*8, dtype=np.{dtype})\n""".format(**locals())
-    funcs = numpy_funcs if mode == "numpy" else other_funcs
-    contains = []
-    avoids = []
-    # fill body and expectatation patterns
+        x   = np.empty(n*8, dtype=np.{dtype})
+        ret = np.empty_like(x)\n""".format(**locals())
+    funcs = set(numpy_funcs if mode == "numpy" else other_funcs)
+    if dtype.startswith('complex'):
+        funcs = funcs.difference(complex_funcs_exclude)
+    contains = set()
+    avoids = set()
+    # fill body and expectation patterns
     for f in funcs:
         b, c, a = func_patterns(f, ['x'], 'ret', dtype, mode, vlen, flags)
-        avoids += a
+        avoids.update(a)
         body += b
-        contains += c
+        contains.update(c)
     body += " "*8 + "return ret"
     # now compile and return it along with its body in __doc__  and patterns
     ldict = {}
@@ -173,16 +181,24 @@ class TestSVMLGeneration(TestCase):
 
     @classmethod
     def _inject_test(cls, dtype, mode, vlen, flags):
+        # unsupported combinations
+        if dtype.startswith('complex') and mode != 'numpy':
+            return 
+        # TODO: address skipped tests below
+        skipped = dtype.startswith('int') and vlen == 2
         args = (dtype, mode, vlen, flags)
-
         # unit test body template
+        @unittest.skipUnless(not skipped, "Not implemented")
         def test_template(self):
             fn, contains, avoids = combo_svml_usecase(*args)
             # look for specific patters in the asm for a given target
             with override_env_config('NUMBA_CPU_NAME', vlen2cpu[vlen]), \
                  override_env_config('NUMBA_CPU_FEATURES', ''):
                 # recompile for overridden CPU
-                jit = compile_isolated(fn, (numba.int64, ), flags=flags)
+                try:
+                    jit = compile_isolated(fn, (numba.int64, ), flags=flags)
+                except:
+                    raise Exception("raised while compiling "+fn.__doc__)
             asm = jit.library.get_asm_str()
             missed = [pattern for pattern in contains if not pattern in asm]
             found = [pattern for pattern in avoids if pattern in asm]
@@ -212,11 +228,16 @@ def autogenerate(cls):
                 flags.set(f)
             flag_list.append(flags)
         # main loop covering all the modes and use-cases
-        for dtype in ('float64', 'float32'):
+        for dtype in ('complex64', 'float64', 'float32', 'int32', ):
             for vlen in vlen2cpu:
                 for flags in flag_list:
                     for mode in "scalar", "range", "prange", "numpy":
                         cls._inject_test(dtype, mode, vlen, flags)
+        # mark important
+        if sys.version_info[0] > 2:
+            for n in ( "test_int32_range4_usecase",  # issue #3016
+                      ):
+                setattr(cls, n, tag("important")(getattr(cls, n)))
 
 
 TestSVMLGeneration.autogenerate()