# # TODO: Add support for vector. # import contextlib import llvm.core as lc import llvm.ee as le import llvm.passes as lp from llvm import LLVMException from . import shortnames as types ### # Utilities ### class FunctionAlreadyExists(NameError): def __init__(self, func): self.func = func def _is_int(ty): return isinstance(ty, lc.IntegerType) def _is_real(ty): tys = [ lc.Type.float(), lc.Type.double(), lc.Type.x86_fp80(), lc.Type.fp128(), lc.Type.ppc_fp128() ] return any(ty == x for x in tys) def _is_vector(ty, of=None): if isinstance(ty, lc.VectorType): if of is not None: return of(ty.element) return True else: return False def _is_pointer(ty): return isinstance(ty, lc.PointerType) def _is_block_terminated(bb): instrs = bb.instructions return len(instrs) > 0 and instrs[-1].is_terminator def _is_struct(ty): return isinstance(ty, lc.StructType) def _is_cstruct(ty): try: return issubclass(ty, CStruct) except TypeError: return False def _list_values(iterable): return [i.value for i in iterable] def _auto_coerce_index(cbldr, idx): if not isinstance(idx, CValue): idx = cbldr.constant(types.int, idx) return idx @contextlib.contextmanager def _change_block_temporarily(builder, bb): origbb = builder.basic_block builder.position_at_end(bb) yield builder.position_at_end(origbb) @contextlib.contextmanager def _change_block_temporarily_dummy(*args): yield class CastError(TypeError): def __init__(self, orig, to): super(CastError, self).__init__("Cannot cast from %s to %s" % (orig, to)) class _IfElse(object): '''if-else construct. Example ------- with cbuilder.ifelse(cond) as ifelse: with ifelse.then(): # code when cond is true # this block is mandatory with ifelse.otherwise(): # code when cond is false # this block is optional ''' def __init__(self, parent, cond): self.parent = parent self.cond = cond self._to_close = [] @contextlib.contextmanager def then(self): self._bbif = self.parent.function.append_basic_block('if.then') self._bbelse = self.parent.function.append_basic_block('if.else') builder = self.parent.builder builder.cbranch(self.cond.value, self._bbif, self._bbelse) builder.position_at_end(self._bbif) yield self._to_close.extend([self._bbif, self._bbelse, builder.basic_block]) @contextlib.contextmanager def otherwise(self): builder = self.parent.builder builder.position_at_end(self._bbelse) yield self._to_close.append(builder.basic_block) def close(self): self._to_close.append(self.parent.builder.basic_block) bbend = self.parent.function.append_basic_block('if.end') builder = self.parent.builder closed_count = 0 for bb in self._to_close: if not _is_block_terminated(bb): with _change_block_temporarily(builder, bb): builder.branch(bbend) closed_count += 1 builder.position_at_end(bbend) if not closed_count: self.parent.unreachable() class _Loop(object): '''while...do loop. Example ------- with cbuilder.loop() as loop: with loop.condition() as setcond: # Put the condition evaluation here setcond( cond ) # set loop condition # Do not put code after setcond(...) with loop.body(): # Put the code of the loop body here Use loop.break_loop() to break out of the loop. Use loop.continue_loop() to jump the condition evaulation. ''' def __init__(self, parent): self.parent = parent @contextlib.contextmanager def condition(self): builder = self.parent.builder self._bbcond = self.parent.function.append_basic_block('loop.cond') self._bbbody = self.parent.function.append_basic_block('loop.body') self._bbend = self.parent.function.append_basic_block('loop.end') builder.branch(self._bbcond) builder.position_at_end(self._bbcond) def setcond(cond): builder.cbranch(cond.value, self._bbbody, self._bbend) yield setcond @contextlib.contextmanager def body(self): builder = self.parent.builder builder.position_at_end(self._bbbody) yield self # close last block if not _is_block_terminated(builder.basic_block): builder.branch(self._bbcond) def break_loop(self): self.parent.builder.branch(self._bbend) def continue_loop(self): self.parent.builder.branch(self._bbcond) def close(self): builder = self.parent.builder builder.position_at_end(self._bbend) class CBuilder(object): ''' A wrapper class for features in llvm-py package to allow user to use C-like high-level language contruct easily. ''' def __init__(self, function): '''constructor function : is an empty function to be populating. ''' self.function = function self.declare_block = self.function.append_basic_block('decl') self.first_body_block = self.function.append_basic_block('body') self.builder = lc.Builder.new(self.first_body_block) self.target_data = le.TargetData.new(self.function.module.data_layout) self._auto_inline_list = [] # Prepare arguments. Make all function arguments behave like variables. self.args = [] for arg in function.args: var = self.var(arg.type, arg, name=arg.name) self.args.append(var) @staticmethod def new_function(mod, name, ret, args): '''factory method Create a new function in the module and return a CBuilder instance. ''' functype = lc.Type.function(ret, args) func = mod.add_function(functype, name=name) return CBuilder(func) def depends(self, fndecl): '''add function dependency Returns a CFunc instance and define the function if it is not defined. fndecl : is a callable that takes a `llvm.core.Module` and returns a function pointer. ''' return CFunc(self, fndecl(self.function.module)) def printf(self, fmt, *args): '''printf() from libc fmt : a character string holding printf format string. *args : additional variable arguments. ''' from .libc import LibC libc = LibC(self) ret = libc.printf(fmt, *args) return CTemp(self, ret) def debug(self, *args): '''debug print Use printf to dump the values of all arguments. ''' type_mapper = { 'i8' : '%c', 'i16': '%hd', 'i32': '%d', 'i64': '%ld', 'double': '%e', } itemsfmt = [] items = [] for i in args: if isinstance(i, str): itemsfmt.append(i.replace('%', '%%')) elif isinstance(i.type, lc.PointerType): itemsfmt.append("%p") items.append(i) else: tyname = str(i.type) if tyname == 'float': # auto convert float to double ty = '%e' i = i.cast(types.double) else: ty = type_mapper[tyname] itemsfmt.append(ty) items.append(i) fmt = ' '.join(itemsfmt) + '\n' return self.printf(self.constant_string(fmt), *items) def sizeof(self, ty): bldr = self.builder ptrty = types.pointer(ty) first = lc.Constant.null(ptrty) second = bldr.gep(first, [lc.Constant.int(types.intp, 1)]) firstint = bldr.ptrtoint(first, types.intp) secondint = bldr.ptrtoint(second, types.intp) diff = bldr.sub(secondint, firstint) return CTemp(self, diff) def min(self, x, y): z = self.var(x.type) with self.ifelse( x < y ) as ifelse: with ifelse.then(): z.assign(x) with ifelse.otherwise(): z.assign(y) return z def var(self, ty, value=None, name=''): '''allocate variable on the stack ty : variable type value : [optional] initializer value name : [optional] name used in LLVM IR ''' with _change_block_temporarily(self.builder, self.declare_block): # goto the first block is_cstruct = _is_cstruct(ty) if is_cstruct: cstruct = ty ty = ty.llvm_type() ptr = self.builder.alloca(ty, name=name) # back to the body if value is not None: if isinstance(value, CValue): value = value.value elif not isinstance(value, lc.Value): value = self.constant(ty, value).value self.builder.store(value, ptr) if is_cstruct: return cstruct(self, ptr) else: return CVar(self, ptr) def var_copy(self, val, name=''): '''allocate a new variable by copying another value The new variable has the same type and value of `val`. ''' return self.var(val.type, val, name=name) def array(self, ty, count, name=''): '''allocate an array on the stack ty : array element type count : array size; can be python int, llvm.core.Constant, or CValue name : [optional] name used in LLVM IR ''' if isinstance(count, int) or isinstance(count, lc.Constant): # Only go to the first block if array size is fixed. contexthelper = _change_block_temporarily else: # Do not go to the first block if the array size is dynamic. contexthelper = _change_block_temporarily_dummy with contexthelper(self.builder, self.declare_block): if _is_cstruct(ty): # array of struct? cstruct = ty ty = ty.llvm_type() if isinstance(count, CValue): count = count.value elif not isinstance(count, lc.Value): count = self.constant(types.int, count).value ptr = self.builder.alloca_array(ty, count, name=name) return CArray(self, ptr) def ret(self, val=None): '''insert return statement val : if is `None`, insert return-void else, return `val` ''' retty = self.function.type.pointee.return_type if val is not None: if val.type != retty: errmsg = "Return type mismatch" raise TypeError(errmsg) self.builder.ret(val.value) else: if retty != types.void: # errmsg = "Cannot return void" # raise TypeError(errmsg, retty) bb = self.function.append_basic_block('unreachable_bb') self.builder.position_at_end(bb) self.builder.ret(self.builder.alloca(retty, name='undef')) else: self.builder.ret_void() @contextlib.contextmanager def ifelse(self, cond): '''start a if-else block cond : branch condition ''' cb = _IfElse(self, cond) yield cb cb.close() @contextlib.contextmanager def loop(self): '''start a loop block ''' cb = _Loop(self) yield cb cb.close() @contextlib.contextmanager def forever(self): '''start a forever loop block ''' with self.loop() as loop: with loop.condition() as setcond: NULL = self.constant_null(types.int) setcond( NULL == NULL ) with loop.body(): yield loop @contextlib.contextmanager def for_range(self, *args): '''start a for-range block. *args : same as arguments of builtin `range()` ''' def check_arg(x): if isinstance(x, int): return self.constant(types.int, x) if not isinstance(x, IntegerValue): raise TypeError(x, "All args must be of integer type.") return x if len(args) == 3: start, stop, step = map(check_arg, args) elif len(args) == 2: start, stop = map(check_arg, args) step = self.constant(start.type, 1) elif len(args) == 1: stop = check_arg(args[0]) start = self.constant(stop.type, 0) step = self.constant(stop.type, 1) else: raise TypeError("Invalid # of arguments: 1, 2 or 3") idx = self.var_copy(start) with self.loop() as loop: with loop.condition() as setcond: setcond( idx < stop ) with loop.body(): yield loop, idx idx += step def position_at_end(self, bb): '''reposition inserter to the end of basic-block bb : a basic block ''' self.basic_block = bb self.builder.position_at_end(bb) def close(self): '''end code generation ''' # Close declaration block with _change_block_temporarily(self.builder, self.declare_block): self.builder.branch(self.first_body_block) # Do the auto inlining for callinst in self._auto_inline_list: lc.inline_function(callinst) def constant(self, ty, val): '''create a constant ty : data type val : initializer ''' if isinstance(ty, lc.IntegerType): res = lc.Constant.int(ty, val) elif ty == types.float or ty == types.double: res = lc.Constant.real(ty, val) else: raise TypeError("Cannot auto build constant " "from %s and value %s" % (ty, val)) return CTemp(self, res) def constant_null(self, ty): '''create a zero filled constant ty : data type ''' res = lc.Constant.null(ty) return CTemp(self, res) def constant_string(self, string): '''create a constant string This will de-duplication string of same content to minimize memory use. ''' mod = self.function.module collision = 0 name_fmt = '.conststr.%x.%x' content = lc.Constant.stringz(string) while True: name = name_fmt % (hash(string), collision) try: # check if the name already exists globalstr = mod.get_global_variable_named(name) except LLVMException: # new constant string globalstr = mod.add_global_variable(content.type, name=name) globalstr.initializer = content globalstr.linkage = lc.LINKAGE_LINKONCE_ODR globalstr.global_constant = True else: # compare existing content existed = str(globalstr.initializer) if existed != str(content): collision += 1 continue # loop until we resolve the name collision return CTemp(self, globalstr.bitcast( types.pointer(content.type.element))) def get_intrinsic(self, intrinsic_id, tys): '''get intrinsic function intrinsic_id : numerical ID of target intrinsic tys : type argument for the intrinsic ''' lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys) return CFunc(self, lfunc) def get_function_named(self, name): '''get function by name ''' m = self.function.module func = m.get_function_named(name) return CFunc(self, func) def is_terminated(self): '''is the current basic-block terminated? ''' return _is_block_terminated(self.builder.basic_block) def atomic_cmpxchg(self, ptr, old, val, ordering, crossthread=True): '''atomic compare-exchange ptr : pointer to data old : old value to compare to val : new value ordering : memory ordering as a string crossthread : set to `False` for single-thread code Returns the old value on success. ''' res = self.builder.atomic_cmpxchg(ptr.value, old.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_xchg(self, ptr, val, ordering, crossthread=True): '''atomic exchange ptr : pointer to data val : new value ordering : memory ordering as a string crossthread : set to `False` for single-thread code Returns the old value ''' res = self.builder.atomic_xchg(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_add(self, ptr, val, ordering, crossthread=True): '''atomic add ptr : pointer to data val : new value ordering : memory ordering as a string crossthread : set to `False` for single-thread code Returns the computation result of the operation ''' res = self.builder.atomic_add(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_sub(self, ptr, val, ordering, crossthread=True): '''atomic sub See `atomic_add` for parameters documentation ''' res = self.builder.atomic_sub(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_and(self, ptr, val, ordering, crossthread=True): '''atomic bitwise and See `atomic_add` for parameters documentation ''' res = self.builder.atomic_and(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_nand(self, ptr, val, ordering, crossthread=True): '''atomic bitwise nand See `atomic_add` for parameters documentation ''' res = self.builder.atomic_nand(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_or(self, ptr, val, ordering, crossthread=True): '''atomic bitwise or See `atomic_add` for parameters documentation ''' res = self.builder.atomic_or(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_xor(self, ptr, val, ordering, crossthread=True): '''atomic bitwise xor See `atomic_add` for parameters documentation ''' res = self.builder.atomic_xor(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_max(self, ptr, val, ordering, crossthread=True): '''atomic signed maximum between value at `ptr` and `val` See `atomic_add` for parameters documentation ''' res = self.builder.atomic_max(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_min(self, ptr, val, ordering, crossthread=True): '''atomic signed minimum between value at `ptr` and `val` See `atomic_add` for parameters documentation ''' res = self.builder.atomic_min(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_umax(self, ptr, val, ordering, crossthread=True): '''atomic unsigned maximum between value at `ptr` and `val` See `atomic_add` for parameters documentation ''' res = self.builder.atomic_umax(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_umin(self, ptr, val, ordering, crossthread=True): '''atomic unsigned minimum between value at `ptr` and `val` See `atomic_add` for parameters documentation ''' res = self.builder.atomic_umin(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_load(self, ptr, ordering, align=1, crossthread=True): '''atomic load ptr : pointer to the value to load align : memory alignment in bytes See `atomic_add` for other documentation of other parameters ''' res = self.builder.atomic_load(ptr.value, ordering, align, crossthread) return CTemp(self, res) def atomic_store(self, val, ptr, ordering, align=1, crossthread=True): '''atomic store ptr : pointer to where to store val : value to store align : memory alignment in bytes See `atomic_add` for other documentation of other parameters ''' res = self.builder.atomic_store(val.value, ptr.value, ordering, align, crossthread) return CTemp(self, res) def fence(self, ordering, crossthread=True): '''insert memory fence ''' res = self.builder.fence(ordering, crossthread) return CTemp(self, res) def alignment(self, ty): '''get minimum alignment of `ty` ''' return self.abi.abi_alignment(ty) @property def abi(self): return self.target_data def unreachable(self): '''insert instruction that causes segfault some platform (Intel), or no-op on others. It has no defined semantic. ''' self.builder.unreachable() def add_auto_inline(self, callinst): self._auto_inline_list.append(callinst) def set_memop_non_temporal(self, ldst): const_one = self.constant(types.int, 1).value md = lc.MetaData.get(self.function.module, [const_one]) ldst.set_metadata('nontemporal', md) class CFuncRef(object): '''create a function reference to use with `CBuilder.depends` Either from name, type and pointer, Or from a llvm.core.FunctionType instance ''' def __init__(self, *args, **kwargs): def one_arg(fn): self._fn = fn self._name = fn.name def three_arg(name, ty, ptr): self._name = name self._type = ty self._ptr = ptr try: three_arg(*args, **kwargs) self._meth = self._from_pointer except TypeError: one_arg(*args, **kwargs) self._meth = self._from_func def __call__(self, module): return self._meth() def _from_func(self): return self._fn def _from_pointer(self): fnptr = types.pointer(self._type) ptr = lc.Constant.int(types.intp, self._ptr) ptr = ptr.inttoptr(fnptr) return ptr def __str__(self): return self._name class CDefinition(object): '''represents function definition Inherit from this class to create a new function definition. Class Members ------------- _name_ : name of the function _retty_ : return type _argtys_ : argument names and types as list of tuples; e.g. [ ( 'myarg', lc.Type.int() ), ... ] ''' _name_ = '' # name of the function; should overide in subclass _retty_ = types.void # return type; can overide in subclass _argtys_ = [] # a list of tuple(name, type, [attributes]); can overide in subclass def __init__(self, *args, **kwargs): self.specialize(*args, **kwargs) self.cbuilder = None def specialize(self, *args, **kwargs): """ Override in subclasses """ def specialize_name(self): """ Specialize the class name to enable multiple function definitions """ cls = type(self) counter = getattr(cls, 'counter', 0) cls._name_ = "%s_%d" % (cls._name_, counter) cls.counter = counter + 1 def define(self, module, optimize=True): '''define the function in the module. Raises NameError if a function of the same name has already been defined. ''' functype = lc.Type.function(self._retty_, [arg[1] for arg in self._argtys_]) name = self._name_ if not name: raise AttributeError("Function name cannot be empty.") func = module.get_or_insert_function(functype, name=name) if not func.is_declaration: # already defined? raise FunctionAlreadyExists(func) # Name all arguments for i, arginfo in enumerate(self._argtys_): name = arginfo[0] func.args[i].name = name if len(arginfo) > 2: for attr in arginfo[2]: func.args[i].add_attribute(attr) # Create builder and populate body self.cbuilder = CBuilder(func) self.body(*self.cbuilder.args) self.cbuilder.close() self.cbuilder = None if optimize: fpm = lp.FunctionPassManager.new(module) pmb = lp.PassManagerBuilder.new() pmb.opt_level = 2 pmb.populate(fpm) fpm.run(func) return func def __call__(self, module): # We don't really have to overload __call__ to do things like # defining functions... try: func = self.define(module) except FunctionAlreadyExists as e: func = e.func return func def __getattr__(self, attr): return getattr(self.cbuilder, attr) def body(self): '''overide this function to define the body. ''' raise NotImplementedError def __str__(self): return self._name_ class CValue(object): def __init__(self, parent, handle): self.__parent = parent self.__handle = handle @property def handle(self): return self.__handle @property def parent(self): return self.__parent def _temp(self, val): return CTemp(self.parent, val) def _get_operator_provider(ty): if _is_pointer(ty): return PointerValue elif _is_int(ty): return IntegerValue elif _is_real(ty): return RealValue elif _is_vector(ty): inner = _get_operator_provider(ty.element) return type(str(ty), (inner, VectorIndexing), {}) elif _is_struct(ty): return StructValue else: assert False, (str(ty), type(ty)) class CTemp(CValue): def __new__(cls, parent, handle): meta = _get_operator_provider(handle.type) base = type(str('%s_%s' % (cls.__name__, handle.type)), (cls, meta), {}) return object.__new__(base) def __init__(self, *args, **kws): super(CTemp, self).__init__(*args, **kws) self._init_mixin() @property def value(self): return self.handle @property def type(self): return self.value.type class CVar(CValue): def __new__(cls, parent, ptr): meta = _get_operator_provider(ptr.type.pointee) base = type(str('%s_%s' % (cls.__name__, ptr.type.pointee)), (cls, meta), {}) return object.__new__(base) def __init__(self, parent, ptr): super(CVar, self).__init__(parent, ptr) self._init_mixin() self.invariant = False def reference(self): return self._temp(self.handle) @property def ref(self): return self.reference() @property def value(self): return self.parent.builder.load(self.ref.value) @property def type(self): return self.ref.type.pointee def assign(self, val, **kws): if self.invariant: raise TypeError("Storing to invariant variable.") self.parent.builder.store(val.value, self.ref.value, **kws) return self def __iadd__(self, rhs): return self.assign(self.__add__(rhs)) def __isub__(self, rhs): return self.assign(self.__sub__(rhs)) def __imul__(self, rhs): return self.assign(self.__mul__(rhs)) def __idiv__(self, rhs): return self.assign(self.__div__(rhs)) def __imod__(self, rhs): return self.assign(self.__mod__(rhs)) def __ilshift__(self, rhs): return self.assign(self.__lshift__(rhs)) def __irshift__(self, rhs): return self.assign(self.__rshift__(rhs)) def __iand__(self, rhs): return self.assign(self.__and__(rhs)) def __ior__(self, rhs): return self.assign(self.__or__(rhs)) def __ixor__(self, rhs): return self.assign(self.__xor__(rhs)) class OperatorMixin(object): def _init_mixin(self): pass class IntegerValue(OperatorMixin): def _init_mixin(self): self._unsigned = False def _get_unsigned(self): return self._unsigned def _set_unsigned(self, unsigned): self._unsigned = bool(unsigned) unsigned = property(_get_unsigned, _set_unsigned) def __add__(self, rhs): return self._temp(self.parent.builder.add(self.value, rhs.value)) def __sub__(self, rhs): return self._temp(self.parent.builder.sub(self.value, rhs.value)) def __mul__(self, rhs): return self._temp(self.parent.builder.mul(self.value, rhs.value)) def __div__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.udiv(self.value, rhs.value)) else: return self._temp(self.parent.builder.sdiv(self.value, rhs.value)) __truediv__ = __div__ __floordiv__ = __div__ def __mod__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.urem(self.value, rhs.value)) else: return self._temp(self.parent.builder.srem(self.value, rhs.value)) def __lshift__(self, rhs): return self._temp(self.parent.builder.shl(self.value, rhs.value)) def __rshift__(self, rhs): if self.unsigned: return self._temp(self.self.parent.builder.lshr(self.value, rhs.value)) else: return self._temp(self.parent.builder.ashr(self.value, rhs.value)) def __and__(self, rhs): return self._temp(self.parent.builder.and_(self.value, rhs.value)) def __or__(self, rhs): return self._temp(self.parent.builder.or_(self.value, rhs.value)) def __xor__(self, rhs): return self._temp(self.parent.builder.xor(self.value, rhs.value)) def __lt__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.icmp(lc.ICMP_ULT, self.value, rhs.value)) else: return self._temp(self.parent.builder.icmp(lc.ICMP_SLT, self.value, rhs.value)) def __le__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.icmp(lc.ICMP_ULE, self.value, rhs.value)) else: return self._temp(self.parent.builder.icmp(lc.ICMP_SLE, self.value, rhs.value)) def __eq__(self, rhs): return self._temp(self.parent.builder.icmp(lc.ICMP_EQ, self.value, rhs.value)) def __ne__(self, rhs): return self._temp(self.parent.builder.icmp(lc.ICMP_NE, self.value, rhs.value)) def __gt__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.icmp(lc.ICMP_UGT, self.value, rhs.value)) else: return self._temp(self.parent.builder.icmp(lc.ICMP_SGT, self.value, rhs.value)) def __ge__(self, rhs): if self.unsigned: return self._temp(self.parent.builder.icmp(lc.ICMP_UGE, self.value, rhs.value)) else: return self._temp(self.parent.builder.icmp(lc.ICMP_SGE, self.value, rhs.value)) def __neg__(self): return self._temp(self.parent.builder.neg(self.value)) def cast(self, ty, unsigned=False): if ty == self.type: return self._temp(self.value) if _is_real(ty): if self.unsigned or unsigned: return self._temp(self.parent.builder.uitofp(self.value, ty)) else: return self._temp(self.parent.builder.sitofp(self.value, ty)) elif _is_int(ty): if self.parent.abi.size(self.type) < self.parent.abi.size(ty): if self.unsigned or unsigned: return self._temp(self.parent.builder.zext(self.value, ty)) else: return self._temp(self.parent.builder.sext(self.value, ty)) else: return self._temp(self.parent.builder.trunc(self.value, ty)) elif _is_pointer(ty) and _is_int(self.type): return self._temp(self.parent.builder.inttoptr(self.value, ty)) elif _is_int(ty) and _is_pointer(self.type): return self._temp(self.parent.builder.ptrtoint(self.value, ty)) raise CastError(self.type, ty) class RealValue(OperatorMixin): def __add__(self, rhs): return self._temp(self.parent.builder.fadd(self.value, rhs.value)) def __sub__(self, rhs): return self._temp(self.parent.builder.fsub(self.value, rhs.value)) def __mul__(self, rhs): return self._temp(self.parent.builder.fmul(self.value, rhs.value)) def __div__(self, rhs): return self._temp(self.parent.builder.fdiv(self.value, rhs.value)) __truediv__ = __div__ def __mod__(self, rhs): return self._temp(self.parent.builder.frem(self.value, rhs.value)) def __lt__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_OLT, self.value, rhs.value)) def __le__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_OLE, self.value, rhs.value)) def __eq__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_OEQ, self.value, rhs.value)) def __ne__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_ONE, self.value, rhs.value)) def __gt__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_OGT, self.value, rhs.value)) def __ge__(self, rhs): return self._temp(self.parent.builder.fcmp(lc.FCMP_OGE, self.value, rhs.value)) def cast(self, ty, unsigned=False): if ty == self.type: return self._temp(self.value) if _is_int(ty): if unsigned: return self._temp(self.parent.builder.fptoui(self.value, ty)) else: return self._temp(self.parent.builder.fptosi(self.value, ty)) if _is_real(ty): if self.parent.abi.size(self.type) > self.parent.abi.size(ty): return self._temp(self.parent.builder.fptrunc(self.value, ty)) else: return self._temp(self.parent.builder.fpext(self.value, ty)) raise CastError(self.type, ty) class PointerIndexing(OperatorMixin): def __getitem__(self, idx): '''implement access indexing Uses GEP. ''' bldr = self.parent.builder if type(idx) is slice: # just handle case by case # Case #1: A[idx:] get pointer offset by idx if not idx.step and not idx.stop: idx = _auto_coerce_index(self.parent, idx.start) ptr = bldr.gep(self.value, [idx.value], inbounds=True) return CArray(self.parent, ptr) else: # return an variable at idx idx = _auto_coerce_index(self.parent, idx) ptr = bldr.gep(self.value, [idx.value], inbounds=True) return CVar(self.parent, ptr) def __setitem__(self, idx, val): idx = _auto_coerce_index(self.parent, idx) bldr = self.parent.builder self[idx].assign(val) class PointerCasting(OperatorMixin): def cast(self, ty): if ty == self.type: return self._temp(self.value) if _is_pointer(ty): return self._temp(self.parent.builder.bitcast(self.value, ty)) if _is_int(ty): return self._temp(self.parent.builder.ptrtoint(self.value, ty)) raise CastError(self.type, ty) class VectorIndexing(OperatorMixin): def __getitem__(self, idx): '''implement access indexing Uses GEP. ''' bldr = self.parent.builder idx = _auto_coerce_index(self.parent, idx) val = bldr.extract_element(self.value, idx.value) return CTemp(self.parent, val) def __setitem__(self, idx, val): idx = _auto_coerce_index(self.parent, idx) bldr = self.parent.builder vec = bldr.insert_element(self.value, val.value, idx.value) bldr.store(vec, self.ref.value) class PointerValue(PointerIndexing, PointerCasting): def load(self, **kws): return self._temp(self.parent.builder.load(self.value, **kws)) def store(self, val, nontemporal=False, **kws): inst = self.parent.builder.store(val.value, self.value, **kws) if nontemporal: self.parent.set_memop_non_temporal(inst) return inst def atomic_load(self, ordering, align=None, crossthread=True): '''atomic load memory for pointer types align : overide to control memory alignment; otherwise the default alignment of the type is used. Other parameters are the same as `CBuilder.atomic_load` ''' if align is None: align = self.parent.alignment(self.type.pointee) inst = self.parent.builder.atomic_load(self.value, ordering, align, crossthread=crossthread) return self._temp(inst) def atomic_store(self, value, ordering, align=None, crossthread=True): '''atomic memory store for pointer types align : overide to control memory alignment; otherwise the default alignment of the type is used. Other parameters are the same as `CBuilder.atomic_store` ''' if align is None: align = self.parent.alignment(self.type.pointee) self.parent.builder.atomic_store(value.ptr, self.value, ordering, align=align, crossthread=crossthread) def atomic_cmpxchg(self, old, new, ordering, crossthread=True): '''atomic compare-exchange for pointer types Other parameters are the same as `CBuilder.atomic_cmpxchg` ''' inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, new.value, ordering, crossthread=crossthread) return self._temp(inst) def as_struct(self, cstruct_class, volatile=False): '''load a pointer to a structure and assume a structure interface ''' ptr = self.parent.builder.load(self.value, volatile=volatile) return cstruct_class(self.parent, self.value) class StructValue(OperatorMixin): def as_struct(self, cstruct_class): '''assume a structure interface ''' return cstruct_class(self.parent, self.ref.value) class CFunc(CValue, PointerCasting): '''Wraps function pointer ''' def __init__(self, parent, func): super(CFunc, self).__init__(parent, func) @property def function(self): return self.handle def __call__(self, *args, **opts): '''Call the function with the given arguments *args : variable arguments of CValue instances ''' arg_values = _list_values(args) ftype = self.function.type.pointee for i, (exp, got) in enumerate(zip(ftype.args, arg_values)): if exp != got.type: raise TypeError("At call to %s, " "argument %d mismatch: %s != %s" % (self.function.name, i, exp, got.type)) res = self.parent.builder.call(self.function, arg_values) if hasattr(self.function, 'calling_convention'): res.calling_convention = self.function.calling_convention if opts.get('inline'): self.parent.add_auto_inline(res) if ftype.return_type != lc.Type.void(): return CTemp(self.parent, res) @property def value(self): return self.function @property def type(self): return self.function.type class CArray(CValue, PointerIndexing, PointerCasting): '''wraps a array Similar to C arrays ''' def __init__(self, parent, base): super(CArray, self).__init__(parent, base) @property def value(self): return self.handle def reference(self): return self._temp(self.value) @property def type(self): return self.value.type def vector_load(self, count, align=0): parent = self.parent builder = parent.builder values = [self[i] for i in range(count)] vecty = types.vector(self.type.pointee, count) vec = builder.load(builder.bitcast(self.value, types.pointer(vecty)), align=align) return self._temp(vec) def vector_store(self, vec, align=0): if vec.type.element != self.type.pointee: raise TypeError("Type mismatch; expect %s but got %s" % \ (vec.type.element, self.type.pointee)) parent = self.parent builder = parent.builder vecptr = builder.bitcast(self.value, types.pointer(vec.type)) builder.store(vec.value, vecptr, align=align) return self class CStruct(CValue): '''Wraps a structure Structure in LLVM can be identified by name of layout. Subclass to define a new structure. All fields are defined in the `_fields_` class attribute as a list of tuple (name, type). Can define new methods which gets inlined to the parent CBuilder. ''' def __init__(self, parent, ptr): super(CStruct, self).__init__(parent, ptr) makeind = lambda x: self.parent.constant(types.int, x).value for i, (fd, _) in enumerate(self._fields_): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) gep.name = "%s.%s" % (type(self).__name__, fd) if hasattr(self, fd): raise AttributeError("Field name shadows another attribute") setattr(self, fd, CVar(self.parent, gep)) self.type = self.llvm_type() def reference(self): return self._temp(self.handle) @classmethod def llvm_type(cls): return lc.Type.struct([v for k, v in cls._fields_]) @classmethod def from_numba_struct(cls, context, struct_type): class Struct(cls): _fields_ = [(name, type.to_llvm(context)) for name, type in struct_type.fields] return Struct class CExternal(object): '''subclass to define external interface All class attributes that are `llvm.core.FunctionType` are converted to `CFunc` instance during instantiation. ''' _calling_convention_ = None # default def __init__(self, cbuilder): is_func = lambda x: isinstance(x, lc.FunctionType) non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') ) to_declare = [] for fname in filter(non_magic, vars(type(self))): ftype = getattr(self, fname) if is_func(ftype): to_declare.append((fname, ftype)) mod = cbuilder.function.module for fname, ftype in to_declare: func = mod.get_or_insert_function(ftype, name=fname) if self._calling_convention_: func.calling_convention = self._calling_convention_ if func.type.pointee != ftype: raise NameError("Function has already been declared " "with a different type: %s != %s" % (func.type, ftype) ) setattr(self, fname, CFunc(cbuilder, func))