Repository: numba/numba

Issue:
Trouble transferring array with string values on cuda
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->
Hello! First off, thanks for developing and maintaining numba!

As title. Here is the code.
```python
from numba import cuda
import numpy as np

@cuda.jit
def gpu_run(gpu_result,N):
  idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x 
  if (idx < N):
    gpu_result[idx] = 'a'

def main():
  gpu_result = cuda.to_device(np.zeros(9).astype("<U12"))
  gpu_run[2, 4](gpu_result,8)
  print(gpu_result.copy_to_host())
  cuda.synchronize()

if __name__ == "__main__":
  main()
```
it produces the following output:
```python
/usr/local/lib/python3.7/dist-packages/numba/cuda/cudadrv/nvvm.py in _replace_llvm_memset_usage(m)
    687     align_attr = re.search(r'align (\d+)', params[0])
    688     if not align_attr:
--> 689         raise ValueError("No alignment attribute found on memset dest")
    690     else:
    691         align = align_attr.group(1)

ValueError: No alignment attribute found on memset dest
```
<details><summary>Traceback</summary>
<p>

```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-158cd6de03c4> in <module>()
     15 
     16 if __name__ == "__main__":
---> 17   main()

9 frames
<ipython-input-20-158cd6de03c4> in main()
     10 def main():
     11   gpu_result = cuda.to_device(np.zeros(9).astype("<U12"))
---> 12   gpu_run[2, 4](gpu_result,8)
     13   print(gpu_result.copy_to_host())
     14   cuda.synchronize()

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in __call__(self, *args)
    768     def __call__(self, *args):
    769         return self.dispatcher.call(args, self.griddim, self.blockdim,
--> 770                                     self.stream, self.sharedmem)
    771 
    772 

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in call(self, args, griddim, blockdim, stream, sharedmem)
    859         argtypes = tuple(
    860             [self.typingctx.resolve_argument_type(a) for a in args])
--> 861         kernel = self.compile(argtypes)
    862         kernel.launch(args, griddim, blockdim, stream, sharedmem)
    863 

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in compile(self, sig)
    933             self.definitions[(cc, argtypes)] = kernel
    934             if self._bind:
--> 935                 kernel.bind()
    936             self.sigs.append(sig)
    937         return kernel

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in bind(self)
    574         Force binding to current CUDA context
    575         """
--> 576         self._func.get()
    577 
    578     @property

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in get(self)
    444         cufunc = self.cache.get(device.id)
    445         if cufunc is None:
--> 446             ptx = self.ptx.get()
    447 
    448             # Link

/usr/local/lib/python3.7/dist-packages/numba/cuda/compiler.py in get(self)
    414             arch = nvvm.get_arch_option(*cc)
    415             ptx = nvvm.llvm_to_ptx(self.llvmir, arch=arch,
--> 416                                    **self._extra_options)
    417             self.cache[cc] = ptx
    418             if config.DUMP_ASSEMBLY:

/usr/local/lib/python3.7/dist-packages/numba/cuda/cudadrv/nvvm.py in llvm_to_ptx(llvmir, **opts)
    519     llvmir = llvmir.replace('llvm.numba_nvvm.atomic', 'llvm.nvvm.atomic')
    520 
--> 521     llvmir = llvm39_to_34_ir(llvmir)
    522     cu.add_module(llvmir.encode('utf8'))
    523     cu.add_module(libdevice.get())

/usr/local/lib/python3.7/dist-packages/numba/cuda/cudadrv/nvvm.py in llvm39_to_34_ir(ir)
    662                 line = re_parenthesized_list.sub(
    663                     _replace_llvm_memset_usage,
--> 664                     line,
    665                     )
    666         if 'declare' in line:

/usr/local/lib/python3.7/dist-packages/numba/cuda/cudadrv/nvvm.py in _replace_llvm_memset_usage(m)
    687     align_attr = re.search(r'align (\d+)', params[0])
    688     if not align_attr:
--> 689         raise ValueError("No alignment attribute found on memset dest")
    690     else:
    691         align = align_attr.group(1)

ValueError: No alignment attribute found on memset dest
```

</p>
</details>

