numba icon indicating copy to clipboard operation
numba copied to clipboard

llvmlite assertion when using deferred type as typed dict values

Open gmarkall opened this issue 2 years ago • 7 comments

From: https://numba.discourse.group/t/cannot-gettypeinfo-on-a-type-that-is-unsized-error-on-jitclass-with-typed-dict-variable-that-has-deferred-type-as-value-type/1191

import numba as nb
from collections import OrderedDict
from numba import deferred_type, optional
from numba.experimental import jitclass
from numba import types

node_type = deferred_type()

@jitclass([
  ('parent', optional(node_type)),
  ('children', types.DictType(types.int32, node_type)),
])
class TreeNode(object):
    def __init__(self, parent=None):
        self.parent = parent
        self.children = nb.typed.Dict.empty(types.int32, node_type)

node_type.define(TreeNode.class_type.instance_type)

t = TreeNode()
t2 = TreeNode(t2)

produces:

$ python repro.py 
reducer_override for <class 'type'>
python: /opt/conda/conda-bld/llvmdev_1637166008009/work/lib/IR/DataLayout.cpp:713: llvm::Align llvm::DataLayout::getAlignment(llvm::Type*, bool) const: Assertion `Ty->isSized() && "Cannot getTypeInfo() on a type that is unsized!"' failed.
Aborted (core dumped)

This may be an llvmlite bug or a combination of Numba doing something bad and llvmlite not catching / handling it very well, but I'm just opening the issue with the initial reproducer to start tracking it.

Note: Edits made to the the reproducer so it can run to completion once the Numba issue is resolved (the initial revision tried to pass 1 as parent, when it should have been a tree node type).

To do:

  • [ ] Create an llvmlite reproducer / issue and investigate whether the assertion failure can easily be prevented.

gmarkall avatar Aug 30 '22 09:08 gmarkall

Using a typed dict with a deferred type as the value type reproduces the issue:

from numba import deferred_type, njit, typed, void
from numba import types

node_type = deferred_type()
node_type.define(types.int32)

@njit(void())
def f():
    typed.Dict.empty(types.int32, node_type)

produces the same assertion.

gmarkall avatar Aug 30 '22 09:08 gmarkall

(partial) Python backtrace at segfault:

(gdb) py-bt
Traceback (most recent call first):
  File "/home/gmarkall/numbadev/llvmlite/llvmlite/binding/ffi.py", line 151, in __call__
    return self._cfn(*args, **kwargs)
  File "/home/gmarkall/numbadev/llvmlite/llvmlite/binding/targets.py", line 154, in get_pointee_abi_size
    size = ffi.lib.LLVMPY_ABISizeOfElementType(self, ty)
  File "/home/gmarkall/numbadev/llvmlite/llvmlite/ir/types.py", line 53, in get_abi_size
    return target_data.get_pointee_abi_size(llty)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 1105, in get_abi_sizeof
    return ty.get_abi_size(self.target_data)
  File "/home/gmarkall/numbadev/numba/numba/typed/dictobject.py", line 240, in codegen
    sz_val = context.get_abi_sizeof(ll_val)
  File "/home/gmarkall/numbadev/numba/numba/core/base.py", line 1220, in wrapper
    return fn(*args, **kwargs)
  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/lowering.py", line 1133, in _lower_call_normal
    res = impl(self.builder, argvals, self.loc)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 891, in lower_call
    res = self._lower_call_normal(fnty, expr, signature)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 1162, in lower_expr
    res = self.lower_call(resty, expr)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 626, in lower_assign
    return self.lower_expr(ty, value)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 439, in lower_inst
    val = self.lower_assign(ty, inst)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 265, in lower_block
    self.lower_inst(inst)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 251, in lower_function_body
    self.lower_block(block)
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 222, in lower_normal_function
    entry_block_tail = self.lower_function_body()
  File "/home/gmarkall/numbadev/numba/numba/core/lowering.py", line 168, in lower
    self.lower_normal_function(self.fndesc)
  File "/home/gmarkall/numbadev/numba/numba/core/typed_passes.py", line 394, in run_pass
    lower.lower()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 273, in check
    mangled = func(compiler_state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler_machinery.py", line 311, in _runPass
    mutated |= check(pss.run_pass, internal_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 356, in run
    self._runPass(idx, pass_inst, state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 486, in _compile_core
    pm.run(self.state)
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 520, in _compile_bytecode
    return self._compile_core()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 452, in compile_extra
    return self._compile_bytecode()
  File "/home/gmarkall/numbadev/numba/numba/core/compiler.py", line 716, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 152, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 139, in _compile_cached
    retval = self._compile_core(args, return_type)
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 125, in compile
    status, retval = self._compile_cached(args, return_type)
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 965, in compile
    cres = self._compiler.compile(args, return_type)
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 363, in get_call_template
    self.compile(tuple(args))

gmarkall avatar Aug 30 '22 10:08 gmarkall

The problem is that the context doesn't ensure that the model for a deferred type has been defined prior to trying to get its actual size. The following hack:

diff --git a/numba/core/base.py b/numba/core/base.py
index 9622a3f09..54740d162 100644
--- a/numba/core/base.py
+++ b/numba/core/base.py
@@ -477,7 +477,10 @@ class BaseContext(object):
         The return value is a llvmlite.ir.Type object, or None if the type
         is an opaque pointer (???).
         """
-        return self.data_model_manager[ty].get_data_type()
+        dmm = self.data_model_manager[ty]
+        if isinstance(dmm, datamodel.models.DeferredStructModel):
+            dmm._define()
+        return dmm.get_data_type()
 
     def get_value_type(self, ty):
         return self.data_model_manager[ty].get_value_type()

allows the above code to run to completion, but may not be the right place to insert the fix.

gmarkall avatar Aug 30 '22 10:08 gmarkall

I'm running into the same issue but the fix doesn't work in this case: self-contained example

With the patch, my shell reports that the Python interpreter segfaults but without any message.

Upatched numba==0.56.2 gives:

Assertion failed: (Ty->isSized() && "Cannot getTypeInfo() on a type that is unsized!"), function getTypeSizeInBits, file /Users/ci/miniconda3/envs/numba-ci/conda-bld/llvmdev_1637166012004/work/include/llvm/IR/DataLayout.h, line 653.

danijar avatar Sep 04 '22 10:09 danijar

xref #6077 (possible duplicate)

stuartarchibald avatar Sep 05 '22 10:09 stuartarchibald

It could be a duplicate or a separate issue - this maybe depends on whether we think defining a deferred type should also ensure that the data model is aware of the definition, or whether we expect anything using the data model to make sure the definition is know to the data model at the appropriate time.

gmarkall avatar Sep 06 '22 09:09 gmarkall

I just came across #6209 which is somewhat related to this.

gmarkall avatar Sep 13 '22 11:09 gmarkall

This issue is marked as stale as it has had no activity in the past 30 days. Please close this issue if no further response or action is needed. Otherwise, please respond with any updates and confirm that this issue still needs to be addressed.

github-actions[bot] avatar Oct 14 '22 02:10 github-actions[bot]

This shouldn't be stale - removing "needtriage" so it doesn't go stale again.

gmarkall avatar Oct 17 '22 13:10 gmarkall

The problem is that the context doesn't ensure that the model for a deferred type has been defined prior to trying to get its actual size. The following hack:

diff --git a/numba/core/base.py b/numba/core/base.py
index 9622a3f09..54740d162 100644
--- a/numba/core/base.py
+++ b/numba/core/base.py
@@ -477,7 +477,10 @@ class BaseContext(object):
         The return value is a llvmlite.ir.Type object, or None if the type
         is an opaque pointer (???).
         """
-        return self.data_model_manager[ty].get_data_type()
+        dmm = self.data_model_manager[ty]
+        if isinstance(dmm, datamodel.models.DeferredStructModel):
+            dmm._define()
+        return dmm.get_data_type()
 
     def get_value_type(self, ty):
         return self.data_model_manager[ty].get_value_type()

allows the above code to run to completion, but may not be the right place to insert the fix.

import numba as nb
from collections import OrderedDict
from numba import deferred_type, optional
from numba.experimental import jitclass
from numba import types

node_type = deferred_type()

@jitclass([
  ('parent', optional(node_type)),
  ('children', types.DictType(types.int32, node_type)),
])
class TreeNode(object):
    def __init__(self, parent=None):
        self.parent = parent
        self.children = nb.typed.Dict.empty(types.int32, node_type)

node_type.define(TreeNode.class_type.instance_type)

t = TreeNode()
t.children

Above code raises error:

AttributeError: 'DeferredType' object has no attribute 'name'

jax11235 avatar Nov 06 '22 05:11 jax11235

Above code raises error:

AttributeError: 'DeferredType' object has no attribute 'name'

Indeed, I can reproduce this - so my fix is either incomplete or incorrect :slightly_smiling_face:

gmarkall avatar Dec 05 '22 22:12 gmarkall

Current failure happens on llvmlite/bindings/targets.py::TargetData.get_pointee_abi_size. Below is the last five frames of the stacktrace:

[48]   /Users/guilhermeleobas/git/numba/numba/core/base.py(1220)wrapper()
-> return fn(*args, **kwargs)
[49]   /Users/guilhermeleobas/git/numba/numba/experimental/structref.py(305)codegen()
-> alloc_size = context.get_abi_sizeof(alloc_type)
[50]   /Users/guilhermeleobas/git/numba/numba/core/base.py(1105)get_abi_sizeof()
-> return ty.get_abi_size(self.target_data)
[51]   /Users/guilhermeleobas/miniconda3/envs/numba/lib/python3.9/site-packages/llvmlite/ir/types.py(53)get_abi_size()
-> return target_data.get_pointee_abi_size(llty)
[52] > /Users/guilhermeleobas/miniconda3/envs/numba/lib/python3.9/site-packages/llvmlite/binding/targets.py(156)get_pointee_abi_size()
-> size = ffi.lib.LLVMPY_ABISizeOfElementType(self, ty)

Assertion is triggered when Numba tries to get the size of the struct defined above:

 301         def codegen(context, builder, signature, args):
 302             # FIXME: mostly the same as jitclass ctor_impl()
 303             model = context.data_model_manager[inst_type.get_data_type()]
 304             alloc_type = model.get_value_type()
 305  ->         alloc_size = context.get_abi_sizeof(alloc_type)
 306
 307             meminfo = context.nrt.meminfo_alloc_dtor(
 308                 builder,
 309                 context.get_constant(types.uintp, alloc_size),
 310                 imp_dtor(context, builder.module, inst_type),
 311             )
 312             data_pointer = context.nrt.meminfo_data(builder, meminfo)
 313             data_pointer = builder.bitcast(data_pointer, alloc_type.as_pointer())
 314
 315             # Nullify all data
 316             builder.store(cgutils.get_null_value(alloc_type), data_pointer)
 317
 318             inst_struct = context.make_helper(builder, inst_type)
 319             inst_struct.meminfo = meminfo
 320
 321             return inst_struct._getvalue()
(Pdb++) alloc_type
<<class 'llvmlite.ir.types.LiteralStructType'> {i64, {%"deferred.4702211712.value", i1}, {%"deferred.4702211712.value", i1}}>

guilhermeleobas avatar Dec 05 '22 23:12 guilhermeleobas

Here is some code to apply @gmarkall's patch live:

from numba.core import datamodel
from numba.core.base import BaseContext

def get_data_type_patched(self, ty):
	""" Patched get_data_type, as suggested here: https://github.com/numba/numba/issues/8404#issuecomment-1231473379
		May not work if you're boxing for python interpreter, which triggers pickling
	"""
	dmm = self.data_model_manager[ty]
	if isinstance(dmm, datamodel.models.DeferredStructModel):
		dmm._define()
	return dmm.get_data_type()

setattr(BaseContext, "get_data_type", get_data_type_patched)

I think as long as you are not boxing the jitclass for use in the interpreter, like in @guilhermeleobas's example, you won't trigger the pickling error.

Azmisov avatar Oct 30 '23 01:10 Azmisov

I ran into some segfaults which seemed to be caused by using deferred type. So I don't recommend trying to use the patch anymore. In general, probably best to avoid using deferred types with jitclass as it seems quite buggy.

Azmisov avatar Nov 02 '23 02:11 Azmisov