1010#
1111# FIXME:
1212# - sub-classing: parameters of '__interface__' types must accept derived types
13- # - boxing/unboxing
13+ # - boxing/unboxing of algorithms
1414# - GC: result/model objects returned by daal4py wrappers are newly allocated shared pointers, need to get gc'ed
1515# - float32 tables, input type selection etc.
1616# - key-word/optional input arguments
2121from numba import types , cgutils
2222from numba .extending import (intrinsic , typeof_impl , overload , overload_method ,
2323 overload_attribute , box , unbox , make_attribute_wrapper ,
24- type_callable , models , register_model , lower_builtin ,
25- NativeValue , lower_cast , get_cython_function_address )
24+ type_callable , models , register_model , lower_builtin , lower_getattr ,
25+ NativeValue , lower_cast , get_cython_function_address , typeof_impl )
2626from numba .targets .imputils import impl_ret_new_ref
2727from numba .typing .templates import (signature , AbstractTemplate , infer , infer_getattr ,
2828 ConcreteTemplate , AttributeTemplate , bound_function , infer_global )
@@ -155,6 +155,7 @@ class algo_factory(object):
155155 'itable_type' : itable_type ,
156156 'size_t' : types .uint64 ,
157157 'double' : types .float64 ,
158+ 'float' : types .float32 ,
158159 'bool' : types .boolean ,
159160 'std::string&' : string_type ,
160161 }
@@ -169,15 +170,15 @@ def from_d4p(self, spec):
169170 return D4PSpec (spec ['pyclass' ],
170171 spec ['c_name' ],
171172 params = spec ['params' ],
172- input_types = [(x [0 ], self . all_nbtypes [ x [1 ].rstrip ('*' )] , d4p_dtypes [x [2 ]]) for x in spec ['input_types' ]],
173+ input_types = [(x [0 ], x [1 ].rstrip ('*' ), d4p_dtypes [x [2 ]]) for x in spec ['input_types' ]],
173174 result_dist = d4p_dtypes [spec ['result_dist' ]])
174175 elif 'attrs' in spec :
175176 # filter out (do not support) properties for which we do not know the numba type
176177 attrs = []
177178 for x in spec ['attrs' ]:
178179 typ = x [1 ].rstrip ('*' )
179180 if typ in self .all_nbtypes :
180- attrs .append ((x [0 ], self . all_nbtypes [ typ ] ))
181+ attrs .append ((x [0 ], typ ))
181182 else :
182183 warnings .warn ("Warning: couldn't find numba type for '" + x [1 ] + "'. Ignored." )
183184 return D4PSpec (spec ['pyclass' ],
@@ -203,7 +204,6 @@ def activate(self):
203204 return
204205 self .spec = self .from_d4p (self .spec )
205206 self .mk_ctor ()
206- self .mk_compute ()
207207 self .mk_attrs ()
208208 self .mk_boxing ()
209209
@@ -223,6 +223,7 @@ def __init__(self):
223223 # also register their opaque data model
224224 self .NbType = mk_simple (self .name + '_nbtype' )
225225 self .all_nbtypes [self .name ] = self .NbType ()
226+
226227 register_model (self .NbType )(models .OpaqueModel )
227228
228229 # some of the classes can be parameters to others and have a default NULL/None
@@ -241,7 +242,13 @@ def mk_ctor(self):
241242 '''
242243
243244 if not self .spec or not self .spec .params :
244- # this must be a result or model, not an algo class
245+ if self .spec :
246+ # this must be a result or model, not an algo class
247+ # we don't want a constructor but Numba must know ts model for boxing/unboxing
248+ @typeof_impl .register (self .spec .pyclass )
249+ def typeof_index (val , c ):
250+ return self .all_nbtypes [self .name ]
251+
245252 return
246253
247254 # FIXME: check args
@@ -282,108 +289,121 @@ def _ovld(*args, **kwargs):
282289 impl = loc_vars ['_ovld_impl' ]
283290 return impl
284291
285-
286- def mk_compute (self ):
287- '''provide the typing and lowering for calling compute on an algo object'''
288-
289- if not self .spec or not self .spec .input_types :
290- # this must be a result or model, not an algo class
291- return
292-
293- algo_type = self .NbType
294- result_type = self .all_nbtypes [self .name + '_result' ]
295- compute_name = '.' .join ([self .spec .pyclass .__module__ .strip ('_' ), self .spec .pyclass .__name__ , 'compute' ])
296-
297- @infer_getattr
298- class AlgoAttributes (AttributeTemplate ):
299- '''declares numba signatures of attributes/methods of algo objects'''
300- key = algo_type
301-
302- @bound_function (compute_name )
303- def resolve_compute (self , dict , args , kws ):
304- # FIXME: keyword args
305- # FIXME: check args
306- return signature (result_type , * args )
307-
308-
309- @lower_builtin (compute_name , self .all_nbtypes [self .name ], * [self .all_nbtypes [x [1 ]] if isinstance (x [1 ], str ) else x [1 ] for x in self .spec .input_types ])
310- def lower_compute (context , builder , sig , args ):
311- '''lowers compute method algo objects'''
312- # First prepare list of argument types
313- lir_types = [lir .IntType (8 ).as_pointer ()] # the first arg is always our algo object (shrd_ptr)
314- c_args = [args [0 ]] # the first arg is always our algo object (shrd_ptr)
315- for i in range (1 , len (args )):
316- lirt = get_lir_type (context , sig .args [i ])
317- if isinstance (lirt , list ): # Array!
318- # generate lir code to extract actual arguments
319- # collect args/types in list
320- lir_types += lirt
321- in_arrtype = sig .args [i ]
322- in_array = context .make_array (in_arrtype )(context , builder , args [i ])
323- in_shape = cgutils .unpack_tuple (builder , in_array .shape )
324- c_args += [in_array .data , in_shape [0 ], in_shape [1 ]]
325- else :
326- lir_types .append (lirt )
327- c_args .append (args [i ])
328- # Now we can define the signature and call the C-function
329- fnty = lir .FunctionType (lir .IntType (8 ).as_pointer (), lir_types )
330- fn = builder .module .get_or_insert_function (fnty , name = 'compute_' + self .spec .c_name )
331- # finally we call the function
332- return builder .call (fn , c_args )
333-
334-
335- def add_attr (self , NbType , attr , attr_type , c_func ):
292+ def gen_call (context , builder , sig , args , c_func ):
336293 '''
337- Generate getter for attribute 'attr' on objects of numba result/model type .
338- Calls c_func to retrieve the attribute from the given result/model object .
339- Converts to ndarray if attr_type is Array
294+ This is generating the llvm code for calling a d4p C function .
295+ May also convert to ndarray .
296+ Used by our dynamically generated/exec'ed @lower_builtin/@lower_getattr functions below.
340297 '''
341-
342- if isinstance ( attr_type , str ):
343- attr_type = self . all_nbtypes [ attr_type ]
344- is_array = isinstance ( attr_type , types . Array )
345-
346- @ intrinsic
347- def get_attr_impl ( typingctx , obj ):
348- '''
349- This is creating the llvm wrapper calling the C function.
350- May also convert to ndarray.
351- '''
352- def codegen ( context , builder , sig , args ):
353- assert ( len ( args ) == 1 )
354- c_func_ret_type = lir . IntType ( 8 ). as_pointer () if is_array else context . get_data_type ( attr_type )
355- # First call the getter
356- fnty = lir . FunctionType ( c_func_ret_type , [ lir . IntType ( 8 ). as_pointer () ])
357- fn = builder . module . get_or_insert_function ( fnty , name = c_func )
358- ptr = builder . call ( fn , args )
359- return nt2nd ( context , builder , ptr , sig .return_type ) if is_array else ptr
360-
361- return attr_type ( obj ), codegen
362-
363- @ overload_attribute ( NbType , attr )
364- def get_attr ( obj ):
365- '''declaring getter for attribute 'attr' of objects of type "NbType"'''
366- def getter ( obj ):
367- return get_attr_impl ( obj )
368- return getter
298+ lir_types = [ lir . IntType ( 8 ). as_pointer ()] # the first arg is always our algo object (shrd_ptr)
299+ c_args = [ args [ 0 ]] # the first arg is always our algo object (shrd_ptr)
300+ # prepare our args (usually none for most get_* attribuutes/properties)
301+ for i in range ( 1 , len ( args )):
302+ lirt = get_lir_type ( context , sig . args [ i ])
303+ if isinstance ( lirt , list ): # Array!
304+ # generate lir code to extract actual arguments
305+ # collect args/types in list
306+ lir_types += lirt
307+ in_arrtype = sig . args [ i ]
308+ in_array = context . make_array ( in_arrtype )( context , builder , args [ i ])
309+ in_shape = cgutils . unpack_tuple ( builder , in_array . shape )
310+ c_args += [ in_array . data , in_shape [ 0 ], in_shape [ 1 ]]
311+ else :
312+ lir_types . append ( lirt )
313+ c_args . append ( args [ i ])
314+ #ret_typ = sig if c_func.startswith('get_') else sig.return_type
315+ # Our getter might return an array, which needs special handling
316+ ret_is_array = isinstance ( sig .return_type , types . Array )
317+ # define our llvm return type
318+ c_func_ret_type = lir . IntType ( 8 ). as_pointer () if ret_is_array else context . get_data_type ( sig . return_type )
319+ # Now we can define the signature
320+ fnty = lir . FunctionType ( c_func_ret_type , lir_types )
321+ # Get function
322+ fn = builder . module . get_or_insert_function ( fnty , name = c_func )
323+ # and finally generate the call
324+ ptr = builder . call ( fn , c_args )
325+ return nt2nd ( context , builder , ptr , sig . return_type ) if ret_is_array else ptr
369326
370327
371328 def mk_attrs (self ):
372- '''Make attributes of result and model known to numba and how to get their values.'''
329+ '''
330+ Provide the typing and lowering for getters and calling compute
331+ Again, we can't use static code for our infer_getarr class, so we need
332+ to generate code that we exec.
333+ Apparently the @infer_getattr/@bound_function and @lower_builtin need to
334+ be in the same context, so we need to include the @lower_builtin stuff
335+ there, too.
336+ '''
373337
374- if not self .spec or not self .spec .attrs :
375- # this must be algo class, which does not have attributes
376- # or it is an alias only
338+ if not self .spec :
377339 return
378340
379- for a in self .spec .attrs :
380- self .add_attr (self .NbType , a [0 ], a [1 ], '_' .join (['get' , self .spec .c_name , a [0 ]]))
341+ # Initing the code we want to exec
342+ lower_code = ''
343+ infer_code = '''# This is our class providing the resolve_* methods
344+ @infer_getattr
345+ class AlgoAttributes_{0}(AttributeTemplate):
346+ "declares numba signatures of attributes/methods of {0} objects"
347+ key = algo_factory.all_nbtypes['{0}'].__class__
348+ ''' .format (self .spec .c_name )
349+
350+ # The python name stub (module + class)
351+ name_stub = '_' + '.' .join ([self .spec .pyclass .__module__ .strip ('_' ), self .spec .pyclass .__name__ ])
352+ result_type = None
353+
354+ # First handle properties (result/model classes)
355+ if self .spec .attrs :
356+ for a in self .spec .attrs :
357+ c_func = '_' .join (['get' , self .spec .c_name , a [0 ]])
358+ full_name = '.' .join ([name_stub , a [0 ]])
359+ # for typing we simply provide resolve_* method for each attribute
360+ infer_code += '''
361+ def resolve_{0}(self, obj):
362+ return algo_factory.all_nbtypes['{1}']
363+ ''' .format (a [0 ], a [1 ])
364+
365+ # lowering code for properties through @lower_getattr
366+ lower_code += '''
367+ @lower_getattr(algo_factory.all_nbtypes['{0}'], '{1}') # getters have no args (other than self)
368+ def lower_{2}(context, builder, typ, val):
369+ return algo_factory.gen_call(context, builder, signature(algo_factory.all_nbtypes['{3}']), [val], '{2}')
370+ ''' .format (self .name , a [0 ], c_func , a [1 ])
371+
372+ # Now we handle compute method algorithms classes
373+ if self .spec .input_types :
374+ compute_name = '.' .join ([name_stub , 'compute' ])
375+
376+ # using bound_function for typing
377+ infer_code += '''
378+ @bound_function("{0}")
379+ def resolve_compute(self, dict, args, kws):
380+ # FIXME: keyword args
381+ # FIXME: check args
382+ return signature(algo_factory.all_nbtypes['{1}_result'], *args)
383+ ''' .format (compute_name , self .name )
384+
385+ # lowering methods provided with lower_builtin
386+ lower_code += '''
387+ @lower_builtin('{0}', algo_factory.all_nbtypes['{1}'], *[{2}])
388+ def lower_compute(context, builder, sig, args):
389+ return algo_factory.gen_call(context, builder, sig, args, 'compute_{3}')
390+ ''' .format (compute_name ,
391+ self .name ,
392+ ', ' .join (["algo_factory.all_nbtypes['{}']" .format (x [1 ]) for x in self .spec .input_types ]), #if isinstance(x[1], str) else "'{}'")
393+ self .spec .c_name )
394+
395+ loc_vars = {}
396+ exec (infer_code + lower_code , globals (), loc_vars )
381397
382398
383- def cy_unbox (self ):
399+ def cy_unboxer (self ):
400+ '''
401+ Return the address of the cython generated C(++) function which
402+ provides the actual DAAL C++ pointer for our result/model Python object.
403+ '''
384404 addr = get_cython_function_address ("_daal4py" , 'unbox_' + self .spec .c_name )
385- functype = ctypes . CFUNCTYPE ( ctypes . c_void_p , ctypes . py_object )
386- return functype ( addr )
405+ return addr
406+
387407
388408 def mk_boxing (self ):
389409 '''provide boxing and unboxing'''
@@ -392,24 +412,29 @@ def mk_boxing(self):
392412 # we only support models/results at this point
393413 return
394414
395- self .unbox = self .cy_unbox ( )
415+ ll . add_symbol ( 'unbox_' + self .spec . c_name , self .cy_unboxer () )
396416
397417 @box (self .NbType )
398418 def box_me (typ , val , c ):
419+ 'Call Cythons contructor with the C++ pointer and return Python object.'
399420 ll_intp = c .context .get_value_type (types .uintp )
400421 addr = c .builder .ptrtoint (val , ll_intp )
401422 v = c .box (types .uintp , addr )
402423 py_obj = c .pyapi .unserialize (c .pyapi .serialize_object (self .spec .pyclass ))
403424 res = c .pyapi .call_function_objargs (py_obj , (v ,))
404425 return res
405426
406- #@unbox(self.NbType)
407- #def unbox_me(typ, obj, c):
408- # print('unbox_'+self.spec.c_name, self.NbType())
409- # val = cgutils.alloca_once(c.builder, lir.IntType(8).as_pointer())
410- # longobj = self.unbox(self.spec.c_name, val)
411- # c.builder.store(longobj, val)
412- # return NativeValue(c.builder.load(val), is_error=c.pyapi.c_api_error())
427+ @unbox (self .NbType )
428+ def unbox_me (typ , obj , c ):
429+ 'Call C++ unboxing function and return as NativeValue'
430+ # Define the signature
431+ fnty = lir .FunctionType (lir .IntType (8 ).as_pointer (), [lir .IntType (8 ).as_pointer (),])
432+ # Get function
433+ fn = c .builder .module .get_or_insert_function (fnty , name = 'unbox_' + self .spec .c_name )
434+ # finally generate the call
435+ ptr = c .builder .call (fn , [obj ])
436+ return NativeValue (ptr , is_error = c .pyapi .c_api_error ())
437+
413438
414439
415440##############################################################################
0 commit comments