artiq/artiq/compiler/ir.py
pca006132 6ec003c1c9 compiler: fixed dead code eliminator
Instead of removing basic blocks with no predecessor, we will now mark
and remove all blocks that are unreachable from the entry block. This
can handle loops that are dead code. This is needed as we will now
generate more complicated code for exception handling which the old dead
code eliminator failed to handle.
2022-01-26 07:16:54 +08:00

1507 lines
44 KiB
Python

"""
The :mod:`ir` module contains the intermediate representation
of the ARTIQ compiler.
"""
from collections import OrderedDict
from pythonparser import ast
from . import types, builtins, iodelay
# Generic SSA IR classes
def escape_name(name):
if all([str.isalnum(x) or x == "." for x in name]):
return name
else:
return "\"{}\"".format(name.replace("\"", "\\\""))
class TBasicBlock(types.TMono):
def __init__(self):
super().__init__("label")
def is_basic_block(typ):
return isinstance(typ, TBasicBlock)
class TOption(types.TMono):
def __init__(self, value):
super().__init__("option", {"value": value})
def is_option(typ):
return isinstance(typ, TOption)
class TKeyword(types.TMono):
def __init__(self, value):
super().__init__("keyword", {"value": value})
def is_keyword(typ):
return isinstance(typ, TKeyword)
# See rpc_proto.rs and comm_kernel.py:_{send,receive}_rpc_value.
def rpc_tag(typ, error_handler):
typ = typ.find()
if types.is_tuple(typ):
assert len(typ.elts) < 256
return b"t" + bytes([len(typ.elts)]) + \
b"".join([rpc_tag(elt_type, error_handler)
for elt_type in typ.elts])
elif builtins.is_none(typ):
return b"n"
elif builtins.is_bool(typ):
return b"b"
elif builtins.is_int(typ, types.TValue(32)):
return b"i"
elif builtins.is_int(typ, types.TValue(64)):
return b"I"
elif builtins.is_float(typ):
return b"f"
elif builtins.is_str(typ):
return b"s"
elif builtins.is_bytes(typ):
return b"B"
elif builtins.is_bytearray(typ):
return b"A"
elif builtins.is_list(typ):
return b"l" + rpc_tag(builtins.get_iterable_elt(typ), error_handler)
elif builtins.is_array(typ):
num_dims = typ["num_dims"].value
return b"a" + bytes([num_dims]) + rpc_tag(typ["elt"], error_handler)
elif builtins.is_range(typ):
return b"r" + rpc_tag(builtins.get_iterable_elt(typ), error_handler)
elif is_keyword(typ):
return b"k" + rpc_tag(typ.params["value"], error_handler)
elif types.is_function(typ) or types.is_method(typ) or types.is_rpc(typ):
raise ValueError("RPC tag for functional value")
elif '__objectid__' in typ.attributes:
return b"O"
else:
error_handler(typ)
class Value:
"""
An SSA value that keeps track of its uses.
:ivar type: (:class:`.types.Type`) type of this value
:ivar uses: (list of :class:`Value`) values that use this value
"""
def __init__(self, typ):
self.uses, self.type = set(), typ.find()
def replace_all_uses_with(self, value):
for user in set(self.uses):
user.replace_uses_of(self, value)
def __str__(self):
return self.as_entity(type_printer=types.TypePrinter())
class Constant(Value):
"""
A constant value.
:ivar value: (Python object) value
"""
def __init__(self, value, typ):
super().__init__(typ)
self.value = value
def as_operand(self, type_printer):
return self.as_entity(type_printer)
def as_entity(self, type_printer):
return "{} {}".format(type_printer.name(self.type),
repr(self.value))
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
return isinstance(other, Constant) and \
other.type == self.type and other.value == self.value
def __ne__(self, other):
return not (self == other)
class NamedValue(Value):
"""
An SSA value that has a name.
:ivar name: (string) name of this value
:ivar function: (:class:`Function`) function containing this value
"""
def __init__(self, typ, name):
super().__init__(typ)
self.name, self.function = name, None
self.is_removed = False
def set_name(self, new_name):
if self.function is not None:
self.function._remove_name(self.name)
self.name = self.function._add_name(new_name)
else:
self.name = new_name
def _set_function(self, new_function):
if self.function != new_function:
if self.function is not None:
self.function._remove_name(self.name)
self.function = new_function
if self.function is not None:
self.name = self.function._add_name(self.name)
def _detach(self):
self.function = None
def as_operand(self, type_printer):
return "{} %{}".format(type_printer.name(self.type),
escape_name(self.name))
class User(NamedValue):
"""
An SSA value that has operands.
:ivar operands: (list of :class:`Value`) operands of this value
"""
def __init__(self, operands, typ, name):
super().__init__(typ, name)
self.operands = []
self.set_operands(operands)
def set_operands(self, new_operands):
for operand in set(self.operands):
operand.uses.remove(self)
self.operands = new_operands
for operand in set(self.operands):
operand.uses.add(self)
def drop_references(self):
self.set_operands([])
def replace_uses_of(self, value, replacement):
assert value in self.operands
for index, operand in enumerate(self.operands):
if operand == value:
self.operands[index] = replacement
value.uses.remove(self)
replacement.uses.add(self)
class Instruction(User):
"""
An SSA instruction.
:ivar loc: (:class:`pythonparser.source.Range` or None)
source location
"""
def __init__(self, operands, typ, name=""):
assert isinstance(operands, list)
assert isinstance(typ, types.Type)
super().__init__(operands, typ, name)
self.basic_block = None
self.loc = None
def copy(self, mapper):
self_copy = self.__class__.__new__(self.__class__)
Instruction.__init__(self_copy, list(map(mapper, self.operands)),
self.type, self.name)
self_copy.loc = self.loc
return self_copy
def set_basic_block(self, new_basic_block):
self.basic_block = new_basic_block
if self.basic_block is not None:
self._set_function(self.basic_block.function)
else:
self._set_function(None)
def opcode(self):
"""String representation of the opcode."""
return "???"
def _detach(self):
self.set_basic_block(None)
def remove_from_parent(self):
if self.basic_block is not None:
self.basic_block.remove(self)
def erase(self):
self.remove_from_parent()
self.drop_references()
# Check this after drop_references in case this
# is a self-referencing phi.
assert all(use.is_removed for use in self.uses)
def replace_with(self, value):
self.replace_all_uses_with(value)
if isinstance(value, Instruction):
self.basic_block.replace(self, value)
self.drop_references()
else:
self.erase()
def _operands_as_string(self, type_printer):
return ", ".join([operand.as_operand(type_printer) for operand in self.operands])
def as_entity(self, type_printer):
if builtins.is_none(self.type) and len(self.uses) == 0:
prefix = ""
else:
prefix = "%{} = {} ".format(escape_name(self.name),
type_printer.name(self.type))
if any(self.operands):
return "{}{} {}".format(prefix, self.opcode(),
self._operands_as_string(type_printer))
else:
return "{}{}".format(prefix, self.opcode())
class Phi(Instruction):
"""
An SSA instruction that joins data flow.
Use :meth:`incoming` and :meth:`add_incoming` instead of
directly reading :attr:`operands` or calling :meth:`set_operands`.
"""
def __init__(self, typ, name=""):
super().__init__([], typ, name)
def opcode(self):
return "phi"
def incoming(self):
operand_iter = iter(self.operands)
while True:
try:
yield next(operand_iter), next(operand_iter)
except StopIteration:
return
def incoming_blocks(self):
return (block for (value, block) in self.incoming())
def incoming_values(self):
return (value for (value, block) in self.incoming())
def incoming_value_for_block(self, target_block):
for (value, block) in self.incoming():
if block == target_block:
return value
assert False
def add_incoming(self, value, block):
assert value.type == self.type
self.operands.append(value)
value.uses.add(self)
self.operands.append(block)
block.uses.add(self)
def remove_incoming_value(self, value):
index = self.operands.index(value)
assert index % 2 == 0
self.operands[index].uses.remove(self)
self.operands[index + 1].uses.remove(self)
del self.operands[index:index + 2]
def remove_incoming_block(self, block):
index = self.operands.index(block)
assert index % 2 == 1
self.operands[index - 1].uses.remove(self)
self.operands[index].uses.remove(self)
del self.operands[index - 1:index + 1]
def as_entity(self, type_printer):
if builtins.is_none(self.type):
prefix = ""
else:
prefix = "%{} = {} ".format(escape_name(self.name),
type_printer.name(self.type))
if any(self.operands):
operand_list = ["%{} => {}".format(escape_name(block.name),
value.as_operand(type_printer))
for value, block in self.incoming()]
return "{}{} [{}]".format(prefix, self.opcode(), ", ".join(operand_list))
else:
return "{}{} [???]".format(prefix, self.opcode())
class Terminator(Instruction):
"""
An SSA instruction that performs control flow.
"""
def successors(self):
return [operand for operand in self.operands if isinstance(operand, BasicBlock)]
class BasicBlock(NamedValue):
"""
A block of instructions with no control flow inside it.
:ivar instructions: (list of :class:`Instruction`)
"""
_dump_loc = True
def __init__(self, instructions, name=""):
super().__init__(TBasicBlock(), name)
self.instructions = []
self.set_instructions(instructions)
def set_instructions(self, new_insns):
for insn in self.instructions:
insn.detach()
self.instructions = new_insns
for insn in self.instructions:
insn.set_basic_block(self)
def remove_from_parent(self):
if self.function is not None:
self.function.remove(self)
def erase(self):
# self.instructions is updated while iterating
for insn in reversed(self.instructions):
insn.erase()
self.remove_from_parent()
# Check this after erasing instructions in case the block
# loops into itself.
assert all(use.is_removed for use in self.uses)
def prepend(self, insn):
assert isinstance(insn, Instruction)
insn.set_basic_block(self)
self.instructions.insert(0, insn)
return insn
def append(self, insn):
assert isinstance(insn, Instruction)
insn.set_basic_block(self)
self.instructions.append(insn)
return insn
def index(self, insn):
return self.instructions.index(insn)
def insert(self, insn, before):
assert isinstance(insn, Instruction)
insn.set_basic_block(self)
self.instructions.insert(self.index(before), insn)
return insn
def remove(self, insn):
assert insn in self.instructions
insn._detach()
self.instructions.remove(insn)
return insn
def replace(self, insn, replacement):
self.insert(replacement, before=insn)
self.remove(insn)
def is_terminated(self):
return any(self.instructions) and isinstance(self.instructions[-1], Terminator)
def terminator(self):
assert self.is_terminated()
return self.instructions[-1]
def successors(self):
return self.terminator().successors()
def predecessors(self):
return [use.basic_block for use in self.uses if isinstance(use, Terminator)]
def as_entity(self, type_printer):
# Header
lines = ["{}:".format(escape_name(self.name))]
if self.function is not None:
lines[0] += " ; predecessors: {}".format(
", ".join(sorted([escape_name(pred.name) for pred in self.predecessors()])))
# Annotated instructions
loc = None
for insn in self.instructions:
if self._dump_loc and loc != insn.loc:
loc = insn.loc
if loc is None:
lines.append("; <synthesized>")
else:
source_lines = loc.source_lines()
beg_col, end_col = loc.column(), loc.end().column()
source_lines[-1] = \
source_lines[-1][:end_col] + "\x1b[0m" + source_lines[-1][end_col:]
source_lines[0] = \
source_lines[0][:beg_col] + "\x1b[1;32m" + source_lines[0][beg_col:]
line_desc = "{}:{}".format(loc.source_buffer.name, loc.line())
lines += ["; {} {}".format(line_desc, line.rstrip("\n"))
for line in source_lines]
lines.append(" " + insn.as_entity(type_printer))
return "\n".join(lines)
def __repr__(self):
return "<artiq.compiler.ir.BasicBlock {}>".format(repr(self.name))
class Argument(NamedValue):
"""
A function argument.
:ivar loc: (:class:`pythonparser.source.Range` or None)
source location
"""
def __init__(self, typ, name):
super().__init__(typ, name)
self.loc = None
def as_entity(self, type_printer):
return self.as_operand(type_printer)
class Function:
"""
A function containing SSA IR.
:ivar loc: (:class:`pythonparser.source.Range` or None)
source location of function definition
:ivar is_internal:
(bool) if True, the function should not be accessible from outside
the module it is contained in
:ivar is_cold:
(bool) if True, the function should be considered rarely called
:ivar is_generated:
(bool) if True, the function will not appear in backtraces
:ivar flags: (set of str) Code generation flags.
Flag ``fast-math`` is the equivalent of gcc's ``-ffast-math``.
"""
def __init__(self, typ, name, arguments, loc=None):
self.type, self.name, self.loc = typ, name, loc
self.names, self.arguments, self.basic_blocks = set(), [], []
self.next_name = 1
self.set_arguments(arguments)
self.is_internal = False
self.is_cold = False
self.is_generated = False
self.flags = {}
def _remove_name(self, name):
self.names.remove(name)
def _add_name(self, base_name):
if base_name == "":
name = "UNN.{}".format(self.next_name)
self.next_name += 1
elif base_name in self.names:
name = "{}.{}".format(base_name, self.next_name)
self.next_name += 1
else:
name = base_name
self.names.add(name)
return name
def set_arguments(self, new_arguments):
for argument in self.arguments:
argument._set_function(None)
self.arguments = new_arguments
for argument in self.arguments:
argument._set_function(self)
def add(self, basic_block):
basic_block._set_function(self)
self.basic_blocks.append(basic_block)
def remove(self, basic_block):
basic_block._detach()
self.basic_blocks.remove(basic_block)
def entry(self):
assert any(self.basic_blocks)
return self.basic_blocks[0]
def exits(self):
return [block for block in self.basic_blocks if not any(block.successors())]
def instructions(self):
for basic_block in self.basic_blocks:
yield from iter(basic_block.instructions)
def as_entity(self, type_printer):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.successors():
if next_block not in visited:
visit(next_block)
postorder.append(block)
visit(self.entry())
lines = []
lines.append("{} {}({}) {{ ; type: {}".format(
type_printer.name(self.type.ret), self.name,
", ".join([arg.as_operand(type_printer) for arg in self.arguments]),
type_printer.name(self.type)))
postorder_blocks = list(reversed(postorder))
orphan_blocks = [block for block in self.basic_blocks if block not in postorder]
for block in postorder_blocks + orphan_blocks:
lines.append(block.as_entity(type_printer))
lines.append("}")
return "\n".join(lines)
def __str__(self):
return self.as_entity(types.TypePrinter())
# Python-specific SSA IR classes
class TEnvironment(types.TMono):
def __init__(self, name, vars, outer=None):
assert isinstance(name, str)
self.env_name = name # for readable type names in LLVM IR
if outer is not None:
assert isinstance(outer, TEnvironment)
env = OrderedDict({"$outer": outer})
env.update(vars)
else:
env = OrderedDict(vars)
super().__init__("environment", env)
def type_of(self, name):
if name in self.params:
return self.params[name].find()
elif "$outer" in self.params:
return self.params["$outer"].type_of(name)
else:
assert False
def outermost(self):
if "$outer" in self.params:
return self.params["$outer"].outermost()
else:
return self
"""
Add a new binding, ensuring hygiene.
:returns: (string) mangled name
"""
def add(self, base_name, typ):
name, counter = base_name, 1
while name in self.params or name == "":
if base_name == "":
name = str(counter)
else:
name = "{}.{}".format(name, counter)
counter += 1
self.params[name] = typ.find()
return name
def is_environment(typ):
return isinstance(typ, TEnvironment)
class EnvironmentArgument(Argument):
"""
A function argument specifying an outer environment.
"""
def as_operand(self, type_printer):
return "environment(...) %{}".format(escape_name(self.name))
class Alloc(Instruction):
"""
An instruction that allocates an object specified by
the type of the intsruction.
"""
def __init__(self, operands, typ, name=""):
for operand in operands: assert isinstance(operand, Value)
super().__init__(operands, typ, name)
def opcode(self):
return "alloc"
def as_operand(self, type_printer):
if is_environment(self.type):
# Only show full environment in the instruction itself
return "%{}".format(escape_name(self.name))
else:
return super().as_operand(type_printer)
class GetLocal(Instruction):
"""
An intruction that loads a local variable from an environment,
possibly going through multiple levels of indirection.
:ivar var_name: (string) variable name
"""
"""
:param env: (:class:`Value`) local environment
:param var_name: (string) local variable name
"""
def __init__(self, env, var_name, name=""):
assert isinstance(env, Value)
assert isinstance(env.type, TEnvironment)
assert isinstance(var_name, str)
super().__init__([env], env.type.type_of(var_name), name)
self.var_name = var_name
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.var_name = self.var_name
return self_copy
def opcode(self):
return "getlocal({})".format(repr(self.var_name))
def environment(self):
return self.operands[0]
class SetLocal(Instruction):
"""
An intruction that stores a local variable into an environment,
possibly going through multiple levels of indirection.
:ivar var_name: (string) variable name
"""
"""
:param env: (:class:`Value`) local environment
:param var_name: (string) local variable name
:param value: (:class:`Value`) value to assign
"""
def __init__(self, env, var_name, value, name=""):
assert isinstance(env, Value)
assert isinstance(env.type, TEnvironment)
assert isinstance(var_name, str)
assert env.type.type_of(var_name) == value.type
assert isinstance(value, Value)
super().__init__([env, value], builtins.TNone(), name)
self.var_name = var_name
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.var_name = self.var_name
return self_copy
def opcode(self):
return "setlocal({})".format(repr(self.var_name))
def environment(self):
return self.operands[0]
def value(self):
return self.operands[1]
class GetAttr(Instruction):
"""
An intruction that loads an attribute from an object,
or extracts a tuple element.
:ivar attr: (string) variable name
"""
"""
:param obj: (:class:`Value`) object or tuple
:param attr: (string or integer) attribute or index
"""
def __init__(self, obj, attr, name=""):
assert isinstance(obj, Value)
assert isinstance(attr, (str, int))
if isinstance(attr, int):
assert isinstance(obj.type, types.TTuple)
typ = obj.type.elts[attr]
elif attr in obj.type.attributes:
typ = obj.type.attributes[attr]
else:
typ = obj.type.constructor.attributes[attr]
if types.is_function(typ) or types.is_rpc(typ):
typ = types.TMethod(obj.type, typ)
super().__init__([obj], typ, name)
self.attr = attr
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.attr = self.attr
return self_copy
def opcode(self):
return "getattr({})".format(repr(self.attr))
def object(self):
return self.operands[0]
class SetAttr(Instruction):
"""
An intruction that stores an attribute to an object.
:ivar attr: (string) variable name
"""
"""
:param obj: (:class:`Value`) object or tuple
:param attr: (string or integer) attribute
:param value: (:class:`Value`) value to store
"""
def __init__(self, obj, attr, value, name=""):
assert isinstance(obj, Value)
assert isinstance(attr, (str, int))
assert isinstance(value, Value)
if isinstance(attr, int):
assert value.type == obj.type.elts[attr].find()
else:
assert value.type == obj.type.attributes[attr].find()
super().__init__([obj, value], builtins.TNone(), name)
self.attr = attr
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.attr = self.attr
return self_copy
def opcode(self):
return "setattr({})".format(repr(self.attr))
def object(self):
return self.operands[0]
def value(self):
return self.operands[1]
class Offset(Instruction):
"""
An intruction that adds an offset to a pointer (indexes into a list).
This is used to represent internally generated pointer arithmetic, and must
remain inside the same object (see :class:`GetElem` and LLVM's GetElementPtr).
"""
"""
:param lst: (:class:`Value`) list
:param index: (:class:`Value`) index
"""
def __init__(self, base, offset, name=""):
assert isinstance(base, Value)
assert isinstance(offset, Value)
typ = types._TPointer(builtins.get_iterable_elt(base.type))
super().__init__([base, offset], typ, name)
def opcode(self):
return "offset"
def base(self):
return self.operands[0]
def index(self):
return self.operands[1]
class GetElem(Instruction):
"""
An intruction that loads an element from a list.
"""
"""
:param lst: (:class:`Value`) list
:param index: (:class:`Value`) index
"""
def __init__(self, lst, index, name=""):
assert isinstance(lst, Value)
assert isinstance(index, Value)
super().__init__([lst, index], builtins.get_iterable_elt(lst.type), name)
def opcode(self):
return "getelem"
def base(self):
return self.operands[0]
def index(self):
return self.operands[1]
class SetElem(Instruction):
"""
An intruction that stores an element into a list.
"""
"""
:param lst: (:class:`Value`) list
:param index: (:class:`Value`) index
:param value: (:class:`Value`) value to store
"""
def __init__(self, lst, index, value, name=""):
assert isinstance(lst, Value)
assert isinstance(index, Value)
assert isinstance(value, Value)
assert builtins.get_iterable_elt(lst.type) == value.type.find()
super().__init__([lst, index, value], builtins.TNone(), name)
def opcode(self):
return "setelem"
def base(self):
return self.operands[0]
def index(self):
return self.operands[1]
def value(self):
return self.operands[2]
class Coerce(Instruction):
"""
A coercion operation for numbers.
"""
def __init__(self, value, typ, name=""):
assert isinstance(value, Value)
assert isinstance(typ, types.Type)
super().__init__([value], typ, name)
def opcode(self):
return "coerce"
def value(self):
return self.operands[0]
class Arith(Instruction):
"""
An arithmetic operation on numbers.
:ivar op: (:class:`pythonparser.ast.operator`) operation
"""
"""
:param op: (:class:`pythonparser.ast.operator`) operation
:param lhs: (:class:`Value`) left-hand operand
:param rhs: (:class:`Value`) right-hand operand
"""
def __init__(self, op, lhs, rhs, name=""):
assert isinstance(op, ast.operator)
assert isinstance(lhs, Value)
assert isinstance(rhs, Value)
assert lhs.type == rhs.type
super().__init__([lhs, rhs], lhs.type, name)
self.op = op
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.op = self.op
return self_copy
def opcode(self):
return "arith({})".format(type(self.op).__name__)
def lhs(self):
return self.operands[0]
def rhs(self):
return self.operands[1]
class Compare(Instruction):
"""
A comparison operation on numbers.
:ivar op: (:class:`pythonparser.ast.cmpop`) operation
"""
"""
:param op: (:class:`pythonparser.ast.cmpop`) operation
:param lhs: (:class:`Value`) left-hand operand
:param rhs: (:class:`Value`) right-hand operand
"""
def __init__(self, op, lhs, rhs, name=""):
assert isinstance(op, ast.cmpop)
assert isinstance(lhs, Value)
assert isinstance(rhs, Value)
assert lhs.type == rhs.type
super().__init__([lhs, rhs], builtins.TBool(), name)
self.op = op
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.op = self.op
return self_copy
def opcode(self):
return "compare({})".format(type(self.op).__name__)
def lhs(self):
return self.operands[0]
def rhs(self):
return self.operands[1]
class Builtin(Instruction):
"""
A builtin operation. Similar to a function call that
never raises.
:ivar op: (string) operation name
"""
"""
:param op: (string) operation name
"""
def __init__(self, op, operands, typ, name=None):
assert isinstance(op, str)
for operand in operands: assert isinstance(operand, Value)
if name is None:
name = "BLT.{}".format(op)
super().__init__(operands, typ, name)
self.op = op
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.op = self.op
return self_copy
def opcode(self):
return "builtin({})".format(self.op)
class Closure(Instruction):
"""
A closure creation operation.
:ivar target_function: (:class:`Function`) function to invoke
"""
"""
:param func: (:class:`Function`) function
:param env: (:class:`Value`) outer environment
"""
def __init__(self, func, env, name=""):
assert isinstance(func, Function)
assert isinstance(env, Value)
assert is_environment(env.type)
super().__init__([env], func.type, name)
self.target_function = func
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.target_function = self.target_function
return self_copy
def opcode(self):
return "closure({})".format(self.target_function.name)
def environment(self):
return self.operands[0]
class Call(Instruction):
"""
A function call operation.
:ivar arg_exprs: (dict of str to `iodelay.Expr`)
iodelay expressions for values of arguments
:ivar static_target_function: (:class:`Function` or None)
statically resolved callee
:ivar is_cold: (bool)
the callee function is cold
"""
"""
:param func: (:class:`Value`) function to call
:param args: (list of :class:`Value`) function arguments
:param arg_exprs: (dict of str to `iodelay.Expr`)
"""
def __init__(self, func, args, arg_exprs, name=""):
assert isinstance(func, Value)
for arg in args: assert isinstance(arg, Value)
for arg in arg_exprs:
assert isinstance(arg, str)
assert isinstance(arg_exprs[arg], iodelay.Expr)
super().__init__([func] + args, func.type.ret, name)
self.arg_exprs = arg_exprs
self.static_target_function = None
self.is_cold = False
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.arg_exprs = self.arg_exprs
self_copy.static_target_function = self.static_target_function
return self_copy
def opcode(self):
return "call"
def target_function(self):
return self.operands[0]
def arguments(self):
return self.operands[1:]
def as_entity(self, type_printer):
result = super().as_entity(type_printer)
if self.static_target_function is not None:
result += " ; calls {}".format(self.static_target_function.name)
return result
class Select(Instruction):
"""
A conditional select instruction.
"""
"""
:param cond: (:class:`Value`) select condition
:param if_true: (:class:`Value`) value of select if condition is truthful
:param if_false: (:class:`Value`) value of select if condition is falseful
"""
def __init__(self, cond, if_true, if_false, name=""):
assert isinstance(cond, Value)
assert builtins.is_bool(cond.type)
assert isinstance(if_true, Value)
assert isinstance(if_false, Value)
assert if_true.type == if_false.type
super().__init__([cond, if_true, if_false], if_true.type, name)
def opcode(self):
return "select"
def condition(self):
return self.operands[0]
def if_true(self):
return self.operands[1]
def if_false(self):
return self.operands[2]
class Quote(Instruction):
"""
A quote operation. Returns a host interpreter value as a constant.
:ivar value: (string) operation name
"""
"""
:param value: (string) operation name
"""
def __init__(self, value, typ, name=""):
super().__init__([], typ, name)
self.value = value
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.value = self.value
return self_copy
def opcode(self):
return "quote({})".format(repr(self.value))
class Branch(Terminator):
"""
An unconditional branch instruction.
"""
"""
:param target: (:class:`BasicBlock`) branch target
"""
def __init__(self, target, name=""):
assert isinstance(target, BasicBlock)
super().__init__([target], builtins.TNone(), name)
def opcode(self):
return "branch"
def target(self):
return self.operands[0]
def set_target(self, new_target):
self.operands[0].uses.remove(self)
self.operands[0] = new_target
self.operands[0].uses.add(self)
class BranchIf(Terminator):
"""
A conditional branch instruction.
"""
"""
:param cond: (:class:`Value`) branch condition
:param if_true: (:class:`BasicBlock`) branch target if condition is truthful
:param if_false: (:class:`BasicBlock`) branch target if condition is falseful
"""
def __init__(self, cond, if_true, if_false, name=""):
assert isinstance(cond, Value)
assert builtins.is_bool(cond.type)
assert isinstance(if_true, BasicBlock)
assert isinstance(if_false, BasicBlock)
assert if_true != if_false # use Branch instead
super().__init__([cond, if_true, if_false], builtins.TNone(), name)
def opcode(self):
return "branchif"
def condition(self):
return self.operands[0]
def if_true(self):
return self.operands[1]
def if_false(self):
return self.operands[2]
class IndirectBranch(Terminator):
"""
An indirect branch instruction.
"""
"""
:param target: (:class:`Value`) branch target
:param destinations: (list of :class:`BasicBlock`) all possible values of `target`
"""
def __init__(self, target, destinations, name=""):
assert isinstance(target, Value)
assert all([isinstance(dest, BasicBlock) for dest in destinations])
super().__init__([target] + destinations, builtins.TNone(), name)
def opcode(self):
return "indirectbranch"
def target(self):
return self.operands[0]
def destinations(self):
return self.operands[1:]
def add_destination(self, destination):
destination.uses.add(self)
self.operands.append(destination)
def _operands_as_string(self, type_printer):
return "{}, [{}]".format(self.operands[0].as_operand(type_printer),
", ".join([dest.as_operand(type_printer)
for dest in self.operands[1:]]))
class Return(Terminator):
"""
A return instruction.
"""
"""
:param value: (:class:`Value`) return value
"""
def __init__(self, value, name=""):
assert isinstance(value, Value)
super().__init__([value], builtins.TNone(), name)
def opcode(self):
return "return"
def value(self):
return self.operands[0]
class Unreachable(Terminator):
"""
An instruction used to mark unreachable branches.
"""
"""
:param target: (:class:`BasicBlock`) branch target
"""
def __init__(self, name=""):
super().__init__([], builtins.TNone(), name)
def opcode(self):
return "unreachable"
class Raise(Terminator):
"""
A raise instruction.
"""
"""
:param value: (:class:`Value`) exception value
:param exn: (:class:`BasicBlock` or None) exceptional target
"""
def __init__(self, value=None, exn=None, name=""):
assert isinstance(value, Value)
operands = [value]
if exn is not None:
assert isinstance(exn, BasicBlock)
operands.append(exn)
super().__init__(operands, builtins.TNone(), name)
def opcode(self):
return "raise"
def value(self):
return self.operands[0]
def exception_target(self):
if len(self.operands) > 1:
return self.operands[1]
class Resume(Terminator):
"""
A resume instruction.
"""
"""
:param exn: (:class:`BasicBlock` or None) exceptional target
"""
def __init__(self, exn=None, name=""):
operands = []
if exn is not None:
assert isinstance(exn, BasicBlock)
operands.append(exn)
super().__init__(operands, builtins.TNone(), name)
def opcode(self):
return "resume"
def exception_target(self):
if len(self.operands) > 0:
return self.operands[0]
class Invoke(Terminator):
"""
A function call operation that supports exception handling.
:ivar arg_exprs: (dict of str to `iodelay.Expr`)
iodelay expressions for values of arguments
:ivar static_target_function: (:class:`Function` or None)
statically resolved callee
:ivar is_cold: (bool)
the callee function is cold
"""
"""
:param func: (:class:`Value`) function to call
:param args: (list of :class:`Value`) function arguments
:param normal: (:class:`BasicBlock`) normal target
:param exn: (:class:`BasicBlock`) exceptional target
:param arg_exprs: (dict of str to `iodelay.Expr`)
"""
def __init__(self, func, args, arg_exprs, normal, exn, name=""):
assert isinstance(func, Value)
for arg in args: assert isinstance(arg, Value)
assert isinstance(normal, BasicBlock)
assert isinstance(exn, BasicBlock)
for arg in arg_exprs:
assert isinstance(arg, str)
assert isinstance(arg_exprs[arg], iodelay.Expr)
super().__init__([func] + args + [normal, exn], func.type.ret, name)
self.arg_exprs = arg_exprs
self.static_target_function = None
self.is_cold = False
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.arg_exprs = self.arg_exprs
self_copy.static_target_function = self.static_target_function
return self_copy
def opcode(self):
return "invoke"
def target_function(self):
return self.operands[0]
def arguments(self):
return self.operands[1:-2]
def normal_target(self):
return self.operands[-2]
def exception_target(self):
return self.operands[-1]
def _operands_as_string(self, type_printer):
result = ", ".join([operand.as_operand(type_printer) for operand in self.operands[:-2]])
result += " to {} unwind {}".format(self.operands[-2].as_operand(type_printer),
self.operands[-1].as_operand(type_printer))
return result
def as_entity(self, type_printer):
result = super().as_entity(type_printer)
if self.static_target_function is not None:
result += " ; calls {}".format(self.static_target_function.name)
return result
class LandingPad(Terminator):
"""
An instruction that gives an incoming exception a name and
dispatches it according to its type.
Once dispatched, the exception should be cast to its proper
type by calling the "exncast" builtin on the landing pad value.
:ivar types: (a list of :class:`builtins.TException`)
exception types corresponding to the basic block operands
"""
def __init__(self, cleanup, name=""):
super().__init__([cleanup], builtins.TException(), name)
self.types = []
self.has_cleanup = True
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.types = list(self.types)
return self_copy
def opcode(self):
return "landingpad"
def cleanup(self):
return self.operands[0]
def clauses(self):
return zip(self.operands[1:], self.types)
def add_clause(self, target, typ):
assert isinstance(target, BasicBlock)
assert typ is None or builtins.is_exception(typ)
self.operands.append(target)
self.types.append(typ.find() if typ is not None else None)
target.uses.add(self)
def _operands_as_string(self, type_printer):
table = []
for target, typ in self.clauses():
if typ is None:
table.append("... => {}".format(target.as_operand(type_printer)))
else:
table.append("{} => {}".format(type_printer.name(typ),
target.as_operand(type_printer)))
return "cleanup {}, [{}]".format(self.cleanup().as_operand(type_printer),
", ".join(table))
class Delay(Terminator):
"""
A delay operation. Ties an :class:`iodelay.Expr` to SSA values so that
inlining could lead to the expression folding to a constant.
:ivar interval: (:class:`iodelay.Expr`) expression
"""
"""
:param interval: (:class:`iodelay.Expr`) expression
:param call: (:class:`Call` or ``Constant(None, builtins.TNone())``)
the call instruction that caused this delay, if any
:param target: (:class:`BasicBlock`) branch target
"""
def __init__(self, interval, decomposition, target, name=""):
assert isinstance(decomposition, Call) or isinstance(decomposition, Invoke) or \
isinstance(decomposition, Builtin) and decomposition.op in ("delay", "delay_mu")
assert isinstance(target, BasicBlock)
super().__init__([decomposition, target], builtins.TNone(), name)
self.interval = interval
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.interval = self.interval
return self_copy
def decomposition(self):
return self.operands[0]
def set_decomposition(self, new_decomposition):
self.operands[0].uses.remove(self)
self.operands[0] = new_decomposition
self.operands[0].uses.add(self)
def target(self):
return self.operands[1]
def set_target(self, new_target):
self.operands[1].uses.remove(self)
self.operands[1] = new_target
self.operands[1].uses.add(self)
def _operands_as_string(self, type_printer):
result = "decomp {}, to {}".format(self.decomposition().as_operand(type_printer),
self.target().as_operand(type_printer))
return result
def opcode(self):
return "delay({})".format(self.interval)
class Loop(Terminator):
"""
A terminator for loop headers that carries metadata useful
for unrolling. It includes an :class:`iodelay.Expr` specifying
the trip count, tied to SSA values so that inlining could lead
to the expression folding to a constant.
:ivar trip_count: (:class:`iodelay.Expr`)
expression for trip count
"""
"""
:param trip_count: (:class:`iodelay.Expr`) expression
:param indvar: (:class:`Phi`)
phi node corresponding to the induction SSA value,
which advances from ``0`` to ``trip_count - 1``
:param cond: (:class:`Value`) branch condition
:param if_true: (:class:`BasicBlock`) branch target if condition is truthful
:param if_false: (:class:`BasicBlock`) branch target if condition is falseful
"""
def __init__(self, trip_count, indvar, cond, if_true, if_false, name=""):
assert isinstance(indvar, Phi)
assert isinstance(cond, Value)
assert builtins.is_bool(cond.type)
assert isinstance(if_true, BasicBlock)
assert isinstance(if_false, BasicBlock)
super().__init__([indvar, cond, if_true, if_false], builtins.TNone(), name)
self.trip_count = trip_count
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.trip_count = self.trip_count
return self_copy
def induction_variable(self):
return self.operands[0]
def condition(self):
return self.operands[1]
def if_true(self):
return self.operands[2]
def if_false(self):
return self.operands[3]
def _operands_as_string(self, type_printer):
result = "indvar {}, if {}, {}, {}".format(
*list(map(lambda value: value.as_operand(type_printer), self.operands)))
return result
def opcode(self):
return "loop({} times)".format(self.trip_count)
class Interleave(Terminator):
"""
An instruction that schedules several threads of execution
in parallel.
"""
def __init__(self, destinations, name=""):
super().__init__(destinations, builtins.TNone(), name)
def opcode(self):
return "interleave"
def destinations(self):
return self.operands
def add_destination(self, destination):
destination.uses.add(self)
self.operands.append(destination)