However, when `gpu_result` is not assigned a new value, it works.
```python
from numba import cuda
import numpy as np

@cuda.jit
def gpu_run(gpu_result,N):
  idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x 
  if (idx < N):
    pass#gpu_result[idx] = 'a'

def main():
  gpu_result = cuda.to_device(np.zeros(9).astype("<U12"))
  gpu_run[2, 4](gpu_result,8)
  print(gpu_result.copy_to_host())#['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
  cuda.synchronize()

if __name__ == "__main__":
  main()
```
Thanks.
Missing conversion from StringLiteral to unicode_type
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

This:
```python
from numba import typeof, types
from numba.core.typing import Context

x = types.literal('string literal')
print(x, type(x))
print("conversion:", x.can_convert_to(Context(), types.unicode_type))
```
does this:
```
Literal[str](string literal) <class 'numba.core.types.misc.StringLiteral'>
conversion: None
```
I think it should be possible to convert a `StringLiteral` to a `unicode_type` but not the converse.

A `can_convert_to()` method needs adding to this class:
https://github.com/numba/numba/blob/3461bcb76a479bffbadbef24b6662107229e2a8a/numba/core/types/misc.py#L40-L41

xref: https://numba.discourse.group/t/how-to-pass-string-argument-to-numba-njit-python-function-from-inside-another-whilst-specifying-signature-type/663 which hits this bug.


Discussion:
With current master, the traceback is:

```
$ python repro.py 
Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 710, in cast
    impl = self._casts.find((fromty, toty))
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 48, in find
    out = self._find(sig)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 57, in _find
    raise NotImplementedError(self, sig)
NotImplementedError: (<numba.core.base.OverloadSelector object at 0x7fbe50b3bee0>, (Literal[str](a), unicode_type))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/core/errors.py", line 744, in new_error_context
    yield
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 230, in lower_block
    self.lower_inst(inst)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 379, in lower_inst
    return self.lower_setitem(inst.target, inst.index, inst.value,
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 470, in lower_setitem
    value = self.context.cast(self.builder, value, valuety,
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 713, in cast
    raise NotImplementedError(
NotImplementedError: Cannot cast Literal[str](a) to unicode_type: %".135" = load i8*, i8** %"$const34.0"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/numbadev/issues/7041/repro.py", line 20, in <module>
    main()
  File "/home/gmarkall/numbadev/issues/7041/repro.py", line 14, in main
    gpu_run[2, 4](gpu_result, 8)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 827, in __call__
    return self.dispatcher.call(args, self.griddim, self.blockdim,
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 970, in call
    kernel = _dispatcher.Dispatcher._cuda_call(self, *args)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 978, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 1092, in compile
    kernel = _Kernel(self.py_func, argtypes, link=self.link,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 518, in __init__
    cres = compile_cuda(self.py_func, types.void, self.argtypes,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 158, in compile_cuda
    cres = compiler.compile_extra(typingctx=typingctx,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 675, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 419, in compile_extra
    return self._compile_bytecode()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 483, in _compile_bytecode
    return self._compile_core()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 462, in _compile_core
    raise e
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 453, in _compile_core
    pm.run(self.state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 339, in run
    raise patched_exception
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 330, in run
    self._runPass(idx, pass_inst, state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 289, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 262, in check
    mangled = func(compiler_state)
  File "/home/gmarkall/numbadev/numba/numba/core/typed_passes.py", line 396, in run_pass
    lower.lower()
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 136, in lower
    self.lower_normal_function(self.fndesc)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 190, in lower_normal_function
    entry_block_tail = self.lower_function_body()
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 216, in lower_function_body
    self.lower_block(block)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 230, in lower_block
    self.lower_inst(inst)
  File "/home/gmarkall/miniconda3/envs/numbanp120/lib/python3.9/contextlib.py", line 135, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/gmarkall/numbadev/numba/numba/core/errors.py", line 751, in new_error_context
    raise newerr.with_traceback(tb)
numba.core.errors.LoweringError: Failed in cuda mode pipeline (step: native lowering)
Cannot cast Literal[str](a) to unicode_type: %".135" = load i8*, i8** %"$const34.0"

File "repro.py", line 9:
def gpu_run(gpu_result, N):
    <source elided>
    if idx < N:
        gpu_result[idx] = "a"
        ^

During: lowering "gpu_result[idx] = $const34.0" at /home/gmarkall/numbadev/issues/7041/repro.py (9)
```
Strings are not supported in the CUDA target, which is the root cause of the issue - see the list of supported features in the CUDA target: https://numba.readthedocs.io/en/latest/cuda/cudapysupported.html

