""" Implementation of LLVM IR instructions. """ from llvmlite.ir import types from llvmlite.ir.values import (Block, Function, Value, NamedValue, Constant, MetaDataArgument, MetaDataString, AttributeSet, Undefined, ArgumentAttributes) from llvmlite.ir._utils import _HasMetadata class Instruction(NamedValue, _HasMetadata): def __init__(self, parent, typ, opname, operands, name='', flags=()): super(Instruction, self).__init__(parent, typ, name=name) assert isinstance(parent, Block) assert isinstance(flags, (tuple, list)) self.opname = opname self.operands = operands self.flags = list(flags) self.metadata = {} @property def function(self): return self.parent.function @property def module(self): return self.parent.function.module def descr(self, buf): opname = self.opname if self.flags: opname = ' '.join([opname] + self.flags) operands = ', '.join([op.get_reference() for op in self.operands]) typ = self.type metadata = self._stringify_metadata(leading_comma=True) buf.append("{0} {1} {2}{3}\n" .format(opname, typ, operands, metadata)) def replace_usage(self, old, new): if old in self.operands: ops = [] for op in self.operands: ops.append(new if op is old else op) self.operands = tuple(ops) self._clear_string_cache() def __repr__(self): return "" % ( self.__class__.__name__, self.name, self.type, self.opname, self.operands) class CallInstrAttributes(AttributeSet): _known = frozenset(['noreturn', 'nounwind', 'readonly', 'readnone', 'noinline', 'alwaysinline']) TailMarkerOptions = frozenset(['tail', 'musttail', 'notail']) class FastMathFlags(AttributeSet): _known = frozenset(['fast', 'nnan', 'ninf', 'nsz', 'arcp', 'contract', 'afn', 'reassoc']) class CallInstr(Instruction): def __init__(self, parent, func, args, name='', cconv=None, tail=None, fastmath=(), attrs=(), arg_attrs=None): self.cconv = (func.calling_convention if cconv is None and isinstance(func, Function) else cconv) # For backwards compatibility with previous API of accepting a "truthy" # value for a hint to the optimizer to potentially tail optimize. if isinstance(tail, str) and tail in TailMarkerOptions: pass elif tail: tail = "tail" else: tail = "" self.tail = tail self.fastmath = FastMathFlags(fastmath) self.attributes = CallInstrAttributes(attrs) self.arg_attributes = {} if arg_attrs: for idx, attrs in arg_attrs.items(): if not (0 <= idx < len(args)): raise ValueError("Invalid argument index {}" .format(idx)) self.arg_attributes[idx] = ArgumentAttributes(attrs) # Fix and validate arguments args = list(args) for i in range(len(func.function_type.args)): arg = args[i] expected_type = func.function_type.args[i] if (isinstance(expected_type, types.MetaDataType) and arg.type != expected_type): arg = MetaDataArgument(arg) if arg.type != expected_type: msg = ("Type of #{0} arg mismatch: {1} != {2}" .format(1 + i, expected_type, arg.type)) raise TypeError(msg) args[i] = arg super(CallInstr, self).__init__(parent, func.function_type.return_type, "call", [func] + list(args), name=name) @property def callee(self): return self.operands[0] @callee.setter def callee(self, newcallee): self.operands[0] = newcallee @property def args(self): return self.operands[1:] def replace_callee(self, newfunc): if newfunc.function_type != self.callee.function_type: raise TypeError("New function has incompatible type") self.callee = newfunc @property def called_function(self): """The callee function""" return self.callee def _descr(self, buf, add_metadata): def descr_arg(i, a): if i in self.arg_attributes: attrs = ' '.join(self.arg_attributes[i]._to_list(a.type)) + ' ' else: attrs = '' return '{0} {1}{2}'.format(a.type, attrs, a.get_reference()) args = ', '.join([descr_arg(i, a) for i, a in enumerate(self.args)]) fnty = self.callee.function_type # Only print function type if variable-argument if fnty.var_arg: ty = fnty # Otherwise, just print the return type. else: # Fastmath flag work only in this case ty = fnty.return_type callee_ref = "{0} {1}".format(ty, self.callee.get_reference()) if self.cconv: callee_ref = "{0} {1}".format(self.cconv, callee_ref) tail_marker = "" if self.tail: tail_marker = "{0} ".format(self.tail) fn_attrs = ' ' + ' '.join(self.attributes._to_list(fnty.return_type))\ if self.attributes else '' fm_attrs = ' ' + ' '.join(self.fastmath._to_list(fnty.return_type))\ if self.fastmath else '' buf.append("{tail}{op}{fastmath} {callee}({args}){attr}{meta}\n".format( tail=tail_marker, op=self.opname, callee=callee_ref, fastmath=fm_attrs, args=args, attr=fn_attrs, meta=(self._stringify_metadata(leading_comma=True) if add_metadata else ""), )) def descr(self, buf): self._descr(buf, add_metadata=True) class InvokeInstr(CallInstr): def __init__(self, parent, func, args, normal_to, unwind_to, name='', cconv=None, fastmath=(), attrs=(), arg_attrs=None): assert isinstance(normal_to, Block) assert isinstance(unwind_to, Block) super(InvokeInstr, self).__init__(parent, func, args, name, cconv, tail=False, fastmath=fastmath, attrs=attrs, arg_attrs=arg_attrs) self.opname = "invoke" self.normal_to = normal_to self.unwind_to = unwind_to def descr(self, buf): super(InvokeInstr, self)._descr(buf, add_metadata=False) buf.append(" to label {0} unwind label {1}{metadata}\n".format( self.normal_to.get_reference(), self.unwind_to.get_reference(), metadata=self._stringify_metadata(leading_comma=True), )) class Terminator(Instruction): def __init__(self, parent, opname, operands): super(Terminator, self).__init__(parent, types.VoidType(), opname, operands) def descr(self, buf): opname = self.opname operands = ', '.join(["{0} {1}".format(op.type, op.get_reference()) for op in self.operands]) metadata = self._stringify_metadata(leading_comma=True) buf.append("{0} {1}{2}".format(opname, operands, metadata)) class PredictableInstr(Instruction): def set_weights(self, weights): operands = [MetaDataString(self.module, "branch_weights")] for w in weights: if w < 0: raise ValueError("branch weight must be a positive integer") operands.append(Constant(types.IntType(32), w)) md = self.module.add_metadata(operands) self.set_metadata("prof", md) class Ret(Terminator): def __init__(self, parent, opname, return_value=None): operands = [return_value] if return_value is not None else [] super(Ret, self).__init__(parent, opname, operands) @property def return_value(self): if self.operands: return self.operands[0] else: return None def descr(self, buf): return_value = self.return_value metadata = self._stringify_metadata(leading_comma=True) if return_value is not None: buf.append("{0} {1} {2}{3}\n" .format(self.opname, return_value.type, return_value.get_reference(), metadata)) else: buf.append("{0}{1}\n".format(self.opname, metadata)) class Branch(Terminator): pass class ConditionalBranch(PredictableInstr, Terminator): pass class IndirectBranch(PredictableInstr, Terminator): def __init__(self, parent, opname, addr): super(IndirectBranch, self).__init__(parent, opname, [addr]) self.destinations = [] @property def address(self): return self.operands[0] def add_destination(self, block): assert isinstance(block, Block) self.destinations.append(block) def descr(self, buf): destinations = ["label {0}".format(blk.get_reference()) for blk in self.destinations] buf.append("indirectbr {0} {1}, [{2}] {3}\n".format( self.address.type, self.address.get_reference(), ', '.join(destinations), self._stringify_metadata(leading_comma=True), )) class SwitchInstr(PredictableInstr, Terminator): def __init__(self, parent, opname, val, default): super(SwitchInstr, self).__init__(parent, opname, [val]) self.default = default self.cases = [] @property def value(self): return self.operands[0] def add_case(self, val, block): assert isinstance(block, Block) if not isinstance(val, Value): val = Constant(self.value.type, val) self.cases.append((val, block)) def descr(self, buf): cases = ["{0} {1}, label {2}".format(val.type, val.get_reference(), blk.get_reference()) for val, blk in self.cases] buf.append("switch {0} {1}, label {2} [{3}] {4}\n".format( self.value.type, self.value.get_reference(), self.default.get_reference(), ' '.join(cases), self._stringify_metadata(leading_comma=True), )) class Resume(Terminator): pass class SelectInstr(Instruction): def __init__(self, parent, cond, lhs, rhs, name='', flags=()): assert lhs.type == rhs.type super(SelectInstr, self).__init__(parent, lhs.type, "select", [cond, lhs, rhs], name=name, flags=flags) @property def cond(self): return self.operands[0] @property def lhs(self): return self.operands[1] @property def rhs(self): return self.operands[2] def descr(self, buf): buf.append("select {0} {1} {2}, {3} {4}, {5} {6} {7}\n".format( ' '.join(self.flags), self.cond.type, self.cond.get_reference(), self.lhs.type, self.lhs.get_reference(), self.rhs.type, self.rhs.get_reference(), self._stringify_metadata(leading_comma=True), )) class CompareInstr(Instruction): # Define the following in subclasses OPNAME = 'invalid-compare' VALID_OP = {} def __init__(self, parent, op, lhs, rhs, name='', flags=[]): if op not in self.VALID_OP: raise ValueError("invalid comparison %r for %s" % (op, self.OPNAME)) for flag in flags: if flag not in self.VALID_FLAG: raise ValueError("invalid flag %r for %s" % (flag, self.OPNAME)) opname = self.OPNAME if isinstance(lhs.type, types.VectorType): typ = types.VectorType(types.IntType(1), lhs.type.count) else: typ = types.IntType(1) super(CompareInstr, self).__init__(parent, typ, opname, [lhs, rhs], flags=flags, name=name) self.op = op def descr(self, buf): buf.append("{opname}{flags} {op} {ty} {lhs}, {rhs} {meta}\n".format( opname=self.opname, flags=''.join(' ' + it for it in self.flags), op=self.op, ty=self.operands[0].type, lhs=self.operands[0].get_reference(), rhs=self.operands[1].get_reference(), meta=self._stringify_metadata(leading_comma=True), )) class ICMPInstr(CompareInstr): OPNAME = 'icmp' VALID_OP = { 'eq': 'equal', 'ne': 'not equal', 'ugt': 'unsigned greater than', 'uge': 'unsigned greater or equal', 'ult': 'unsigned less than', 'ule': 'unsigned less or equal', 'sgt': 'signed greater than', 'sge': 'signed greater or equal', 'slt': 'signed less than', 'sle': 'signed less or equal', } VALID_FLAG = set() class FCMPInstr(CompareInstr): OPNAME = 'fcmp' VALID_OP = { 'false': 'no comparison, always returns false', 'oeq': 'ordered and equal', 'ogt': 'ordered and greater than', 'oge': 'ordered and greater than or equal', 'olt': 'ordered and less than', 'ole': 'ordered and less than or equal', 'one': 'ordered and not equal', 'ord': 'ordered (no nans)', 'ueq': 'unordered or equal', 'ugt': 'unordered or greater than', 'uge': 'unordered or greater than or equal', 'ult': 'unordered or less than', 'ule': 'unordered or less than or equal', 'une': 'unordered or not equal', 'uno': 'unordered (either nans)', 'true': 'no comparison, always returns true', } VALID_FLAG = {'nnan', 'ninf', 'nsz', 'arcp', 'contract', 'afn', 'reassoc', 'fast'} class CastInstr(Instruction): def __init__(self, parent, op, val, typ, name=''): super(CastInstr, self).__init__(parent, typ, op, [val], name=name) def descr(self, buf): buf.append("{0} {1} {2} to {3} {4}\n".format( self.opname, self.operands[0].type, self.operands[0].get_reference(), self.type, self._stringify_metadata(leading_comma=True), )) class LoadInstr(Instruction): def __init__(self, parent, ptr, name=''): super(LoadInstr, self).__init__(parent, ptr.type.pointee, "load", [ptr], name=name) self.align = None def descr(self, buf): [val] = self.operands if self.align is not None: align = ', align %d' % (self.align) else: align = '' buf.append("load {0}, {1} {2}{3}{4}\n".format( val.type.pointee, val.type, val.get_reference(), align, self._stringify_metadata(leading_comma=True), )) class StoreInstr(Instruction): def __init__(self, parent, val, ptr): super(StoreInstr, self).__init__(parent, types.VoidType(), "store", [val, ptr]) def descr(self, buf): val, ptr = self.operands if self.align is not None: align = ', align %d' % (self.align) else: align = '' buf.append("store {0} {1}, {2} {3}{4}{5}\n".format( val.type, val.get_reference(), ptr.type, ptr.get_reference(), align, self._stringify_metadata(leading_comma=True), )) class LoadAtomicInstr(Instruction): def __init__(self, parent, ptr, ordering, align, name=''): super(LoadAtomicInstr, self).__init__(parent, ptr.type.pointee, "load atomic", [ptr], name=name) self.ordering = ordering self.align = align def descr(self, buf): [val] = self.operands buf.append("load atomic {0}, {1} {2} {3}, align {4}{5}\n".format( val.type.pointee, val.type, val.get_reference(), self.ordering, self.align, self._stringify_metadata(leading_comma=True), )) class StoreAtomicInstr(Instruction): def __init__(self, parent, val, ptr, ordering, align): super(StoreAtomicInstr, self).__init__(parent, types.VoidType(), "store atomic", [val, ptr]) self.ordering = ordering self.align = align def descr(self, buf): val, ptr = self.operands buf.append("store atomic {0} {1}, {2} {3} {4}, align {5}{6}\n".format( val.type, val.get_reference(), ptr.type, ptr.get_reference(), self.ordering, self.align, self._stringify_metadata(leading_comma=True), )) class AllocaInstr(Instruction): def __init__(self, parent, typ, count, name): operands = [count] if count else () super(AllocaInstr, self).__init__(parent, typ.as_pointer(), "alloca", operands, name) self.align = None def descr(self, buf): buf.append("{0} {1}".format(self.opname, self.type.pointee)) if self.operands: op, = self.operands buf.append(", {0} {1}".format(op.type, op.get_reference())) if self.align is not None: buf.append(", align {0}".format(self.align)) if self.metadata: buf.append(self._stringify_metadata(leading_comma=True)) class GEPInstr(Instruction): def __init__(self, parent, ptr, indices, inbounds, name): typ = ptr.type lasttyp = None lastaddrspace = 0 for i in indices: lasttyp, typ = typ, typ.gep(i) # inherit the addrspace from the last seen pointer if isinstance(lasttyp, types.PointerType): lastaddrspace = lasttyp.addrspace if (not isinstance(typ, types.PointerType) and isinstance(lasttyp, types.PointerType)): typ = lasttyp else: typ = typ.as_pointer(lastaddrspace) super(GEPInstr, self).__init__(parent, typ, "getelementptr", [ptr] + list(indices), name=name) self.pointer = ptr self.indices = indices self.inbounds = inbounds def descr(self, buf): indices = ['{0} {1}'.format(i.type, i.get_reference()) for i in self.indices] op = "getelementptr inbounds" if self.inbounds else "getelementptr" buf.append("{0} {1}, {2} {3}, {4} {5}\n".format( op, self.pointer.type.pointee, self.pointer.type, self.pointer.get_reference(), ', '.join(indices), self._stringify_metadata(leading_comma=True), )) class PhiInstr(Instruction): def __init__(self, parent, typ, name, flags=()): super(PhiInstr, self).__init__(parent, typ, "phi", (), name=name, flags=flags) self.incomings = [] def descr(self, buf): incs = ', '.join('[{0}, {1}]'.format(v.get_reference(), b.get_reference()) for v, b in self.incomings) buf.append("phi {0} {1} {2} {3}\n".format( ' '.join(self.flags), self.type, incs, self._stringify_metadata(leading_comma=True), )) def add_incoming(self, value, block): assert isinstance(block, Block) self.incomings.append((value, block)) def replace_usage(self, old, new): self.incomings = [((new if val is old else val), blk) for (val, blk) in self.incomings] class ExtractElement(Instruction): def __init__(self, parent, vector, index, name=''): if not isinstance(vector.type, types.VectorType): raise TypeError("vector needs to be of VectorType.") if not isinstance(index.type, types.IntType): raise TypeError("index needs to be of IntType.") typ = vector.type.element super(ExtractElement, self).__init__(parent, typ, "extractelement", [vector, index], name=name) def descr(self, buf): operands = ", ".join("{0} {1}".format( op.type, op.get_reference()) for op in self.operands) buf.append("{opname} {operands}\n".format( opname=self.opname, operands=operands)) class InsertElement(Instruction): def __init__(self, parent, vector, value, index, name=''): if not isinstance(vector.type, types.VectorType): raise TypeError("vector needs to be of VectorType.") if not value.type == vector.type.element: raise TypeError( "value needs to be of type {} not {}.".format( vector.type.element, value.type)) if not isinstance(index.type, types.IntType): raise TypeError("index needs to be of IntType.") typ = vector.type super(InsertElement, self).__init__(parent, typ, "insertelement", [vector, value, index], name=name) def descr(self, buf): operands = ", ".join("{0} {1}".format( op.type, op.get_reference()) for op in self.operands) buf.append("{opname} {operands}\n".format( opname=self.opname, operands=operands)) class ShuffleVector(Instruction): def __init__(self, parent, vector1, vector2, mask, name=''): if not isinstance(vector1.type, types.VectorType): raise TypeError("vector1 needs to be of VectorType.") if vector2 != Undefined: if vector2.type != vector1.type: raise TypeError("vector2 needs to be " + "Undefined or of the same type as vector1.") if (not isinstance(mask, Constant) or not isinstance(mask.type, types.VectorType) or not (isinstance(mask.type.element, types.IntType) and mask.type.element.width == 32)): raise TypeError("mask needs to be a constant i32 vector.") typ = types.VectorType(vector1.type.element, mask.type.count) index_range = range(vector1.type.count if vector2 == Undefined else 2 * vector1.type.count) if not all(ii.constant in index_range for ii in mask.constant): raise IndexError( "mask values need to be in {0}".format(index_range), ) super(ShuffleVector, self).__init__(parent, typ, "shufflevector", [vector1, vector2, mask], name=name) def descr(self, buf): buf.append("shufflevector {0} {1}\n".format( ", ".join("{0} {1}".format(op.type, op.get_reference()) for op in self.operands), self._stringify_metadata(leading_comma=True), )) class ExtractValue(Instruction): def __init__(self, parent, agg, indices, name=''): typ = agg.type try: for i in indices: typ = typ.elements[i] except (AttributeError, IndexError): raise TypeError("Can't index at %r in %s" % (list(indices), agg.type)) super(ExtractValue, self).__init__(parent, typ, "extractvalue", [agg], name=name) self.aggregate = agg self.indices = indices def descr(self, buf): indices = [str(i) for i in self.indices] buf.append("extractvalue {0} {1}, {2} {3}\n".format( self.aggregate.type, self.aggregate.get_reference(), ', '.join(indices), self._stringify_metadata(leading_comma=True), )) class InsertValue(Instruction): def __init__(self, parent, agg, elem, indices, name=''): typ = agg.type try: for i in indices: typ = typ.elements[i] except (AttributeError, IndexError): raise TypeError("Can't index at %r in %s" % (list(indices), agg.type)) if elem.type != typ: raise TypeError("Can only insert %s at %r in %s: got %s" % (typ, list(indices), agg.type, elem.type)) super(InsertValue, self).__init__(parent, agg.type, "insertvalue", [agg, elem], name=name) self.aggregate = agg self.value = elem self.indices = indices def descr(self, buf): indices = [str(i) for i in self.indices] buf.append("insertvalue {0} {1}, {2} {3}, {4} {5}\n".format( self.aggregate.type, self.aggregate.get_reference(), self.value.type, self.value.get_reference(), ', '.join(indices), self._stringify_metadata(leading_comma=True), )) class Unreachable(Instruction): def __init__(self, parent): super(Unreachable, self).__init__(parent, types.VoidType(), "unreachable", (), name='') def descr(self, buf): buf += (self.opname, "\n") class InlineAsm(object): def __init__(self, ftype, asm, constraint, side_effect=False): self.type = ftype.return_type self.function_type = ftype self.asm = asm self.constraint = constraint self.side_effect = side_effect def descr(self, buf): sideeffect = 'sideeffect' if self.side_effect else '' fmt = 'asm {sideeffect} "{asm}", "{constraint}"\n' buf.append(fmt.format(sideeffect=sideeffect, asm=self.asm, constraint=self.constraint)) def get_reference(self): buf = [] self.descr(buf) return "".join(buf) def __str__(self): return "{0} {1}".format(self.type, self.get_reference()) class AtomicRMW(Instruction): def __init__(self, parent, op, ptr, val, ordering, name): super(AtomicRMW, self).__init__(parent, val.type, "atomicrmw", (ptr, val), name=name) self.operation = op self.ordering = ordering def descr(self, buf): ptr, val = self.operands fmt = ("atomicrmw {op} {ptrty} {ptr}, {valty} {val} {ordering} " "{metadata}\n") buf.append(fmt.format(op=self.operation, ptrty=ptr.type, ptr=ptr.get_reference(), valty=val.type, val=val.get_reference(), ordering=self.ordering, metadata=self._stringify_metadata( leading_comma=True), )) class CmpXchg(Instruction): """This instruction has changed since llvm3.5. It is not compatible with older llvm versions. """ def __init__(self, parent, ptr, cmp, val, ordering, failordering, name): outtype = types.LiteralStructType([val.type, types.IntType(1)]) super(CmpXchg, self).__init__(parent, outtype, "cmpxchg", (ptr, cmp, val), name=name) self.ordering = ordering self.failordering = failordering def descr(self, buf): ptr, cmpval, val = self.operands fmt = "cmpxchg {ptrty} {ptr}, {ty} {cmp}, {ty} {val} {ordering} " \ "{failordering} {metadata}\n" buf.append(fmt.format(ptrty=ptr.type, ptr=ptr.get_reference(), ty=cmpval.type, cmp=cmpval.get_reference(), val=val.get_reference(), ordering=self.ordering, failordering=self.failordering, metadata=self._stringify_metadata( leading_comma=True), )) class _LandingPadClause(object): def __init__(self, value): self.value = value def __str__(self): return "{kind} {type} {value}".format( kind=self.kind, type=self.value.type, value=self.value.get_reference()) class CatchClause(_LandingPadClause): kind = 'catch' class FilterClause(_LandingPadClause): kind = 'filter' def __init__(self, value): assert isinstance(value, Constant) assert isinstance(value.type, types.ArrayType) super(FilterClause, self).__init__(value) class LandingPadInstr(Instruction): def __init__(self, parent, typ, name='', cleanup=False): super(LandingPadInstr, self).__init__(parent, typ, "landingpad", [], name=name) self.cleanup = cleanup self.clauses = [] def add_clause(self, clause): assert isinstance(clause, _LandingPadClause) self.clauses.append(clause) def descr(self, buf): fmt = "landingpad {type}{cleanup}{clauses}\n" buf.append(fmt.format(type=self.type, cleanup=' cleanup' if self.cleanup else '', clauses=''.join(["\n {0}".format(clause) for clause in self.clauses]), )) class Fence(Instruction): """ The `fence` instruction. As of LLVM 5.0.1: fence [syncscope("")] ; yields void """ VALID_FENCE_ORDERINGS = {"acquire", "release", "acq_rel", "seq_cst"} def __init__(self, parent, ordering, targetscope=None, name=''): super(Fence, self).__init__(parent, types.VoidType(), "fence", (), name=name) if ordering not in self.VALID_FENCE_ORDERINGS: msg = "Invalid fence ordering \"{0}\"! Should be one of {1}." raise ValueError(msg .format(ordering, ", ".join(self.VALID_FENCE_ORDERINGS))) self.ordering = ordering self.targetscope = targetscope def descr(self, buf): if self.targetscope is None: syncscope = "" else: syncscope = 'syncscope("{0}") '.format(self.targetscope) fmt = "fence {syncscope}{ordering}\n" buf.append(fmt.format(syncscope=syncscope, ordering=self.ordering, )) class Comment(Instruction): """ A line comment. """ def __init__(self, parent, text): super(Comment, self).__init__(parent, types.VoidType(), ";", (), name='') assert "\n" not in text, "Comment cannot contain new line" self.text = text def descr(self, buf): buf.append(f"; {self.text}")