`Instruction does not dominate all uses!` when trying to create Struct Model

I’m trying to create a numba compatible Decimal math library (it’s still in the it’s very early days). I’m currently experimenting with different approaches, but I was recommended to use a StructModel to represent the core of my dtype. Currently, I’m following the Interval Example, however I’m having some problems with getting my code to work.

Here is the calling code:

from numba import njit

@njit
def uses_decimal():
    d = Decimal(True, "123", 12, 1)
    return d

uses_decimal()

I’m getting the following exception when I try to use it:

RuntimeError: Failed in nopython mode pipeline (step: native lowering)
Instruction does not dominate all uses!
  %.141 = load i8*, i8** %.58, align 8
  ret i8* %.141

I’ve also attached my current implementation below. Does anyone know why I’m getting this LLVM error?

from contextlib import ExitStack

from numba import types
from numba.core import cgutils
from numba.extending import (
    models,
    register_model,
    typeof_impl,
    as_numba_type,
    type_callable,
    make_attribute_wrapper,
    lower_builtin,
    box,
    unbox,
    NativeValue,
)


class Decimal:

    def __init__(self, sign, base, exponent, special_code):
        self.sign = sign
        self.base = base
        self.exponent = exponent
        self.special_code = special_code


class DecimalType(types.Type):

    def __init__(self):
        super(DecimalType, self).__init__(name="Decimal")


decimal_type = DecimalType()


@typeof_impl.register(Decimal)
def typeof_index(val, c):
    return decimal_type


as_numba_type.register(Decimal, decimal_type)


@type_callable(Decimal)
def type_decimal(context):
    def typer(sign, base, exponent, special_code):
        sign_is_bool = isinstance(sign, types.Boolean)
        base_is_string = isinstance(base, types.UnicodeType)
        exponent_is_int = isinstance(exponent, types.Integer)
        special_code_is_int = isinstance(special_code, types.Integer)

        if sign_is_bool and base_is_string and exponent_is_int and special_code_is_int:
            return decimal_type
    return typer


@register_model(DecimalType)
class DecimalModel(models.StructModel):

    def __init__(self, dmm, fe_type):
        members = [
            ("sign", types.boolean),
            ("base", types.unicode_type),
            ("exponent", types.int64),
            ("special_code", types.int64),
        ]
        models.StructModel.__init__(self, dmm, fe_type, members)


make_attribute_wrapper(DecimalType, "sign", "sign")
make_attribute_wrapper(DecimalType, "base", "base")
make_attribute_wrapper(DecimalType, "exponent", "exponent")
make_attribute_wrapper(DecimalType, "special_code", "special_code")


@lower_builtin(Decimal, types.boolean, types.unicode_type, types.int64, types.int64)
def impl_decimal(context, builder, sig, args):
    typ = sig.return_type
    sign, base, exponent, special_code = args
    decimal = cgutils.create_struct_proxy(typ)(context, builder)
    decimal.sign = sign
    decimal.base = base
    decimal.exponent = exponent
    decimal.special_code = special_code
    return decimal._getvalue()


@unbox(DecimalType)
def unbox_decimal(typ, obj, c):
    is_error_ptr = cgutils.alloca_once_value(c.builder, cgutils.false_bit)
    decimal = cgutils.create_struct_proxy(typ)(c.context, c.builder)

    with ExitStack() as stack:
        sign_obj = c.pyapi.object_getattr_string(obj, "sign")
        with cgutils.early_exit_if_null(c.builder, stack, sign_obj):
            c.builder.store(cgutils.true_bit, is_error_ptr)
        sign_native = c.unbox(types.boolean, sign_obj)
        c.pyapi.decref(sign_obj)
        with cgutils.early_exit_if(c.builder, stack, sign_native.is_error):
            c.builder.store(cgutils.true_bit, is_error_ptr)

        base_obj = c.pyapi.object_getattr_string(obj, "base")
        with cgutils.early_exit_if_null(c.builder, stack, base_obj):
            c.builder.store(cgutils.true_bit, is_error_ptr)
        base_native = c.unbox(types.unicode_type, base_obj)
        c.pyapi.decref(base_obj)
        with cgutils.early_exit_if(c.builder, stack, base_native.is_error):
            c.builder.store(cgutils.true_bit, is_error_ptr)

        exponent_obj = c.pyapi.object_getattr_string(obj, "exponent")
        with cgutils.early_exit_if_null(c.builder, stack, exponent_obj):
            c.builder.store(cgutils.true_bit, is_error_ptr)
        exponent_native = c.unbox(types.int64, exponent_obj)
        c.pyapi.decref(exponent_obj)
        with cgutils.early_exit_if(c.builder, stack, exponent_native.is_error):
            c.builder.store(cgutils.true_bit, is_error_ptr)

        special_code_obj = c.pyapi.object_getattr_string(obj, "special_code")
        with cgutils.early_exit_if_null(c.builder, stack, special_code_obj):
            c.builder.store(cgutils.true_bit, is_error_ptr)
        special_code_native = c.unbox(types.int64, special_code_obj)
        c.pyapi.decref(special_code_obj)
        with cgutils.early_exit_if(c.builder, stack, special_code_native.is_error):
            c.builder.store(cgutils.true_bit, is_error_ptr)

        decimal.sign = sign_native.value
        decimal.base = base_native.value
        decimal.exponent = exponent_native.value
        decimal.special_code = special_code_native.value

        return NativeValue(decimal._getvalue(), is_error=c.builder.load(is_error_ptr))