However, I'm looking into whether this simple use case can be supported. @JamesHoi if your reproducer above worked, are there operations you want to do with strings other than assigning them to array elements in order to use the CUDA target for your application?
xref https://github.com/numba/numba/pull/6917 which implements the type conversion.
Also xref #7052 which adds a missing (sometimes) type conversion. I still need to go back and check if this makes any difference to this issue (in conjunction with #6917 perhaps).
With current master and the commit from #6917 applied, the error is a little different, but the problem appears to be similar:

```
$ python repro.py 
/home/gmarkall/numbadev/numba/numba/cuda/compiler.py:858: NumbaPerformanceWarning: Grid size (2) < 2 * SM count (144) will likely result in GPU under utilization due to low occupancy.
  warn(NumbaPerformanceWarning(msg))
Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 710, in cast
    impl = self._casts.find((fromty, toty))
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 48, in find
    out = self._find(sig)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 57, in _find
    raise NotImplementedError(self, sig)
NotImplementedError: (<numba.core.base.OverloadSelector object at 0x7faab9e10eb0>, (unicode_type, [unichr x 12]))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/numbadev/numba/numba/core/errors.py", line 744, in new_error_context
    yield
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 230, in lower_block
    self.lower_inst(inst)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 379, in lower_inst
    return self.lower_setitem(inst.target, inst.index, inst.value,
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 473, in lower_setitem
    return impl(self.builder, (target, index, value))
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 1190, in __call__
    res = self._imp(self._context, builder, self._sig, args, loc=loc)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 1220, in wrapper
    return fn(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/np/arrayobj.py", line 506, in setitem_array
    val = context.cast(builder, val, valty, aryty.dtype)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 713, in cast
    raise NotImplementedError(
NotImplementedError: Cannot cast unicode_type to [unichr x 12]: %".158" = load {i8*, i64, i32, i32, i64, i8*, i8*}, {i8*, i64, i32, i32, i64, i8*, i8*}* %".137"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/numbadev/issues/7041/repro.py", line 20, in <module>
    main()
  File "/home/gmarkall/numbadev/issues/7041/repro.py", line 14, in main
    gpu_run[2, 4](gpu_result, 8)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 861, in __call__
    return self.dispatcher.call(args, self.griddim, self.blockdim,
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 994, in call
    kernel = _dispatcher.Dispatcher._cuda_call(self, *args)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 1002, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 1099, in compile
    kernel = _Kernel(self.py_func, argtypes, link=self.link,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 538, in __init__
    cres = compile_cuda(self.py_func, types.void, self.argtypes,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/cuda/compiler.py", line 163, in compile_cuda
    cres = compiler.compile_extra(typingctx=typingctx,
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 675, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 419, in compile_extra
    return self._compile_bytecode()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 483, in _compile_bytecode
    return self._compile_core()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 462, in _compile_core
    raise e
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 453, in _compile_core
    pm.run(self.state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 343, in run
    raise patched_exception
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 334, in run
    self._runPass(idx, pass_inst, state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 289, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 262, in check
    mangled = func(compiler_state)
  File "/home/gmarkall/numbadev/numba/numba/core/typed_passes.py", line 396, in run_pass
    lower.lower()
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 136, in lower
    self.lower_normal_function(self.fndesc)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 190, in lower_normal_function
    entry_block_tail = self.lower_function_body()
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 216, in lower_function_body
    self.lower_block(block)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 230, in lower_block
    self.lower_inst(inst)
  File "/home/gmarkall/miniconda3/envs/numbanp120/lib/python3.9/contextlib.py", line 135, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/gmarkall/numbadev/numba/numba/core/errors.py", line 754, in new_error_context
    raise newerr.with_traceback(tb)
numba.core.errors.LoweringError: Failed in cuda mode pipeline (step: native lowering)
Cannot cast unicode_type to [unichr x 12]: %".158" = load {i8*, i64, i32, i32, i64, i8*, i8*}, {i8*, i64, i32, i32, i64, i8*, i8*}* %".137"

File "repro.py", line 9:
def gpu_run(gpu_result, N):
    <source elided>
    if idx < N:
        gpu_result[idx] = "XYZ"
        ^

During: lowering "gpu_result[idx] = $const34.0" at /home/gmarkall/numbadev/issues/7041/repro.py (9)
```
Would fixing this also fix #6897?

