I have an application that uses a lot of user defined types (structrefs) and I’m running into an issue where there is a combinatoric explosion in the number of specialized overloads/types that I need to do something like
StructA.field1 < StructB.field2 dynamically, because the two struct types need to be specialized at compile time in addition to the attribute literals. For context these sorts of comparison operations are actually generating new objects in a declarative fashion which makes things a bit trickier.
I would like to get around this with @intrinsics by just ripping out the field types and pointer offsets at compile time and then load the value at the appropriate offset of the struct pointers. This way I can reign in the combinatoric explosion by reducing a statement like this “StructA.field1” to a type and an offset.
So far I have figured out how to get the struct’s data pointer, and offsets by attribute. But I’m struggling with loading the value at the offset. It seems to come out consistently as the same value, but it’s not the same value I put in. I feel like the issue has something to do with LLVM’s ‘gep’ function but I’m not sure.
import numpy as np
from numba import types, njit, i8, u8, i4, u1, literally, generated_jit, f8
from numba.typed import List
from numba.types import ListType, unicode_type, void
from numba.core import types, imputils, cgutils
from llvmlite.ir import types as ll_types
from numba.extending import overload_method, intrinsic, overload_attribute
from numba.experimental.structref import new, _Utils
@intrinsic
def _struct_get_attr(typingctx, inst_type, attr_type):
attr = attr_type.literal_value
print(attr)
def codegen(context, builder, sig, args):
val_ty,_ = sig.args
val,_ = args
utils = _Utils(context, builder, val_ty)
dataval = utils.get_data_struct(val)
ptr = dataval._get_ptr_by_name(attr)
print("ptr",ptr)
ret = builder.load(ptr)
print("ret",ret)
return ret
sig = f8(inst_type, attr_type)
return sig, codegen
@intrinsic
def _struct_get_attr_pointer(typingctx, inst_type, attr_type):
attr = attr_type.literal_value
print(attr)
def codegen(context, builder, sig, args):
val_ty,_ = sig.args
val,_ = args
utils = _Utils(context, builder, val_ty)
dataval = utils.get_data_struct(val)
ptr = dataval._get_ptr_by_name(attr)
ret = builder.ptrtoint(ptr, cgutils.intp_t)
return ret
sig = i8(inst_type, attr_type)
return sig, codegen
@intrinsic
def _struct_get_data_pointer(typingctx, inst_type):
def codegen(context, builder, sig, args):
val_ty, = sig.args
val, = args
utils = _Utils(context, builder, val_ty)
dataptr = utils.get_data_pointer(val)
ret = builder.ptrtoint(dataptr, cgutils.intp_t)
return ret
sig = i8(inst_type,)
return sig, codegen
@intrinsic
def _read_pointer(typingctx, typ_ty, ptr):
typ = typ_ty.instance_type
def codegen(context, builder, sig, args):
_,ptr = args
llrtype = context.get_value_type(typ)
ptr = builder.inttoptr(ptr, ll_types.PointerType(llrtype))
print("ptr",ptr)
ret = builder.load(ptr)
print("ret",ret)
# return imputils.impl_ret_borrowed(context, builder, typ, ret)
return ret
sig = typ(typ_ty,ptr)
return sig, codegen
#Omitted Implementation for brevity
BOOP, BOOPType = define_fact("BOOP", {"A" : "string", "B" : "number"})
@njit
def foo():
b = BOOP("?", -1.0)# -1.0
#This works
print(_struct_get_attr(b,"B"))
#This doesn't work
base = _struct_get_data_pointer(b)
ptr = _struct_get_attr_pointer(b,'B')
print(base, ptr, ptr-base) #45773816 45773880 64
print(_read_pointer(f8,ptr)) #-9.868309920863368e+148
foo()