@box(DecimalType)
def box_decimal(typ, val, c):
    ret_ptr = cgutils.alloca_once(c.builder, c.pyapi.pyobj)
    fail_obj = c.pyapi.get_null_object()

    with ExitStack() as stack:
        decimal = cgutils.create_struct_proxy(typ)(c.context, c.builder, value=val)

        sign_obj = c.box(types.boolean, decimal.sign)
        with cgutils.early_exit_if_null(c.builder, stack, sign_obj):
            c.builder.store(fail_obj, ret_ptr)

        base_obj = c.box(types.unicode_type, decimal.base)
        with cgutils.early_exit_if_null(c.builder, stack, base_obj):
            c.pyapi.decref(sign_obj)
            c.builder.store(fail_obj, ret_ptr)

        exponent_obj = c.box(types.int64, decimal.exponent)
        with cgutils.early_exit_if_null(c.builder, stack, exponent_obj):
            c.pyapi.decref(sign_obj)
            c.pyapi.decref(base_obj)
            c.builder.store(fail_obj, ret_ptr)

        special_code_obj = c.box(types.int64, decimal.special_code)
        with cgutils.early_exit_if_null(c.builder, stack, special_code_obj):
            c.pyapi.decref(sign_obj)
            c.pyapi.decref(base_obj)
            c.pyapi.decref(exponent_obj)
            c.builder.store(fail_obj, ret_ptr)

        class_obj = c.pyapi.unserialize(c.pyapi.serialize_object(Decimal))
        with cgutils.early_exit_if_null(c.builder, stack, class_obj):
            c.pyapi.decref(sign_obj)
            c.pyapi.decref(base_obj)
            c.pyapi.decref(exponent_obj)
            c.pyapi.decref(special_code_obj)
            c.builder.store(fail_obj, ret_ptr)

        # NOTE: The result of this call is not checked as the cleanup
        # has to occur regardless of whether it is successful. If it
        # fails `res` is set to NULL and a Python exception is set.
        res = c.pyapi.call_function_objargs(class_obj, (sign_obj, base_obj, exponent_obj, special_code_obj))
        c.pyapi.decref(sign_obj)
        c.pyapi.decref(base_obj)
        c.pyapi.decref(exponent_obj)
        c.pyapi.decref(special_code_obj)
        c.pyapi.decref(class_obj)
        c.builder.store(res, ret_ptr)

        return c.builder.load(ret_ptr)

I forgot to post the full error message; here it is!

Exception origin:
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/llvmlite/binding/module.py", line 115, in verify
    raise RuntimeError(str(outmsg))

  warnings.warn(msg,
Traceback (most recent call last):
  File "/home/kai/Projects/nb-decimal/nb_decimal/nb_decimal.py", line 196, in <module>
    uses_decimal()
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 487, in _compile_for_args
    raise e
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 420, in _compile_for_args
    return_val = self.compile(tuple(argtypes))
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 965, in compile
    cres = self._compiler.compile(args, return_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 125, in compile
    status, retval = self._compile_cached(args, return_type)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 139, in _compile_cached
    retval = self._compile_core(args, return_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/dispatcher.py", line 152, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler.py", line 770, in compile_extra
    return pipeline.compile_extra(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler.py", line 461, in compile_extra
    return self._compile_bytecode()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler.py", line 529, in _compile_bytecode
    return self._compile_core()
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler.py", line 508, in _compile_core
    raise e
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler.py", line 495, in _compile_core
    pm.run(self.state)
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler_machinery.py", line 368, in run
    raise patched_exception
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler_machinery.py", line 356, in run
    self._runPass(idx, pass_inst, state)
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler_machinery.py", line 311, in _runPass
    mutated |= check(pss.run_pass, internal_state)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/compiler_machinery.py", line 273, in check
    mangled = func(compiler_state)
              ^^^^^^^^^^^^^^^^^^^^
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/typed_passes.py", line 468, in run_pass
    lower.create_cpython_wrapper(flags.release_gil)
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/lowering.py", line 297, in create_cpython_wrapper
    self.context.create_cpython_wrapper(self.library, self.fndesc,
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/cpu.py", line 191, in create_cpython_wrapper
    library.add_ir_module(wrapper_module)
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/numba/core/codegen.py", line 729, in add_ir_module
    ll_module.verify()
  File "/home/kai/.cache/pypoetry/virtualenvs/nb-decimal-6XUNvw28-py3.11/lib/python3.11/site-packages/llvmlite/binding/module.py", line 115, in verify
    raise RuntimeError(str(outmsg))
RuntimeError: Failed in nopython mode pipeline (step: native lowering)
Instruction does not dominate all uses!
  %.141 = load i8*, i8** %.58, align 8
  ret i8* %.141

Something is wrong with the IR generation. You are probably using a variable that has not been initialized. To fix this, you should return after exiting the `ExitStack’ context.

@unbox(DecimalType)
def unbox_decimal(typ, obj, c):
    ...
    with ExitStack() as stack:
        ...
    return NativeValue(decimal._getvalue(), is_error=c.builder.load(is_error_ptr))


@box(DecimalType)
def box_decimal(typ, val, c):
    ...
    with ExitStack() as stack:
        ...
    return c.builder.load(ret_ptr)