def generic_resolve(self, instance, attr): if attr in instance.struct: # It's a struct field => the type is well-known return instance.struct[attr] elif attr in instance.jitmethods: # It's a jitted method => typeinfer it meth = instance.jitmethods[attr] disp_type = types.Dispatcher(meth) class MethodTemplate(templates.AbstractTemplate): key = (self.key, attr) def generic(self, args, kws): args = (instance, ) + tuple(args) sig = disp_type.get_call_type(self.context, args, kws) return sig.as_method() return types.BoundFunction(MethodTemplate, instance) elif attr in instance.jitprops: # It's a jitted property => typeinfer its getter impdct = instance.jitprops[attr] getter = impdct['get'] disp_type = types.Dispatcher(getter) sig = disp_type.get_call_type(self.context, (instance, ), {}) return sig.return_type
def get_attr_impl(context, builder, typ, value, attr): """ Generic getattr() for @jitclass instances. """ if attr in typ.struct: # It's a struct field inst = context.make_helper(builder, typ, value=value) data_pointer = inst.data data = context.make_data_helper(builder, typ.get_data_type(), ref=data_pointer) return imputils.impl_ret_borrowed(context, builder, typ.struct[attr], getattr(data, _mangle_attr(attr))) elif attr in typ.jit_props: # It's a jitted property getter = typ.jit_props[attr]['get'] sig = templates.signature(None, typ) dispatcher = types.Dispatcher(getter) sig = dispatcher.get_call_type(context.typing_context, [typ], {}) call = context.get_function(dispatcher, sig) out = call(builder, [value]) _add_linking_libs(context, call) return imputils.impl_ret_new_ref(context, builder, sig.return_type, out) raise NotImplementedError('attribute {0!r} not implemented'.format(attr))
def set_attr_impl(context, builder, sig, args, attr): """ Generic setattr() for @jitclass instances. """ typ, valty = sig.args target, val = args if attr in typ.struct: # It's a struct member inst = context.make_helper(builder, typ, value=target) data_ptr = inst.data data = context.make_data_helper(builder, typ.get_data_type(), ref=data_ptr) # Get old value attr_type = typ.struct[attr] oldvalue = getattr(data, _mangle_attr(attr)) # Store n setattr(data, _mangle_attr(attr), val) context.nrt.incref(builder, attr_type, val) # Delete old value context.nrt.decref(builder, attr_type, oldvalue) elif attr in typ.jitprops: # It's a jitted property setter = typ.jitprops[attr]["set"] disp_type = types.Dispatcher(setter) sig = disp_type.get_call_type(context.typing_context, (typ, valty), {}) call = context.get_function(disp_type, sig) call(builder, (target, val)) _add_linking_libs(context, call) else: raise NotImplementedError("attribute {0!r} not implemented".format(attr))
def generic_resolve(self, instance, attr): if attr in instance.struct: # It's a struct field => the type is well-known return instance.struct[attr] elif attr in instance.jit_methods: # It's a jitted method => typeinfer it meth = instance.jit_methods[attr] disp_type = types.Dispatcher(meth) class MethodTemplate(templates.AbstractTemplate): key = (self.key, attr) def generic(self, args, kws): args = (instance, ) + tuple(args) sig = disp_type.get_call_type(self.context, args, kws) return sig.as_method() return types.BoundFunction(MethodTemplate, instance) elif attr in instance.jit_static_methods: # It's a jitted method => typeinfer it meth = instance.jit_static_methods[attr] disp_type = types.Dispatcher(meth) class StaticMethodTemplate(templates.AbstractTemplate): key = (self.key, attr) def generic(self, args, kws): # Don't add instance as the first argument for a static # method. sig = disp_type.get_call_type(self.context, args, kws) # sig itself does not include ClassInstanceType as it's # first argument, so instead of calling sig.as_method() # we insert the recvr. This is equivalent to # sig.replace(args=(instance,) + sig.args).as_method(). return sig.replace(recvr=instance) return types.BoundFunction(StaticMethodTemplate, instance) elif attr in instance.jit_props: # It's a jitted property => typeinfer its getter impdct = instance.jit_props[attr] getter = impdct['get'] disp_type = types.Dispatcher(getter) sig = disp_type.get_call_type(self.context, (instance, ), {}) return sig.return_type
def generic(self, args, kws): instance = args[0] if isinstance(instance, types.ClassInstanceType) and \ _dunder_meth in instance.jit_methods: meth = instance.jit_methods[_dunder_meth] disp_type = types.Dispatcher(meth) sig = disp_type.get_call_type(self.context, args, kws) return sig
def imp(context, builder, sig, args): instance_type = sig.args[0] method = instance_type.jitmethods[attr] disp_type = types.Dispatcher(method) call = context.get_function(disp_type, sig) out = call(builder, args) _add_linking_libs(context, call) return imputils.impl_ret_new_ref(context, builder, sig.return_type, out)
def test_weaktype(self): d = Dummy() e = Dummy() a = types.Dispatcher(d) b = types.Dispatcher(d) c = types.Dispatcher(e) self.assertIs(a.dispatcher, d) self.assertIs(b.dispatcher, d) self.assertIs(c.dispatcher, e) # Equality of alive references self.assertTrue(a == b) self.assertFalse(a != b) self.assertTrue(a != c) self.assertFalse(a == c) z = types.int8 self.assertFalse(a == z) self.assertFalse(b == z) self.assertFalse(c == z) self.assertTrue(a != z) self.assertTrue(b != z) self.assertTrue(c != z) # Hashing and mappings s = set([a, b, c]) self.assertEqual(len(s), 2) self.assertIn(a, s) self.assertIn(b, s) self.assertIn(c, s) # Kill the references d = e = None gc.collect() with self.assertRaises(ReferenceError): a.dispatcher with self.assertRaises(ReferenceError): b.dispatcher with self.assertRaises(ReferenceError): c.dispatcher # Dead references are always unequal self.assertFalse(a == b) self.assertFalse(a == c) self.assertFalse(b == c) self.assertFalse(a == z) self.assertTrue(a != b) self.assertTrue(a != c) self.assertTrue(b != c) self.assertTrue(a != z)
def generic(self, args, kws): # Redirect resolution to __init__ instance_type = self.key.instance_type ctor = instance_type.jitmethods['__init__'] boundargs = (instance_type.get_reference_type(), ) + args disp_type = types.Dispatcher(ctor) sig = disp_type.get_call_type(self.context, boundargs, kws) # Actual constructor returns an instance value (not None) out = templates.signature(instance_type, *sig.args[1:]) return out
def generic(self, args, kws): """ Type the overloaded function by compiling the appropriate implementation for the given args. """ disp, new_args = self._get_impl(args, kws) if disp is None: return # Compile and type it for the given types disp_type = types.Dispatcher(disp) # Store the compiled overload for use in the lowering phase if there's # no inlining required (else functions are being compiled which will # never be used as they are inlined) if not self._inline.is_never_inline: # need to run the compiler front end up to type inference to compute # a signature from numba.core import typed_passes, compiler ir = compiler.run_frontend(disp_type.dispatcher.py_func) resolve = disp_type.dispatcher.get_call_template template, pysig, folded_args, kws = resolve(new_args, kws) typemap, return_type, calltypes = typed_passes.type_inference_stage( self.context, ir, folded_args, None) sig = Signature(return_type, folded_args, None) # this stores a load of info for the cost model function if supplied # it by default is None self._inline_overloads[sig.args] = {"folded_args": folded_args} # this stores the compiled overloads, if there's no compiled # overload available i.e. function is always inlined, the key still # needs to exist for type resolution # NOTE: If lowering is failing on a `_EmptyImplementationEntry`, # the inliner has failed to inline this entry corretly. impl_init = _EmptyImplementationEntry("always inlined") self._compiled_overloads[sig.args] = impl_init if not self._inline.is_always_inline: # this branch is here because a user has supplied a function to # determine whether to inline or not. As a result both compiled # function and inliner info needed, delaying the computation of # this leads to an internal state mess at present. TODO: Fix! sig = disp_type.get_call_type(self.context, new_args, kws) self._compiled_overloads[sig.args] = disp_type.get_overload( sig) # store the inliner information, it's used later in the cost # model function call iinfo = _inline_info(ir, typemap, calltypes, sig) self._inline_overloads[sig.args] = { "folded_args": folded_args, "iinfo": iinfo, } else: sig = disp_type.get_call_type(self.context, new_args, kws) self._compiled_overloads[sig.args] = disp_type.get_overload(sig) return sig
def __init__(self, py_func, locals={}, targetoptions={}, impl_kind='direct', pipeline_class=compiler.Compiler): """ Parameters ---------- py_func: function object to be compiled locals: dict, optional Mapping of local variable names to Numba types. Used to override the types deduced by the type inference engine. targetoptions: dict, optional Target-specific config options. impl_kind: str Select the compiler mode for `@jit` and `@generated_jit` pipeline_class: type numba.compiler.CompilerBase The compiler pipeline type. """ self.typingctx = self.targetdescr.typing_context self.targetctx = self.targetdescr.target_context pysig = utils.pysignature(py_func) arg_count = len(pysig.parameters) can_fallback = not targetoptions.get('nopython', False) _DispatcherBase.__init__(self, arg_count, py_func, pysig, can_fallback, exact_match_required=False) functools.update_wrapper(self, py_func) self.targetoptions = targetoptions self.locals = locals self._cache = NullCache() compiler_class = self._impl_kinds[impl_kind] self._impl_kind = impl_kind self._compiler = compiler_class(py_func, self.targetdescr, targetoptions, locals, pipeline_class) self._cache_hits = collections.Counter() self._cache_misses = collections.Counter() self._type = types.Dispatcher(self) self.typingctx.insert_global(self, self._type) # Remember target restriction self._required_target_backend = targetoptions.get('target_backend')
def generic(self, args, kws): # Redirect resolution to __init__ instance_type = self.key.instance_type ctor = instance_type.jitmethods["__init__"] boundargs = (instance_type.get_reference_type(),) + args disp_type = types.Dispatcher(ctor) sig = disp_type.get_call_type(self.context, boundargs, kws) if not isinstance(sig.return_type, types.NoneType): raise TypeError(f"__init__() should return None, not '{sig.return_type}'") # Actual constructor returns an instance value (not None) out = templates.signature(instance_type, *sig.args[1:]) return out
def imp(context, builder, sig, args): instance_type = sig.args[0] if attr in instance_type.jit_methods: method = instance_type.jit_methods[attr] elif attr in instance_type.jit_static_methods: method = instance_type.jit_static_methods[attr] # imp gets called as a method, where the first argument is # self. We drop this for a static method. sig = sig.replace(args=sig.args[1:]) args = args[1:] disp_type = types.Dispatcher(method) call = context.get_function(disp_type, sig) out = call(builder, args) _add_linking_libs(context, call) return imputils.impl_ret_new_ref(context, builder, sig.return_type, out)
def ctor_impl(context, builder, sig, args): """ Generic constructor (__new__) for jitclasses. """ # Allocate the instance inst_typ = sig.return_type alloc_type = context.get_data_type(inst_typ.get_data_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_typ), ) 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_typ) inst_struct.meminfo = meminfo inst_struct.data = data_pointer # Call the jitted __init__ # TODO: extract the following into a common util init_sig = (sig.return_type,) + sig.args init = inst_typ.jit_methods['__init__'] disp_type = types.Dispatcher(init) call = context.get_function(disp_type, types.void(*init_sig)) _add_linking_libs(context, call) realargs = [inst_struct._getvalue()] + list(args) call(builder, realargs) # Prepare return value ret = inst_struct._getvalue() return imputils.impl_ret_new_ref(context, builder, inst_typ, ret)
def _build_impl(self, cache_key, args, kws): """Build and cache the implementation. Given the positional (`args`) and keyword arguments (`kws`), obtains the `overload` implementation and wrap it in a Dispatcher object. The expected argument types are returned for use by type-inference. The expected argument types are only different from the given argument types if there is an imprecise type in the given argument types. Parameters ---------- cache_key : hashable The key used for caching the implementation. args : Tuple[Type] Types of positional argument. kws : Dict[Type] Types of keyword argument. Returns ------- disp, args : On success, returns `(Dispatcher, Tuple[Type])`. On failure, returns `(None, None)`. """ from numba import jit # Get the overload implementation for the given types ovf_result = self._overload_func(*args, **kws) if ovf_result is None: # No implementation => fail typing self._impl_cache[cache_key] = None, None return None, None elif isinstance(ovf_result, tuple): # The implementation returned a signature that the type-inferencer # should be using. sig, pyfunc = ovf_result args = sig.args kws = {} cache_key = None # don't cache else: # Regular case pyfunc = ovf_result # Check type of pyfunc if not isinstance(pyfunc, FunctionType): msg = ("Implementator function returned by `@overload` " "has an unexpected type. Got {}") raise AssertionError(msg.format(pyfunc)) # check that the typing and impl sigs match up if self._strict: self._validate_sigs(self._overload_func, pyfunc) # Make dispatcher jitdecor = jit(nopython=True, **self._jit_options) disp = jitdecor(pyfunc) # Make sure that the implementation can be fully compiled disp_type = types.Dispatcher(disp) disp_type.get_call_type(self.context, args, kws) if cache_key is not None: self._impl_cache[cache_key] = disp, args return disp, args
def generic(self, args, kws): """ Type the overloaded function by compiling the appropriate implementation for the given args. """ disp, new_args = self._get_impl(args, kws) if disp is None: return # Compile and type it for the given types disp_type = types.Dispatcher(disp) # Store the compiled overload for use in the lowering phase if there's # no inlining required (else functions are being compiled which will # never be used as they are inlined) if not self._inline.is_never_inline: # need to run the compiler front end up to type inference to compute # a signature from numba.core import typed_passes, compiler from numba.core.inline_closurecall import InlineWorker fcomp = disp._compiler flags = compiler.Flags() # Updating these causes problems?! #fcomp.targetdescr.options.parse_as_flags(flags, # fcomp.targetoptions) #flags = fcomp._customize_flags(flags) # spoof a compiler pipline like the one that will be in use tyctx = fcomp.targetdescr.typing_context tgctx = fcomp.targetdescr.target_context compiler_inst = fcomp.pipeline_class( tyctx, tgctx, None, None, None, flags, None, ) inline_worker = InlineWorker( tyctx, tgctx, fcomp.locals, compiler_inst, flags, None, ) # If the inlinee contains something to trigger literal arg dispatch # then the pipeline call will unconditionally fail due to a raised # ForceLiteralArg exception. Therefore `resolve` is run first, as # type resolution must occur at some point, this will hit any # `literally` calls and because it's going via the dispatcher will # handle them correctly i.e. ForceLiteralArg propagates. This having # the desired effect of ensuring the pipeline call is only made in # situations that will succeed. For context see #5887. resolve = disp_type.dispatcher.get_call_template template, pysig, folded_args, kws = resolve(new_args, kws) ir = inline_worker.run_untyped_passes(disp_type.dispatcher.py_func) (typemap, return_type, calltypes, _) = typed_passes.type_inference_stage(self.context, ir, folded_args, None) sig = Signature(return_type, folded_args, None) # this stores a load of info for the cost model function if supplied # it by default is None self._inline_overloads[sig.args] = {'folded_args': folded_args} # this stores the compiled overloads, if there's no compiled # overload available i.e. function is always inlined, the key still # needs to exist for type resolution # NOTE: If lowering is failing on a `_EmptyImplementationEntry`, # the inliner has failed to inline this entry corretly. impl_init = _EmptyImplementationEntry('always inlined') self._compiled_overloads[sig.args] = impl_init if not self._inline.is_always_inline: # this branch is here because a user has supplied a function to # determine whether to inline or not. As a result both compiled # function and inliner info needed, delaying the computation of # this leads to an internal state mess at present. TODO: Fix! sig = disp_type.get_call_type(self.context, new_args, kws) self._compiled_overloads[sig.args] = disp_type.get_overload( sig) # store the inliner information, it's used later in the cost # model function call iinfo = _inline_info(ir, typemap, calltypes, sig) self._inline_overloads[sig.args] = { 'folded_args': folded_args, 'iinfo': iinfo } else: sig = disp_type.get_call_type(self.context, new_args, kws) self._compiled_overloads[sig.args] = disp_type.get_overload(sig) return sig
def _numba_type_(self): return types.Dispatcher(self)