Resolving patch:
diff --git a/numba/cuda/compiler.py b/numba/cuda/compiler.py
--- a/numba/cuda/compiler.py
+++ b/numba/cuda/compiler.py
@@ -15,10 +15,11 @@
 from numba.core.compiler import (CompilerBase, DefaultPassBuilder,
                                  compile_result, Flags, Option)
 from numba.core.compiler_lock import global_compiler_lock
-from numba.core.compiler_machinery import (LoweringPass, PassManager,
-                                           register_pass)
+from numba.core.compiler_machinery import (LoweringPass, AnalysisPass,
+                                           PassManager, register_pass)
 from numba.core.dispatcher import CompilingCounter, OmittedArg
-from numba.core.errors import NumbaInvalidConfigWarning
+from numba.core.errors import (NumbaPerformanceWarning,
+                               NumbaInvalidConfigWarning, TypingError)
 from numba.core.typed_passes import IRLegalization, NativeLowering
 from numba.core.typing.typeof import Purpose, typeof
 from warnings import warn
@@ -29,7 +30,6 @@
 from .errors import missing_launch_config_msg, normalize_kernel_dimensions
 from .api import get_current_device
 from .args import wrap_arg
-from numba.core.errors import NumbaPerformanceWarning
 from .descriptor import cuda_target
 from . import types as cuda_types
 
@@ -103,6 +103,41 @@ def run_pass(self, state):
         return True
 
 
+@register_pass(mutates_CFG=False, analysis_only=True)
+class CUDALegalization(AnalysisPass):
+
+    _name = "cuda_legalization"
+
+    def __init__(self):
+        AnalysisPass.__init__(self)
+
+    def run_pass(self, state):
+        # Early return if NVVM 7
+        from numba.cuda.cudadrv.nvvm import NVVM
+        if NVVM().is_nvvm70:
+            return False
+        # NVVM < 7, need to check for charseq
+        typmap = state.typemap
+
+        def check_dtype(dtype):
+            if isinstance(dtype, (types.UnicodeCharSeq, types.CharSeq)):
+                msg = (f"{k} is a char sequence type. This type is not "
+                       "supported with CUDA toolkit versions < 11.2. To "
+                       "use this type, you need to update your CUDA "
+                       "toolkit - try 'conda install cudatoolkit=11' if "
+                       "you are using conda to manage your environment.")
+                raise TypingError(msg)
+            elif isinstance(dtype, types.Record):
+                for subdtype in dtype.fields.items():
+                    # subdtype is a (name, _RecordField) pair
+                    check_dtype(subdtype[1].type)
+
+        for k, v in typmap.items():
+            if isinstance(v, types.Array):
+                check_dtype(v.dtype)
+        return False
+
+
 class CUDACompiler(CompilerBase):
     def define_pipelines(self):
         dpb = DefaultPassBuilder
@@ -113,6 +148,7 @@ def define_pipelines(self):
 
         typed_passes = dpb.define_typed_pipeline(self.state)
         pm.passes.extend(typed_passes.passes)
+        pm.add_pass(CUDALegalization, "CUDA legalization")
 
         lowering_passes = self.define_cuda_lowering_pipeline(self.state)
         pm.passes.extend(lowering_passes.passes)
diff --git a/numba/cuda/target.py b/numba/cuda/target.py
--- a/numba/cuda/target.py
+++ b/numba/cuda/target.py
@@ -89,7 +89,8 @@ def load_additional_registries(self):
         # side effect of import needed for numba.cpython.*, the builtins
         # registry is updated at import time.
         from numba.cpython import numbers, tupleobj, slicing # noqa: F401
