Repository: numba/numba

Issue:
LLVM ERROR with `any()` + `cache=True`
Suppose we have a script file called "cache_test.py":

```py
from numba import jit

@jit(nopython=True, cache=True)
def test(a):
    return (a < 0).any()
```

Run the function `test`:

First time:

```py
>>> import numpy as np
>>> from cache_test import test
>>> a = np.arange(10)
>>> test(a)
False
```

Then quit Python.

Second time:

```py
>>> import numpy as np
>>> from cache_test import test
>>> a = np.arange(10)
>>> test(a)
LLVM ERROR: Program used external function '__ZN5numba7targets9arraymath6np_any12$3clocals$3e12flat_any$242E5ArrayIbLi1E1C7mutable7alignedE' which could not be resolved!
```

```py
>>> numba.__version__
'0.31.0'
```



Discussion:
The same error occurs with `0.30.1`, where the error message is easier to read:

```py
LLVM ERROR: Program used external function '_numba.targets.arraymath.np_any.<locals>.flat_any$2.array(bool,_1d,_C)' which could not be resolved!
```

I am able to replicate the error.

Btw, we borrowed the C++ mangling scheme.  So you can use `c++filt` to demangle the name:

```bash
$ c++filt '__ZN5numba7targets9arraymath6np_any12$3clocals$3e12flat_any$242E5ArrayIbLi1E1C7mutable7alignedE'
numba::targets::arraymath::np_any::$3clocals$3e::flat_any$242(Array<bool, 1, C, mutable, aligned>)
```
The error is due to the inner function (the impl for np.any) is not linked into the caller.  In fact, all ~~`@overload`~~ (Correction:`@overload_method`) decorated method has this problem.

Resolving patch:
diff --git a/numba/caching.py b/numba/caching.py
--- a/numba/caching.py
+++ b/numba/caching.py
@@ -184,6 +184,28 @@ def from_function(cls, py_func, py_file):
         return self
 
 
+class _UserProvidedCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
+    """
+    A locator that always point to the user provided directory in
+    `numba.config.CACHE_DIR`
+    """
+    def __init__(self, py_func, py_file):
+        self._py_file = py_file
+        self._lineno = py_func.__code__.co_firstlineno
+        subpath = os.path.dirname(self._py_file).lstrip(os.path.sep)
+        self._cache_path = os.path.join(config.CACHE_DIR, subpath)
+
+    def get_cache_path(self):
+        return self._cache_path
+
+    @classmethod
+    def from_function(cls, py_func, py_file):
+        if not config.CACHE_DIR:
+            return
+        parent = super(_UserProvidedCacheLocator, cls)
+        return parent.from_function(py_func, py_file)
+
+
 class _InTreeCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
     """
     A locator for functions backed by a regular Python module with a
@@ -283,7 +305,9 @@ class _CacheImpl(object):
     - control the filename of the cache.
     - provide the cache locator
     """
