Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
384 lines (305 sloc) 11.2 KB
"""Utilities for defining a mutable struct.
A mutable struct is passed by reference;
hence, structref (a reference to a struct).
"""
from numba import njit
from numba.core import types, imputils, cgutils
from numba.core.datamodel import default_manager, models
from numba.core.extending import (
infer_getattr,
lower_getattr_generic,
lower_setattr_generic,
box,
unbox,
NativeValue,
intrinsic,
overload,
)
from numba.core.typing.templates import AttributeTemplate
class _Utils:
"""Internal builder-code utils for structref definitions.
"""
def __init__(self, context, builder, struct_type):
"""
Parameters
----------
context :
a numba target context
builder :
a llvmlite IRBuilder
struct_type : numba.core.types.StructRef
"""
self.context = context
self.builder = builder
self.struct_type = struct_type
def new_struct_ref(self, mi):
"""Encapsulate the MemInfo from a `StructRefPayload` in a `StructRef`
"""
context = self.context
builder = self.builder
struct_type = self.struct_type
st = cgutils.create_struct_proxy(struct_type)(context, builder)
st.meminfo = mi
return st
def get_struct_ref(self, val):
"""Return a helper for accessing a StructRefType
"""
context = self.context
builder = self.builder
struct_type = self.struct_type
return cgutils.create_struct_proxy(struct_type)(
context, builder, value=val
)
def get_data_pointer(self, val):
"""Get the data pointer to the payload from a `StructRefType`.
"""
context = self.context
builder = self.builder
struct_type = self.struct_type
structval = self.get_struct_ref(val)
meminfo = structval.meminfo
data_ptr = context.nrt.meminfo_data(builder, meminfo)
valtype = struct_type.get_data_type()
model = context.data_model_manager[valtype]
alloc_type = model.get_value_type()
data_ptr = builder.bitcast(data_ptr, alloc_type.as_pointer())
return data_ptr
def get_data_struct(self, val):
"""Get a getter/setter helper for accessing a `StructRefPayload`
"""
context = self.context
builder = self.builder
struct_type = self.struct_type
data_ptr = self.get_data_pointer(val)
valtype = struct_type.get_data_type()
dataval = cgutils.create_struct_proxy(valtype)(
context, builder, ref=data_ptr
)
return dataval
def define_attributes(struct_typeclass):
"""Define attributes on `struct_typeclass`.
Defines both setters and getters in jit-code.
This is called directly in `register()`.
"""
@infer_getattr
class StructAttribute(AttributeTemplate):
key = struct_typeclass
def generic_resolve(self, typ, attr):
if attr in typ.field_dict:
attrty = typ.field_dict[attr]
return attrty
@lower_getattr_generic(struct_typeclass)
def struct_getattr_impl(context, builder, typ, val, attr):
utils = _Utils(context, builder, typ)
dataval = utils.get_data_struct(val)
ret = getattr(dataval, attr)
fieldtype = typ.field_dict[attr]
return imputils.impl_ret_borrowed(context, builder, fieldtype, ret)
@lower_setattr_generic(struct_typeclass)
def struct_setattr_impl(context, builder, sig, args, attr):
[inst_type, val_type] = sig.args
[instance, val] = args
utils = _Utils(context, builder, inst_type)
dataval = utils.get_data_struct(instance)
# cast val to the correct type
field_type = inst_type.field_dict[attr]
casted = context.cast(builder, val, val_type, field_type)
# read old
old_value = getattr(dataval, attr)
# incref new value
context.nrt.incref(builder, val_type, casted)
# decref old value (must be last in case new value is old value)
context.nrt.decref(builder, val_type, old_value)
# write new
setattr(dataval, attr, casted)
def define_boxing(struct_type, obj_class):
"""Define the boxing & unboxing logic for `struct_type` to `obj_class`.
Defines both boxing and unboxing.
- boxing turns an instance of `struct_type` into a PyObject of `obj_class`
- unboxing turns an instance of `obj_class` into an instance of
`struct_type` in jit-code.
Use this directly instead of `define_proxy()` when the user does not
want any constructor to be defined.
"""
if struct_type is types.StructRef:
raise ValueError(f"cannot register {types.StructRef}")
obj_ctor = obj_class._numba_box_
@box(struct_type)
def box_struct_ref(typ, val, c):
"""
Convert a raw pointer to a Python int.
"""
utils = _Utils(c.context, c.builder, typ)
struct_ref = utils.get_struct_ref(val)
meminfo = struct_ref.meminfo
mip_type = types.MemInfoPointer(types.voidptr)
boxed_meminfo = c.box(mip_type, meminfo)
ctor_pyfunc = c.pyapi.unserialize(c.pyapi.serialize_object(obj_ctor))
ty_pyobj = c.pyapi.unserialize(c.pyapi.serialize_object(typ))
res = c.pyapi.call_function_objargs(
ctor_pyfunc, [ty_pyobj, boxed_meminfo],
)
c.pyapi.decref(ctor_pyfunc)
c.pyapi.decref(ty_pyobj)
c.pyapi.decref(boxed_meminfo)
return res
@unbox(struct_type)
def unbox_struct_ref(typ, obj, c):
mi_obj = c.pyapi.object_getattr_string(obj, "_meminfo")
mip_type = types.MemInfoPointer(types.voidptr)
mi = c.unbox(mip_type, mi_obj).value
utils = _Utils(c.context, c.builder, typ)
struct_ref = utils.new_struct_ref(mi)
out = struct_ref._getvalue()
c.pyapi.decref(mi_obj)
return NativeValue(out)
def define_constructor(py_class, struct_typeclass, fields):
"""Define the jit-code constructor for `struct_typeclass` using the
Python type `py_class` and the required `fields`.
Use this instead of `define_proxy()` if the user does not want boxing
logic defined.
"""
# Build source code for the constructor
params = ', '.join(fields)
indent = ' ' * 8
init_fields_buf = []
for k in fields:
init_fields_buf.append(f"st.{k} = {k}")
init_fields = f'\n{indent}'.join(init_fields_buf)
source = f"""
def ctor({params}):
struct_type = struct_typeclass(list(zip({list(fields)}, [{params}])))
def impl({params}):
st = new(struct_type)
{init_fields}
return st
return impl
"""
glbs = dict(struct_typeclass=struct_typeclass, new=new)
exec(source, glbs)
ctor = glbs['ctor']
# Make it an overload
overload(py_class)(ctor)
def define_proxy(py_class, struct_typeclass, fields):
"""Defines a PyObject proxy for a structref.
This makes `py_class` a valid constructor for creating a instance of
`struct_typeclass` that contains the members as defined by `fields`.
Parameters
----------
py_class : type
The Python class for constructing an instance of `struct_typeclass`.
struct_typeclass : numba.core.types.Type
The structref type class to bind to.
fields : Sequence[str]
A sequence of field names.
Returns
-------
None
"""
define_constructor(py_class, struct_typeclass, fields)
define_boxing(struct_typeclass, py_class)
def register(struct_type):
"""Register a `numba.core.types.StructRef` for use in jit-code.
This defines the data-model for lowering an instance of `struct_type`.
This defines attributes accessor and mutator for an instance of
`struct_type`.
Parameters
----------
struct_type : type
A subclass of `numba.core.types.StructRef`.
Returns
-------
struct_type : type
Returns the input argument so this can act like a decorator.
Examples
--------
.. code-block::
class MyStruct(numba.core.types.StructRef):
... # the simplest subclass can be empty
numba.experimental.structref.register(MyStruct)
"""
if struct_type is types.StructRef:
raise ValueError(f"cannot register {types.StructRef}")
default_manager.register(struct_type, models.StructRefModel)
define_attributes(struct_type)
return struct_type
@intrinsic
def new(typingctx, struct_type):
"""new(struct_type)
A jit-code only intrinsic. Used to allocate an **empty** mutable struct.
The fields are zero-initialized and must be set manually after calling
the function.
Example:
instance = new(MyStruct)
instance.field = field_value
"""
from numba.experimental.jitclass.base import imp_dtor
inst_type = struct_type.instance_type
def codegen(context, builder, signature, args):
# FIXME: mostly the same as jitclass ctor_impl()
model = context.data_model_manager[inst_type.get_data_type()]
alloc_type = model.get_value_type()
alloc_size = context.get_abi_sizeof(alloc_type)
meminfo = context.nrt.meminfo_alloc_dtor(
builder,
context.get_constant(types.uintp, alloc_size),
imp_dtor(context, builder.module, inst_type),
)
data_pointer = context.nrt.meminfo_data(builder, meminfo)
data_pointer = builder.bitcast(data_pointer, alloc_type.as_pointer())
# Nullify all data
builder.store(cgutils.get_null_value(alloc_type), data_pointer)
inst_struct = context.make_helper(builder, inst_type)
inst_struct.meminfo = meminfo
return inst_struct._getvalue()
sig = inst_type(struct_type)
return sig, codegen
class StructRefProxy:
"""A PyObject proxy to the Numba allocated structref data structure.
Notes
-----
* Subclasses should not define ``__init__``.
* Subclasses can override ``__new__``.
"""
__slots__ = ('_type', '_meminfo')
@classmethod
def _numba_box_(cls, ty, mi):
"""Called by boxing logic, the conversion of Numba internal
representation into a PyObject.
Parameters
----------
ty :
a Numba type instance.
mi :
a wrapped MemInfoPointer.
Returns
-------
instance :
a StructRefProxy instance.
"""
instance = super().__new__(cls)
instance._type = ty
instance._meminfo = mi
return instance
def __new__(cls, *args):
"""Construct a new instance of the structref.
This takes positional-arguments only due to limitation of the compiler.
The arguments are mapped to ``cls(*args)`` in jit-code.
"""
try:
# use cached ctor if available
ctor = cls.__numba_ctor
except AttributeError:
# lazily define the ctor
@njit
def ctor(*args):
return cls(*args)
# cache it to attribute to avoid recompilation
cls.__numba_ctor = ctor
return ctor(*args)
@property
def _numba_type_(self):
"""Returns the Numba type instance for this structref instance.
Subclasses should NOT override.
"""
return self._type
You can’t perform that action at this time.