Repository: numba/numba

Issue:
simple recursion throws LLVM exception in 0.29.0
My original goal for recursion from issue #136 was working after the introduction of support for simple recursion, but now in v 0.29.0 this simple example has broken:

```
import numpy as np
from numba import jit

@jit(nopython=True, nogil=True)
def _updateNodeRecursive2(last):
    if last > 3:
        return

    for i in range(4):
        last += 1
        _updateNodeRecursive2(last)

@jit(nopython=True, nogil=True)
def _constructTree2():
    last = np.int32(-1)
    _updateNodeRecursive2(last)

def test():
    _constructTree2()
```

produces:

```
python: /test/llvm-3.8.1.src/lib/ExecutionEngine/ExecutionEngine.cpp:218: void llvm::ExecutionEngine::addGlobalMapping(llvm::StringRef, uint64_t): Assertion `(!CurVal || !Addr) && "GlobalMapping already established!"' failed.
Aborted (core dumped)
```


Discussion:
Can you tell us which python version and OS you are using?  I tried on Mac and Linux with python3.5 and cannot reproduce the problem. 
This is python 2.7.10, both on Linux 2.6.32-642.6.2.el6.x86_64 and Linux 2.6.32-279.11.1.el6.x86_64.

llvmlite (0.14.0), LLVM version 3.8.1


Are you using the conda packages?  Or, did you build numba, llvmlite, or llvm from source?
Does the error occur by just running `test()` from the snippet?

I tested with centos5 2.6.18-410.el5 x86_64 and newer ubuntu14.04 3.13.0-32 x86_64 and still can't reproduce the problem.  

Since there's nogil=True, did you see the problem in a multithreaded execution?


This is built from source, not conda. Yes the error occurs just as:

```
$ python
Python 2.7.10 (default, Dec  3 2015, 16:18:18)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test2
>>> test2.test()
python: /dnelson/test/llvm-3.8.1.src/lib/ExecutionEngine/ExecutionEngine.cpp:218: void llvm::ExecutionEngine::addGlobalMapping(llvm::StringRef, uint64_t): Assertion `(!CurVal || !Addr) && "GlobalMapping already established!"' failed.
Aborted
```

I have deleted the `nogil=True` and the behavior is identical.
I have also verified the same behavior on python 2.7.12 on "windows bash"/Ubuntu:

```
$ python
Python 2.7.12 (default, Dec  6 2016, 16:01:22)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test2
>>> test2.test()
python2.7.12: /home/dnelson/llvm-3.8.1.src/lib/ExecutionEngine/ExecutionEngine.cpp:218: void llvm::ExecutionEngine::addGlobalMapping(llvm::StringRef, uint64_t): Assertion `(!CurVal || !Addr) && "GlobalMapping already established!"' failed.
Aborted (core dumped)
$ uname -a
Linux HOSTNAME 3.4.0+ #1 PREEMPT Thu Aug 1 17:06:05 CST 2013 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:        14.04
Codename:       trusty
```

I am surprised we cannot reproduce?
The conda build of LLVM applies 4 patches from the LLVM bug tracker that have not been merged yet:

https://github.com/numba/llvmlite/blob/master/conda-recipes/llvmdev/meta.yaml#L16-L23

(The comments give links for each patch.)

I think the next step will be for @sklam to build without those patches and see if the problem appears.
I have verified the problem is not present in the conda packages (on the same system):

```
Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jul  2 2016, 17:42:40)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux2

conda                     4.2.13                   py27_0
llvmlite                  0.14.0                   py27_0
numba                     0.29.0              np111py27_0
```

I cannot see that any of those patches have anything to do with the error, but I guess it must be somewhere in there.
The only other possibly relevant difference is that we compile LLVM with gcc 4.8 on CentOS 5.
I can reproduce now.  Our build recipe didn't enable assertion.  Building with assertion reveals the error.

Resolving patch:
diff --git a/numba/targets/codegen.py b/numba/targets/codegen.py
--- a/numba/targets/codegen.py
+++ b/numba/targets/codegen.py
@@ -415,7 +415,7 @@ class RuntimeLinker(object):
     PREFIX = '.numba.unresolved$'
 
     def __init__(self):
-        self._unresolved = defaultdict(list)
+        self._unresolved = utils.UniqueDict()
         self._defined = set()
         self._resolved = []
 
@@ -429,11 +429,14 @@ def scan_unresolved_symbols(self, module, engine):
         for gv in module.global_variables:
             if gv.name.startswith(prefix):
                 sym = gv.name[len(prefix):]
+                # Avoid remapping to existing GV
+                if engine.get_global_value_address(gv.name):
+                    continue
                 # Allocate a memory space for the pointer
                 abortfn = engine.get_function_address('nrt_unresolved_abort')
                 ptr = ctypes.c_void_p(abortfn)
                 engine.add_global_mapping(gv, ctypes.addressof(ptr))
-                self._unresolved[sym].append(ptr)
+                self._unresolved[sym] = ptr
 
     def scan_defined_symbols(self, module):
         """
@@ -454,9 +457,9 @@ def resolve(self, engine):
             # Get runtime address
             fnptr = engine.get_function_address(name)
             # Fix all usage
-            for ptr in self._unresolved[name]:
-                ptr.value = fnptr
-                self._resolved.append((name, ptr))   # keep ptr alive
+            ptr = self._unresolved[name]
+            ptr.value = fnptr
+            self._resolved.append((name, ptr))   # keep ptr alive
             # Delete resolved
             del self._unresolved[name]
 


Test patch: