Repository: numba/numba

Issue:
Numba master breaks cuDF
Failures with cuDF branch-0.20 with Numba master:

```
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[float32] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[float64] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[int16] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[int32] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[int64] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[uint16] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[uint32] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[uint64] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
FAILED cudf/tests/test_udf_binops.py::test_generic_ptx[uint8] - numba.cuda.cudadrv.error.NvvmError: Failed to compile
```

because of e.g.:

```
__________________________________________________________________________________________________________________________________________________ test_generic_ptx[uint8] __________________________________________________________________________________________________________________________________________________

dtype = 'uint8'

    @pytest.mark.parametrize(
        "dtype", sorted(list(dtypeutils.NUMERIC_TYPES - {"int8"}))
    )
    def test_generic_ptx(dtype):
    
        size = 500
    
        lhs_arr = np.random.random(size).astype(dtype)
        lhs_col = Series(lhs_arr)._column
    
        rhs_arr = np.random.random(size).astype(dtype)
        rhs_col = Series(rhs_arr)._column
    
        @numba.cuda.jit(device=True)
        def generic_function(a, b):
            return a ** 3 + b
    
        nb_type = numpy_support.from_dtype(np.dtype(dtype))
        type_signature = (nb_type, nb_type)
    
        result = generic_function.compile(type_signature)
>       ptx = generic_function.inspect_ptx(type_signature)

cudf/tests/test_udf_binops.py:41: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../numba/numba/cuda/compiler.py:319: in inspect_ptx
    ptx = nvvm.llvm_to_ptx(llvmir, opt=opt, arch=arch, **nvvm_options)
../../../numba/numba/cuda/cudadrv/nvvm.py:708: in llvm_to_ptx
    ptx = cu.compile(**opts)
../../../numba/numba/cuda/cudadrv/nvvm.py:294: in compile
    self._try_error(err, 'Failed to compile\n')
../../../numba/numba/cuda/cudadrv/nvvm.py:312: in _try_error
    self.driver.check_error(err, "%s\n%s" % (msg, self.get_log()))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <numba.cuda.cudadrv.nvvm.NVVM object at 0x7f31684ae910>, error = 9, msg = "Failed to compile\n\n<unnamed> (85, 24): parse invalid redefinition of function '_ZN5numba7cpython7numbers14int_power_impl12$3clocals$3e14int_power$2416Eyx'", exit = False

    def check_error(self, error, msg, exit=False):
        if error:
            exc = NvvmError(msg, RESULT_CODE_NAMES[error])
            if exit:
                print(exc)
                sys.exit(1)
            else:
>               raise exc
E               numba.cuda.cudadrv.error.NvvmError: Failed to compile
E               
E               <unnamed> (85, 24): parse invalid redefinition of function '_ZN5numba7cpython7numbers14int_power_impl12$3clocals$3e14int_power$2416Eyx'
E               NVVM_ERROR_COMPILATION

../../../numba/numba/cuda/cudadrv/nvvm.py:192: NvvmError
```

Minimal reproducer:

```python
from numba import cuda, types

@cuda.jit(device=True)
def generic_function(a, b):
    return a ** 3 + b

type_signature = (types.int8, types.int8)
generic_function.compile(type_signature)
generic_function.inspect_ptx(type_signature)
```

produces:

```
$ python repro.py 
Traceback (most recent call last):
  File "repro.py", line 11, in <module>
    generic_function.inspect_ptx(type_signature)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 319, in inspect_ptx
    ptx = nvvm.llvm_to_ptx(llvmir, opt=opt, arch=arch, **nvvm_options)
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/nvvm.py", line 708, in llvm_to_ptx
    ptx = cu.compile(**opts)
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/nvvm.py", line 294, in compile
    self._try_error(err, 'Failed to compile\n')
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/nvvm.py", line 312, in _try_error
    self.driver.check_error(err, "%s\n%s" % (msg, self.get_log()))
  File "/home/gmarkall/numbadev/numba/numba/cuda/cudadrv/nvvm.py", line 192, in check_error
    raise exc
numba.cuda.cudadrv.error.NvvmError: Failed to compile

<unnamed> (85, 24): parse invalid redefinition of function '_ZN5numba7cpython7numbers14int_power_impl12$3clocals$3e13int_power$242Exx'
NVVM_ERROR_COMPILATION
```


