def minmax(typesystem, args, op): if len(args) < 2: return res = args[0] for arg in args[1:]: lhs_type = get_type(res) rhs_type = get_type(arg) res_type = typesystem.promote(lhs_type, rhs_type) if lhs_type != res_type: res = nodes.CoercionNode(res, res_type) if rhs_type != res_type: arg = nodes.CoercionNode(arg, res_type) lhs_temp = nodes.TempNode(res_type) rhs_temp = nodes.TempNode(res_type) res_temp = nodes.TempNode(res_type) lhs = lhs_temp.load(invariant=True) rhs = rhs_temp.load(invariant=True) expr = ast.IfExp(ast.Compare(lhs, [op], [rhs]), lhs, rhs) body = [ ast.Assign([lhs_temp.store()], res), ast.Assign([rhs_temp.store()], arg), ast.Assign([res_temp.store()], expr), ] res = nodes.ExpressionNode(body, res_temp.load(invariant=True)) return res
def single_compare(self, node): rhs = node.comparators[0] if is_obj(node.left.type): node = self.single_compare_objects(node) elif node.left.type.is_pointer and rhs.type.is_pointer: # Coerce pointers to integer values before comparing node.left = nodes.CoercionNode(node.left, Py_uintptr_t) node.comparators = [nodes.CoercionNode(rhs, Py_uintptr_t)] elif node.left.type.is_complex and rhs.type.is_complex: real1, imag1 = extract(node.left) real2, imag2 = extract(rhs) op = type(node.ops[0]) if op == ast.Eq: lhs = compare(real1, ast.Eq(), real2) rhs = compare(imag1, ast.Eq(), imag2) result = ast.BoolOp(ast.And(), [lhs, rhs]) elif op == ast.NotEq: lhs = compare(real1, ast.NotEq(), real2) rhs = compare(imag1, ast.NotEq(), imag2) result = ast.BoolOp(ast.Or(), [lhs, rhs]) else: raise NotImplementedError("ordered comparisons are not " "implemented for complex numbers") node = nodes.typednode(result, bool_) return node
def round_(typesystem, node, number, ndigits): argtype = get_type(number) if len(node.args) == 1 and argtype.is_int: # round(myint) -> float(myint) return nodes.CoercionNode(node.args[0], double) if argtype.is_float or argtype.is_int: dst_type = double else: dst_type = object_ node.args[0] = nodes.CoercionNode(node.args[0], object_) node.variable = Variable(dst_type) return node # nodes.CoercionNode(node, double)
def str_to_int(self, dst_type, node): # TODO: int <-> string conversions are explicit, this should not # TODO: be a coercion if self.nopython: node = nodes.CoercionNode( function_util.external_call( self.context, self.llvm_module, ('atol' if dst_type.is_int else 'atof'), args=[node.node]), dst_type, name=node.name, ) else: if dst_type.is_int: cvtobj = function_util.external_call( self.context, self.llvm_module, 'PyInt_FromString' if not PY3 else 'PyLong_FromString', args=[node.node, nodes.NULL, nodes.const(10, int_)]) else: cvtobj = function_util.external_call( self.context, self.llvm_module, 'PyFloat_FromString', args=[node.node, nodes.const(0, Py_ssize_t)]) node = nodes.CoerceToNative(nodes.ObjectTempNode(cvtobj), dst_type, name=node.name) result = self.visit(node) return result
def resolve_call(context, call_node, obj_call_node, func_type): """ Find the right type inferrer function for a call to an attribute of a certain module. call_node: the original ast.Call node that we need to resolve the type for obj_call_node: the nodes.ObjectCallNode that would replace the ast.Call unless we override that with another node. func_type: module_attribute |__________> module: Python module |__________> attr: Attribute name |__________> value: Attribute value Returns a new AST node that should replace the ast.Call node. """ result = dispatch_on_value(context, call_node, func_type) if result is not None and not isinstance(result, ast.AST): assert isinstance(result, Type), (Type, result) type = result result = obj_call_node # result.variable = symtab.Variable(type) result = nodes.CoercionNode(result, type) return result
def round_(context, node, number, ndigits): # is_math = is_math_function(node.args, round) argtype = get_type(number) if len(node.args) == 1 and argtype.is_int: # round(myint) -> float(myint) return nodes.CoercionNode(node.args[0], double) if argtype.is_float or argtype.is_int: dst_type = double else: dst_type = object_ node.args[0] = nodes.CoercionNode(node.args[0], object_) node.variable = Variable(dst_type) return node # nodes.CoercionNode(node, double)
def ord_(typesystem, node, expr): type = get_type(expr) if type.is_int and type.typename in ("char", "uchar"): return nodes.CoercionNode(expr, int_) elif type.is_string: # TODO: pass
def _resolve_int_number(self, func, node, argtype, dst_type, ext_name): assert len(node.args) == 2 arg1, arg2 = node.args if arg1.variable.type.is_string: return nodes.CoercionNode(nodes.ObjectTempNode( self.external_call(ext_name, args=[arg1, nodes.NULL, arg2])), dst_type=dst_type)
def build_array(self): self.dst_data = nodes.LLVMValueRefNode(void.pointer(), None) self.dst_shape = nodes.LLVMValueRefNode(self.shape_type, None) self.dst_strides = nodes.LLVMValueRefNode(self.shape_type, None) array_node = nodes.ArrayNewNode( self.type, self.dst_data, self.dst_shape, self.dst_strides, base=self.value.clone) return nodes.CoercionNode(array_node, self.type)
def resolve_pow(type, args): have_mod = len(args) == 3 if (type.is_int or type.is_float) and not have_mod and not is_win32: result = resolve_intrinsic(args, pow, type) else: result = nodes.call_pyfunc(pow, args) return nodes.CoercionNode(result, type)
def resolve_pow(env, restype, args): promote = env.crnt.typesystem.promote if restype.is_numeric: type = reduce(promote, [double, restype] + [a.type for a in args]) signature = type(*[type] * len(args)) result = nodes.MathCallNode(signature, args, None, name='pow') else: result = nodes.call_pyfunc(pow, args) return nodes.CoercionNode(result, restype)
def _resolve_builtin_call_or_object(self, node, func): """ Resolve an ast.Call() of a built-in function, or call the built-in through the object layer otherwise. """ result = self._resolve_builtin_call(node, func) if result is None: result = nodes.call_pyfunc(func, node.args) return nodes.CoercionNode(result, node.type)
def visit_Subscript(self, node): if isinstance(node.value, nodes.ArrayAttributeNode): if node.value.is_read_only and isinstance(node.ctx, ast.Store): raise error.NumbaError("Attempt to load read-only attribute") # Short-circuit visiting a Slice child if this is a nopython # string slice. if (self.nopython and node.value.type.is_c_string and node.type.is_c_string): return self.visit(self._c_string_slice(node)) # logging.debug(ast.dump(node)) # TODO: do this in the respective cases below when needed self.generic_visit(node) node_type = node.value.type if node_type.is_object or (node_type.is_array and node.slice.type.is_object): # Array or object slicing if isinstance(node.ctx, ast.Load): result = function_util.external_call( self.context, self.llvm_module, 'PyObject_GetItem', args=[node.value, node.slice]) node = nodes.CoercionNode(result, dst_type=node.type) node = self.visit(node) else: # This is handled in visit_Assign pass elif (node.value.type.is_array and not node.type.is_array and node.slice.type.is_int): # Array index with integer indices node = nodes.DataPointerNode(node.value, node.slice, node.ctx) elif node.value.type.is_c_string and node.type.is_c_string: node.value = nodes.CoercionNode(node.value, dst_type=object_) node.type = object_ node = nodes.CoercionNode(nodes.ObjectTempNode(node), dst_type=c_string_type) node = self.visit(node) return node
def get_closure_scope(func_signature, func_obj): """ Retrieve the closure from the NumbaFunction from the func_closure attribute. func_signature: signature of closure function func_obj: LLVM Value referencing the closure function as a Python object """ closure_scope_type = func_signature.args[0] offset = numbawrapper.numbafunc_closure_field_offset closure = nodes.LLVMValueRefNode(void.pointer(), func_obj) closure = nodes.CoercionNode(closure, char.pointer()) closure_field = nodes.pointer_add(closure, nodes.const(offset, size_t)) closure_field = nodes.CoercionNode(closure_field, closure_scope_type.pointer()) closure_scope = nodes.DereferenceNode(closure_field) return closure_scope
def len_(typesystem, node, obj): # Simplify len(array) to ndarray.shape[0] argtype = get_type(obj) if argtype.is_array: shape_attr = nodes.ArrayAttributeNode('shape', node.args[0]) new_node = nodes.index(shape_attr, 0) return new_node elif argtype.is_string: return nodes.CoercionNode(nodes.typednode(node, size_t), Py_ssize_t) return Py_ssize_t # Object call
def single_compare(self, node): rhs = node.comparators[0] if is_obj(node.left.type): node = self.single_compare_objects(node) elif node.left.type.is_pointer and rhs.type.is_pointer: # Coerce pointers to integer values before comparing node.left = nodes.CoercionNode(node.left, Py_uintptr_t) node.comparators = [nodes.CoercionNode(rhs, Py_uintptr_t)] elif node.left.type.is_complex and rhs.type.is_complex: real1, imag1 = extract(node.left) real2, imag2 = extract(rhs) lhs = compare(real1, real2) rhs = compare(imag1, imag2) result = ast.BoolOp(ast.And(), [lhs, rhs]) node = nodes.typednode(result, bool_) return node
def pow_(typesystem, node, base, exponent, mod=None): if mod: warnings.warn( "pow() with modulo (third) argument not natively supported") return nodes.call_pyfunc(pow, [base, exponent, mod]) from . import mathmodule dst_type = mathmodule.binop_type(typesystem, base, exponent) result = mathmodule.infer_math_call(typesystem, node, base, exponent, mod) if dst_type.is_int: # TODO: Implement pow(int) in llvmmath return nodes.CoercionNode(result, dst_type) return result
def resolve_libc_math(args, py_func, type): signature = intrinsic_signature(len(args), type) math_name = get_funcname(py_func) name = math_suffix(math_name, type) use_double_impl = not have_impl(name) if use_double_impl: assert have_double_impl(math_name) signature = double(*[double] * len(args)) name = math_suffix(math_name, double) result = nodes.MathCallNode(signature, args, llvm_func=None, py_func=py_func, name=name) return nodes.CoercionNode(result, type)
def retrieve_closure_from_numbafunc(self, node): """ Retrieve the closure scope from ((NumbaFunctionObject *) numba_func).func_closure """ # TODO: use llvmwrapper.get_closure_scope() pointer = nodes.ptrfromobj(node.func) type = typedefs.NumbaFunctionObject.ref() closure_obj_struct = nodes.CoercionNode(pointer, type) cur_scope = nodes.StructAttribute(closure_obj_struct, 'func_closure', ctx=ast.Load(), type=type) return cur_scope
def setup_error_return(self, node, ret_type): """ Set FunctionDef.error_return to the AST statement that returns a "bad value" that can be used as error indicator. """ value = nodes.badval(ret_type) if value is not None: value = nodes.CoercionNode(value, dst_type=ret_type).cloneable error_return = ast.Return(value=value) if self.nopython and is_obj(self.func_signature.return_type): error_return = nodes.WithPythonNode(body=[error_return]) error_return = self.visit(error_return) node.error_return = error_return
def visit_CoerceToObject(self, node): new_node = node node_type = node.node.type if node_type.is_bool: new_node = function_util.external_call(self.context, self.llvm_module, "PyBool_FromLong", args=[node.node]) elif node_type.is_numeric: cls = None args = node.node, if node_type.is_int: cls = self._get_int_conversion_func(node_type, pyapi._from_long) elif node_type.is_float: cls = pyapi.PyFloat_FromDouble elif node_type.is_complex: cls = pyapi.PyComplex_FromDoubles complex_value = nodes.CloneableNode(node.node) args = [ nodes.ComplexAttributeNode(complex_value, "real"), nodes.ComplexAttributeNode(complex_value.clone, "imag") ] else: raise error.NumbaError( node, "Don't know how to coerce type %r to PyObject" % node_type) if cls: new_node = function_util.external_call(self.context, self.llvm_module, cls.__name__, args=args) elif node_type.is_pointer and not node_type.is_string(): # Create ctypes pointer object ctypes_pointer_type = node_type.to_ctypes() args = [ nodes.CoercionNode(node.node, int64), nodes.ObjectInjectNode(ctypes_pointer_type, object_) ] new_node = nodes.call_pyfunc(ctypes.cast, args) self.generic_visit(new_node) return new_node
def single_compare_objects(self, node): op = type(node.ops[0]) if op not in opmap: raise error.NumbaError( node, "%s comparisons not yet implemented" % (op, )) # Build arguments for PyObject_RichCompareBool operator = nodes.const(opmap[op], int_) args = [node.left, node.comparators[0], operator] # Call PyObject_RichCompareBool compare = function_util.external_call(self.context, self.llvm_module, 'PyObject_RichCompare', args=args) # Coerce int result to bool return nodes.CoercionNode(compare, node.type)
def visit_Raise(self, node): # Create void * temporaries args = [] # Type, Value, Traceback, Cause for arg in [node.type, node.inst, node.tback, None]: if arg: arg = nodes.CoercionNode(arg, object_) arg = nodes.PointerFromObject(arg) else: arg = nodes.NULL args.append(arg) # Call numba/external/utitilies/cpyutils.c:do_raise() set_exc = function_util.utility_call(self.context, self.llvm_module, 'Raise', args) result = self.visit(set_exc) return result
def typedcontainer_infer(compile_typedcontainer, type_node, iterable_node): """ Type inferer for typed containers, register with numba.register_inferer(). :param compile_typedcontainer: item_type -> typed container extension class :param type_node: type parameter to typed container constructor :param iterable_node: value parameter to typed container constructor (optional) """ assert type_node is not None type = get_type(type_node) if type.is_cast: elem_type = type.dst_type # Pre-compile typed list implementation typedcontainer_ctor = compile_typedcontainer(elem_type) # Inject the typedlist directly to avoid runtime implementation lookup iterable_node = iterable_node or nodes.const(None, object_) result = nodes.call_pyfunc(typedcontainer_ctor, (iterable_node,)) return nodes.CoercionNode(result, typedcontainer_ctor.exttype) return object_
def visit_For(self, node): while_node = make_while_from_for(node) test = nodes.const(True, bool_) while_node.test = test impl = loopimpl.find_iterator_impl(node) # Get the iterator, loop body, and the item iter = impl.getiter(self.context, node, self.llvm_module) body = impl.body(self.context, node, self.llvm_module) item = impl.next(self.context, node, self.llvm_module) # Coerce item to LHS and assign item = nodes.CoercionNode(item, node.target.type) target_assmnt = ast.Assign(targets=[node.target], value=item) # Update While node body body.insert(0, target_assmnt) while_node.body = body nodes.merge_cfg_in_while(while_node) return ast.Suite(body=[iter, while_node])
def unellipsify(node, slices, subscript_node): """ Given an array node `node`, process all AST slices and create the final type: - process newaxes (None or numpy.newaxis) - replace Ellipsis with a bunch of ast.Slice objects - process integer indices - append any missing slices in trailing dimensions """ type = node.variable.type if not type.is_array: assert type.is_object return object_, node if (len(slices) == 1 and nodes.is_constant_index(slices[0]) and slices[0].value.pyval is Ellipsis): # A[...] return type, node result = [] seen_ellipsis = False # Filter out newaxes newaxes = [newaxis for newaxis in slices if nodes.is_newaxis(newaxis)] n_indices = len(slices) - len(newaxes) full_slice = ast.Slice(lower=None, upper=None, step=None) full_slice.variable = Variable(typesystem.slice_) ast.copy_location(full_slice, slices[0]) # process ellipses and count integer indices indices_seen = 0 for slice_node in slices[::-1]: slice_type = slice_node.variable.type if slice_type.is_ellipsis: if seen_ellipsis: result.append(full_slice) else: nslices = type.ndim - n_indices + 1 result.extend([full_slice] * nslices) seen_ellipsis = True elif (slice_type.is_slice or slice_type.is_int or nodes.is_newaxis(slice_node)): indices_seen += slice_type.is_int result.append(slice_node) else: # TODO: Coerce all object operands to integer indices? # TODO: (This will break indexing with the Ellipsis object or # TODO: with slice objects that we couldn't infer) return object_, nodes.CoercionNode(node, object_) # Reverse our reversed processed list of slices result.reverse() # append any missing slices (e.g. a2d[:] result_length = len(result) - len(newaxes) if result_length < type.ndim: nslices = type.ndim - result_length result.extend([full_slice] * nslices) subscript_node.slice = ast.ExtSlice(result) ast.copy_location(subscript_node.slice, slices[0]) # create the final array type and set it in value.variable result_dtype = node.variable.type.dtype result_ndim = node.variable.type.ndim + len(newaxes) - indices_seen if result_ndim > 0: result_type = result_dtype[(slice(None),) * result_ndim] elif result_ndim == 0: result_type = result_dtype else: result_type = object_ return result_type, node
def rewrite_array_iteration(self, node): """ Convert 1D array iteration to for-range and indexing: for value in my_array: ... becomes for i in my_array.shape[0]: value = my_array[i] ... """ logger.debug(ast.dump(node)) orig_target = node.target orig_iter = node.iter #-------------------------------------------------------------------- # Replace node.target with a temporary #-------------------------------------------------------------------- target_name = orig_target.id + '.idx' target_temp = nodes.TempNode(Py_ssize_t) node.target = target_temp.store() #-------------------------------------------------------------------- # Create range(A.shape[0]) #-------------------------------------------------------------------- call_func = ast.Name(id='range', ctx=ast.Load()) nodes.typednode(call_func, typesystem.range_) shape_index = ast.Index(nodes.ConstNode(0, typesystem.Py_ssize_t)) shape_index.type = typesystem.npy_intp stop = ast.Subscript(value=nodes.ShapeAttributeNode(orig_iter), slice=shape_index, ctx=ast.Load()) nodes.typednode(stop, npy_intp) #-------------------------------------------------------------------- # Create range iterator and replace node.iter #-------------------------------------------------------------------- call_args = [ nodes.ConstNode(0, typesystem.Py_ssize_t), nodes.CoercionNode(stop, typesystem.Py_ssize_t), nodes.ConstNode(1, typesystem.Py_ssize_t), ] node.iter = ast.Call(func=call_func, args=call_args) nodes.typednode(node.iter, call_func.type) node.index = target_temp.load(invariant=True) #-------------------------------------------------------------------- # Add assignment to new target variable at the start of the body #-------------------------------------------------------------------- index = ast.Index(value=node.index) index.type = target_temp.type subscript = ast.Subscript(value=orig_iter, slice=index, ctx=ast.Load()) nodes.typednode(subscript, get_type(orig_iter).dtype) #-------------------------------------------------------------------- # Add assignment to new target variable at the start of the body #-------------------------------------------------------------------- assign = ast.Assign(targets=[orig_target], value=subscript) node.body = [assign] + node.body #-------------------------------------------------------------------- # Specialize new for loop through range iteration #-------------------------------------------------------------------- return self.visit(node)
def build_wrapper_function_ast(env, wrapper_lfunc, llvm_module): """ Build AST for LLVM function wrapper. lfunc: LLVM function to wrap llvm_module: module the wrapper is being defined in The resulting AST has a NativeCallNode to the wrapped function. The arguments are LLVMValueRefNode nodes which still need their llvm_value set to the object from the tuple. This happens in visit_FunctionWrapperNode during codegen. """ func = env.crnt.func func_signature = env.crnt.func_signature func_name = env.crnt.func_name # Insert external declaration lfunc = llvm_module.get_or_insert_function( func_signature.to_llvm(env.context), env.crnt.lfunc.name) # Build AST wrapper = nodes.FunctionWrapperNode(lfunc, func_signature, func, fake_pyfunc, func_name) error_return = ast.Return(nodes.CoercionNode(nodes.NULL_obj, object_)) is_closure = bool(closures.is_closure_signature(func_signature)) nargs = len(func_signature.args) - is_closure # Call wrapped function with unpacked object arguments # (delay actual arguments) args = [nodes.LLVMValueRefNode(object_, None) for i in range(nargs)] if is_closure: # Insert m_self as scope argument type closure_scope = get_closure_scope(func_signature, wrapper_lfunc.args[0]) args.insert(0, closure_scope) func_call = nodes.NativeCallNode(func_signature, args, lfunc) if not is_obj(func_signature.return_type): # Check for error using PyErr_Occurred() func_call = nodes.PyErr_OccurredNode(func_call) # Coerce and return result if func_signature.return_type.is_void: wrapper.body = func_call result_node = nodes.ObjectInjectNode(None) else: wrapper.body = None result_node = func_call wrapper.return_result = ast.Return(value=nodes.CoercionNode(result_node, object_)) # Update wrapper wrapper.error_return = error_return wrapper.cellvars = [] wrapper.wrapped_nargs = nargs wrapper.wrapped_args = args[is_closure:] return wrapper
def cast(node, dst_type): if len(node.args) == 0: return nodes.ConstNode(0, dst_type) else: return nodes.CoercionNode(node.args[0], dst_type=dst_type)
def chr_(typesystem, node, expr): type = get_type(expr) if type.is_int: return nodes.CoercionNode(expr, char)