-        from numba.cpython import rangeobj, iterators, unicode # noqa: F401
+        from numba.cpython import rangeobj, iterators # noqa: F401
+        from numba.cpython import unicode, charseq # noqa: F401
         from numba.cpython import cmathimpl
         from numba.np import arrayobj # noqa: F401
         from numba.np import npdatetime # noqa: F401


Test patch:
diff --git a/numba/cuda/tests/cudapy/test_const_string.py b/numba/cuda/tests/cudapy/test_const_string.py
--- a/numba/cuda/tests/cudapy/test_const_string.py
+++ b/numba/cuda/tests/cudapy/test_const_string.py
@@ -1,10 +1,14 @@
 import re
-from numba.cuda.testing import unittest, skip_on_cudasim
+import numpy as np
+from numba import config, cuda
+from numba.core.errors import TypingError
+from numba.cuda.cudadrv.nvvm import NVVM
+from numba.cuda.testing import unittest, skip_on_cudasim, CUDATestCase
 from llvmlite import ir
 
 
 @skip_on_cudasim("This is testing CUDA backend code generation")
-class TestCudaConstString(unittest.TestCase):
+class TestConstStringCodegen(unittest.TestCase):
     def test_const_string(self):
         # These imports are incompatible with CUDASIM
         from numba.cuda.descriptor import cuda_target
@@ -52,5 +56,161 @@ def test_const_string(self):
         self.assertEqual(len(matches), 1)
 
 
