Repository: numba/numba

Issue:
CUDA: Linking PTX only works for eager compilation
Making the following change to the PTX linking test:

```diff
diff --git a/numba/cuda/tests/cudadrv/test_linker.py b/numba/cuda/tests/cudadrv/test_linker.py
index 0a4dc1f8a..87833906c 100644
--- a/numba/cuda/tests/cudadrv/test_linker.py
+++ b/numba/cuda/tests/cudadrv/test_linker.py
@@ -73,7 +73,7 @@ class TestLinker(CUDATestCase):
 
         link = os.path.join(os.path.dirname(__file__), 'data', 'jitlink.ptx')
 
-        @cuda.jit('void(int32[:], int32[:])', link=[link])
+        @cuda.jit(link=[link])
         def foo(x, y):
             i = cuda.grid(1)
             x[i] += bar(y[i])
```

results in the following failure:

```
======================================================================
ERROR: test_linking (numba.cuda.tests.cudadrv.test_linker.TestLinker)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/driver.py", line 2707, in complete
    driver.cuLinkComplete(self.handle, byref(cubin), byref(size))
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/driver.py", line 319, in safe_cuda_api_call
    self._check_ctypes_error(fname, retcode)
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/driver.py", line 384, in _check_ctypes_error
    raise CudaAPIError(retcode, msg)
numba.cuda.cudadrv.driver.CudaAPIError: [300] Call to cuLinkComplete results in CUDA_ERROR_INVALID_SOURCE

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/devices.py", line 232, in _require_cuda_context
    return fn(*args, **kws)
  File "/home/gmarkall/numbadev/numba/numba/cuda/tests/cudadrv/test_linker.py", line 84, in test_linking
    foo[1, 1](A, B)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 723, in __call__
    return self.dispatcher.call(args, self.griddim, self.blockdim,
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 911, in call
    kernel = _dispatcher.Dispatcher._cuda_call(self, *args)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 919, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 1089, in compile
    kernel.bind()
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 466, in bind
    self._codelibrary.get_cufunc()
  File "/home/gmarkall/numbadev/numba/numba/cuda/codegen.py", line 201, in get_cufunc
    cubin = self.get_cubin(cc=device.compute_capability)
  File "/home/gmarkall/numbadev/numba/numba/cuda/codegen.py", line 178, in get_cubin
    cubin_buf, size = linker.complete()
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/driver.py", line 2709, in complete
    raise LinkerError("%s\n%s" % (e, self.error_log))
numba.cuda.cudadrv.driver.LinkerError: [300] Call to cuLinkComplete results in CUDA_ERROR_INVALID_SOURCE
error   : Undefined reference to 'bar' in '<cudapy-ptx>'
```

I note that the PTX is never linked (no call to `Linker.add_file()` is reached, but haven't investigated further.


Discussion:

Resolving patch:
diff --git a/numba/cuda/decorators.py b/numba/cuda/decorators.py
--- a/numba/cuda/decorators.py
+++ b/numba/cuda/decorators.py
@@ -107,7 +107,8 @@ def autojitwrapper(func):
                                           fastmath=fastmath)
             else:
                 def autojitwrapper(func):
-                    return jit(func, device=device, debug=debug, opt=opt, **kws)
+                    return jit(func, device=device, debug=debug, opt=opt,
+                               link=link, **kws)
 
             return autojitwrapper
         # func_or_sig is a function


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_linker.py b/numba/cuda/tests/cudadrv/test_linker.py
--- a/numba/cuda/tests/cudadrv/test_linker.py
+++ b/numba/cuda/tests/cudadrv/test_linker.py
@@ -66,14 +66,18 @@ def test_linker_basic(self):
         linker = Linker.new()
         del linker
 
-    @require_context
-    def test_linking(self):
+    def _test_linking(self, eager):
         global bar  # must be a global; other it is recognized as a freevar
         bar = cuda.declare_device('bar', 'int32(int32)')
 
         link = os.path.join(os.path.dirname(__file__), 'data', 'jitlink.ptx')
 
-        @cuda.jit('void(int32[:], int32[:])', link=[link])
+        if eager:
+            args = ['void(int32[:], int32[:])']
+        else:
+            args = []
+
+        @cuda.jit(*args, link=[link])
         def foo(x, y):
             i = cuda.grid(1)
             x[i] += bar(y[i])
@@ -85,7 +89,12 @@ def foo(x, y):
 
         self.assertTrue(A[0] == 123 + 2 * 321)
 
-    @require_context
+    def test_linking_lazy_compile(self):
+        self._test_linking(eager=False)
+
+    def test_linking_eager_compile(self):
+        self._test_linking(eager=True)
+
     def test_try_to_link_nonexistent(self):
         with self.assertRaises(LinkerError) as e:
             @cuda.jit('void(int32[::1])', link=['nonexistent.a'])
@@ -93,7 +102,6 @@ def f(x):
                 x[0] = 0
         self.assertIn('nonexistent.a not found', e.exception.args)
 
-    @require_context
     def test_set_registers_no_max(self):
         """Ensure that the jitted kernel used in the test_set_registers_* tests
         uses more than 57 registers - this ensures that test_set_registers_*
@@ -103,19 +111,16 @@ def test_set_registers_no_max(self):
         compiled = compiled.specialize(np.empty(32), *range(6))
         self.assertGreater(compiled.get_regs_per_thread(), 57)
 
-    @require_context
     def test_set_registers_57(self):
         compiled = cuda.jit(max_registers=57)(func_with_lots_of_registers)
         compiled = compiled.specialize(np.empty(32), *range(6))
         self.assertLessEqual(compiled.get_regs_per_thread(), 57)
 
-    @require_context
     def test_set_registers_38(self):
         compiled = cuda.jit(max_registers=38)(func_with_lots_of_registers)
         compiled = compiled.specialize(np.empty(32), *range(6))
         self.assertLessEqual(compiled.get_regs_per_thread(), 38)
 
-    @require_context
     def test_set_registers_eager(self):
         sig = void(float64[::1], int64, int64, int64, int64, int64, int64)
         compiled = cuda.jit(sig, max_registers=38)(func_with_lots_of_registers)