Discussion:
11a739762600a1d6f1ab75988876a48c015c391f is responsible for this.
This is from PR #6735.
Actually compilation is not needed to trigger this, it's just a bug in `inspect_ptx`:

```python
from numba import cuda, int8

@cuda.jit(device=True)
def generic_function(a):
    return a ** 3

generic_function.inspect_ptx((int8,))
```

will produce the same error.
The issue is that device functions generate PTX by calling `llvm_to_ptx` on their IR returned by their own `inspect_llvm()` method: https://github.com/numba/numba/blob/39271156a52c58ca18b15aebcb1c85e4a07e49ed/numba/cuda/compiler.py#L312-L320

The `inspect_llvm()` method concatenates the IR for all modules so that the user gets to see it all - however, this isn't in the right form for compilation any more - instead the `inspect_ptx()` function should use the `CompileResult`'s code library to get the PTX.
Starter patch:

```diff
diff --git a/numba/cuda/compiler.py b/numba/cuda/compiler.py
index 41f3058a6..07f1424d2 100644
--- a/numba/cuda/compiler.py
+++ b/numba/cuda/compiler.py
@@ -290,12 +290,8 @@ class DeviceFunctionTemplate(serialize.ReduceMixin):
         -------
         llvmir : str
         """
-        # Force a compilation to occur if none has yet - this can be needed if
-        # the user attempts to inspect LLVM IR or PTX before the function has
-        # been called for the given arguments from a jitted kernel.
-        self.compile(args)
-        cres = self._compileinfos[args]
-        return "\n\n".join([str(mod) for mod in cres.library.modules])
+        modules = self.compile(args).library.modules
+        return "\n\n".join([str(mod) for mod in modules])
 
     def inspect_ptx(self, args, nvvm_options={}):
         """Returns the PTX compiled for *args* for the currently active GPU
@@ -309,15 +305,7 @@ class DeviceFunctionTemplate(serialize.ReduceMixin):
         -------
         ptx : bytes
         """
-        llvmir = self.inspect_llvm(args)
-        # Make PTX
-        cuctx = get_context()
-        device = cuctx.device
-        cc = device.compute_capability
-        arch = nvvm.get_arch_option(*cc)
-        opt = 3 if self.opt else 0
-        ptx = nvvm.llvm_to_ptx(llvmir, opt=opt, arch=arch, **nvvm_options)
-        return ptx
+        return self.compile(args).library.get_asm_str()
```

Needs a test case adding and also fixing up tests that decode the PTX (or alternatively encoding the result first).

Resolving patch:
diff --git a/numba/cuda/compiler.py b/numba/cuda/compiler.py
--- a/numba/cuda/compiler.py
+++ b/numba/cuda/compiler.py
@@ -25,7 +25,7 @@
 import numba
 from .cudadrv.devices import get_context
 from .cudadrv.libs import get_cudalib
-from .cudadrv import nvvm, driver
+from .cudadrv import driver
 from .errors import missing_launch_config_msg, normalize_kernel_dimensions
 from .api import get_current_device
 from .args import wrap_arg
@@ -297,8 +297,13 @@ def compile(self, args):
         Returns the `CompileResult`.
         """
         if args not in self.overloads:
+            nvvm_options = {
+                'opt': 3 if self.opt else 0,
+                'debug': self.debug,
+            }
+
             cres = compile_cuda(self.py_func, None, args, debug=self.debug,
-                                inline=self.inline)
+                                inline=self.inline, nvvm_options=nvvm_options)
             first_definition = not self.overloads
             self.overloads[args] = cres
             libs = [cres.library]
@@ -327,12 +332,8 @@ def inspect_llvm(self, args):
         -------
         llvmir : str
         """