+# Inspired by the reproducer from Issue #7041.
+class TestConstString(CUDATestCase):
+
+    def skip_on_nvvm34(self):
+        # There is no NVVM on cudasim, but we might as well run this test.
+        if not config.ENABLE_CUDASIM and not NVVM().is_nvvm70:
+            self.skipTest('Character sequences unsupported on NVVM 3.4')
+
+    def skip_on_nvvm70(self):
+        if NVVM().is_nvvm70:
+            self.skipTest('Character sequences are permitted with NVVM 7.0')
+
+    def test_assign_const_unicode_string(self):
+        self.skip_on_nvvm34()
+
+        @cuda.jit
+        def str_assign(arr):
+            i = cuda.grid(1)
+            if i < len(arr):
+                arr[i] = "XYZ"
+
+        n_strings = 8
+        arr = np.zeros(n_strings + 1, dtype="<U12")
+        str_assign[1, n_strings](arr)
+
+        # Expected result, e.g.:
+        #     ['XYZ' 'XYZ' 'XYZ' 'XYZ' 'XYZ' 'XYZ' 'XYZ' 'XYZ' '']
+        expected = np.zeros_like(arr)
+        expected[:-1] = 'XYZ'
+        expected[-1] = ''
+        np.testing.assert_equal(arr, expected)
+
+    def test_assign_const_byte_string(self):
+        self.skip_on_nvvm34()
+
+        @cuda.jit
+        def bytes_assign(arr):
+            i = cuda.grid(1)
+            if i < len(arr):
+                arr[i] = b"XYZ"
+
+        n_strings = 8
+        arr = np.zeros(n_strings + 1, dtype="S12")
+        bytes_assign[1, n_strings](arr)
+
+        # Expected result, e.g.:
+        #     [b'XYZ' b'XYZ' b'XYZ' b'XYZ' b'XYZ' b'XYZ' b'XYZ' b'XYZ' b'']
+        expected = np.zeros_like(arr)
+        expected[:-1] = b'XYZ'
+        expected[-1] = b''
+        np.testing.assert_equal(arr, expected)
+
+    def test_assign_const_string_in_record(self):
+        self.skip_on_nvvm34()
+
+        @cuda.jit
+        def f(a):
+            a[0]['x'] = 1
+            a[0]['y'] = 'ABC'
+            a[1]['x'] = 2
+            a[1]['y'] = 'XYZ'
+
+        dt = np.dtype([('x', np.int32), ('y', np.dtype('<U12'))])
+        a = np.zeros(2, dt)
+
+        f[1, 1](a)
+
+        reference = np.asarray([(1, 'ABC'), (2, 'XYZ')], dtype=dt)
+        np.testing.assert_array_equal(reference, a)
+
+    def test_assign_const_bytes_in_record(self):
+        self.skip_on_nvvm34()
+
+        @cuda.jit
+        def f(a):
+            a[0]['x'] = 1
+            a[0]['y'] = b'ABC'
+            a[1]['x'] = 2
+            a[1]['y'] = b'XYZ'
+
+        dt = np.dtype([('x', np.float32), ('y', np.dtype('S12'))])
+        a = np.zeros(2, dt)
+
+        f[1, 1](a)
+
+        reference = np.asarray([(1, b'ABC'), (2, b'XYZ')], dtype=dt)
+        np.testing.assert_array_equal(reference, a)
+
+    @skip_on_cudasim('Const strings not disallowed on cudasim')
+    def test_const_unicode_string_disallowed_nvvm34(self):
+        self.skip_on_nvvm70()
+
+        @cuda.jit
+        def str_assign(arr):
+            arr[0] = "XYZ"
+
+        arr = np.zeros(1, dtype="<U12")
+        with self.assertRaisesRegex(TypingError, '.*is a char sequence.*'):
+            str_assign[1, 1](arr)
+
+    @skip_on_cudasim('Const strings not disallowed on cudasim')
+    def test_const_byte_string_disallowed_nvvm34(self):
+        self.skip_on_nvvm70()
+
+        @cuda.jit
+        def bytes_assign(arr):
+            arr[0] = b"XYZ"
+
+        arr = np.zeros(1, dtype="S12")
+        with self.assertRaisesRegex(TypingError, '.*is a char sequence.*'):
+            bytes_assign[1, 1](arr)
+
+    @skip_on_cudasim('Const strings not disallowed on cudasim')
+    def test_record_const_unicode_string_disallowed_nvvm34(self):
+        self.skip_on_nvvm70()
+
+        @cuda.jit
+        def str_assign(arr):
+            arr[0]['y'] = "XYZ"
+
+        dt = np.dtype([('x', np.int32), ('y', np.dtype('<U12'))])
+        arr = np.zeros(1, dtype=dt)
+
+        with self.assertRaisesRegex(TypingError, '.*is a char sequence.*'):
+            str_assign[1, 1](arr)
+
+    @skip_on_cudasim('Const strings not disallowed on cudasim')
+    def test_record_const_bytes_disallowed_nvvm34(self):
+        self.skip_on_nvvm70()
+
+        @cuda.jit
+        def str_assign(arr):
+            arr[0]['y'] = b"XYZ"
+
+        dt = np.dtype([('x', np.int32), ('y', np.dtype('S12'))])
+        arr = np.zeros(1, dtype=dt)
+
+        with self.assertRaisesRegex(TypingError, '.*is a char sequence.*'):
+            str_assign[1, 1](arr)
+
+    @skip_on_cudasim('Const strings not disallowed on cudasim')
+    def test_nested_record_const_unicode_string_disallowed_nvvm34(self):
+        self.skip_on_nvvm70()
+
+        @cuda.jit
+        def str_assign(arr):
+            arr[0]['y']['z'] = "XYZ"
+
+        subdt = np.dtype([('z', np.dtype('<U12')), ('a', np.int32)])
+        dt = np.dtype([('x', np.int32), ('y', subdt)])
+        arr = np.zeros(1, dtype=dt)
+
+        with self.assertRaisesRegex(TypingError, '.*is a char sequence.*'):
+            str_assign[1, 1](arr)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/numba/cuda/tests/cudapy/test_record_dtype.py b/numba/cuda/tests/cudapy/test_record_dtype.py
--- a/numba/cuda/tests/cudapy/test_record_dtype.py
+++ b/numba/cuda/tests/cudapy/test_record_dtype.py
@@ -81,7 +81,7 @@ def record_read_2d_array(r, a):
         ('a', np.float64),
         ('b', np.int32),
         ('c', np.complex64),
-        ('d', (np.str_, 5))
+        ('d', (np.uint8, 5))
     ],
     align=True
 )