Dereferencing pointer to arbitrary struct element

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()

Ohh gosh, silly me. This does actually work, I just needed to add

print(b.A)

At the end of foo() to make sure the struct didn’t get freed before I accessed it’s members. I probably just need to incref to make sure it doesn’t get collected.

Would appreciate any comments from the community however if I’m doing something colossally stupid with this.