-        # Force a compilation to occur if none has yet - this can be needed if
-        # the user attempts to inspect LLVM IR or PTX before the function has
-        # been called for the given arguments from a jitted kernel.
-        self.compile(args)
-        cres = self.overloads[args]
-        return "\n\n".join([str(mod) for mod in cres.library.modules])
+        modules = self.compile(args).library.modules
+        return "\n\n".join([str(mod) for mod in modules])
 
     def inspect_ptx(self, args, nvvm_options={}):
         """Returns the PTX compiled for *args* for the currently active GPU
@@ -346,15 +347,15 @@ def inspect_ptx(self, args, nvvm_options={}):
         -------
         ptx : bytes
         """
-        llvmir = self.inspect_llvm(args)
-        # Make PTX
-        cuctx = get_context()
-        device = cuctx.device
-        cc = device.compute_capability
-        arch = nvvm.get_arch_option(*cc)
-        opt = 3 if self.opt else 0
-        ptx = nvvm.llvm_to_ptx(llvmir, opt=opt, arch=arch, **nvvm_options)
-        return ptx
+        msg = ('inspect_ptx for device functions is deprecated. Use '
+               'compile_ptx instead.')
+        warn(msg, category=NumbaDeprecationWarning)
+
+        if nvvm_options:
+            msg = ('nvvm_options are ignored. Use compile_ptx if you want to '
+                   'set NVVM options.')
+            warn(msg, category=NumbaDeprecationWarning)
+        return self.compile(args).library.get_asm_str().encode()
 
 
 def compile_device_dispatcher(pyfunc, debug=False, inline=False, opt=True):


Test patch:
diff --git a/numba/cuda/tests/cudapy/test_inspect.py b/numba/cuda/tests/cudapy/test_inspect.py
--- a/numba/cuda/tests/cudapy/test_inspect.py
+++ b/numba/cuda/tests/cudapy/test_inspect.py
@@ -1,4 +1,5 @@
 import numpy as np
+import warnings
 
 from io import StringIO
 from numba import cuda, float32, float64, int32, intp
@@ -204,6 +205,49 @@ def f(x):
         msg = 'ptx will always return a dict in future'
         self.assertIn(msg, str(warns.warnings[0]))
 
+    def test_inspect_ptx(self):
+        # Ensure that inspect_ptx works - see issue #6950
+        @cuda.jit(device=True)
+        def use_power(x):
+            return x ** 3
+
+        ptx = use_power.inspect_ptx((int32,)).decode()
+
+        # A simple sanity check - look for multiplications used to implement
+        # `x ** 3` as `x * x * x`.
+        self.assertIn('mul.wide.s32', ptx)
+        self.assertIn('mul.lo.s64', ptx)
+
+    def test_inspect_ptx_deprecation(self):
+        # Make sure we warn the user that inspect_ptx is deprecated and tell
+        # them to use compile_ptx instead.
+        @cuda.jit(device=True)
+        def use_power(x):
+            return x ** 3
+
+        with warnings.catch_warnings(record=True) as w:
+            use_power.inspect_ptx((int32,))
+
+        self.assertEqual(w[0].category, NumbaDeprecationWarning)
+        self.assertIn('Use compile_ptx instead', str(w[0].message))
+
+    def test_inspect_ptx_nvvm_options_deprecation(self):
+        # Ensure that we warn the user of the deprecation of inspect_ptx and
+        # that nvvm_options are ignored if they pass nvvm_options to
+        # inspect_ptx.
+        @cuda.jit(device=True)
+        def use_power(x):
+            return x ** 3
+
+        with warnings.catch_warnings(record=True) as w:
+            nvvm_options = {'opt': 0}
+            use_power.inspect_ptx((int32,), nvvm_options=nvvm_options)
+
+        self.assertEqual(w[0].category, NumbaDeprecationWarning)
+        self.assertIn('Use compile_ptx instead', str(w[0].message))
+        self.assertEqual(w[1].category, NumbaDeprecationWarning)
+        self.assertIn('nvvm_options are ignored', str(w[1].message))
+
 
 if __name__ == '__main__':
     unittest.main()