-    _locator_classes = [_InTreeCacheLocator, _UserWideCacheLocator,
+    _locator_classes = [_UserProvidedCacheLocator,
+                        _InTreeCacheLocator,
+                        _UserWideCacheLocator,
                         _IPythonCacheLocator]
 
     def __init__(self, py_func):
diff --git a/numba/config.py b/numba/config.py
--- a/numba/config.py
+++ b/numba/config.py
@@ -116,6 +116,10 @@ def _readenv(name, ctor, default):
         # Enable logging of cache operation
         DEBUG_CACHE = _readenv("NUMBA_DEBUG_CACHE", int, DEBUG)
 
+        # Redirect cache directory
+        # Contains path to the directory
+        CACHE_DIR = _readenv("NUMBA_CACHE_DIR", str, "")
+
         # Enable tracing support
         TRACE = _readenv("NUMBA_TRACE", int, 0)
 
diff --git a/numba/typing/templates.py b/numba/typing/templates.py
--- a/numba/typing/templates.py
+++ b/numba/typing/templates.py
@@ -504,6 +504,10 @@ def method_impl(context, builder, sig, args):
             disp_type = types.Dispatcher(disp)
             sig = disp_type.get_call_type(typing_context, sig.args, {})
             call = context.get_function(disp_type, sig)
+            # Link dependent library
+            cg = context.codegen()
+            for lib in getattr(call, 'libs', ()):
+                cg.add_linking_library(lib)
             return call(builder, args)
 
     def _resolve(self, typ, attr):


Test patch:
diff --git a/numba/tests/test_extending.py b/numba/tests/test_extending.py
--- a/numba/tests/test_extending.py
+++ b/numba/tests/test_extending.py
@@ -1,20 +1,22 @@
 from __future__ import print_function, division, absolute_import
 
-from collections import namedtuple
 import math
 import sys
 import pickle
+import multiprocessing
 
 import numpy as np
 
 from numba import unittest_support as unittest
-from numba import jit, types, errors, typeof, numpy_support, cgutils, typing
+from numba import jit, types, errors, typing
 from numba.compiler import compile_isolated
-from .support import TestCase, captured_stdout, tag
+from .support import (TestCase, captured_stdout, tag, temp_directory,
+                      override_config)
 
 from numba.extending import (typeof_impl, type_callable,
                              lower_builtin, lower_cast,
                              overload, overload_attribute,
+                             overload_method,
                              models, register_model,
                              box, unbox, NativeValue,
                              make_attribute_wrapper,
@@ -23,7 +25,6 @@
                              )
 from numba.typing.templates import (
     ConcreteTemplate, signature, infer)
-from numba.targets.imputils import impl_ret_borrowed
 
 # Pandas-like API implementation
 from .pdlike_usecase import Index, Series
@@ -208,6 +209,17 @@ def len_impl(arg):
         return len_impl
 
 
+@overload_method(MyDummyType, 'length')
+def overload_method_length(arg):
+    def imp(arg):
+        return len(arg)
+    return imp
+
+
+def cache_overload_method_usecase(x):
+    return x.length()
+
+
 def call_func1_nullary():
     return func1()
 
@@ -461,6 +473,63 @@ def test_no_cpython_wrapper(self):
         self.assertIn(expectmsg, errmsg)
 
 
+def _assert_cache_stats(cfunc, expect_hit, expect_misses):
+    hit = cfunc._cache_hits[cfunc.signatures[0]]
+    if hit != expect_hit:
+        raise AssertionError('cache not used')
+    miss = cfunc._cache_misses[cfunc.signatures[0]]
+    if miss != expect_misses:
+        raise AssertionError('cache not used')
+
+
+class TestOverloadMethodCaching(TestCase):
+    # Nested multiprocessing.Pool raises AssertionError:
+    # "daemonic processes are not allowed to have children"
+    _numba_parallel_test_ = False
+
+    def test_caching_overload_method(self):
+        self._cache_dir = temp_directory(self.__class__.__name__)
+        with override_config('CACHE_DIR', self._cache_dir):
+            self.run_caching_overload_method()
+
+    def run_caching_overload_method(self):
+        cfunc = jit(nopython=True, cache=True)(cache_overload_method_usecase)
+        self.assertPreciseEqual(cfunc(MyDummy()), 13)
+        _assert_cache_stats(cfunc, 0, 1)
+        llvmir = cfunc.inspect_llvm((mydummy_type,))
+        # Ensure the inner method is not a declaration
+        decls = [ln for ln in llvmir.splitlines()
+                 if ln.startswith('declare') and 'overload_method_length' in ln]
+        self.assertEqual(len(decls), 0)
+        # Test in a separate process
+        try:
+            ctx = multiprocessing.get_context('spawn')
+        except AttributeError:
+            ctx = multiprocessing
+        q = ctx.Queue()
+        p = ctx.Process(target=run_caching_overload_method,
+                        args=(q, self._cache_dir))
+        p.start()
+        q.put(MyDummy())
+        p.join()
+        # Ensure subprocess exited normally
+        self.assertEqual(p.exitcode, 0)
+        res = q.get(timeout=1)
+        self.assertEqual(res, 13)
+
+
+def run_caching_overload_method(q, cache_dir):
+    """
+    Used by TestOverloadMethodCaching.test_caching_overload_method
+    """
+    with override_config('CACHE_DIR', cache_dir):
+        arg = q.get()
+        cfunc = jit(nopython=True, cache=True)(cache_overload_method_usecase)
+        res = cfunc(arg)
+        q.put(res)
+        # Check cache stat
+        _assert_cache_stats(cfunc, 1, 0)
+
 class TestIntrinsic(TestCase):
     def test_ll_pointer_cast(self):
         """