1120 lines
33 KiB
Python
1120 lines
33 KiB
Python
import contextlib
|
|
import functools
|
|
|
|
from llvmlite.ir import instructions, types, values
|
|
|
|
_CMP_MAP = {
|
|
'>': 'gt',
|
|
'<': 'lt',
|
|
'==': 'eq',
|
|
'!=': 'ne',
|
|
'>=': 'ge',
|
|
'<=': 'le',
|
|
}
|
|
|
|
|
|
def _unop(opname, cls=instructions.Instruction):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, arg, name='', flags=()):
|
|
instr = cls(self.block, arg.type, opname, [arg], name, flags)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _binop(opname, cls=instructions.Instruction):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, lhs, rhs, name='', flags=()):
|
|
if lhs.type != rhs.type:
|
|
raise ValueError("Operands must be the same type, got (%s, %s)"
|
|
% (lhs.type, rhs.type))
|
|
instr = cls(self.block, lhs.type, opname, (lhs, rhs), name, flags)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _binop_with_overflow(opname, cls=instructions.Instruction):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, lhs, rhs, name=''):
|
|
if lhs.type != rhs.type:
|
|
raise ValueError("Operands must be the same type, got (%s, %s)"
|
|
% (lhs.type, rhs.type))
|
|
ty = lhs.type
|
|
if not isinstance(ty, types.IntType):
|
|
raise TypeError("expected an integer type, got %s" % (ty,))
|
|
bool_ty = types.IntType(1)
|
|
|
|
mod = self.module
|
|
fnty = types.FunctionType(types.LiteralStructType([ty, bool_ty]),
|
|
[ty, ty])
|
|
fn = mod.declare_intrinsic("llvm.%s.with.overflow" % (opname,),
|
|
[ty], fnty)
|
|
ret = self.call(fn, [lhs, rhs], name=name)
|
|
return ret
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _uniop(opname, cls=instructions.Instruction):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, operand, name=''):
|
|
instr = cls(self.block, operand.type, opname, [operand], name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _uniop_intrinsic_int(opname):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, operand, name=''):
|
|
if not isinstance(operand.type, types.IntType):
|
|
raise TypeError(
|
|
"expected an integer type, got %s" %
|
|
operand.type)
|
|
fn = self.module.declare_intrinsic(opname, [operand.type])
|
|
return self.call(fn, [operand], name)
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _uniop_intrinsic_float(opname):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, operand, name=''):
|
|
if not isinstance(
|
|
operand.type, (types.FloatType, types.DoubleType)):
|
|
raise TypeError("expected a float type, got %s" % operand.type)
|
|
fn = self.module.declare_intrinsic(opname, [operand.type])
|
|
return self.call(fn, [operand], name)
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _uniop_intrinsic_with_flag(opname):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, operand, flag, name=''):
|
|
if not isinstance(operand.type, types.IntType):
|
|
raise TypeError(
|
|
"expected an integer type, got %s" %
|
|
operand.type)
|
|
if not (isinstance(flag.type, types.IntType) and
|
|
flag.type.width == 1):
|
|
raise TypeError("expected an i1 type, got %s" % flag.type)
|
|
fn = self.module.declare_intrinsic(
|
|
opname, [operand.type, flag.type])
|
|
return self.call(fn, [operand, flag], name)
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _triop_intrinsic(opname):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, a, b, c, name=''):
|
|
if a.type != b.type or b.type != c.type:
|
|
raise TypeError(
|
|
"expected types to be the same, got %s, %s, %s" % (
|
|
a.type,
|
|
b.type,
|
|
c.type))
|
|
elif not isinstance(
|
|
a.type,
|
|
(types.HalfType, types.FloatType, types.DoubleType)):
|
|
raise TypeError(
|
|
"expected an floating point type, got %s" %
|
|
a.type)
|
|
fn = self.module.declare_intrinsic(opname, [a.type, b.type, c.type])
|
|
return self.call(fn, [a, b, c], name)
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _castop(opname, cls=instructions.CastInstr):
|
|
def wrap(fn):
|
|
@functools.wraps(fn)
|
|
def wrapped(self, val, typ, name=''):
|
|
if val.type == typ:
|
|
return val
|
|
instr = cls(self.block, opname, val, typ, name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
return wrapped
|
|
|
|
return wrap
|
|
|
|
|
|
def _label_suffix(label, suffix):
|
|
"""Returns (label + suffix) or a truncated version if it's too long.
|
|
Parameters
|
|
----------
|
|
label : str
|
|
Label name
|
|
suffix : str
|
|
Label suffix
|
|
"""
|
|
if len(label) > 50:
|
|
nhead = 25
|
|
return ''.join([label[:nhead], '..', suffix])
|
|
else:
|
|
return label + suffix
|
|
|
|
|
|
class IRBuilder(object):
|
|
def __init__(self, block=None):
|
|
self._block = block
|
|
self._anchor = len(block.instructions) if block else 0
|
|
self.debug_metadata = None
|
|
|
|
@property
|
|
def block(self):
|
|
"""
|
|
The current basic block.
|
|
"""
|
|
return self._block
|
|
|
|
basic_block = block
|
|
|
|
@property
|
|
def function(self):
|
|
"""
|
|
The current function.
|
|
"""
|
|
return self.block.parent
|
|
|
|
@property
|
|
def module(self):
|
|
"""
|
|
The current module.
|
|
"""
|
|
return self.block.parent.module
|
|
|
|
def position_before(self, instr):
|
|
"""
|
|
Position immediately before the given instruction. The current block
|
|
is also changed to the instruction's basic block.
|
|
"""
|
|
self._block = instr.parent
|
|
self._anchor = self._block.instructions.index(instr)
|
|
|
|
def position_after(self, instr):
|
|
"""
|
|
Position immediately after the given instruction. The current block
|
|
is also changed to the instruction's basic block.
|
|
"""
|
|
self._block = instr.parent
|
|
self._anchor = self._block.instructions.index(instr) + 1
|
|
|
|
def position_at_start(self, block):
|
|
"""
|
|
Position at the start of the basic *block*.
|
|
"""
|
|
self._block = block
|
|
self._anchor = 0
|
|
|
|
def position_at_end(self, block):
|
|
"""
|
|
Position at the end of the basic *block*.
|
|
"""
|
|
self._block = block
|
|
self._anchor = len(block.instructions)
|
|
|
|
def append_basic_block(self, name=''):
|
|
"""
|
|
Append a basic block, with the given optional *name*, to the current
|
|
function. The current block is not changed. The new block is returned.
|
|
"""
|
|
return self.function.append_basic_block(name)
|
|
|
|
def remove(self, instr):
|
|
"""Remove the given instruction."""
|
|
idx = self._block.instructions.index(instr)
|
|
del self._block.instructions[idx]
|
|
if self._block.terminator == instr:
|
|
self._block.terminator = None
|
|
if self._anchor > idx:
|
|
self._anchor -= 1
|
|
|
|
@contextlib.contextmanager
|
|
def goto_block(self, block):
|
|
"""
|
|
A context manager which temporarily positions the builder at the end
|
|
of basic block *bb* (but before any terminator).
|
|
"""
|
|
old_block = self.basic_block
|
|
term = block.terminator
|
|
if term is not None:
|
|
self.position_before(term)
|
|
else:
|
|
self.position_at_end(block)
|
|
try:
|
|
yield
|
|
finally:
|
|
self.position_at_end(old_block)
|
|
|
|
@contextlib.contextmanager
|
|
def goto_entry_block(self):
|
|
"""
|
|
A context manager which temporarily positions the builder at the
|
|
end of the function's entry block.
|
|
"""
|
|
with self.goto_block(self.function.entry_basic_block):
|
|
yield
|
|
|
|
@contextlib.contextmanager
|
|
def _branch_helper(self, bbenter, bbexit):
|
|
self.position_at_end(bbenter)
|
|
yield bbexit
|
|
if self.basic_block.terminator is None:
|
|
self.branch(bbexit)
|
|
|
|
@contextlib.contextmanager
|
|
def if_then(self, pred, likely=None):
|
|
"""
|
|
A context manager which sets up a conditional basic block based
|
|
on the given predicate (a i1 value). If the conditional block
|
|
is not explicitly terminated, a branch will be added to the next
|
|
block.
|
|
If *likely* is given, its boolean value indicates whether the
|
|
predicate is likely to be true or not, and metadata is issued
|
|
for LLVM's optimizers to account for that.
|
|
"""
|
|
bb = self.basic_block
|
|
bbif = self.append_basic_block(name=_label_suffix(bb.name, '.if'))
|
|
bbend = self.append_basic_block(name=_label_suffix(bb.name, '.endif'))
|
|
br = self.cbranch(pred, bbif, bbend)
|
|
if likely is not None:
|
|
br.set_weights([99, 1] if likely else [1, 99])
|
|
|
|
with self._branch_helper(bbif, bbend):
|
|
yield bbend
|
|
|
|
self.position_at_end(bbend)
|
|
|
|
@contextlib.contextmanager
|
|
def if_else(self, pred, likely=None):
|
|
"""
|
|
A context manager which sets up two conditional basic blocks based
|
|
on the given predicate (a i1 value).
|
|
A tuple of context managers is yield'ed. Each context manager
|
|
acts as a if_then() block.
|
|
*likely* has the same meaning as in if_then().
|
|
|
|
Typical use::
|
|
with builder.if_else(pred) as (then, otherwise):
|
|
with then:
|
|
# emit instructions for when the predicate is true
|
|
with otherwise:
|
|
# emit instructions for when the predicate is false
|
|
"""
|
|
bb = self.basic_block
|
|
bbif = self.append_basic_block(name=_label_suffix(bb.name, '.if'))
|
|
bbelse = self.append_basic_block(name=_label_suffix(bb.name, '.else'))
|
|
bbend = self.append_basic_block(name=_label_suffix(bb.name, '.endif'))
|
|
br = self.cbranch(pred, bbif, bbelse)
|
|
if likely is not None:
|
|
br.set_weights([99, 1] if likely else [1, 99])
|
|
|
|
then = self._branch_helper(bbif, bbend)
|
|
otherwise = self._branch_helper(bbelse, bbend)
|
|
|
|
yield then, otherwise
|
|
|
|
self.position_at_end(bbend)
|
|
|
|
def _insert(self, instr):
|
|
if self.debug_metadata is not None and 'dbg' not in instr.metadata:
|
|
instr.metadata['dbg'] = self.debug_metadata
|
|
self._block.instructions.insert(self._anchor, instr)
|
|
self._anchor += 1
|
|
|
|
def _set_terminator(self, term):
|
|
assert not self.block.is_terminated
|
|
self._insert(term)
|
|
self.block.terminator = term
|
|
return term
|
|
|
|
#
|
|
# Arithmetic APIs
|
|
#
|
|
|
|
@_binop('shl')
|
|
def shl(self, lhs, rhs, name=''):
|
|
"""
|
|
Left integer shift:
|
|
name = lhs << rhs
|
|
"""
|
|
|
|
@_binop('lshr')
|
|
def lshr(self, lhs, rhs, name=''):
|
|
"""
|
|
Logical (unsigned) right integer shift:
|
|
name = lhs >> rhs
|
|
"""
|
|
|
|
@_binop('ashr')
|
|
def ashr(self, lhs, rhs, name=''):
|
|
"""
|
|
Arithmetic (signed) right integer shift:
|
|
name = lhs >> rhs
|
|
"""
|
|
|
|
@_binop('add')
|
|
def add(self, lhs, rhs, name=''):
|
|
"""
|
|
Integer addition:
|
|
name = lhs + rhs
|
|
"""
|
|
|
|
@_binop('fadd')
|
|
def fadd(self, lhs, rhs, name=''):
|
|
"""
|
|
Floating-point addition:
|
|
name = lhs + rhs
|
|
"""
|
|
|
|
@_binop('sub')
|
|
def sub(self, lhs, rhs, name=''):
|
|
"""
|
|
Integer subtraction:
|
|
name = lhs - rhs
|
|
"""
|
|
|
|
@_binop('fsub')
|
|
def fsub(self, lhs, rhs, name=''):
|
|
"""
|
|
Floating-point subtraction:
|
|
name = lhs - rhs
|
|
"""
|
|
|
|
@_binop('mul')
|
|
def mul(self, lhs, rhs, name=''):
|
|
"""
|
|
Integer multiplication:
|
|
name = lhs * rhs
|
|
"""
|
|
|
|
@_binop('fmul')
|
|
def fmul(self, lhs, rhs, name=''):
|
|
"""
|
|
Floating-point multiplication:
|
|
name = lhs * rhs
|
|
"""
|
|
|
|
@_binop('udiv')
|
|
def udiv(self, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer division:
|
|
name = lhs / rhs
|
|
"""
|
|
|
|
@_binop('sdiv')
|
|
def sdiv(self, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer division:
|
|
name = lhs / rhs
|
|
"""
|
|
|
|
@_binop('fdiv')
|
|
def fdiv(self, lhs, rhs, name=''):
|
|
"""
|
|
Floating-point division:
|
|
name = lhs / rhs
|
|
"""
|
|
|
|
@_binop('urem')
|
|
def urem(self, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer remainder:
|
|
name = lhs % rhs
|
|
"""
|
|
|
|
@_binop('srem')
|
|
def srem(self, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer remainder:
|
|
name = lhs % rhs
|
|
"""
|
|
|
|
@_binop('frem')
|
|
def frem(self, lhs, rhs, name=''):
|
|
"""
|
|
Floating-point remainder:
|
|
name = lhs % rhs
|
|
"""
|
|
|
|
@_binop('or')
|
|
def or_(self, lhs, rhs, name=''):
|
|
"""
|
|
Bitwise integer OR:
|
|
name = lhs | rhs
|
|
"""
|
|
|
|
@_binop('and')
|
|
def and_(self, lhs, rhs, name=''):
|
|
"""
|
|
Bitwise integer AND:
|
|
name = lhs & rhs
|
|
"""
|
|
|
|
@_binop('xor')
|
|
def xor(self, lhs, rhs, name=''):
|
|
"""
|
|
Bitwise integer XOR:
|
|
name = lhs ^ rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('sadd')
|
|
def sadd_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer addition with overflow:
|
|
name = {result, overflow bit} = lhs + rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('smul')
|
|
def smul_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer multiplication with overflow:
|
|
name = {result, overflow bit} = lhs * rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('ssub')
|
|
def ssub_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer subtraction with overflow:
|
|
name = {result, overflow bit} = lhs - rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('uadd')
|
|
def uadd_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer addition with overflow:
|
|
name = {result, overflow bit} = lhs + rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('umul')
|
|
def umul_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer multiplication with overflow:
|
|
name = {result, overflow bit} = lhs * rhs
|
|
"""
|
|
|
|
@_binop_with_overflow('usub')
|
|
def usub_with_overflow(self, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer subtraction with overflow:
|
|
name = {result, overflow bit} = lhs - rhs
|
|
"""
|
|
|
|
#
|
|
# Unary APIs
|
|
#
|
|
|
|
def not_(self, value, name=''):
|
|
"""
|
|
Bitwise integer complement:
|
|
name = ~value
|
|
"""
|
|
if isinstance(value.type, types.VectorType):
|
|
rhs = values.Constant(value.type, (-1,) * value.type.count)
|
|
else:
|
|
rhs = values.Constant(value.type, -1)
|
|
return self.xor(value, rhs, name=name)
|
|
|
|
def neg(self, value, name=''):
|
|
"""
|
|
Integer negative:
|
|
name = -value
|
|
"""
|
|
return self.sub(values.Constant(value.type, 0), value, name=name)
|
|
|
|
@_unop('fneg')
|
|
def fneg(self, arg, name='', flags=()):
|
|
"""
|
|
Floating-point negative:
|
|
name = -arg
|
|
"""
|
|
|
|
#
|
|
# Comparison APIs
|
|
#
|
|
|
|
def _icmp(self, prefix, cmpop, lhs, rhs, name):
|
|
try:
|
|
op = _CMP_MAP[cmpop]
|
|
except KeyError:
|
|
raise ValueError("invalid comparison %r for icmp" % (cmpop,))
|
|
if cmpop not in ('==', '!='):
|
|
op = prefix + op
|
|
instr = instructions.ICMPInstr(self.block, op, lhs, rhs, name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def icmp_signed(self, cmpop, lhs, rhs, name=''):
|
|
"""
|
|
Signed integer comparison:
|
|
name = lhs <cmpop> rhs
|
|
|
|
where cmpop can be '==', '!=', '<', '<=', '>', '>='
|
|
"""
|
|
return self._icmp('s', cmpop, lhs, rhs, name)
|
|
|
|
def icmp_unsigned(self, cmpop, lhs, rhs, name=''):
|
|
"""
|
|
Unsigned integer (or pointer) comparison:
|
|
name = lhs <cmpop> rhs
|
|
|
|
where cmpop can be '==', '!=', '<', '<=', '>', '>='
|
|
"""
|
|
return self._icmp('u', cmpop, lhs, rhs, name)
|
|
|
|
def fcmp_ordered(self, cmpop, lhs, rhs, name='', flags=()):
|
|
"""
|
|
Floating-point ordered comparison:
|
|
name = lhs <cmpop> rhs
|
|
|
|
where cmpop can be '==', '!=', '<', '<=', '>', '>=', 'ord', 'uno'
|
|
"""
|
|
if cmpop in _CMP_MAP:
|
|
op = 'o' + _CMP_MAP[cmpop]
|
|
else:
|
|
op = cmpop
|
|
instr = instructions.FCMPInstr(
|
|
self.block, op, lhs, rhs, name=name, flags=flags)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def fcmp_unordered(self, cmpop, lhs, rhs, name='', flags=()):
|
|
"""
|
|
Floating-point unordered comparison:
|
|
name = lhs <cmpop> rhs
|
|
|
|
where cmpop can be '==', '!=', '<', '<=', '>', '>=', 'ord', 'uno'
|
|
"""
|
|
if cmpop in _CMP_MAP:
|
|
op = 'u' + _CMP_MAP[cmpop]
|
|
else:
|
|
op = cmpop
|
|
instr = instructions.FCMPInstr(
|
|
self.block, op, lhs, rhs, name=name, flags=flags)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def select(self, cond, lhs, rhs, name='', flags=()):
|
|
"""
|
|
Ternary select operator:
|
|
name = cond ? lhs : rhs
|
|
"""
|
|
instr = instructions.SelectInstr(self.block, cond, lhs, rhs, name=name,
|
|
flags=flags)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
#
|
|
# Cast APIs
|
|
#
|
|
|
|
@_castop('trunc')
|
|
def trunc(self, value, typ, name=''):
|
|
"""
|
|
Truncating integer downcast to a smaller type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('zext')
|
|
def zext(self, value, typ, name=''):
|
|
"""
|
|
Zero-extending integer upcast to a larger type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('sext')
|
|
def sext(self, value, typ, name=''):
|
|
"""
|
|
Sign-extending integer upcast to a larger type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('fptrunc')
|
|
def fptrunc(self, value, typ, name=''):
|
|
"""
|
|
Floating-point downcast to a less precise type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('fpext')
|
|
def fpext(self, value, typ, name=''):
|
|
"""
|
|
Floating-point upcast to a more precise type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('bitcast')
|
|
def bitcast(self, value, typ, name=''):
|
|
"""
|
|
Pointer cast to a different pointer type:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('addrspacecast')
|
|
def addrspacecast(self, value, typ, name=''):
|
|
"""
|
|
Pointer cast to a different address space:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('fptoui')
|
|
def fptoui(self, value, typ, name=''):
|
|
"""
|
|
Convert floating-point to unsigned integer:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('uitofp')
|
|
def uitofp(self, value, typ, name=''):
|
|
"""
|
|
Convert unsigned integer to floating-point:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('fptosi')
|
|
def fptosi(self, value, typ, name=''):
|
|
"""
|
|
Convert floating-point to signed integer:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('sitofp')
|
|
def sitofp(self, value, typ, name=''):
|
|
"""
|
|
Convert signed integer to floating-point:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('ptrtoint')
|
|
def ptrtoint(self, value, typ, name=''):
|
|
"""
|
|
Cast pointer to integer:
|
|
name = (typ) value
|
|
"""
|
|
|
|
@_castop('inttoptr')
|
|
def inttoptr(self, value, typ, name=''):
|
|
"""
|
|
Cast integer to pointer:
|
|
name = (typ) value
|
|
"""
|
|
|
|
#
|
|
# Memory APIs
|
|
#
|
|
|
|
def alloca(self, typ, size=None, name=''):
|
|
"""
|
|
Stack-allocate a slot for *size* elements of the given type.
|
|
(default one element)
|
|
"""
|
|
if size is None:
|
|
pass
|
|
elif isinstance(size, (values.Value, values.Constant)):
|
|
assert isinstance(size.type, types.IntType)
|
|
else:
|
|
# If it is not a Value instance,
|
|
# assume to be a Python integer.
|
|
size = values.Constant(types.IntType(32), size)
|
|
|
|
al = instructions.AllocaInstr(self.block, typ, size, name)
|
|
self._insert(al)
|
|
return al
|
|
|
|
def load(self, ptr, name='', align=None):
|
|
"""
|
|
Load value from pointer, with optional guaranteed alignment:
|
|
name = *ptr
|
|
"""
|
|
if not isinstance(ptr.type, types.PointerType):
|
|
msg = "cannot load from value of type %s (%r): not a pointer"
|
|
raise TypeError(msg % (ptr.type, str(ptr)))
|
|
ld = instructions.LoadInstr(self.block, ptr, name)
|
|
ld.align = align
|
|
self._insert(ld)
|
|
return ld
|
|
|
|
def store(self, value, ptr, align=None):
|
|
"""
|
|
Store value to pointer, with optional guaranteed alignment:
|
|
*ptr = name
|
|
"""
|
|
if not isinstance(ptr.type, types.PointerType):
|
|
msg = "cannot store to value of type %s (%r): not a pointer"
|
|
raise TypeError(msg % (ptr.type, str(ptr)))
|
|
if ptr.type.pointee != value.type:
|
|
raise TypeError("cannot store %s to %s: mismatching types"
|
|
% (value.type, ptr.type))
|
|
st = instructions.StoreInstr(self.block, value, ptr)
|
|
st.align = align
|
|
self._insert(st)
|
|
return st
|
|
|
|
def load_atomic(self, ptr, ordering, align, name=''):
|
|
"""
|
|
Load value from pointer, with optional guaranteed alignment:
|
|
name = *ptr
|
|
"""
|
|
if not isinstance(ptr.type, types.PointerType):
|
|
msg = "cannot load from value of type %s (%r): not a pointer"
|
|
raise TypeError(msg % (ptr.type, str(ptr)))
|
|
ld = instructions.LoadAtomicInstr(
|
|
self.block, ptr, ordering, align, name)
|
|
self._insert(ld)
|
|
return ld
|
|
|
|
def store_atomic(self, value, ptr, ordering, align):
|
|
"""
|
|
Store value to pointer, with optional guaranteed alignment:
|
|
*ptr = name
|
|
"""
|
|
if not isinstance(ptr.type, types.PointerType):
|
|
msg = "cannot store to value of type %s (%r): not a pointer"
|
|
raise TypeError(msg % (ptr.type, str(ptr)))
|
|
if ptr.type.pointee != value.type:
|
|
raise TypeError("cannot store %s to %s: mismatching types"
|
|
% (value.type, ptr.type))
|
|
st = instructions.StoreAtomicInstr(
|
|
self.block, value, ptr, ordering, align)
|
|
self._insert(st)
|
|
return st
|
|
|
|
#
|
|
# Terminators APIs
|
|
#
|
|
|
|
def switch(self, value, default):
|
|
"""
|
|
Create a switch-case with a single *default* target.
|
|
"""
|
|
swt = instructions.SwitchInstr(self.block, 'switch', value, default)
|
|
self._set_terminator(swt)
|
|
return swt
|
|
|
|
def branch(self, target):
|
|
"""
|
|
Unconditional branch to *target*.
|
|
"""
|
|
br = instructions.Branch(self.block, "br", [target])
|
|
self._set_terminator(br)
|
|
return br
|
|
|
|
def cbranch(self, cond, truebr, falsebr):
|
|
"""
|
|
Conditional branch to *truebr* if *cond* is true, else to *falsebr*.
|
|
"""
|
|
br = instructions.ConditionalBranch(self.block, "br",
|
|
[cond, truebr, falsebr])
|
|
self._set_terminator(br)
|
|
return br
|
|
|
|
def branch_indirect(self, addr):
|
|
"""
|
|
Indirect branch to target *addr*.
|
|
"""
|
|
br = instructions.IndirectBranch(self.block, "indirectbr", addr)
|
|
self._set_terminator(br)
|
|
return br
|
|
|
|
def ret_void(self):
|
|
"""
|
|
Return from function without a value.
|
|
"""
|
|
return self._set_terminator(
|
|
instructions.Ret(self.block, "ret void"))
|
|
|
|
def ret(self, value):
|
|
"""
|
|
Return from function with the given *value*.
|
|
"""
|
|
return self._set_terminator(
|
|
instructions.Ret(self.block, "ret", value))
|
|
|
|
def resume(self, landingpad):
|
|
"""
|
|
Resume an in-flight exception.
|
|
"""
|
|
br = instructions.Branch(self.block, "resume", [landingpad])
|
|
self._set_terminator(br)
|
|
return br
|
|
|
|
# Call APIs
|
|
|
|
def call(self, fn, args, name='', cconv=None, tail=False, fastmath=(),
|
|
attrs=(), arg_attrs=None):
|
|
"""
|
|
Call function *fn* with *args*:
|
|
name = fn(args...)
|
|
"""
|
|
inst = instructions.CallInstr(self.block, fn, args, name=name,
|
|
cconv=cconv, tail=tail, fastmath=fastmath,
|
|
attrs=attrs, arg_attrs=arg_attrs)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
def asm(self, ftype, asm, constraint, args, side_effect, name=''):
|
|
"""
|
|
Inline assembler.
|
|
"""
|
|
asm = instructions.InlineAsm(ftype, asm, constraint, side_effect)
|
|
return self.call(asm, args, name)
|
|
|
|
def load_reg(self, reg_type, reg_name, name=''):
|
|
"""
|
|
Load a register value into an LLVM value.
|
|
Example: v = load_reg(IntType(32), "eax")
|
|
"""
|
|
ftype = types.FunctionType(reg_type, [])
|
|
return self.asm(ftype, "", "={%s}" % reg_name, [], False, name)
|
|
|
|
def store_reg(self, value, reg_type, reg_name, name=''):
|
|
"""
|
|
Store an LLVM value inside a register
|
|
Example:
|
|
store_reg(Constant(IntType(32), 0xAAAAAAAA), IntType(32), "eax")
|
|
"""
|
|
ftype = types.FunctionType(types.VoidType(), [reg_type])
|
|
return self.asm(ftype, "", "{%s}" % reg_name, [value], True, name)
|
|
|
|
def invoke(self, fn, args, normal_to, unwind_to,
|
|
name='', cconv=None, fastmath=(), attrs=(), arg_attrs=None):
|
|
inst = instructions.InvokeInstr(self.block, fn, args, normal_to,
|
|
unwind_to, name=name, cconv=cconv,
|
|
fastmath=fastmath, attrs=attrs,
|
|
arg_attrs=arg_attrs)
|
|
self._set_terminator(inst)
|
|
return inst
|
|
|
|
# GEP APIs
|
|
|
|
def gep(self, ptr, indices, inbounds=False, name=''):
|
|
"""
|
|
Compute effective address (getelementptr):
|
|
name = getelementptr ptr, <indices...>
|
|
"""
|
|
instr = instructions.GEPInstr(self.block, ptr, indices,
|
|
inbounds=inbounds, name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
# Vector Operations APIs
|
|
|
|
def extract_element(self, vector, idx, name=''):
|
|
"""
|
|
Returns the value at position idx.
|
|
"""
|
|
instr = instructions.ExtractElement(self.block, vector, idx, name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def insert_element(self, vector, value, idx, name=''):
|
|
"""
|
|
Returns vector with vector[idx] replaced by value.
|
|
The result is undefined if the idx is larger or equal the vector length.
|
|
"""
|
|
instr = instructions.InsertElement(self.block, vector, value, idx,
|
|
name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def shuffle_vector(self, vector1, vector2, mask, name=''):
|
|
"""
|
|
Constructs a permutation of elements from *vector1* and *vector2*.
|
|
Returns a new vector in the same length of *mask*.
|
|
|
|
* *vector1* and *vector2* must have the same element type.
|
|
* *mask* must be a constant vector of integer types.
|
|
"""
|
|
instr = instructions.ShuffleVector(self.block, vector1, vector2, mask,
|
|
name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
# Aggregate APIs
|
|
|
|
def extract_value(self, agg, idx, name=''):
|
|
"""
|
|
Extract member number *idx* from aggregate.
|
|
"""
|
|
if not isinstance(idx, (tuple, list)):
|
|
idx = [idx]
|
|
instr = instructions.ExtractValue(self.block, agg, idx, name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
def insert_value(self, agg, value, idx, name=''):
|
|
"""
|
|
Insert *value* into member number *idx* from aggregate.
|
|
"""
|
|
if not isinstance(idx, (tuple, list)):
|
|
idx = [idx]
|
|
instr = instructions.InsertValue(self.block, agg, value, idx, name=name)
|
|
self._insert(instr)
|
|
return instr
|
|
|
|
# PHI APIs
|
|
|
|
def phi(self, typ, name='', flags=()):
|
|
inst = instructions.PhiInstr(self.block, typ, name=name, flags=flags)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
# Special API
|
|
|
|
def unreachable(self):
|
|
inst = instructions.Unreachable(self.block)
|
|
self._set_terminator(inst)
|
|
return inst
|
|
|
|
def atomic_rmw(self, op, ptr, val, ordering, name=''):
|
|
inst = instructions.AtomicRMW(
|
|
self.block, op, ptr, val, ordering, name=name)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
def cmpxchg(self, ptr, cmp, val, ordering, failordering=None, name=''):
|
|
"""
|
|
Atomic compared-and-set:
|
|
atomic {
|
|
old = *ptr
|
|
success = (old == cmp)
|
|
if (success)
|
|
*ptr = val
|
|
}
|
|
name = { old, success }
|
|
|
|
If failordering is `None`, the value of `ordering` is used.
|
|
"""
|
|
failordering = ordering if failordering is None else failordering
|
|
inst = instructions.CmpXchg(self.block, ptr, cmp, val, ordering,
|
|
failordering, name=name)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
def landingpad(self, typ, name='', cleanup=False):
|
|
inst = instructions.LandingPadInstr(self.block, typ, name, cleanup)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
def assume(self, cond):
|
|
"""
|
|
Optimizer hint: assume *cond* is always true.
|
|
"""
|
|
fn = self.module.declare_intrinsic("llvm.assume")
|
|
return self.call(fn, [cond])
|
|
|
|
def fence(self, ordering, targetscope=None, name=''):
|
|
"""
|
|
Add a memory barrier, preventing certain reorderings of load and/or
|
|
store accesses with
|
|
respect to other processors and devices.
|
|
"""
|
|
inst = instructions.Fence(self.block, ordering, targetscope, name=name)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
def comment(self, text):
|
|
"""
|
|
Puts a single-line comment into the generated IR. This will be ignored
|
|
by LLVM, but can be useful for debugging the output of a compiler. Adds
|
|
a comment to the source file.
|
|
|
|
* *text* is a string that does not contain new line characters.
|
|
"""
|
|
inst = instructions.Comment(self.block, text)
|
|
self._insert(inst)
|
|
return inst
|
|
|
|
@_uniop_intrinsic_int("llvm.bswap")
|
|
def bswap(self, cond):
|
|
"""
|
|
Used to byte swap integer values with an even number of bytes (positive
|
|
multiple of 16 bits)
|
|
"""
|
|
|
|
@_uniop_intrinsic_int("llvm.bitreverse")
|
|
def bitreverse(self, cond):
|
|
"""
|
|
Reverse the bitpattern of an integer value; for example 0b10110110
|
|
becomes 0b01101101.
|
|
"""
|
|
|
|
@_uniop_intrinsic_int("llvm.ctpop")
|
|
def ctpop(self, cond):
|
|
"""
|
|
Counts the number of bits set in a value.
|
|
"""
|
|
|
|
@_uniop_intrinsic_with_flag("llvm.ctlz")
|
|
def ctlz(self, cond, flag):
|
|
"""
|
|
Counts leading zero bits in *value*. Boolean *flag* indicates whether
|
|
the result is defined for ``0``.
|
|
"""
|
|
|
|
@_uniop_intrinsic_with_flag("llvm.cttz")
|
|
def cttz(self, cond, flag):
|
|
"""
|
|
Counts trailing zero bits in *value*. Boolean *flag* indicates whether
|
|
the result is defined for ``0``.
|
|
"""
|
|
|
|
@_triop_intrinsic("llvm.fma")
|
|
def fma(self, a, b, c):
|
|
"""
|
|
Perform the fused multiply-add operation.
|
|
"""
|
|
|
|
def convert_from_fp16(self, a, to=None, name=''):
|
|
"""
|
|
Convert from an i16 to the given FP type
|
|
"""
|
|
if not to:
|
|
raise TypeError("expected a float return type")
|
|
if not isinstance(to, (types.FloatType, types.DoubleType)):
|
|
raise TypeError("expected a float type, got %s" % to)
|
|
if not (isinstance(a.type, types.IntType) and a.type.width == 16):
|
|
raise TypeError("expected an i16 type, got %s" % a.type)
|
|
|
|
opname = 'llvm.convert.from.fp16'
|
|
fn = self.module.declare_intrinsic(opname, [to])
|
|
return self.call(fn, [a], name)
|
|
|
|
@_uniop_intrinsic_float("llvm.convert.to.fp16")
|
|
def convert_to_fp16(self, a):
|
|
"""
|
|
Convert the given FP number to an i16
|
|
"""
|