diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/artiq/compiler/algorithms/__init__.py b/artiq/compiler/algorithms/__init__.py deleted file mode 100644 index 50fc4304b..000000000 --- a/artiq/compiler/algorithms/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .inline import inline -from .unroll import unroll diff --git a/artiq/compiler/algorithms/inline.py b/artiq/compiler/algorithms/inline.py deleted file mode 100644 index ce3e3315f..000000000 --- a/artiq/compiler/algorithms/inline.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -:func:`inline` inlines a call instruction in ARTIQ IR. -The call instruction must have a statically known callee, -it must be second to last in the basic block, and the basic -block must have exactly one successor. -""" - -from .. import types, builtins, iodelay, ir - -def inline(call_insn): - assert isinstance(call_insn, ir.Call) - assert call_insn.static_target_function is not None - assert len(call_insn.basic_block.successors()) == 1 - assert call_insn.basic_block.index(call_insn) == \ - len(call_insn.basic_block.instructions) - 2 - - value_map = {} - source_function = call_insn.static_target_function - target_function = call_insn.basic_block.function - target_predecessor = call_insn.basic_block - target_successor = call_insn.basic_block.successors()[0] - - if builtins.is_none(source_function.type.ret): - target_return_phi = None - else: - target_return_phi = target_successor.prepend(ir.Phi(source_function.type.ret)) - - closure = target_predecessor.insert(ir.GetAttr(call_insn.target_function(), '__closure__'), - before=call_insn) - for actual_arg, formal_arg in zip([closure] + call_insn.arguments(), - source_function.arguments): - value_map[formal_arg] = actual_arg - - for source_block in source_function.basic_blocks: - target_block = ir.BasicBlock([], "i." + source_block.name) - target_function.add(target_block) - value_map[source_block] = target_block - - def mapper(value): - if isinstance(value, ir.Constant): - return value - else: - return value_map[value] - - for source_insn in source_function.instructions(): - target_block = value_map[source_insn.basic_block] - if isinstance(source_insn, ir.Return): - if target_return_phi is not None: - target_return_phi.add_incoming(mapper(source_insn.value()), target_block) - target_insn = ir.Branch(target_successor) - elif isinstance(source_insn, ir.Phi): - target_insn = ir.Phi() - elif isinstance(source_insn, ir.Delay): - target_insn = source_insn.copy(mapper) - target_insn.interval = source_insn.interval.fold(call_insn.arg_exprs) - elif isinstance(source_insn, ir.Loop): - target_insn = source_insn.copy(mapper) - target_insn.trip_count = source_insn.trip_count.fold(call_insn.arg_exprs) - elif isinstance(source_insn, ir.Call): - target_insn = source_insn.copy(mapper) - target_insn.arg_exprs = \ - { arg: source_insn.arg_exprs[arg].fold(call_insn.arg_exprs) - for arg in source_insn.arg_exprs } - else: - target_insn = source_insn.copy(mapper) - target_insn.name = "i." + source_insn.name - value_map[source_insn] = target_insn - target_block.append(target_insn) - - for source_insn in source_function.instructions(): - if isinstance(source_insn, ir.Phi): - target_insn = value_map[source_insn] - for block, value in source_insn.incoming(): - target_insn.add_incoming(value_map[value], value_map[block]) - - target_predecessor.terminator().replace_with(ir.Branch(value_map[source_function.entry()])) - if target_return_phi is not None: - call_insn.replace_all_uses_with(target_return_phi) - call_insn.erase() diff --git a/artiq/compiler/algorithms/unroll.py b/artiq/compiler/algorithms/unroll.py deleted file mode 100644 index 392a31a8c..000000000 --- a/artiq/compiler/algorithms/unroll.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -:func:`unroll` unrolls a loop instruction in ARTIQ IR. -The loop's trip count must be constant. -The loop body must not have any control flow instructions -except for one branch back to the loop head. -The loop body must be executed if the condition to which -the instruction refers is true. -""" - -from .. import types, builtins, iodelay, ir -from ..analyses import domination - -def _get_body_blocks(root, limit): - postorder = [] - - visited = set() - def visit(block): - visited.add(block) - for next_block in block.successors(): - if next_block not in visited and next_block is not limit: - visit(next_block) - postorder.append(block) - - visit(root) - - postorder.reverse() - return postorder - -def unroll(loop_insn): - loop_head = loop_insn.basic_block - function = loop_head.function - assert isinstance(loop_insn, ir.Loop) - assert len(loop_head.predecessors()) == 2 - assert len(loop_insn.if_false().predecessors()) == 1 - assert iodelay.is_const(loop_insn.trip_count) - - trip_count = loop_insn.trip_count.fold().value - if trip_count == 0: - loop_insn.replace_with(ir.Branch(loop_insn.if_false())) - return - - source_blocks = _get_body_blocks(loop_insn.if_true(), loop_head) - source_indvar = loop_insn.induction_variable() - source_tail = loop_insn.if_false() - unroll_target = loop_head - for n in range(trip_count): - value_map = {source_indvar: ir.Constant(n, source_indvar.type)} - - for source_block in source_blocks: - target_block = ir.BasicBlock([], "u{}.{}".format(n, source_block.name)) - function.add(target_block) - value_map[source_block] = target_block - - def mapper(value): - if isinstance(value, ir.Constant): - return value - elif value in value_map: - return value_map[value] - else: - return value - - for source_block in source_blocks: - target_block = value_map[source_block] - for source_insn in source_block.instructions: - if isinstance(source_insn, ir.Phi): - target_insn = ir.Phi() - else: - target_insn = source_insn.copy(mapper) - target_insn.name = "u{}.{}".format(n, source_insn.name) - target_block.append(target_insn) - value_map[source_insn] = target_insn - - for source_block in source_blocks: - for source_insn in source_block.instructions: - if isinstance(source_insn, ir.Phi): - target_insn = value_map[source_insn] - for block, value in source_insn.incoming(): - target_insn.add_incoming(value_map[value], value_map[block]) - - assert isinstance(unroll_target.terminator(), (ir.Branch, ir.Loop)) - unroll_target.terminator().replace_with(ir.Branch(value_map[source_blocks[0]])) - unroll_target = value_map[source_blocks[-1]] - - assert isinstance(unroll_target.terminator(), ir.Branch) - assert len(source_blocks[-1].successors()) == 1 - unroll_target.terminator().replace_with(ir.Branch(source_tail)) - - for source_block in reversed(source_blocks): - for source_insn in reversed(source_block.instructions): - for use in set(source_insn.uses): - if isinstance(use, ir.Phi): - assert use.basic_block == loop_head - use.remove_incoming_value(source_insn) - source_insn.erase() - - for source_block in reversed(source_blocks): - source_block.erase() diff --git a/artiq/compiler/analyses/__init__.py b/artiq/compiler/analyses/__init__.py deleted file mode 100644 index bea15e2ca..000000000 --- a/artiq/compiler/analyses/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .domination import DominatorTree -from .devirtualization import Devirtualization -from .invariant_detection import InvariantDetection diff --git a/artiq/compiler/analyses/devirtualization.py b/artiq/compiler/analyses/devirtualization.py deleted file mode 100644 index 3a35639f2..000000000 --- a/artiq/compiler/analyses/devirtualization.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -:class:`Devirtualizer` performs method resolution at -compile time. - -Devirtualization is implemented using a lattice -with three states: unknown → assigned once → diverges. -The lattice is computed individually for every -variable in scope as well as every -(instance type, field name) pair. -""" - -from pythonparser import algorithm -from .. import asttyped, ir, types - -def _advance(target_map, key, value): - if key not in target_map: - target_map[key] = value # unknown → assigned once - else: - target_map[key] = None # assigned once → diverges - -class FunctionResolver(algorithm.Visitor): - def __init__(self, variable_map): - self.variable_map = variable_map - - self.scope_map = dict() - self.queue = [] - - self.in_assign = False - self.current_scopes = [] - - def finalize(self): - for thunk in self.queue: - thunk() - - def visit_scope(self, node): - self.current_scopes.append(node) - self.generic_visit(node) - self.current_scopes.pop() - - def visit_in_assign(self, node): - self.in_assign = True - self.visit(node) - self.in_assign = False - - def visit_Assign(self, node): - self.visit(node.value) - self.visit_in_assign(node.targets) - - def visit_ForT(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - self.visit(node.body) - self.visit(node.orelse) - - def visit_withitemT(self, node): - self.visit(node.context_expr) - self.visit_in_assign(node.optional_vars) - - def visit_comprehension(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - self.visit(node.ifs) - - def visit_ModuleT(self, node): - self.visit_scope(node) - - def visit_FunctionDefT(self, node): - _advance(self.scope_map, (self.current_scopes[-1], node.name), node) - self.visit_scope(node) - - def visit_NameT(self, node): - if self.in_assign: - # Just give up if we assign anything at all to a variable, and - # assume it diverges. - _advance(self.scope_map, (self.current_scopes[-1], node.id), None) - else: - # Look up the final value in scope_map and copy it into variable_map. - keys = [(scope, node.id) for scope in reversed(self.current_scopes)] - def thunk(): - for key in keys: - if key in self.scope_map: - self.variable_map[node] = self.scope_map[key] - return - self.queue.append(thunk) - -class MethodResolver(algorithm.Visitor): - def __init__(self, variable_map, method_map): - self.variable_map = variable_map - self.method_map = method_map - - # embedding.Stitcher.finalize generates initialization statements - # of form "constructor.meth = meth_body". - def visit_Assign(self, node): - if node.value not in self.variable_map: - return - - value = self.variable_map[node.value] - for target in node.targets: - if isinstance(target, asttyped.AttributeT): - if types.is_constructor(target.value.type): - instance_type = target.value.type.instance - elif types.is_instance(target.value.type): - instance_type = target.value.type - else: - continue - _advance(self.method_map, (instance_type, target.attr), value) - -class Devirtualization: - def __init__(self): - self.variable_map = dict() - self.method_map = dict() - - def visit(self, node): - function_resolver = FunctionResolver(self.variable_map) - function_resolver.visit(node) - function_resolver.finalize() - - method_resolver = MethodResolver(self.variable_map, self.method_map) - method_resolver.visit(node) diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py deleted file mode 100644 index e5d96ae39..000000000 --- a/artiq/compiler/analyses/domination.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -:class:`DominatorTree` computes the dominance relation over -control flow graphs. - -See http://www.cs.rice.edu/~keith/EMBED/dom.pdf. -""" - -class GenericDominatorTree: - def __init__(self): - self._assign_names() - self._compute() - - def _traverse_in_postorder(self): - raise NotImplementedError - - def _prev_block_names(self, block): - raise NotImplementedError - - def _assign_names(self): - postorder = self._traverse_in_postorder() - - self._start_name = len(postorder) - 1 - self._block_of_name = postorder - self._name_of_block = {} - for block_name, block in enumerate(postorder): - self._name_of_block[block] = block_name - - def _intersect(self, block_name_1, block_name_2): - finger_1, finger_2 = block_name_1, block_name_2 - while finger_1 != finger_2: - while finger_1 < finger_2: - finger_1 = self._doms[finger_1] - while finger_2 < finger_1: - finger_2 = self._doms[finger_2] - return finger_1 - - def _compute(self): - self._doms = {} - - # Start block dominates itself. - self._doms[self._start_name] = self._start_name - - # We don't yet know what blocks dominate all other blocks. - for block_name in range(self._start_name): - self._doms[block_name] = None - - changed = True - while changed: - changed = False - - # For all blocks except start block, in reverse postorder... - for block_name in reversed(range(self._start_name)): - # Select a new immediate dominator from the blocks we have - # already processed, and remember all others. - # We've already processed at least one previous block because - # of the graph traverse order. - new_idom, prev_block_names = None, [] - for prev_block_name in self._prev_block_names(block_name): - if new_idom is None and self._doms[prev_block_name] is not None: - new_idom = prev_block_name - else: - prev_block_names.append(prev_block_name) - - # Find a common previous block - for prev_block_name in prev_block_names: - if self._doms[prev_block_name] is not None: - new_idom = self._intersect(prev_block_name, new_idom) - - if self._doms[block_name] != new_idom: - self._doms[block_name] = new_idom - changed = True - - def immediate_dominator(self, block): - return self._block_of_name[self._doms[self._name_of_block[block]]] - - def dominators(self, block): - # Blocks that are statically unreachable from entry are considered - # dominated by every other block. - if block not in self._name_of_block: - yield from self._block_of_name - return - - block_name = self._name_of_block[block] - yield self._block_of_name[block_name] - while block_name != self._doms[block_name]: - block_name = self._doms[block_name] - yield self._block_of_name[block_name] - -class DominatorTree(GenericDominatorTree): - def __init__(self, function): - self.function = function - super().__init__() - - def _traverse_in_postorder(self): - 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.function.entry()) - - return postorder - - def _prev_block_names(self, block_name): - for block in self._block_of_name[block_name].predecessors(): - # Only return predecessors that are statically reachable from entry. - if block in self._name_of_block: - yield self._name_of_block[block] - -class PostDominatorTree(GenericDominatorTree): - def __init__(self, function): - self.function = function - super().__init__() - - def _traverse_in_postorder(self): - postorder = [] - - visited = set() - def visit(block): - visited.add(block) - for next_block in block.predecessors(): - if next_block not in visited: - visit(next_block) - postorder.append(block) - - for block in self.function.basic_blocks: - if not any(block.successors()): - visit(block) - - postorder.append(None) # virtual exit block - return postorder - - def _prev_block_names(self, block_name): - succ_blocks = self._block_of_name[block_name].successors() - if len(succ_blocks) > 0: - for block in succ_blocks: - yield self._name_of_block[block] - else: - yield self._start_name diff --git a/artiq/compiler/analyses/invariant_detection.py b/artiq/compiler/analyses/invariant_detection.py deleted file mode 100644 index 665c7317b..000000000 --- a/artiq/compiler/analyses/invariant_detection.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -:class:`InvariantDetection` determines which attributes can be safely -marked kernel invariant. -""" - -from pythonparser import diagnostic -from .. import ir, types - -class InvariantDetection: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - self.attr_locs = dict() - self.attr_written = set() - - for func in functions: - self.process_function(func) - - for key in self.attr_locs: - if key not in self.attr_written: - typ, attr = key - if attr in typ.constant_attributes: - continue - - diag = diagnostic.Diagnostic("note", - "attribute '{attr}' of type '{type}' is never written to; " + - "it could be marked as kernel invariant to potentially increase performance", - {"attr": attr, - "type": typ.name}, - self.attr_locs[key]) - self.engine.process(diag) - - def process_function(self, func): - for block in func.basic_blocks: - for insn in block.instructions: - if not isinstance(insn, (ir.GetAttr, ir.SetAttr)): - continue - if not types.is_instance(insn.object().type): - continue - - key = (insn.object().type, insn.attr) - if isinstance(insn, ir.GetAttr): - if types.is_method(insn.type): - continue - if key not in self.attr_locs and insn.loc is not None: - self.attr_locs[key] = insn.loc - elif isinstance(insn, ir.SetAttr): - self.attr_written.add(key) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py deleted file mode 100644 index 10b197fa4..000000000 --- a/artiq/compiler/asttyped.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -The typedtree module exports the PythonParser AST enriched with -typing information. -""" - -from pythonparser import ast - -class commontyped(ast.commonloc): - """A mixin for typed AST nodes.""" - - _types = ("type",) - - def _reprfields(self): - return self._fields + self._locs + self._types - -class scoped(object): - """ - :ivar typing_env: (dict with string keys and :class:`.types.Type` values) - map of variable names to variable types - :ivar globals_in_scope: (set of string keys) - set of variables resolved as globals - """ - -# Typed versions of untyped nodes -class argT(ast.arg, commontyped): - pass - -class ClassDefT(ast.ClassDef): - _types = ("constructor_type",) -class FunctionDefT(ast.FunctionDef, scoped): - _types = ("signature_type",) -class QuotedFunctionDefT(FunctionDefT): - """ - :ivar flags: (set of str) Code generation flags (see :class:`ir.Function`). - """ -class ModuleT(ast.Module, scoped): - pass - -class ExceptHandlerT(ast.ExceptHandler): - _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter - _types = ("name_type",) - -class ForT(ast.For): - """ - :ivar trip_count: (:class:`iodelay.Expr`) - :ivar trip_interval: (:class:`iodelay.Expr`) - """ - -class withitemT(ast.withitem): - _types = ("enter_type", "exit_type") - -class SliceT(ast.Slice, commontyped): - pass - -class AttributeT(ast.Attribute, commontyped): - pass -class BinOpT(ast.BinOp, commontyped): - pass -class BoolOpT(ast.BoolOp, commontyped): - pass -class CallT(ast.Call, commontyped): - """ - :ivar iodelay: (:class:`iodelay.Expr`) - :ivar arg_exprs: (dict of str to :class:`iodelay.Expr`) - """ -class CompareT(ast.Compare, commontyped): - pass -class DictT(ast.Dict, commontyped): - pass -class DictCompT(ast.DictComp, commontyped, scoped): - pass -class EllipsisT(ast.Ellipsis, commontyped): - pass -class GeneratorExpT(ast.GeneratorExp, commontyped, scoped): - pass -class IfExpT(ast.IfExp, commontyped): - pass -class LambdaT(ast.Lambda, commontyped, scoped): - pass -class ListT(ast.List, commontyped): - pass -class ListCompT(ast.ListComp, commontyped, scoped): - pass -class NameT(ast.Name, commontyped): - pass -class NameConstantT(ast.NameConstant, commontyped): - pass -class NumT(ast.Num, commontyped): - pass -class SetT(ast.Set, commontyped): - pass -class SetCompT(ast.SetComp, commontyped, scoped): - pass -class StrT(ast.Str, commontyped): - pass -class StarredT(ast.Starred, commontyped): - pass -class SubscriptT(ast.Subscript, commontyped): - pass -class TupleT(ast.Tuple, commontyped): - pass -class UnaryOpT(ast.UnaryOp, commontyped): - pass -class YieldT(ast.Yield, commontyped): - pass -class YieldFromT(ast.YieldFrom, commontyped): - pass - -# Novel typed nodes -class CoerceT(ast.expr, commontyped): - _fields = ('value',) # other_value deliberately not in _fields -class QuoteT(ast.expr, commontyped): - _fields = ('value',) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py deleted file mode 100644 index 54b25e719..000000000 --- a/artiq/compiler/builtins.py +++ /dev/null @@ -1,339 +0,0 @@ -""" -The :mod:`builtins` module contains the builtin Python -and ARTIQ types, such as int or float. -""" - -from collections import OrderedDict -from . import types - -# Types - -class TNone(types.TMono): - def __init__(self): - super().__init__("NoneType") - -class TBool(types.TMono): - def __init__(self): - super().__init__("bool") - - @staticmethod - def zero(): - return False - - @staticmethod - def one(): - return True - -class TInt(types.TMono): - def __init__(self, width=None): - if width is None: - width = types.TVar() - super().__init__("int", {"width": width}) - - @staticmethod - def zero(): - return 0 - - @staticmethod - def one(): - return 1 - -def TInt32(): - return TInt(types.TValue(32)) - -def TInt64(): - return TInt(types.TValue(64)) - -def _int_printer(typ, printer, depth, max_depth): - if types.is_var(typ["width"]): - return "numpy.int?" - else: - return "numpy.int{}".format(types.get_value(typ.find()["width"])) -types.TypePrinter.custom_printers["int"] = _int_printer - -class TFloat(types.TMono): - def __init__(self): - super().__init__("float") - - @staticmethod - def zero(): - return 0.0 - - @staticmethod - def one(): - return 1.0 - -class TStr(types.TMono): - def __init__(self): - super().__init__("str") - -class TBytes(types.TMono): - def __init__(self): - super().__init__("bytes") - -class TByteArray(types.TMono): - def __init__(self): - super().__init__("bytearray") - -class TList(types.TMono): - def __init__(self, elt=None): - if elt is None: - elt = types.TVar() - super().__init__("list", {"elt": elt}) - -class TArray(types.TMono): - def __init__(self, elt=None, num_dims=1): - if elt is None: - elt = types.TVar() - if isinstance(num_dims, int): - # Make TArray more convenient to instantiate from (ARTIQ) user code. - num_dims = types.TValue(num_dims) - # For now, enforce number of dimensions to be known, as we'd otherwise - # need to implement custom unification logic for the type of `shape`. - # Default to 1 to keep compatibility with old user code from before - # multidimensional array support. - assert isinstance(num_dims.value, int), "Number of dimensions must be resolved" - - super().__init__("array", {"elt": elt, "num_dims": num_dims}) - self.attributes = OrderedDict([ - ("buffer", types._TPointer(elt)), - ("shape", types.TTuple([TInt32()] * num_dims.value)), - ]) - -def _array_printer(typ, printer, depth, max_depth): - return "numpy.array(elt={}, num_dims={})".format( - printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value) -types.TypePrinter.custom_printers["array"] = _array_printer - -class TRange(types.TMono): - def __init__(self, elt=None): - if elt is None: - elt = types.TVar() - super().__init__("range", {"elt": elt}) - self.attributes = OrderedDict([ - ("start", elt), - ("stop", elt), - ("step", elt), - ]) - -class TException(types.TMono): - # All exceptions share the same internal layout: - # * Pointer to the unique global with the name of the exception (str) - # (which also serves as the EHABI type_info). - # * File, line and column where it was raised (str, int, int). - # * Message, which can contain substitutions {0}, {1} and {2} (str). - # * Three 64-bit integers, parameterizing the message (numpy.int64). - - # Keep this in sync with the function ARTIQIRGenerator.alloc_exn. - attributes = OrderedDict([ - ("__name__", TStr()), - ("__file__", TStr()), - ("__line__", TInt32()), - ("__col__", TInt32()), - ("__func__", TStr()), - ("__message__", TStr()), - ("__param0__", TInt64()), - ("__param1__", TInt64()), - ("__param2__", TInt64()), - ]) - - def __init__(self, name="Exception", id=0): - super().__init__(name) - self.id = id - -def fn_bool(): - return types.TConstructor(TBool()) - -def fn_int(): - return types.TConstructor(TInt()) - -def fn_int32(): - return types.TBuiltinFunction("int32") - -def fn_int64(): - return types.TBuiltinFunction("int64") - -def fn_float(): - return types.TConstructor(TFloat()) - -def fn_str(): - return types.TConstructor(TStr()) - -def fn_bytes(): - return types.TConstructor(TBytes()) - -def fn_bytearray(): - return types.TConstructor(TByteArray()) - -def fn_list(): - return types.TConstructor(TList()) - -def fn_array(): - return types.TConstructor(TArray()) - -def fn_Exception(): - return types.TExceptionConstructor(TException("Exception")) - -def fn_IndexError(): - return types.TExceptionConstructor(TException("IndexError")) - -def fn_ValueError(): - return types.TExceptionConstructor(TException("ValueError")) - -def fn_ZeroDivisionError(): - return types.TExceptionConstructor(TException("ZeroDivisionError")) - -def fn_RuntimeError(): - return types.TExceptionConstructor(TException("RuntimeError")) - -def fn_range(): - return types.TBuiltinFunction("range") - -def fn_len(): - return types.TBuiltinFunction("len") - -def fn_round(): - return types.TBuiltinFunction("round") - -def fn_abs(): - return types.TBuiltinFunction("abs") - -def fn_min(): - return types.TBuiltinFunction("min") - -def fn_max(): - return types.TBuiltinFunction("max") - -def fn_make_array(): - return types.TBuiltinFunction("make_array") - -def fn_print(): - return types.TBuiltinFunction("print") - -def fn_kernel(): - return types.TBuiltinFunction("kernel") - -def obj_parallel(): - return types.TBuiltin("parallel") - -def obj_interleave(): - return types.TBuiltin("interleave") - -def obj_sequential(): - return types.TBuiltin("sequential") - -def fn_delay(): - return types.TBuiltinFunction("delay") - -def fn_now_mu(): - return types.TBuiltinFunction("now_mu") - -def fn_delay_mu(): - return types.TBuiltinFunction("delay_mu") - -def fn_at_mu(): - return types.TBuiltinFunction("at_mu") - -def fn_rtio_log(): - return types.TBuiltinFunction("rtio_log") - -# Accessors - -def is_none(typ): - return types.is_mono(typ, "NoneType") - -def is_bool(typ): - return types.is_mono(typ, "bool") - -def is_int(typ, width=None): - if width is not None: - return types.is_mono(typ, "int", width=width) - else: - return types.is_mono(typ, "int") - -def is_int32(typ): - return is_int(typ, types.TValue(32)) - -def is_int64(typ): - return is_int(typ, types.TValue(64)) - -def get_int_width(typ): - if is_int(typ): - return types.get_value(typ.find()["width"]) - -def is_float(typ): - return types.is_mono(typ, "float") - -def is_str(typ): - return types.is_mono(typ, "str") - -def is_bytes(typ): - return types.is_mono(typ, "bytes") - -def is_bytearray(typ): - return types.is_mono(typ, "bytearray") - -def is_numeric(typ): - typ = typ.find() - return isinstance(typ, types.TMono) and \ - typ.name in ('int', 'float') - -def is_list(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "list", elt=elt) - else: - return types.is_mono(typ, "list") - -def is_array(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "array", elt=elt) - else: - return types.is_mono(typ, "array") - -def is_listish(typ, elt=None): - if is_list(typ, elt) or is_array(typ, elt): - return True - elif elt is None: - return is_str(typ) or is_bytes(typ) or is_bytearray(typ) - else: - return False - -def is_range(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "range", {"elt": elt}) - else: - return types.is_mono(typ, "range") - -def is_exception(typ, name=None): - if name is None: - return isinstance(typ.find(), TException) - else: - return isinstance(typ.find(), TException) and \ - typ.name == name - -def is_iterable(typ): - return is_listish(typ) or is_range(typ) - -def get_iterable_elt(typ): - # TODO: Arrays count as listish, but this returns the innermost element type for - # n-dimensional arrays, rather than the n-1 dimensional result of iterating over - # the first axis, which makes the name a bit misleading. - if is_str(typ) or is_bytes(typ) or is_bytearray(typ): - return TInt(types.TValue(8)) - elif types._is_pointer(typ) or is_iterable(typ): - return typ.find()["elt"].find() - else: - assert False - -def is_collection(typ): - typ = typ.find() - return isinstance(typ, types.TTuple) or \ - types.is_mono(typ, "list") - -def is_allocated(typ): - return not (is_none(typ) or is_bool(typ) or is_int(typ) or - is_float(typ) or is_range(typ) or - types._is_pointer(typ) or types.is_function(typ) or - types.is_external_function(typ) or types.is_rpc(typ) or - types.is_method(typ) or types.is_tuple(typ) or - types.is_value(typ)) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py deleted file mode 100644 index 89866c7c8..000000000 --- a/artiq/compiler/embedding.py +++ /dev/null @@ -1,1201 +0,0 @@ -""" -The :class:`Stitcher` class allows to transparently combine compiled -Python code and Python code executed on the host system: it resolves -the references to the host objects and translates the functions -annotated as ``@kernel`` when they are referenced. -""" - -import os, re, linecache, inspect, textwrap, types as pytypes, numpy -from collections import OrderedDict, defaultdict - -from pythonparser import ast, algorithm, source, diagnostic, parse_buffer -from pythonparser import lexer as source_lexer, parser as source_parser - -from Levenshtein import ratio as similarity, jaro_winkler - -from ..language import core as language_core -from . import types, builtins, asttyped, math_fns, prelude -from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, TypedtreePrinter -from .transforms.asttyped_rewriter import LocalExtractor - - -class SpecializedFunction: - def __init__(self, instance_type, host_function): - self.instance_type = instance_type - self.host_function = host_function - - def __eq__(self, other): - if isinstance(other, tuple): - return (self.instance_type == other[0] or - self.host_function == other[1]) - else: - return (self.instance_type == other.instance_type or - self.host_function == other.host_function) - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.instance_type, self.host_function)) - - -class EmbeddingMap: - def __init__(self): - self.object_current_key = 0 - self.object_forward_map = {} - self.object_reverse_map = {} - self.module_map = {} - self.type_map = {} - self.function_map = {} - - # Modules - def store_module(self, module, module_type): - self.module_map[module] = module_type - - def retrieve_module(self, module): - return self.module_map[module] - - def has_module(self, module): - return module in self.module_map - - # Types - def store_type(self, host_type, instance_type, constructor_type): - self._rename_type(instance_type) - self.type_map[host_type] = (instance_type, constructor_type) - - def retrieve_type(self, host_type): - return self.type_map[host_type] - - def has_type(self, host_type): - return host_type in self.type_map - - def _rename_type(self, new_instance_type): - # Generally, user-defined types that have exact same name (which is to say, classes - # defined inside functions) do not pose a problem to the compiler. The two places which - # cannot handle this are: - # 1. {TInstance,TConstructor}.__hash__ - # 2. LLVM type names - # Since handling #2 requires renaming on ARTIQ side anyway, it's more straightforward - # to do it once when embedding (since non-embedded code cannot define classes in - # functions). Also, easier to debug. - n = 0 - for host_type in self.type_map: - instance_type, constructor_type = self.type_map[host_type] - if instance_type.name == new_instance_type.name: - n += 1 - new_instance_type.name = "{}.{}".format(new_instance_type.name, n) - - def attribute_count(self): - count = 0 - for host_type in self.type_map: - instance_type, constructor_type = self.type_map[host_type] - count += len(instance_type.attributes) - count += len(constructor_type.attributes) - return count - - # Functions - def store_function(self, function, ir_function_name): - self.function_map[function] = ir_function_name - - def retrieve_function(self, function): - return self.function_map[function] - - def specialize_function(self, instance_type, host_function): - return SpecializedFunction(instance_type, host_function) - - # Objects - def store_object(self, obj_ref): - obj_id = id(obj_ref) - if obj_id in self.object_reverse_map: - return self.object_reverse_map[obj_id] - - self.object_current_key += 1 - self.object_forward_map[self.object_current_key] = obj_ref - self.object_reverse_map[obj_id] = self.object_current_key - return self.object_current_key - - def retrieve_object(self, obj_key): - return self.object_forward_map[obj_key] - - def iter_objects(self): - for obj_id in self.object_forward_map.keys(): - obj_ref = self.object_forward_map[obj_id] - if isinstance(obj_ref, (pytypes.FunctionType, pytypes.MethodType, - pytypes.BuiltinFunctionType, pytypes.ModuleType, - SpecializedFunction)): - continue - elif isinstance(obj_ref, type): - _, obj_typ = self.type_map[obj_ref] - else: - obj_typ, _ = self.type_map[type(obj_ref)] - yield obj_id, obj_ref, obj_typ - - def has_rpc(self): - return any(filter(lambda x: inspect.isfunction(x) or inspect.ismethod(x), - self.object_forward_map.values())) - - -class ASTSynthesizer: - def __init__(self, embedding_map, value_map, quote_function=None, expanded_from=None): - self.source = "" - self.source_buffer = source.Buffer(self.source, "") - self.embedding_map = embedding_map - self.value_map = value_map - self.quote_function = quote_function - self.expanded_from = expanded_from - self.diagnostics = [] - - def finalize(self): - self.source_buffer.source = self.source - return self.source_buffer - - def _add(self, fragment): - range_from = len(self.source) - self.source += fragment - range_to = len(self.source) - return source.Range(self.source_buffer, range_from, range_to, - expanded_from=self.expanded_from) - - def fast_quote_list(self, value): - elts = [None] * len(value) - is_T = False - if len(value) > 0: - v = value[0] - is_T = True - if isinstance(v, int): - T = int - elif isinstance(v, float): - T = float - elif isinstance(v, numpy.int32): - T = numpy.int32 - elif isinstance(v, numpy.int64): - T = numpy.int64 - else: - is_T = False - if is_T: - for v in value: - if not isinstance(v, T): - is_T = False - break - if is_T: - is_int = T != float - if T == int: - typ = builtins.TInt() - elif T == float: - typ = builtins.TFloat() - elif T == numpy.int32: - typ = builtins.TInt32() - elif T == numpy.int64: - typ = builtins.TInt64() - else: - assert False - text = [repr(elt) for elt in value] - start = len(self.source) - self.source += ", ".join(text) - if is_int: - for i, (v, t) in enumerate(zip(value, text)): - l = len(t) - elts[i] = asttyped.NumT( - n=int(v), ctx=None, type=typ, - loc=source.Range( - self.source_buffer, start, start + l, - expanded_from=self.expanded_from)) - start += l + 2 - else: - for i, (v, t) in enumerate(zip(value, text)): - l = len(t) - elts[i] = asttyped.NumT( - n=v, ctx=None, type=typ, - loc=source.Range( - self.source_buffer, start, start + l, - expanded_from=self.expanded_from)) - start += l + 2 - else: - for index, elt in enumerate(value): - elts[index] = self.quote(elt) - if index < len(value) - 1: - self._add(", ") - return elts - - def quote(self, value): - """Construct an AST fragment equal to `value`.""" - if value is None: - typ = builtins.TNone() - return asttyped.NameConstantT(value=value, type=typ, - loc=self._add(repr(value))) - elif isinstance(value, (bool, numpy.bool_)): - typ = builtins.TBool() - coerced = bool(value) - return asttyped.NameConstantT(value=coerced, type=typ, - loc=self._add(repr(coerced))) - elif value is float: - typ = builtins.fn_float() - return asttyped.NameConstantT(value=None, type=typ, - loc=self._add("float")) - elif value is numpy.int32: - typ = builtins.fn_int32() - return asttyped.NameConstantT(value=None, type=typ, - loc=self._add("numpy.int32")) - elif value is numpy.int64: - typ = builtins.fn_int64() - return asttyped.NameConstantT(value=None, type=typ, - loc=self._add("numpy.int64")) - elif value is numpy.array: - typ = builtins.fn_array() - return asttyped.NameConstantT(value=None, type=typ, - loc=self._add("numpy.array")) - elif value is numpy.full: - typ = builtins.fn_make_array() - return asttyped.NameConstantT(value=None, type=typ, - loc=self._add("numpy.full")) - elif isinstance(value, (int, float)): - if isinstance(value, int): - typ = builtins.TInt() - elif isinstance(value, float): - typ = builtins.TFloat() - return asttyped.NumT(n=value, ctx=None, type=typ, - loc=self._add(repr(value))) - elif isinstance(value, numpy.int32): - typ = builtins.TInt32() - return asttyped.NumT(n=int(value), ctx=None, type=typ, - loc=self._add(repr(value))) - elif isinstance(value, numpy.int64): - typ = builtins.TInt64() - return asttyped.NumT(n=int(value), ctx=None, type=typ, - loc=self._add(repr(value))) - elif isinstance(value, str): - return asttyped.StrT(s=value, ctx=None, type=builtins.TStr(), - loc=self._add(repr(value))) - elif isinstance(value, bytes): - return asttyped.StrT(s=value, ctx=None, type=builtins.TBytes(), - loc=self._add(repr(value))) - elif isinstance(value, bytearray): - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - loc = quote_loc.join(unquote_loc) - - return asttyped.QuoteT(value=value, type=builtins.TByteArray(), loc=loc) - elif isinstance(value, list): - begin_loc = self._add("[") - elts = self.fast_quote_list(value) - end_loc = self._add("]") - return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(), - begin_loc=begin_loc, end_loc=end_loc, - loc=begin_loc.join(end_loc)) - elif isinstance(value, tuple): - begin_loc = self._add("(") - elts = self.fast_quote_list(value) - end_loc = self._add(")") - return asttyped.TupleT(elts=elts, ctx=None, - type=types.TTuple([e.type for e in elts]), - begin_loc=begin_loc, end_loc=end_loc, - loc=begin_loc.join(end_loc)) - elif isinstance(value, numpy.ndarray): - return self.call(numpy.array, [list(value)], {}) - elif inspect.isfunction(value) or inspect.ismethod(value) or \ - isinstance(value, pytypes.BuiltinFunctionType) or \ - isinstance(value, SpecializedFunction) or \ - isinstance(value, numpy.ufunc): - if inspect.ismethod(value): - quoted_self = self.quote(value.__self__) - function_type = self.quote_function(value.__func__, self.expanded_from) - method_type = types.TMethod(quoted_self.type, function_type) - - dot_loc = self._add('.') - name_loc = self._add(value.__func__.__name__) - loc = quoted_self.loc.join(name_loc) - return asttyped.QuoteT(value=value, type=method_type, - self_loc=quoted_self.loc, loc=loc) - else: # function - function_type = self.quote_function(value, self.expanded_from) - - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - loc = quote_loc.join(unquote_loc) - return asttyped.QuoteT(value=value, type=function_type, loc=loc) - elif isinstance(value, pytypes.ModuleType): - if self.embedding_map.has_module(value): - module_type = self.embedding_map.retrieve_module(value) - else: - module_type = types.TModule(value.__name__, OrderedDict()) - module_type.attributes['__objectid__'] = builtins.TInt32() - self.embedding_map.store_module(value, module_type) - - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - loc = quote_loc.join(unquote_loc) - - self.value_map[module_type].append((value, loc)) - return asttyped.QuoteT(value=value, type=module_type, loc=loc) - else: - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - loc = quote_loc.join(unquote_loc) - - if isinstance(value, type): - typ = value - else: - typ = type(value) - - if self.embedding_map.has_type(typ): - instance_type, constructor_type = self.embedding_map.retrieve_type(typ) - - if hasattr(value, 'kernel_invariants') and \ - value.kernel_invariants != instance_type.constant_attributes: - attr_diff = value.kernel_invariants.difference( - instance_type.constant_attributes) - if len(attr_diff) > 0: - diag = diagnostic.Diagnostic("warning", - "object {value} of type {typ} declares attribute(s) {attrs} as " - "kernel invariant, but other objects of the same type do not; " - "the invariant annotation on this object will be ignored", - {"value": repr(value), - "typ": types.TypePrinter().name(instance_type, max_depth=0), - "attrs": ", ".join(["'{}'".format(attr) for attr in attr_diff])}, - loc) - self.diagnostics.append(diag) - attr_diff = instance_type.constant_attributes.difference( - value.kernel_invariants) - if len(attr_diff) > 0: - diag = diagnostic.Diagnostic("warning", - "object {value} of type {typ} does not declare attribute(s) {attrs} as " - "kernel invariant, but other objects of the same type do; " - "the invariant annotation on other objects will be ignored", - {"value": repr(value), - "typ": types.TypePrinter().name(instance_type, max_depth=0), - "attrs": ", ".join(["'{}'".format(attr) for attr in attr_diff])}, - loc) - self.diagnostics.append(diag) - value.kernel_invariants = value.kernel_invariants.intersection( - instance_type.constant_attributes) - else: - if issubclass(typ, BaseException): - if hasattr(typ, 'artiq_builtin'): - exception_id = 0 - else: - exception_id = self.embedding_map.store_object(typ) - instance_type = builtins.TException("{}.{}".format(typ.__module__, - typ.__qualname__), - id=exception_id) - constructor_type = types.TExceptionConstructor(instance_type) - else: - instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__qualname__), - OrderedDict()) - instance_type.attributes['__objectid__'] = builtins.TInt32() - constructor_type = types.TConstructor(instance_type) - constructor_type.attributes['__objectid__'] = builtins.TInt32() - instance_type.constructor = constructor_type - - self.embedding_map.store_type(typ, instance_type, constructor_type) - - if hasattr(value, 'kernel_invariants'): - assert isinstance(value.kernel_invariants, set) - instance_type.constant_attributes = value.kernel_invariants - - if isinstance(value, type): - self.value_map[constructor_type].append((value, loc)) - return asttyped.QuoteT(value=value, type=constructor_type, - loc=loc) - else: - self.value_map[instance_type].append((value, loc)) - return asttyped.QuoteT(value=value, type=instance_type, - loc=loc) - - def call(self, callee, args, kwargs, callback=None): - """ - Construct an AST fragment calling a function specified by - an AST node `function_node`, with given arguments. - """ - if callback is not None: - callback_node = self.quote(callback) - cb_begin_loc = self._add("(") - - callee_node = self.quote(callee) - arg_nodes = [] - kwarg_nodes = [] - kwarg_locs = [] - - begin_loc = self._add("(") - for index, arg in enumerate(args): - arg_nodes.append(self.quote(arg)) - if index < len(args) - 1: - self._add(", ") - if any(args) and any(kwargs): - self._add(", ") - for index, kw in enumerate(kwargs): - arg_loc = self._add(kw) - equals_loc = self._add("=") - kwarg_locs.append((arg_loc, equals_loc)) - kwarg_nodes.append(self.quote(kwargs[kw])) - if index < len(kwargs) - 1: - self._add(", ") - end_loc = self._add(")") - - if callback is not None: - cb_end_loc = self._add(")") - - node = asttyped.CallT( - func=callee_node, - args=arg_nodes, - keywords=[ast.keyword(arg=kw, value=value, - arg_loc=arg_loc, equals_loc=equals_loc, - loc=arg_loc.join(value.loc)) - for kw, value, (arg_loc, equals_loc) - in zip(kwargs, kwarg_nodes, kwarg_locs)], - starargs=None, kwargs=None, - type=types.TVar(), iodelay=None, arg_exprs={}, - begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None, - loc=callee_node.loc.join(end_loc)) - - if callback is not None: - node = asttyped.CallT( - func=callback_node, - args=[node], keywords=[], starargs=None, kwargs=None, - type=builtins.TNone(), iodelay=None, arg_exprs={}, - begin_loc=cb_begin_loc, end_loc=cb_end_loc, star_loc=None, dstar_loc=None, - loc=callback_node.loc.join(cb_end_loc)) - - return node - - -def suggest_identifier(id, names): - sorted_names = sorted(names, key=lambda other: jaro_winkler(id, other), reverse=True) - if len(sorted_names) > 0: - if jaro_winkler(id, sorted_names[0]) > 0.0 and similarity(id, sorted_names[0]) > 0.5: - return sorted_names[0] - -class StitchingASTTypedRewriter(ASTTypedRewriter): - def __init__(self, engine, prelude, globals, host_environment, quote): - super().__init__(engine, prelude) - self.globals = globals - self.env_stack.append(self.globals) - - self.host_environment = host_environment - self.quote = quote - - def visit_arg(self, node): - typ = self._find_name(node.arg, node.loc) - # ignore annotations; these are handled in _quote_function - return asttyped.argT(type=typ, - arg=node.arg, annotation=None, - arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) - - def visit_quoted_function(self, node, function): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - # We quote the defaults so they end up in the global data in LLVM IR. - # This way there is no "life before main", i.e. they do not have to be - # constructed before the main translated call executes; but the Python - # semantics is kept. - defaults = function.__defaults__ or () - quoted_defaults = [] - for default, default_node in zip(defaults, node.args.defaults): - quoted_defaults.append(self.quote(default, default_node.loc)) - node.args.defaults = quoted_defaults - - node = asttyped.QuotedFunctionDefT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - signature_type=types.TVar(), return_type=types.TVar(), - name=node.name, args=node.args, returns=node.returns, - body=node.body, decorator_list=node.decorator_list, - keyword_loc=node.keyword_loc, name_loc=node.name_loc, - arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs, - loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_Name(self, node): - typ = super()._try_find_name(node.id) - if typ is not None: - # Value from device environment. - return asttyped.NameT(type=typ, id=node.id, ctx=node.ctx, - loc=node.loc) - else: - # Try to find this value in the host environment and quote it. - if node.id == "print": - return self.quote(print, node.loc) - elif node.id in self.host_environment: - return self.quote(self.host_environment[node.id], node.loc) - else: - names = set() - names.update(self.host_environment.keys()) - for typing_env in reversed(self.env_stack): - names.update(typing_env.keys()) - - suggestion = suggest_identifier(node.id, names) - if suggestion is not None: - diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything; did you mean '{suggestion}'?", - {"name": node.id, "suggestion": suggestion}, - node.loc) - self.engine.process(diag) - else: - diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything", {"name": node.id}, - node.loc) - self.engine.process(diag) - -class StitchingInferencer(Inferencer): - def __init__(self, engine, value_map, quote): - super().__init__(engine) - self.value_map = value_map - self.quote = quote - self.attr_type_cache = {} - - def _compute_attr_type(self, object_value, object_type, object_loc, attr_name, loc): - if not hasattr(object_value, attr_name): - if attr_name.startswith('_'): - names = set(filter(lambda name: not name.startswith('_'), - dir(object_value))) - else: - names = set(dir(object_value)) - suggestion = suggest_identifier(attr_name, names) - - note = diagnostic.Diagnostic("note", - "attribute accessed here", {}, - loc) - if suggestion is not None: - diag = diagnostic.Diagnostic("error", - "host object does not have an attribute '{attr}'; " - "did you mean '{suggestion}'?", - {"attr": attr_name, "suggestion": suggestion}, - object_loc, notes=[note]) - else: - diag = diagnostic.Diagnostic("error", - "host object does not have an attribute '{attr}'", - {"attr": attr_name}, - object_loc, notes=[note]) - self.engine.process(diag) - return - - # Figure out the ARTIQ type of the value of the attribute. - # We do this by quoting it, as if to serialize. This has some - # overhead (i.e. synthesizing a source buffer), but has the advantage - # of having the host-to-ARTIQ mapping code in only one place and - # also immediately getting proper diagnostics on type errors. - attr_value = getattr(object_value, attr_name) - if (inspect.ismethod(attr_value) and - types.is_instance(object_type) and - # Check that the method is indeed defined on the class, - # and not just this instance. The check is written in - # the inverted form and not as hasattr(type(attr_value)) - # since the method may as well be defined on a superclass. - attr_name not in object_value.__dict__): - # In cases like: - # class c: - # @kernel - # def f(self): pass - # we want f to be defined on the class, not on the instance. - attributes = object_type.constructor.attributes - attr_value = SpecializedFunction(object_type, attr_value.__func__) - else: - attributes = object_type.attributes - - attr_value_type = None - - if isinstance(attr_value, list): - # Fast path for lists of scalars. - IS_FLOAT = 1 - IS_INT32 = 2 - IS_INT64 = 4 - - state = 0 - for elt in attr_value: - if elt.__class__ == float: - state |= IS_FLOAT - elif elt.__class__ == int: - if -2**31 < elt < 2**31-1: - state |= IS_INT32 - elif -2**63 < elt < 2**63-1: - state |= IS_INT64 - else: - state = -1 - break - else: - state = -1 - - if state == IS_FLOAT: - attr_value_type = builtins.TList(builtins.TFloat()) - elif state == IS_INT32: - attr_value_type = builtins.TList(builtins.TInt32()) - elif state == IS_INT64: - attr_value_type = builtins.TList(builtins.TInt64()) - - if attr_value_type is None: - note = diagnostic.Diagnostic("note", - "while inferring a type for an attribute '{attr}' of a host object", - {"attr": attr_name}, - loc) - - with self.engine.context(note): - # Slow path. We don't know what exactly is the attribute value, - # so we quote it only for the error message that may possibly result. - ast = self.quote(attr_value, object_loc.expanded_from) - Inferencer(engine=self.engine).visit(ast) - IntMonomorphizer(engine=self.engine).visit(ast) - attr_value_type = ast.type - - return attributes, attr_value_type - - def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc): - # The inferencer can only observe types, not values; however, - # when we work with host objects, we have to get the values - # somewhere, since host interpreter does not have types. - # Since we have categorized every host object we quoted according to - # its type, we now interrogate every host object we have to ensure - # that we can successfully serialize the value of the attribute we - # are now adding at the code generation stage. - object_type = value_node.type.find() - for object_value, object_loc in self.value_map[object_type]: - attr_type_key = (id(object_value), attr_name) - try: - attributes, attr_value_type = self.attr_type_cache[attr_type_key] - except KeyError: - attributes, attr_value_type = \ - self._compute_attr_type(object_value, object_type, object_loc, attr_name, loc) - self.attr_type_cache[attr_type_key] = attributes, attr_value_type - - if attr_name not in attributes: - # We just figured out what the type should be. Add it. - attributes[attr_name] = attr_value_type - else: - # Does this conflict with an earlier guess? - try: - attributes[attr_name].unify(attr_value_type) - except types.UnificationError as e: - printer = types.TypePrinter() - diag = diagnostic.Diagnostic("error", - "host object has an attribute '{attr}' of type {typea}, which is" - " different from previously inferred type {typeb} for the same attribute", - {"typea": printer.name(attr_value_type), - "typeb": printer.name(attributes[attr_name]), - "attr": attr_name}, - object_loc) - self.engine.process(diag) - - super()._unify_attribute(result_type, value_node, attr_name, attr_loc, loc) - - def visit_QuoteT(self, node): - if inspect.ismethod(node.value): - if types.is_rpc(types.get_method_function(node.type)): - return - self._unify_method_self(method_type=node.type, - attr_name=node.value.__func__.__name__, - attr_loc=None, - loc=node.loc, - self_loc=node.self_loc) - -class TypedtreeHasher(algorithm.Visitor): - def generic_visit(self, node): - def freeze(obj): - if isinstance(obj, ast.AST): - return self.visit(obj) - elif isinstance(obj, list): - return hash(tuple(freeze(elem) for elem in obj)) - elif isinstance(obj, types.Type): - return hash(obj.find()) - else: - # We don't care; only types change during inference. - pass - - fields = node._fields - if hasattr(node, '_types'): - fields = fields + node._types - return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields)) - -class Stitcher: - def __init__(self, core, dmgr, engine=None, print_as_rpc=True): - self.core = core - self.dmgr = dmgr - if engine is None: - self.engine = diagnostic.Engine(all_errors_are_fatal=True) - else: - self.engine = engine - - self.name = "" - self.typedtree = [] - self.inject_at = 0 - self.globals = {} - - # We don't want some things from the prelude as they are provided in - # the host Python namespace and gain special meaning when quoted. - self.prelude = prelude.globals() - if print_as_rpc: - self.prelude.pop("print") - self.prelude.pop("array") - - self.functions = {} - - self.embedding_map = EmbeddingMap() - self.value_map = defaultdict(lambda: []) - self.definitely_changed = False - - def stitch_call(self, function, args, kwargs, callback=None): - # We synthesize source code for the initial call so that - # diagnostics would have something meaningful to display to the user. - synthesizer = self._synthesizer(self._function_loc(function.artiq_embedded.function)) - call_node = synthesizer.call(function, args, kwargs, callback) - synthesizer.finalize() - self.typedtree.append(call_node) - - def finalize(self): - inferencer = StitchingInferencer(engine=self.engine, - value_map=self.value_map, - quote=self._quote) - typedtree_hasher = TypedtreeHasher() - - # Iterate inference to fixed point. - old_typedtree_hash = None - old_attr_count = None - while True: - inferencer.visit(self.typedtree) - if self.definitely_changed: - changed = True - self.definitely_changed = False - else: - typedtree_hash = typedtree_hasher.visit(self.typedtree) - attr_count = self.embedding_map.attribute_count() - changed = old_attr_count != attr_count or \ - old_typedtree_hash != typedtree_hash - old_typedtree_hash = typedtree_hash - old_attr_count = attr_count - - if not changed: - break - - # After we've discovered every referenced attribute, check if any kernel_invariant - # specifications refers to ones we didn't encounter. - for host_type in self.embedding_map.type_map: - instance_type, constructor_type = self.embedding_map.type_map[host_type] - if not hasattr(instance_type, "constant_attributes"): - # Exceptions lack user-definable attributes. - continue - - for attribute in instance_type.constant_attributes: - if attribute in instance_type.attributes: - # Fast path; if the ARTIQ Python type has the attribute, then every observed - # value is guaranteed to have it too. - continue - - for value, loc in self.value_map[instance_type]: - if hasattr(value, attribute): - continue - - diag = diagnostic.Diagnostic("warning", - "object {value} of type {typ} declares attribute '{attr}' as " - "kernel invariant, but the instance referenced here does not " - "have this attribute", - {"value": repr(value), - "typ": types.TypePrinter().name(instance_type, max_depth=0), - "attr": attribute}, - loc) - self.engine.process(diag) - - # After we have found all functions, synthesize a module to hold them. - source_buffer = source.Buffer("", "") - self.typedtree = asttyped.ModuleT( - typing_env=self.globals, globals_in_scope=set(), - body=self.typedtree, loc=source.Range(source_buffer, 0, 0)) - - def _inject(self, node): - self.typedtree.insert(self.inject_at, node) - self.inject_at += 1 - - def _synthesizer(self, expanded_from=None): - return ASTSynthesizer(expanded_from=expanded_from, - embedding_map=self.embedding_map, - value_map=self.value_map, - quote_function=self._quote_function) - - def _function_loc(self, function): - if isinstance(function, SpecializedFunction): - function = function.host_function - if hasattr(function, 'artiq_embedded') and function.artiq_embedded.function: - function = function.artiq_embedded.function - - if isinstance(function, str): - return source.Range(source.Buffer(function, ""), 0, 0) - - filename = function.__code__.co_filename - line = function.__code__.co_firstlineno - name = function.__code__.co_name - - source_line = linecache.getline(filename, line).lstrip() - while source_line.startswith("@") or source_line == "": - line += 1 - source_line = linecache.getline(filename, line).lstrip() - - if "" in function.__qualname__: - column = 0 # can't get column of lambda - else: - column = re.search("def", source_line).start(0) - source_buffer = source.Buffer(source_line, filename, line) - return source.Range(source_buffer, column, column) - - def _call_site_note(self, call_loc, fn_kind): - if call_loc: - if fn_kind == 'syscall': - return [diagnostic.Diagnostic("note", - "in system call here", {}, - call_loc)] - elif fn_kind == 'rpc': - return [diagnostic.Diagnostic("note", - "in function called remotely here", {}, - call_loc)] - elif fn_kind == 'kernel': - return [diagnostic.Diagnostic("note", - "in kernel function here", {}, - call_loc)] - else: - assert False - else: - return [] - - def _type_of_param(self, function, loc, param, fn_kind): - if param.annotation is not inspect.Parameter.empty: - # Type specified explicitly. - return self._extract_annot(function, param.annotation, - "argument '{}'".format(param.name), loc, - fn_kind) - elif fn_kind == 'syscall': - # Syscalls must be entirely annotated. - diag = diagnostic.Diagnostic("error", - "system call argument '{argument}' must have a type annotation", - {"argument": param.name}, - self._function_loc(function), - notes=self._call_site_note(loc, fn_kind)) - self.engine.process(diag) - elif fn_kind == 'rpc' and param.default is not inspect.Parameter.empty: - notes = [] - notes.append(diagnostic.Diagnostic("note", - "expanded from here while trying to infer a type for an" - " unannotated optional argument '{argument}' from its default value", - {"argument": param.name}, - self._function_loc(function))) - if loc is not None: - notes.append(self._call_site_note(loc, fn_kind)) - - with self.engine.context(*notes): - # Try and infer the type from the default value. - # This is tricky, because the default value might not have - # a well-defined type in APython. - # In this case, we bail out, but mention why we do it. - ast = self._quote(param.default, None) - Inferencer(engine=self.engine).visit(ast) - IntMonomorphizer(engine=self.engine).visit(ast) - return ast.type - else: - # Let the rest of the program decide. - return types.TVar() - - def _quote_embedded_function(self, function, flags): - # we are now parsing new functions... definitely changed the type - self.definitely_changed = True - - if isinstance(function, SpecializedFunction): - host_function = function.host_function - else: - host_function = function - - if not hasattr(host_function, "artiq_embedded"): - raise ValueError("{} is not an embedded function".format(repr(host_function))) - - # Extract function source. - embedded_function = host_function.artiq_embedded.function - if isinstance(embedded_function, str): - # This is a function to be eval'd from the given source code in string form. - # Mangle the host function's id() into the fully qualified name to make sure - # there are no collisions. - source_code = embedded_function - embedded_function = host_function - filename = "" - module_name = "__eval_{}".format(id(host_function)) - first_line = 1 - else: - source_code = inspect.getsource(embedded_function) - filename = embedded_function.__code__.co_filename - module_name = embedded_function.__globals__['__name__'] - first_line = embedded_function.__code__.co_firstlineno - - # Extract function annotation. - signature = inspect.signature(embedded_function) - loc = self._function_loc(embedded_function) - - arg_types = OrderedDict() - optarg_types = OrderedDict() - for param in signature.parameters.values(): - if param.kind == inspect.Parameter.VAR_POSITIONAL or \ - param.kind == inspect.Parameter.VAR_KEYWORD: - diag = diagnostic.Diagnostic("error", - "variadic arguments are not supported; '{argument}' is variadic", - {"argument": param.name}, - self._function_loc(function), - notes=self._call_site_note(loc, fn_kind='kernel')) - self.engine.process(diag) - - arg_type = self._type_of_param(function, loc, param, fn_kind='kernel') - if param.default is inspect.Parameter.empty: - arg_types[param.name] = arg_type - else: - optarg_types[param.name] = arg_type - - if signature.return_annotation is not inspect.Signature.empty: - ret_type = self._extract_annot(function, signature.return_annotation, - "return type", loc, fn_kind='kernel') - else: - ret_type = types.TVar() - - # Extract function environment. - host_environment = dict() - host_environment.update(embedded_function.__globals__) - cells = embedded_function.__closure__ - cell_names = embedded_function.__code__.co_freevars - host_environment.update({var: cells[index] for index, var in enumerate(cell_names)}) - - # Find out how indented we are. - initial_whitespace = re.search(r"^\s*", source_code).group(0) - initial_indent = len(initial_whitespace.expandtabs()) - - # Parse. - source_buffer = source.Buffer(source_code, filename, first_line) - lexer = source_lexer.Lexer(source_buffer, version=(3, 6), diagnostic_engine=self.engine) - lexer.indent = [(initial_indent, - source.Range(source_buffer, 0, len(initial_whitespace)), - initial_whitespace)] - parser = source_parser.Parser(lexer, version=(3, 6), diagnostic_engine=self.engine) - function_node = parser.file_input().body[0] - - # Mangle the name, since we put everything into a single module. - full_function_name = "{}.{}".format(module_name, host_function.__qualname__) - if isinstance(function, SpecializedFunction): - instance_type = function.instance_type - function_node.name = "_Z{}{}I{}{}Ezz".format(len(full_function_name), full_function_name, - len(instance_type.name), instance_type.name) - else: - function_node.name = "_Z{}{}zz".format(len(full_function_name), full_function_name) - - # Record the function in the function map so that LLVM IR generator - # can handle quoting it. - self.embedding_map.store_function(function, function_node.name) - - # Fill in the function type before typing it to handle recursive - # invocations. - self.functions[function] = types.TFunction(arg_types, optarg_types, ret_type) - - # Rewrite into typed form. - asttyped_rewriter = StitchingASTTypedRewriter( - engine=self.engine, prelude=self.prelude, - globals=self.globals, host_environment=host_environment, - quote=self._quote) - function_node = asttyped_rewriter.visit_quoted_function(function_node, embedded_function) - function_node.flags = flags - - # Add it into our typedtree so that it gets inferenced and codegen'd. - self._inject(function_node) - - # Tie the typing knot. - self.functions[function].unify(function_node.signature_type) - - return function_node - - def _extract_annot(self, function, annot, kind, call_loc, fn_kind): - if annot is None: - annot = builtins.TNone() - - if isinstance(function, SpecializedFunction): - host_function = function.host_function - else: - host_function = function - - if hasattr(host_function, 'artiq_embedded'): - embedded_function = host_function.artiq_embedded.function - else: - embedded_function = host_function - - if isinstance(embedded_function, str): - embedded_function = host_function - - if isinstance(annot, str): - try: - annot = eval(annot, embedded_function.__globals__) - except Exception: - diag = diagnostic.Diagnostic( - "error", - "type annotation for {kind}, {annot}, cannot be evaluated", - {"kind": kind, "annot": repr(annot)}, - self._function_loc(function), - notes=self._call_site_note(call_loc, fn_kind)) - self.engine.process(diag) - - if not isinstance(annot, types.Type): - diag = diagnostic.Diagnostic("error", - "type annotation for {kind}, '{annot}', is not an ARTIQ type", - {"kind": kind, "annot": repr(annot)}, - self._function_loc(function), - notes=self._call_site_note(call_loc, fn_kind)) - self.engine.process(diag) - - return types.TVar() - else: - return annot - - def _quote_syscall(self, function, loc): - signature = inspect.signature(function) - - arg_types = OrderedDict() - optarg_types = OrderedDict() - for param in signature.parameters.values(): - if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD: - diag = diagnostic.Diagnostic("error", - "system calls must only use positional arguments; '{argument}' isn't", - {"argument": param.name}, - self._function_loc(function), - notes=self._call_site_note(loc, fn_kind='syscall')) - self.engine.process(diag) - - if param.default is inspect.Parameter.empty: - arg_types[param.name] = self._type_of_param(function, loc, param, - fn_kind='syscall') - else: - diag = diagnostic.Diagnostic("error", - "system call argument '{argument}' must not have a default value", - {"argument": param.name}, - self._function_loc(function), - notes=self._call_site_note(loc, fn_kind='syscall')) - self.engine.process(diag) - - if signature.return_annotation is not inspect.Signature.empty: - ret_type = self._extract_annot(function, signature.return_annotation, - "return type", loc, fn_kind='syscall') - else: - diag = diagnostic.Diagnostic("error", - "system call must have a return type annotation", {}, - self._function_loc(function), - notes=self._call_site_note(loc, fn_kind='syscall')) - self.engine.process(diag) - ret_type = types.TVar() - - function_type = types.TExternalFunction(arg_types, ret_type, - name=function.artiq_embedded.syscall, - flags=function.artiq_embedded.flags) - self.functions[function] = function_type - return function_type - - def _quote_rpc(self, function, loc): - if isinstance(function, SpecializedFunction): - host_function = function.host_function - else: - host_function = function - ret_type = builtins.TNone() - - if isinstance(host_function, pytypes.BuiltinFunctionType): - pass - elif (isinstance(host_function, pytypes.FunctionType) or \ - isinstance(host_function, pytypes.MethodType)): - if isinstance(host_function, pytypes.FunctionType): - signature = inspect.signature(host_function) - else: - # inspect bug? - signature = inspect.signature(host_function.__func__) - if signature.return_annotation is not inspect.Signature.empty: - ret_type = self._extract_annot(host_function, signature.return_annotation, - "return type", loc, fn_kind='rpc') - else: - assert False - - is_async = False - if hasattr(host_function, "artiq_embedded") and \ - "async" in host_function.artiq_embedded.flags: - is_async = True - - if not builtins.is_none(ret_type) and is_async: - note = diagnostic.Diagnostic("note", - "function called here", {}, - loc) - diag = diagnostic.Diagnostic("fatal", - "functions that return a value cannot be defined as async RPCs", {}, - self._function_loc(host_function.artiq_embedded.function), - notes=[note]) - self.engine.process(diag) - - function_type = types.TRPC(ret_type, - service=self.embedding_map.store_object(host_function), - is_async=is_async) - self.functions[function] = function_type - return function_type - - def _quote_function(self, function, loc): - if isinstance(function, SpecializedFunction): - host_function = function.host_function - else: - host_function = function - - if function in self.functions: - return self.functions[function] - - math_type = math_fns.match(function) - if math_type is not None: - self.functions[function] = math_type - elif not hasattr(host_function, "artiq_embedded") or \ - (host_function.artiq_embedded.core_name is None and - host_function.artiq_embedded.portable is False and - host_function.artiq_embedded.syscall is None and - host_function.artiq_embedded.forbidden is False): - self._quote_rpc(function, loc) - elif host_function.artiq_embedded.function is not None: - if host_function.__name__ == "": - note = diagnostic.Diagnostic("note", - "lambda created here", {}, - self._function_loc(host_function.artiq_embedded.function)) - diag = diagnostic.Diagnostic("fatal", - "lambdas cannot be used as kernel functions", {}, - loc, - notes=[note]) - self.engine.process(diag) - - core_name = host_function.artiq_embedded.core_name - if core_name is not None and self.dmgr.get(core_name) != self.core: - note = diagnostic.Diagnostic("note", - "called from this function", {}, - loc) - diag = diagnostic.Diagnostic("fatal", - "this function runs on a different core device '{name}'", - {"name": host_function.artiq_embedded.core_name}, - self._function_loc(host_function.artiq_embedded.function), - notes=[note]) - self.engine.process(diag) - - self._quote_embedded_function(function, - flags=host_function.artiq_embedded.flags) - elif host_function.artiq_embedded.syscall is not None: - # Insert a storage-less global whose type instructs the compiler - # to perform a system call instead of a regular call. - self._quote_syscall(function, loc) - elif host_function.artiq_embedded.forbidden is not None: - diag = diagnostic.Diagnostic("fatal", - "this function cannot be called as an RPC", {}, - self._function_loc(host_function), - notes=self._call_site_note(loc, fn_kind='rpc')) - self.engine.process(diag) - else: - assert False - - return self.functions[function] - - def _quote(self, value, loc): - synthesizer = self._synthesizer(loc) - node = synthesizer.quote(value) - synthesizer.finalize() - if len(synthesizer.diagnostics) > 0: - for warning in synthesizer.diagnostics: - self.engine.process(warning) - return node diff --git a/artiq/compiler/import_cache.py b/artiq/compiler/import_cache.py deleted file mode 100644 index 9300f5c39..000000000 --- a/artiq/compiler/import_cache.py +++ /dev/null @@ -1,58 +0,0 @@ -import sys -import builtins -import linecache -import tokenize -import logging -import importlib.machinery as im - -from artiq.experiment import kernel, portable - - -__all__ = ["install_hook"] - - -logger = logging.getLogger(__name__) - - -cache = dict() -im_exec_module = None -linecache_getlines = None - - -def hook_exec_module(self, module): - im_exec_module(self, module) - if (hasattr(module, "__file__") - # Heuristic to determine if the module may contain ARTIQ kernels. - # This breaks if kernel is not imported the usual way. - and ((getattr(module, "kernel", None) is kernel) - or (getattr(module, "portable", None) is portable))): - fn = module.__file__ - try: - with tokenize.open(fn) as fp: - lines = fp.readlines() - if lines and not lines[-1].endswith("\n"): - lines[-1] += "\n" - cache[fn] = lines - except: - logger.warning("failed to add '%s' to cache", fn, exc_info=True) - else: - logger.debug("added '%s' to cache", fn) - - -def hook_getlines(filename, module_globals=None): - if filename in cache: - return cache[filename] - else: - return linecache_getlines(filename, module_globals) - - -def install_hook(): - global im_exec_module, linecache_getlines - - im_exec_module = im.SourceFileLoader.exec_module - im.SourceFileLoader.exec_module = hook_exec_module - - linecache_getlines = linecache.getlines - linecache.getlines = hook_getlines - - logger.debug("hook installed") diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py deleted file mode 100644 index 6cab8588c..000000000 --- a/artiq/compiler/iodelay.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -The :mod:`iodelay` module contains the classes describing -the statically inferred RTIO delay arising from executing -a function. -""" - -from functools import reduce - -class Expr: - def __add__(lhs, rhs): - assert isinstance(rhs, Expr) - return Add(lhs, rhs) - __iadd__ = __add__ - - def __sub__(lhs, rhs): - assert isinstance(rhs, Expr) - return Sub(lhs, rhs) - __isub__ = __sub__ - - def __mul__(lhs, rhs): - assert isinstance(rhs, Expr) - return Mul(lhs, rhs) - __imul__ = __mul__ - - def __truediv__(lhs, rhs): - assert isinstance(rhs, Expr) - return TrueDiv(lhs, rhs) - __itruediv__ = __truediv__ - - def __floordiv__(lhs, rhs): - assert isinstance(rhs, Expr) - return FloorDiv(lhs, rhs) - __ifloordiv__ = __floordiv__ - - def __ne__(lhs, rhs): - return not (lhs == rhs) - - def free_vars(self): - return set() - - def fold(self, vars=None): - return self - -class Const(Expr): - _priority = 1 - - def __init__(self, value): - assert isinstance(value, (int, float)) - self.value = value - - def __str__(self): - return str(self.value) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value - - def eval(self, env): - return self.value - -class Var(Expr): - _priority = 1 - - def __init__(self, name): - assert isinstance(name, str) - self.name = name - - def __str__(self): - return self.name - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name - - def free_vars(self): - return {self.name} - - def fold(self, vars=None): - if vars is not None and self.name in vars: - return vars[self.name] - else: - return self - -class Conv(Expr): - _priority = 1 - - def __init__(self, operand, ref_period): - assert isinstance(operand, Expr) - assert isinstance(ref_period, float) - self.operand, self.ref_period = operand, ref_period - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and \ - lhs.ref_period == rhs.ref_period and \ - lhs.operand == rhs.operand - - def free_vars(self): - return self.operand.free_vars() - -class MUToS(Conv): - def __str__(self): - return "mu->s({})".format(self.operand) - - def eval(self, env): - return self.operand.eval(env) * self.ref_period - - def fold(self, vars=None): - operand = self.operand.fold(vars) - if isinstance(operand, Const): - return Const(operand.value * self.ref_period) - else: - return MUToS(operand, ref_period=self.ref_period) - -class SToMU(Conv): - def __str__(self): - return "s->mu({})".format(self.operand) - - def eval(self, env): - return int(self.operand.eval(env) / self.ref_period) - - def fold(self, vars=None): - operand = self.operand.fold(vars) - if isinstance(operand, Const): - return Const(int(operand.value / self.ref_period)) - else: - return SToMU(operand, ref_period=self.ref_period) - -class BinOp(Expr): - def __init__(self, lhs, rhs): - self.lhs, self.rhs = lhs, rhs - - def __str__(self): - lhs = "({})".format(self.lhs) if self.lhs._priority > self._priority else str(self.lhs) - rhs = "({})".format(self.rhs) if self.rhs._priority > self._priority else str(self.rhs) - return "{} {} {}".format(lhs, self._symbol, rhs) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and lhs.rhs == rhs.rhs - - def eval(self, env): - return self.__class__._op(self.lhs.eval(env), self.rhs.eval(env)) - - def free_vars(self): - return self.lhs.free_vars() | self.rhs.free_vars() - - def _fold_binop(self, lhs, rhs): - if isinstance(lhs, Const) and lhs.__class__ == rhs.__class__: - return Const(self.__class__._op(lhs.value, rhs.value)) - elif isinstance(lhs, (MUToS, SToMU)) and lhs.__class__ == rhs.__class__: - return lhs.__class__(self.__class__(lhs.operand, rhs.operand), - ref_period=lhs.ref_period).fold() - else: - return self.__class__(lhs, rhs) - - def fold(self, vars=None): - return self._fold_binop(self.lhs.fold(vars), self.rhs.fold(vars)) - -class BinOpFixpoint(BinOp): - def _fold_binop(self, lhs, rhs): - if isinstance(lhs, Const) and lhs.value == self._fixpoint: - return rhs - elif isinstance(rhs, Const) and rhs.value == self._fixpoint: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class Add(BinOpFixpoint): - _priority = 2 - _symbol = "+" - _op = lambda a, b: a + b - _fixpoint = 0 - -class Mul(BinOpFixpoint): - _priority = 1 - _symbol = "*" - _op = lambda a, b: a * b - _fixpoint = 1 - -class Sub(BinOp): - _priority = 2 - _symbol = "-" - _op = lambda a, b: a - b - - def _fold_binop(self, lhs, rhs): - if isinstance(rhs, Const) and rhs.value == 0: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class Div(BinOp): - def _fold_binop(self, lhs, rhs): - if isinstance(rhs, Const) and rhs.value == 1: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class TrueDiv(Div): - _priority = 1 - _symbol = "/" - _op = lambda a, b: a / b if b != 0 else 0 - -class FloorDiv(Div): - _priority = 1 - _symbol = "//" - _op = lambda a, b: a // b if b != 0 else 0 - -class Max(Expr): - _priority = 1 - - def __init__(self, operands): - assert isinstance(operands, list) - assert all([isinstance(operand, Expr) for operand in operands]) - assert operands != [] - self.operands = operands - - def __str__(self): - return "max({})".format(", ".join([str(operand) for operand in self.operands])) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.operands == rhs.operands - - def free_vars(self): - return reduce(lambda a, b: a | b, [operand.free_vars() for operand in self.operands]) - - def eval(self, env): - return max([operand.eval() for operand in self.operands]) - - def fold(self, vars=None): - consts, exprs = [], [] - for operand in self.operands: - operand = operand.fold(vars) - if isinstance(operand, Const): - consts.append(operand.value) - elif operand not in exprs: - exprs.append(operand) - if len(consts) > 0: - exprs.append(Const(max(consts))) - if len(exprs) == 1: - return exprs[0] - else: - return Max(exprs) - -def is_const(expr, value=None): - expr = expr.fold() - if value is None: - return isinstance(expr, Const) - else: - return isinstance(expr, Const) and expr.value == value - -def is_zero(expr): - return is_const(expr, 0) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py deleted file mode 100644 index 3f984606f..000000000 --- a/artiq/compiler/ir.py +++ /dev/null @@ -1,1504 +0,0 @@ -""" -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 - - 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 not any(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 not any(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("; ") - 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 "".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 Reraise(Terminator): - """ - A reraise 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 "reraise" - - 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 = [] - - 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) diff --git a/artiq/compiler/kernel.ld b/artiq/compiler/kernel.ld deleted file mode 100644 index 6523d631a..000000000 --- a/artiq/compiler/kernel.ld +++ /dev/null @@ -1,53 +0,0 @@ -/* Force ld to make the ELF header as loadable. */ -PHDRS -{ - headers PT_LOAD FILEHDR PHDRS ; - text PT_LOAD ; - data PT_LOAD ; - dynamic PT_DYNAMIC ; - eh_frame PT_GNU_EH_FRAME ; -} - -SECTIONS -{ - /* Push back .text section enough so that ld.lld not complain */ - . = SIZEOF_HEADERS; - - .text : - { - *(.text .text.*) - } : text - - .rodata : - { - *(.rodata .rodata.*) - } - - .eh_frame : - { - KEEP(*(.eh_frame)) - } : text - - .eh_frame_hdr : - { - KEEP(*(.eh_frame_hdr)) - } : text : eh_frame - - .data : - { - *(.data) - } : data - - .dynamic : - { - *(.dynamic) - } : data : dynamic - - .bss (NOLOAD) : ALIGN(4) - { - __bss_start = .; - *(.sbss .sbss.* .bss .bss.*); - . = ALIGN(4); - _end = .; - } -} diff --git a/artiq/compiler/math_fns.py b/artiq/compiler/math_fns.py deleted file mode 100644 index 35d23b534..000000000 --- a/artiq/compiler/math_fns.py +++ /dev/null @@ -1,132 +0,0 @@ -r""" -The :mod:`math_fns` module lists math-related functions from NumPy recognized -by the ARTIQ compiler so host function objects can be :func:`match`\ ed to -the compiler type metadata describing their core device analogue. -""" - -from collections import OrderedDict -import numpy -from . import builtins, types - -# Some special mathematical functions are exposed via their scipy.special -# equivalents. Since the rest of the ARTIQ core does not depend on SciPy, -# gracefully handle it not being present, making the functions simply not -# available. -try: - import scipy.special as scipy_special -except ImportError: - scipy_special = None - -#: float -> float numpy.* math functions for which llvm.* intrinsics exist. -unary_fp_intrinsics = [(name, "llvm." + name + ".f64") for name in [ - "sin", - "cos", - "exp", - "exp2", - "log", - "log10", - "log2", - "fabs", - "floor", - "ceil", - "trunc", - "sqrt", -]] + [ - # numpy.rint() seems to (NumPy 1.19.0, Python 3.8.5, Linux x86_64) - # implement round-to-even, but unfortunately, rust-lang/libm only - # provides round(), which always rounds away from zero. - # - # As there is no equivalent of the latter in NumPy (nor any other - # basic rounding function), expose round() as numpy.rint anyway, - # even if the rounding modes don't match up, so there is some way - # to do rounding on the core device. (numpy.round() has entirely - # different semantics; it rounds to a configurable number of - # decimals.) - ("rint", "llvm.round.f64"), -] - -#: float -> float numpy.* math functions lowered to runtime calls. -unary_fp_runtime_calls = [ - ("tan", "tan"), - ("arcsin", "asin"), - ("arccos", "acos"), - ("arctan", "atan"), - ("sinh", "sinh"), - ("cosh", "cosh"), - ("tanh", "tanh"), - ("arcsinh", "asinh"), - ("arccosh", "acosh"), - ("arctanh", "atanh"), - ("expm1", "expm1"), - ("cbrt", "cbrt"), -] - -#: float -> float numpy.* math functions lowered to runtime calls. -unary_fp_runtime_calls = [ - ("tan", "tan"), - ("arcsin", "asin"), - ("arccos", "acos"), - ("arctan", "atan"), - ("sinh", "sinh"), - ("cosh", "cosh"), - ("tanh", "tanh"), - ("arcsinh", "asinh"), - ("arccosh", "acosh"), - ("arctanh", "atanh"), - ("expm1", "expm1"), - ("cbrt", "cbrt"), -] - -scipy_special_unary_runtime_calls = [ - ("erf", "erf"), - ("erfc", "erfc"), - ("gamma", "tgamma"), - ("gammaln", "lgamma"), - ("j0", "j0"), - ("j1", "j1"), - ("y0", "y0"), - ("y1", "y1"), -] -# Not mapped: jv/yv, libm only supports integer orders. - -#: (float, float) -> float numpy.* math functions lowered to runtime calls. -binary_fp_runtime_calls = [ - ("arctan2", "atan2"), - ("copysign", "copysign"), - ("fmax", "fmax"), - ("fmin", "fmin"), - # ("ldexp", "ldexp"), # One argument is an int; would need a bit more plumbing. - ("hypot", "hypot"), - ("nextafter", "nextafter"), -] - -#: Array handling builtins (special treatment due to allocations). -numpy_builtins = ["transpose"] - - -def fp_runtime_type(name, arity): - args = [("arg{}".format(i), builtins.TFloat()) for i in range(arity)] - return types.TExternalFunction( - OrderedDict(args), - builtins.TFloat(), - name, - # errno isn't observable from ARTIQ Python. - flags={"nounwind", "nowrite"}, - broadcast_across_arrays=True) - - -math_fn_map = { - getattr(numpy, symbol): fp_runtime_type(mangle, arity=1) - for symbol, mangle in (unary_fp_intrinsics + unary_fp_runtime_calls) -} -for symbol, mangle in binary_fp_runtime_calls: - math_fn_map[getattr(numpy, symbol)] = fp_runtime_type(mangle, arity=2) -for name in numpy_builtins: - math_fn_map[getattr(numpy, name)] = types.TBuiltinFunction("numpy." + name) -if scipy_special is not None: - for symbol, mangle in scipy_special_unary_runtime_calls: - math_fn_map[getattr(scipy_special, symbol)] = fp_runtime_type(mangle, arity=1) - - -def match(obj): - return math_fn_map.get(obj, None) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py deleted file mode 100644 index d43404b20..000000000 --- a/artiq/compiler/module.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -The :class:`Module` class encapsulates a single Python module, -which corresponds to a single ARTIQ translation unit (one LLVM -bitcode file and one object file, unless LTO is used). -A :class:`Module` can be created from a typed AST. - -The :class:`Source` class parses a single source file or -string and infers types for it using a trivial :module:`prelude`. -""" - -import os -from pythonparser import source, diagnostic, parse_buffer -from . import prelude, types, transforms, analyses, validators - -class Source: - def __init__(self, source_buffer, engine=None): - if engine is None: - self.engine = diagnostic.Engine(all_errors_are_fatal=True) - else: - self.engine = engine - self.embedding_map = None - self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) - - asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, - prelude=prelude.globals()) - inferencer = transforms.Inferencer(engine=engine) - - self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) - self.typedtree = asttyped_rewriter.visit(self.parsetree) - self.globals = asttyped_rewriter.globals - inferencer.visit(self.typedtree) - - @classmethod - def from_string(cls, source_string, name="input.py", first_line=1, engine=None): - return cls(source.Buffer(source_string + "\n", name, first_line), engine=engine) - - @classmethod - def from_filename(cls, filename, engine=None): - with open(filename) as f: - return cls(source.Buffer(f.read(), filename, 1), engine=engine) - -class Module: - def __init__(self, src, ref_period=1e-6, attribute_writeback=True, remarks=False): - self.attribute_writeback = attribute_writeback - self.engine = src.engine - self.embedding_map = src.embedding_map - self.name = src.name - self.globals = src.globals - - int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine) - cast_monomorphizer = transforms.CastMonomorphizer(engine=self.engine) - inferencer = transforms.Inferencer(engine=self.engine) - monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) - escape_validator = validators.EscapeValidator(engine=self.engine) - iodelay_estimator = transforms.IODelayEstimator(engine=self.engine, - ref_period=ref_period) - constness_validator = validators.ConstnessValidator(engine=self.engine) - artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, - module_name=src.name, - ref_period=ref_period) - dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) - local_access_validator = validators.LocalAccessValidator(engine=self.engine) - local_demoter = transforms.LocalDemoter() - constant_hoister = transforms.ConstantHoister() - devirtualization = analyses.Devirtualization() - interleaver = transforms.Interleaver(engine=self.engine) - invariant_detection = analyses.InvariantDetection(engine=self.engine) - - int_monomorphizer.visit(src.typedtree) - cast_monomorphizer.visit(src.typedtree) - inferencer.visit(src.typedtree) - monomorphism_validator.visit(src.typedtree) - escape_validator.visit(src.typedtree) - iodelay_estimator.visit_fixpoint(src.typedtree) - constness_validator.visit(src.typedtree) - devirtualization.visit(src.typedtree) - self.artiq_ir = artiq_ir_generator.visit(src.typedtree) - artiq_ir_generator.annotate_calls(devirtualization) - dead_code_eliminator.process(self.artiq_ir) - interleaver.process(self.artiq_ir) - local_access_validator.process(self.artiq_ir) - local_demoter.process(self.artiq_ir) - constant_hoister.process(self.artiq_ir) - if remarks: - invariant_detection.process(self.artiq_ir) - - def build_llvm_ir(self, target): - """Compile the module to LLVM IR for the specified target.""" - llvm_ir_generator = transforms.LLVMIRGenerator( - engine=self.engine, module_name=self.name, target=target, - embedding_map=self.embedding_map) - return llvm_ir_generator.process(self.artiq_ir, - attribute_writeback=self.attribute_writeback) - - def __repr__(self): - printer = types.TypePrinter() - globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals] - return "" % (repr(self.name), ",\n ".join(globals)) diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py deleted file mode 100644 index 13f319650..000000000 --- a/artiq/compiler/prelude.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -The :mod:`prelude` module contains the initial global environment -in which ARTIQ kernels are evaluated. -""" - -from . import builtins - -def globals(): - return { - # Value constructors - "bool": builtins.fn_bool(), - "int": builtins.fn_int(), - "float": builtins.fn_float(), - "str": builtins.fn_str(), - "bytes": builtins.fn_bytes(), - "bytearray": builtins.fn_bytearray(), - "list": builtins.fn_list(), - "array": builtins.fn_array(), - "range": builtins.fn_range(), - "int32": builtins.fn_int32(), - "int64": builtins.fn_int64(), - - # Exception constructors - "Exception": builtins.fn_Exception(), - "IndexError": builtins.fn_IndexError(), - "ValueError": builtins.fn_ValueError(), - "ZeroDivisionError": builtins.fn_ZeroDivisionError(), - "RuntimeError": builtins.fn_RuntimeError(), - - # Built-in Python functions - "len": builtins.fn_len(), - "round": builtins.fn_round(), - "abs": builtins.fn_abs(), - "min": builtins.fn_min(), - "max": builtins.fn_max(), - "print": builtins.fn_print(), - - # ARTIQ decorators - "kernel": builtins.fn_kernel(), - "portable": builtins.fn_kernel(), - "rpc": builtins.fn_kernel(), - - # ARTIQ context managers - "parallel": builtins.obj_parallel(), - "interleave": builtins.obj_interleave(), - "sequential": builtins.obj_sequential(), - - # ARTIQ time management functions - "delay": builtins.fn_delay(), - "now_mu": builtins.fn_now_mu(), - "delay_mu": builtins.fn_delay_mu(), - "at_mu": builtins.fn_at_mu(), - - # ARTIQ utility functions - "rtio_log": builtins.fn_rtio_log(), - "core_log": builtins.fn_print(), - } diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py deleted file mode 100644 index e908ce38a..000000000 --- a/artiq/compiler/targets.py +++ /dev/null @@ -1,277 +0,0 @@ -import os, sys, tempfile, subprocess, io -from artiq.compiler import types, ir -from llvmlite import ir as ll, binding as llvm - -llvm.initialize() -llvm.initialize_all_targets() -llvm.initialize_all_asmprinters() - -class RunTool: - def __init__(self, pattern, **tempdata): - self._pattern = pattern - self._tempdata = tempdata - self._tempnames = {} - self._tempfiles = {} - - def __enter__(self): - for key, data in self._tempdata.items(): - if data is None: - fd, filename = tempfile.mkstemp() - os.close(fd) - self._tempnames[key] = filename - else: - with tempfile.NamedTemporaryFile(delete=False) as f: - f.write(data) - self._tempnames[key] = f.name - - cmdline = [] - for argument in self._pattern: - cmdline.append(argument.format(**self._tempnames)) - - # https://bugs.python.org/issue17023 - windows = os.name == "nt" - process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True, shell=windows) - stdout, stderr = process.communicate() - if process.returncode != 0: - raise Exception("{} invocation failed: {}". - format(cmdline[0], stderr)) - - self._tempfiles["__stdout__"] = io.StringIO(stdout) - for key in self._tempdata: - if self._tempdata[key] is None: - self._tempfiles[key] = open(self._tempnames[key], "rb") - return self._tempfiles - - def __exit__(self, exc_typ, exc_value, exc_trace): - for file in self._tempfiles.values(): - file.close() - for filename in self._tempnames.values(): - os.unlink(filename) - -def _dump(target, kind, suffix, content): - if target is not None: - print("====== {} DUMP ======".format(kind.upper()), file=sys.stderr) - content_value = content() - if isinstance(content_value, str): - content_value = bytes(content_value, 'utf-8') - if target == "": - file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) - else: - file = open(target + suffix, "wb") - file.write(content_value) - file.close() - print("{} dumped as {}".format(kind, file.name), file=sys.stderr) - -class Target: - """ - A description of the target environment where the binaries - generated by the ARTIQ compiler will be deployed. - - :var triple: (string) - LLVM target triple, e.g. ``"riscv32"`` - :var data_layout: (string) - LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"`` - :var features: (list of string) - LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]`` - :var print_function: (string) - Name of a formatted print functions (with the signature of ``printf``) - provided by the target, e.g. ``"printf"``. - :var now_pinning: (boolean) - Whether the target implements the now-pinning RTIO optimization. - """ - triple = "unknown" - data_layout = "" - features = [] - print_function = "printf" - now_pinning = True - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_addr2line = "llvm-addr2line" - tool_cxxfilt = "llvm-cxxfilt" - - def __init__(self): - self.llcontext = ll.Context() - - def target_machine(self): - lltarget = llvm.Target.from_triple(self.triple) - llmachine = lltarget.create_target_machine( - features=",".join(["+{}".format(f) for f in self.features]), - reloc="pic", codemodel="default") - llmachine.set_asm_verbosity(True) - return llmachine - - def optimize(self, llmodule): - llpassmgr = llvm.create_module_pass_manager() - - # Register our alias analysis passes. - llpassmgr.add_basic_alias_analysis_pass() - llpassmgr.add_type_based_alias_analysis_pass() - - # Start by cleaning up after our codegen and exposing as much - # information to LLVM as possible. - llpassmgr.add_constant_merge_pass() - llpassmgr.add_cfg_simplification_pass() - llpassmgr.add_instruction_combining_pass() - llpassmgr.add_sroa_pass() - llpassmgr.add_dead_code_elimination_pass() - llpassmgr.add_function_attrs_pass() - llpassmgr.add_global_optimizer_pass() - - # Now, actually optimize the code. - llpassmgr.add_function_inlining_pass(275) - llpassmgr.add_ipsccp_pass() - llpassmgr.add_instruction_combining_pass() - llpassmgr.add_gvn_pass() - llpassmgr.add_cfg_simplification_pass() - llpassmgr.add_licm_pass() - - # Clean up after optimizing. - llpassmgr.add_dead_arg_elimination_pass() - llpassmgr.add_global_dce_pass() - - llpassmgr.run(llmodule) - - def compile(self, module): - """Compile the module to a relocatable object for this target.""" - - if os.getenv("ARTIQ_DUMP_SIG"): - print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr) - print(module, file=sys.stderr) - - if os.getenv("ARTIQ_IR_NO_LOC") is not None: - ir.BasicBlock._dump_loc = False - - type_printer = types.TypePrinter() - _dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", ".txt", - lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir)) - - llmod = module.build_llvm_ir(self) - - try: - llparsedmod = llvm.parse_assembly(str(llmod)) - llparsedmod.verify() - except RuntimeError: - _dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod)) - raise - - _dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", "_unopt.ll", - lambda: str(llparsedmod)) - - self.optimize(llparsedmod) - - _dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", ".ll", - lambda: str(llparsedmod)) - - return llparsedmod - - def assemble(self, llmodule): - llmachine = self.target_machine() - - _dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s", - lambda: llmachine.emit_assembly(llmodule)) - - _dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o", - lambda: llmachine.emit_object(llmodule)) - - return llmachine.emit_object(llmodule) - - def link(self, objects): - """Link the relocatable objects into a shared library for this target.""" - with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] + - ["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] + - ["{{obj{}}}".format(index) for index in range(len(objects))] + - ["-x"] + - ["-o", "{output}"], - output=None, - **{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \ - as results: - library = results["output"].read() - - _dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf", - lambda: library) - - return library - - def compile_and_link(self, modules): - return self.link([self.assemble(self.compile(module)) for module in modules]) - - def strip(self, library): - with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"], - library=library, output=None) \ - as results: - return results["output"].read() - - def symbolize(self, library, addresses): - if addresses == []: - return [] - - # We got a list of return addresses, i.e. addresses of instructions - # just after the call. Offset them back to get an address somewhere - # inside the call instruction (or its delay slot), since that's what - # the backtrace entry should point at. - offset_addresses = [hex(addr - 1) for addr in addresses] - with RunTool([self.tool_addr2line, "--addresses", "--functions", "--inlines", - "--demangle", "--exe={library}"] + offset_addresses, - library=library) \ - as results: - lines = iter(results["__stdout__"].read().rstrip().split("\n")) - backtrace = [] - while True: - try: - address_or_function = next(lines) - except StopIteration: - break - if address_or_function[:2] == "0x": - address = int(address_or_function[2:], 16) + 1 # remove offset - function = next(lines) - else: - address = backtrace[-1][4] # inlined - function = address_or_function - location = next(lines) - - filename, line = location.rsplit(":", 1) - if filename == "??" or filename == "": - continue - if line == "?": - line = -1 - else: - line = int(line) - # can't get column out of addr2line D: - backtrace.append((filename, line, -1, function, address)) - return backtrace - - def demangle(self, names): - with RunTool([self.tool_cxxfilt] + names) as results: - return results["__stdout__"].read().rstrip().split("\n") - -class NativeTarget(Target): - def __init__(self): - super().__init__() - self.triple = llvm.get_default_triple() - host_data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data) - -class RISCVTarget(Target): - triple = "riscv32-unknown-linux" - data_layout = "e-m:e-p:32:32-i64:64-n32-S128" - features = ["m", "a"] - print_function = "core_log" - now_pinning = True - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_addr2line = "llvm-addr2line" - tool_cxxfilt = "llvm-cxxfilt" - -class CortexA9Target(Target): - triple = "armv7-unknown-linux-gnueabihf" - data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" - features = ["dsp", "fp16", "neon", "vfp3"] - print_function = "core_log" - now_pinning = False - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_addr2line = "llvm-addr2line" - tool_cxxfilt = "llvm-cxxfilt" diff --git a/artiq/compiler/testbench/__init__.py b/artiq/compiler/testbench/__init__.py deleted file mode 100644 index 68ea51d7b..000000000 --- a/artiq/compiler/testbench/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -import time, cProfile as profile, pstats - -def benchmark(f, name): - profiler = profile.Profile() - profiler.enable() - - start = time.perf_counter() - end = 0 - runs = 0 - while end - start < 5 or runs < 10: - f() - runs += 1 - end = time.perf_counter() - - profiler.create_stats() - - print("{} {} runs: {:.2f}s, {:.2f}ms/run".format( - runs, name, end - start, (end - start) / runs * 1000)) - - stats = pstats.Stats(profiler) - stats.strip_dirs().sort_stats('time').print_stats(10) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py deleted file mode 100644 index c637c4b3a..000000000 --- a/artiq/compiler/testbench/embedding.py +++ /dev/null @@ -1,46 +0,0 @@ -import sys, os, tokenize - -from artiq.master.databases import DeviceDB -from artiq.master.worker_db import DeviceManager - -import artiq.coredevice.core -from artiq.coredevice.core import Core, CompileError - -def _render_diagnostic(diagnostic, colored): - return "\n".join(diagnostic.render(only_line=True)) - -artiq.coredevice.core._render_diagnostic = _render_diagnostic - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - diag = True - else: - diag = False - - if len(sys.argv) > 1 and sys.argv[1] == "+compile": - del sys.argv[1] - compile_only = True - else: - compile_only = False - - ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") - dmgr = DeviceManager(DeviceDB(ddb_path)) - - with tokenize.open(sys.argv[1]) as f: - testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr} - exec(testcase_code, testcase_vars) - - try: - core = dmgr.get("core") - if compile_only: - core.compile(testcase_vars["entrypoint"], (), {}) - else: - core.run(testcase_vars["entrypoint"], (), {}) - except CompileError as error: - if not diag: - exit(1) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py deleted file mode 100644 index 659b3b052..000000000 --- a/artiq/compiler/testbench/inferencer.py +++ /dev/null @@ -1,103 +0,0 @@ -import sys, fileinput, os -from pythonparser import source, diagnostic, algorithm, parse_buffer -from .. import prelude, types -from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, CastMonomorphizer -from ..transforms import IODelayEstimator -from ..validators import ConstnessValidator - -class Printer(algorithm.Visitor): - """ - :class:`Printer` prints ``:`` and the node type after every typed node, - and ``->`` and the node type before the colon in a function definition. - - In almost all cases (except function definition) this does not result - in valid Python syntax. - - :ivar rewriter: (:class:`pythonparser.source.Rewriter`) rewriter instance - """ - - def __init__(self, buf): - self.rewriter = source.Rewriter(buf) - self.type_printer = types.TypePrinter() - - def rewrite(self): - return self.rewriter.rewrite() - - def visit_FunctionDefT(self, node): - super().generic_visit(node) - - self.rewriter.insert_before(node.colon_loc, - "->{}".format(self.type_printer.name(node.return_type))) - - def visit_ExceptHandlerT(self, node): - super().generic_visit(node) - - if node.name_loc: - self.rewriter.insert_after(node.name_loc, - ":{}".format(self.type_printer.name(node.name_type))) - - def visit_ForT(self, node): - super().generic_visit(node) - - if node.trip_count is not None and node.trip_interval is not None: - self.rewriter.insert_after(node.keyword_loc, - "[{} x {} mu]".format(node.trip_count.fold(), - node.trip_interval.fold())) - - def generic_visit(self, node): - super().generic_visit(node) - - if hasattr(node, "type"): - self.rewriter.insert_after(node.loc, - ":{}".format(self.type_printer.name(node.type))) - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+mono": - del sys.argv[1] - monomorphize = True - else: - monomorphize = False - - if len(sys.argv) > 1 and sys.argv[1] == "+iodelay": - del sys.argv[1] - iodelay = True - else: - iodelay = False - - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - def process_diagnostic(diag): - print("\n".join(diag.render(only_line=True))) - if diag.level == "fatal": - exit() - else: - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - buf = source.Buffer("".join(fileinput.input()).expandtabs(), - os.path.basename(fileinput.filename())) - parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed) - Inferencer(engine=engine).visit(typed) - ConstnessValidator(engine=engine).visit(typed) - if monomorphize: - CastMonomorphizer(engine=engine).visit(typed) - IntMonomorphizer(engine=engine).visit(typed) - Inferencer(engine=engine).visit(typed) - if iodelay: - IODelayEstimator(engine=engine, ref_period=1e6).visit_fixpoint(typed) - - printer = Printer(buf) - printer.visit(typed) - for comment in comments: - if comment.text.find("CHECK") >= 0: - printer.rewriter.remove(comment.loc) - print(printer.rewrite().source) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/irgen.py b/artiq/compiler/testbench/irgen.py deleted file mode 100644 index 275f11c94..000000000 --- a/artiq/compiler/testbench/irgen.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys, os, fileinput -from pythonparser import diagnostic -from .. import ir -from ..module import Module, Source - -def main(): - if os.getenv("ARTIQ_IR_NO_LOC") is not None: - ir.BasicBlock._dump_loc = False - - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - for fn in mod.artiq_ir: - print(fn) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py deleted file mode 100644 index 67a43c008..000000000 --- a/artiq/compiler/testbench/jit.py +++ /dev/null @@ -1,35 +0,0 @@ -import os, sys, fileinput, ctypes -from pythonparser import diagnostic -from llvmlite import binding as llvm -from ..module import Module, Source -from ..targets import NativeTarget - -def main(): - libartiq_support = os.getenv("LIBARTIQ_SUPPORT") - if libartiq_support is not None: - llvm.load_library_permanently(libartiq_support) - - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - source = "".join(fileinput.input()) - source = source.replace("#ARTIQ#", "") - mod = Module(Source.from_string(source.expandtabs(), engine=engine)) - - target = NativeTarget() - llmod = mod.build_llvm_ir(target) - llparsedmod = llvm.parse_assembly(str(llmod)) - llparsedmod.verify() - - llmachine = llvm.Target.from_triple(target.triple).create_target_machine() - lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) - llmain = lljit.get_function_address(llmod.name + ".__modinit__") - ctypes.CFUNCTYPE(None)(llmain)() - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py deleted file mode 100644 index 6bcf031c9..000000000 --- a/artiq/compiler/testbench/llvmgen.py +++ /dev/null @@ -1,31 +0,0 @@ -import sys, fileinput -from pythonparser import diagnostic -from llvmlite import ir as ll -from ..module import Module, Source -from ..targets import NativeTarget - -def main(): - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - - target = NativeTarget() - llmod = mod.build_llvm_ir(target=target) - - # Add main so that the result can be executed with lli - llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main") - llbuilder = ll.IRBuilder(llmain.append_basic_block("entry")) - llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [ - ll.Constant(ll.IntType(8).as_pointer(), None)]) - llbuilder.ret_void() - - print(llmod) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py deleted file mode 100644 index 363c88840..000000000 --- a/artiq/compiler/testbench/perf.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys, os -from pythonparser import diagnostic -from ..module import Module, Source -from ..targets import RISCVTarget -from . import benchmark - -def main(): - if not len(sys.argv) == 2: - print("Expected exactly one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - # Make sure everything's valid - filename = sys.argv[1] - with open(filename) as f: - code = f.read() - source = Source.from_string(code, filename, engine=engine) - module = Module(source) - - benchmark(lambda: Source.from_string(code, filename), - "ARTIQ parsing and inference") - - benchmark(lambda: Module(source), - "ARTIQ transforms and validators") - - benchmark(lambda: RISCVTarget().compile_and_link([module]), - "LLVM optimization and linking") - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/perf_embedding.py b/artiq/compiler/testbench/perf_embedding.py deleted file mode 100644 index d626d5534..000000000 --- a/artiq/compiler/testbench/perf_embedding.py +++ /dev/null @@ -1,72 +0,0 @@ -import sys, os, tokenize -from pythonparser import diagnostic -from ...language.environment import ProcessArgumentManager -from ...master.databases import DeviceDB, DatasetDB -from ...master.worker_db import DeviceManager, DatasetManager -from ..module import Module -from ..embedding import Stitcher -from ..targets import RISCVTarget -from . import benchmark - - -def main(): - if not len(sys.argv) == 2: - print("Expected exactly one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - with tokenize.open(sys.argv[1]) as f: - testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {'__name__': 'testbench'} - exec(testcase_code, testcase_vars) - - device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") - device_mgr = DeviceManager(DeviceDB(device_db_path)) - - dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.pyon") - dataset_mgr = DatasetManager(DatasetDB(dataset_db_path)) - - argument_mgr = ProcessArgumentManager({}) - - def embed(): - experiment = testcase_vars["Benchmark"]((device_mgr, dataset_mgr, argument_mgr)) - - stitcher = Stitcher(core=experiment.core, dmgr=device_mgr) - stitcher.stitch_call(experiment.run, (), {}) - stitcher.finalize() - return stitcher - - stitcher = embed() - module = Module(stitcher) - target = RISCVTarget() - llvm_ir = target.compile(module) - elf_obj = target.assemble(llvm_ir) - elf_shlib = target.link([elf_obj]) - - benchmark(lambda: embed(), - "ARTIQ embedding") - - benchmark(lambda: Module(stitcher), - "ARTIQ transforms and validators") - - benchmark(lambda: target.compile(module), - "LLVM optimizations") - - benchmark(lambda: target.assemble(llvm_ir), - "LLVM machine code emission") - - benchmark(lambda: target.link([elf_obj]), - "Linking") - - benchmark(lambda: target.strip(elf_shlib), - "Stripping debug information") - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/shlib.py b/artiq/compiler/testbench/shlib.py deleted file mode 100644 index 0aa6386d3..000000000 --- a/artiq/compiler/testbench/shlib.py +++ /dev/null @@ -1,30 +0,0 @@ -import sys, os -from pythonparser import diagnostic -from ..module import Module, Source -from ..targets import RISCVTarget - -def main(): - if not len(sys.argv) > 1: - print("Expected at least one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - modules = [] - for filename in sys.argv[1:]: - modules.append(Module(Source.from_filename(filename, engine=engine))) - - llobj = RISCVTarget().compile_and_link(modules) - - basename, ext = os.path.splitext(sys.argv[-1]) - with open(basename + ".so", "wb") as f: - f.write(llobj) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/signature.py b/artiq/compiler/testbench/signature.py deleted file mode 100644 index 5d3ff1aa4..000000000 --- a/artiq/compiler/testbench/signature.py +++ /dev/null @@ -1,44 +0,0 @@ -import sys, fileinput -from pythonparser import diagnostic -from ..module import Module, Source -from .. import types, iodelay - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - diag = True - def process_diagnostic(diag): - print("\n".join(diag.render(only_line=True))) - if diag.level == "fatal": - exit() - else: - diag = False - def process_diagnostic(diag): - print("\n".join(diag.render(colored=False))) - if diag.level in ("fatal", "error"): - exit(1) - - if len(sys.argv) > 1 and sys.argv[1] == "+delay": - del sys.argv[1] - force_delays = True - else: - force_delays = False - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - try: - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - - if force_delays: - for var in mod.globals: - typ = mod.globals[var].find() - if types.is_function(typ) and types.is_indeterminate_delay(typ.delay): - process_diagnostic(typ.delay.find().cause) - - print(repr(mod)) - except: - if not diag: raise - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py deleted file mode 100644 index 5696be95f..000000000 --- a/artiq/compiler/transforms/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .asttyped_rewriter import ASTTypedRewriter -from .inferencer import Inferencer -from .int_monomorphizer import IntMonomorphizer -from .cast_monomorphizer import CastMonomorphizer -from .iodelay_estimator import IODelayEstimator -from .artiq_ir_generator import ARTIQIRGenerator -from .dead_code_eliminator import DeadCodeEliminator -from .local_demoter import LocalDemoter -from .constant_hoister import ConstantHoister -from .interleaver import Interleaver -from .typedtree_printer import TypedtreePrinter -from .llvm_ir_generator import LLVMIRGenerator diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py deleted file mode 100644 index 4520ec049..000000000 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ /dev/null @@ -1,2708 +0,0 @@ -""" -:class:`ARTIQIRGenerator` transforms typed AST into ARTIQ intermediate -representation. ARTIQ IR is designed to be low-level enough that -its operations are elementary--contain no internal branching-- -but without too much detail, such as exposing the reference/value -semantics explicitly. -""" - -from collections import OrderedDict, defaultdict -from functools import reduce -from pythonparser import algorithm, diagnostic, ast -from .. import types, builtins, asttyped, ir, iodelay - -def _readable_name(insn): - if isinstance(insn, ir.Constant): - return str(insn.value) - else: - return insn.name - -def _extract_loc(node): - if "keyword_loc" in node._locs: - return node.keyword_loc - else: - return node.loc - -# We put some effort in keeping generated IR readable, -# i.e. with a more or less linear correspondence to the source. -# This is why basic blocks sometimes seem to be produced in an odd order. -class ARTIQIRGenerator(algorithm.Visitor): - """ - :class:`ARTIQIRGenerator` contains a lot of internal state, - which is effectively maintained in a stack--with push/pop - pairs around any state updates. It is comprised of following: - - :ivar current_loc: (:class:`pythonparser.source.Range`) - source range of the node being currently visited - :ivar current_function: (:class:`ir.Function` or None) - module, def or lambda currently being translated - :ivar current_globals: (set of string) - set of variables that will be resolved in global scope - :ivar current_block: (:class:`ir.BasicBlock`) - basic block to which any new instruction will be appended - :ivar current_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) - the chained function environment, containing variables that - can become upvalues - :ivar current_private_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) - the private function environment, containing internal state - :ivar current_args: (dict of string to :class:`ir.Argument`) - the map of Python names of formal arguments to - the current function to their SSA names - :ivar current_assign: (:class:`ir.Value` or None) - the right-hand side of current assignment statement, or - a component of a composite right-hand side when visiting - a composite left-hand side, such as, in ``x, y = z``, - the 2nd tuple element when visting ``y`` - :ivar break_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``break`` will transfer control - :ivar continue_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``continue`` will transfer control - :ivar return_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``return`` will transfer control - :ivar unwind_target: (:class:`ir.BasicBlock` or None) - the basic block to which unwinding will transfer control - :ivar final_branch: (function (target: :class:`ir.BasicBlock`, block: :class:`ir.BasicBlock) - or None) - the function that appends to ``block`` a jump through the ``finally`` statement - to ``target`` - - There is, additionally, some global state that is used to translate - the results of analyses on AST level to IR level: - - :ivar function_map: (map of :class:`ast.FunctionDefT` to :class:`ir.Function`) - the map from function definition nodes to IR functions - :ivar variable_map: (map of :class:`ast.NameT` to :class:`ir.GetLocal`) - the map from variable name nodes to instructions retrieving - the variable values - :ivar method_map: (map of :class:`ast.AttributeT` to :class:`ir.GetAttribute`) - the map from method resolution nodes to instructions retrieving - the called function inside a translated :class:`ast.CallT` node - - Finally, functions that implement array operations are instantiated on the fly as - necessary. They are kept track of in global dictionaries, with a mangled name - containing types and operations as key: - - :ivar array_op_funcs: the map from mangled name to implementation of - operations on/between arrays - """ - - _size_type = builtins.TInt32() - - def __init__(self, module_name, engine, ref_period): - self.engine = engine - self.functions = [] - self.name = [module_name] if module_name != "" else [] - self.ref_period = ir.Constant(ref_period, builtins.TFloat()) - self.current_loc = None - self.current_function = None - self.current_class = None - self.current_globals = set() - self.current_block = None - self.current_env = None - self.current_private_env = None - self.current_args = None - self.current_assign = None - self.break_target = None - self.continue_target = None - self.return_target = None - self.unwind_target = None - self.final_branch = None - self.function_map = dict() - self.variable_map = dict() - self.method_map = defaultdict(lambda: []) - self.array_op_funcs = dict() - self.raise_assert_func = None - - def annotate_calls(self, devirtualization): - for var_node in devirtualization.variable_map: - callee_node = devirtualization.variable_map[var_node] - if callee_node is None: - continue - callee = self.function_map[callee_node] - - call_target = self.variable_map[var_node] - for use in call_target.uses: - if isinstance(use, (ir.Call, ir.Invoke)) and \ - use.target_function() == call_target: - use.static_target_function = callee - - for type_and_method in devirtualization.method_map: - callee_node = devirtualization.method_map[type_and_method] - if callee_node is None: - continue - callee = self.function_map[callee_node] - - for call in self.method_map[type_and_method]: - assert isinstance(call, (ir.Call, ir.Invoke)) - call.static_target_function = callee - - def add_block(self, name=""): - block = ir.BasicBlock([], name) - self.current_function.add(block) - return block - - def append(self, insn, block=None, loc=None): - if loc is None: - loc = self.current_loc - if block is None: - block = self.current_block - - if insn.loc is None: - insn.loc = loc - return block.append(insn) - - def terminate(self, insn): - if not self.current_block.is_terminated(): - self.append(insn) - else: - insn.drop_references() - - def warn_unreachable(self, node): - diag = diagnostic.Diagnostic("warning", - "unreachable code", {}, - node.loc.begin()) - self.engine.process(diag) - - # Visitors - - def visit(self, obj): - if isinstance(obj, list): - for elt in obj: - if self.current_block.is_terminated(): - self.warn_unreachable(elt) - break - self.visit(elt) - elif isinstance(obj, ast.AST): - try: - old_loc, self.current_loc = self.current_loc, _extract_loc(obj) - return self._visit_one(obj) - finally: - self.current_loc = old_loc - - # Module visitor - - def visit_ModuleT(self, node): - # Treat start of module as synthesized - self.current_loc = None - - try: - typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) - func = ir.Function(typ, ".".join(self.name + ['__modinit__']), [], - loc=node.loc.begin()) - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - env_type = ir.TEnvironment(name=func.name, vars=node.typing_env) - env = self.append(ir.Alloc([], env_type, name="env")) - old_env, self.current_env = self.current_env, env - - priv_env_type = ir.TEnvironment(name=func.name + ".priv", vars={ "$return": typ.ret }) - priv_env = self.append(ir.Alloc([], priv_env_type, name="privenv")) - old_priv_env, self.current_private_env = self.current_private_env, priv_env - - self.generic_visit(node) - self.terminate(ir.Return(ir.Constant(None, builtins.TNone()))) - - return self.functions - finally: - self.current_function = old_func - self.current_block = old_block - self.current_env = old_env - self.current_private_env = old_priv_env - - # Statement visitors - - def visit_ClassDefT(self, node): - klass = self.append(ir.Alloc([], node.constructor_type, - name="class.{}".format(node.name))) - self._set_local(node.name, klass) - - try: - old_class, self.current_class = self.current_class, klass - self.visit(node.body) - finally: - self.current_class = old_class - - def visit_function(self, node, is_lambda=False, is_internal=False, is_quoted=False, - flags={}): - if is_lambda: - name = "lambda@{}:{}".format(node.loc.line(), node.loc.column()) - typ = node.type.find() - else: - name = node.name - typ = node.signature_type.find() - - try: - defaults = [] - if not is_quoted: - for arg_name, default_node in zip(typ.optargs, node.args.defaults): - default = self.visit(default_node) - env_default_name = \ - self.current_env.type.add("$default." + arg_name, default.type) - self.append(ir.SetLocal(self.current_env, env_default_name, default)) - def codegen_default(env_default_name): - return lambda: self.append(ir.GetLocal(self.current_env, env_default_name)) - defaults.append(codegen_default(env_default_name)) - else: - for default_node in node.args.defaults: - def codegen_default(default_node): - return lambda: self.visit(default_node) - defaults.append(codegen_default(default_node)) - - old_name, self.name = self.name, self.name + [name] - - env_arg = ir.EnvironmentArgument(self.current_env.type, "ARG.ENV") - - old_args, self.current_args = self.current_args, {} - - args = [] - for arg_name in typ.args: - arg = ir.Argument(typ.args[arg_name], "ARG." + arg_name) - self.current_args[arg_name] = arg - args.append(arg) - - optargs = [] - for arg_name in typ.optargs: - arg = ir.Argument(ir.TOption(typ.optargs[arg_name]), "ARG." + arg_name) - self.current_args[arg_name] = arg - optargs.append(arg) - - for (arg, arg_node) in zip(args + optargs, node.args.args): - arg.loc = arg_node.loc - - func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs, - loc=node.lambda_loc if is_lambda else node.keyword_loc) - func.is_internal = is_internal - func.flags = flags - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - if not is_lambda: - self.function_map[node] = func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_globals, self.current_globals = self.current_globals, node.globals_in_scope - - env_without_globals = \ - {var: node.typing_env[var] - for var in node.typing_env - if var not in node.globals_in_scope} - env_type = ir.TEnvironment(name=func.name, - vars=env_without_globals, outer=self.current_env.type) - env = self.append(ir.Alloc([], env_type, name="ENV")) - old_env, self.current_env = self.current_env, env - - if not is_lambda: - priv_env_type = ir.TEnvironment(name="{}.private".format(func.name), - vars={ "$return": typ.ret }) - priv_env = self.append(ir.Alloc([], priv_env_type, name="PRV")) - old_priv_env, self.current_private_env = self.current_private_env, priv_env - - self.append(ir.SetLocal(env, "$outer", env_arg)) - for index, arg_name in enumerate(typ.args): - self.append(ir.SetLocal(env, arg_name, args[index])) - for index, (arg_name, codegen_default) in enumerate(zip(typ.optargs, defaults)): - default = codegen_default() - value = self.append(ir.Builtin("unwrap_or", [optargs[index], default], - typ.optargs[arg_name], - name="DEF.{}".format(arg_name))) - self.append(ir.SetLocal(env, arg_name, value)) - - result = self.visit(node.body) - - if is_lambda: - self.terminate(ir.Return(result)) - elif builtins.is_none(typ.ret): - if not self.current_block.is_terminated(): - self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone()))) - else: - if not self.current_block.is_terminated(): - if len(self.current_block.predecessors()) != 0: - diag = diagnostic.Diagnostic("error", - "this function must return a value of type {typ} explicitly", - {"typ": types.TypePrinter().name(typ.ret)}, - node.keyword_loc) - self.engine.process(diag) - - self.current_block.append(ir.Unreachable()) - - finally: - self.name = old_name - self.current_args = old_args - self.current_function = old_func - self.current_block = old_block - self.current_globals = old_globals - self.current_env = old_env - if not is_lambda: - self.current_private_env = old_priv_env - - return self.append(ir.Closure(func, self.current_env)) - - def visit_FunctionDefT(self, node): - func = self.visit_function(node, is_internal=len(self.name) > 0) - if self.current_class is None: - self._set_local(node.name, func) - else: - self.append(ir.SetAttr(self.current_class, node.name, func)) - - def visit_QuotedFunctionDefT(self, node): - self.visit_function(node, is_internal=True, is_quoted=True, flags=node.flags) - - def visit_Return(self, node): - if node.value is None: - return_value = ir.Constant(None, builtins.TNone()) - else: - return_value = self.visit(node.value) - - if self.return_target is None: - self.append(ir.Return(return_value)) - else: - self.append(ir.SetLocal(self.current_private_env, "$return", return_value)) - self.append(ir.Branch(self.return_target)) - - def visit_Expr(self, node): - # Ignore the value, do it for side effects. - result = self.visit(node.value) - - # See comment in visit_Pass. - if isinstance(result, ir.Constant): - self.visit_Pass(node) - - def visit_Pass(self, node): - # Insert a dummy instruction so that analyses which extract - # locations from CFG have something to use. - self.append(ir.Builtin("nop", [], builtins.TNone())) - - def visit_Assign(self, node): - try: - self.current_assign = self.visit(node.value) - assert self.current_assign is not None - for target in node.targets: - self.visit(target) - finally: - self.current_assign = None - - def visit_AugAssign(self, node): - lhs = self.visit(node.target) - rhs = self.visit(node.value) - - if builtins.is_array(lhs.type): - name = type(node.op).__name__ - def make_op(l, r): - return self.append(ir.Arith(node.op, l, r)) - self._broadcast_binop(name, make_op, lhs.type, lhs, rhs, assign_to_lhs=True) - return - - value = self.append(ir.Arith(node.op, lhs, rhs)) - try: - self.current_assign = value - self.visit(node.target) - finally: - self.current_assign = None - - def coerce_to_bool(self, insn, block=None): - if builtins.is_bool(insn.type): - return insn - elif builtins.is_int(insn.type): - return self.append(ir.Compare(ast.NotEq(loc=None), insn, ir.Constant(0, insn.type)), - block=block) - elif builtins.is_float(insn.type): - return self.append(ir.Compare(ast.NotEq(loc=None), insn, ir.Constant(0, insn.type)), - block=block) - elif builtins.is_iterable(insn.type): - length = self.iterable_len(insn) - return self.append(ir.Compare(ast.NotEq(loc=None), length, ir.Constant(0, length.type)), - block=block) - elif builtins.is_none(insn.type): - return ir.Constant(False, builtins.TBool()) - else: - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(insn.type)}, - insn.loc) - diag = diagnostic.Diagnostic("warning", - "this expression, which is always truthful, is coerced to bool", {}, - insn.loc, notes=[note]) - self.engine.process(diag) - return ir.Constant(True, builtins.TBool()) - - def visit_If(self, node): - cond = self.visit(node.test) - cond = self.coerce_to_bool(cond) - head = self.current_block - - if_true = self.add_block("if.body") - self.current_block = if_true - self.visit(node.body) - post_if_true = self.current_block - - if any(node.orelse): - if_false = self.add_block("if.else") - self.current_block = if_false - self.visit(node.orelse) - post_if_false = self.current_block - - tail = self.add_block("if.tail") - self.current_block = tail - if not post_if_true.is_terminated(): - post_if_true.append(ir.Branch(tail)) - - if any(node.orelse): - if not post_if_false.is_terminated(): - post_if_false.append(ir.Branch(tail)) - self.append(ir.BranchIf(cond, if_true, if_false), block=head) - else: - self.append(ir.BranchIf(cond, if_true, tail), block=head) - - def visit_While(self, node): - try: - head = self.add_block("while.head") - self.append(ir.Branch(head)) - self.current_block = head - old_continue, self.continue_target = self.continue_target, head - cond = self.visit(node.test) - cond = self.coerce_to_bool(cond) - post_head = self.current_block - - break_block = self.add_block("while.break") - old_break, self.break_target = self.break_target, break_block - - body = self.add_block("while.body") - self.current_block = body - self.visit(node.body) - post_body = self.current_block - finally: - self.break_target = old_break - self.continue_target = old_continue - - if any(node.orelse): - else_tail = self.add_block("while.else") - self.current_block = else_tail - self.visit(node.orelse) - post_else_tail = self.current_block - - tail = self.add_block("while.tail") - self.current_block = tail - - if any(node.orelse): - if not post_else_tail.is_terminated(): - post_else_tail.append(ir.Branch(tail)) - else: - else_tail = tail - - post_head.append(ir.BranchIf(cond, body, else_tail)) - if not post_body.is_terminated(): - post_body.append(ir.Branch(head)) - break_block.append(ir.Branch(tail)) - - def iterable_len(self, value, typ=_size_type): - if builtins.is_listish(value.type): - if isinstance(value, ir.Constant): - name = None - else: - name = "{}.len".format(value.name) - len = self.append(ir.Builtin("len", [value], builtins.TInt32(), - name=name)) - return self.append(ir.Coerce(len, typ)) - elif builtins.is_range(value.type): - start = self.append(ir.GetAttr(value, "start")) - stop = self.append(ir.GetAttr(value, "stop")) - step = self.append(ir.GetAttr(value, "step")) - spread = self.append(ir.Arith(ast.Sub(loc=None), stop, start)) - return self.append(ir.Arith(ast.FloorDiv(loc=None), spread, step, - name="{}.len".format(value.name))) - else: - assert False - - def iterable_get(self, value, index): - # Assuming the value is within bounds. - if builtins.is_array(value.type): - # Scalar indexing into ndarray. - num_dims = value.type.find()["num_dims"].value - if num_dims > 1: - old_shape = self.append(ir.GetAttr(value, "shape")) - lengths = [self.append(ir.GetAttr(old_shape, i)) for i in range(1, num_dims)] - new_shape = self._make_array_shape(lengths) - - stride = reduce( - lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], lengths[0]) - offset = self.append(ir.Arith(ast.Mult(loc=None), stride, index)) - old_buffer = self.append(ir.GetAttr(value, "buffer")) - new_buffer = self.append(ir.Offset(old_buffer, offset)) - - result_type = builtins.TArray(value.type.find()["elt"], - types.TValue(num_dims - 1)) - return self.append(ir.Alloc([new_buffer, new_shape], result_type)) - else: - buffer = self.append(ir.GetAttr(value, "buffer")) - return self.append(ir.GetElem(buffer, index)) - elif builtins.is_listish(value.type): - return self.append(ir.GetElem(value, index)) - elif builtins.is_range(value.type): - start = self.append(ir.GetAttr(value, "start")) - step = self.append(ir.GetAttr(value, "step")) - offset = self.append(ir.Arith(ast.Mult(loc=None), step, index)) - return self.append(ir.Arith(ast.Add(loc=None), start, offset)) - else: - assert False - - def visit_ForT(self, node): - try: - iterable = self.visit(node.iter) - length = self.iterable_len(iterable) - prehead = self.current_block - - head = self.add_block("for.head") - self.append(ir.Branch(head)) - self.current_block = head - phi = self.append(ir.Phi(length.type, name="IND")) - phi.add_incoming(ir.Constant(0, phi.type), prehead) - cond = self.append(ir.Compare(ast.Lt(loc=None), phi, length, name="CMP")) - - break_block = self.add_block("for.break") - old_break, self.break_target = self.break_target, break_block - - continue_block = self.add_block("for.continue") - old_continue, self.continue_target = self.continue_target, continue_block - self.current_block = continue_block - - updated_index = self.append(ir.Arith(ast.Add(loc=None), phi, ir.Constant(1, phi.type), - name="IND.new")) - phi.add_incoming(updated_index, continue_block) - self.append(ir.Branch(head)) - - body = self.add_block("for.body") - self.current_block = body - elt = self.iterable_get(iterable, phi) - try: - self.current_assign = elt - self.visit(node.target) - finally: - self.current_assign = None - self.visit(node.body) - post_body = self.current_block - finally: - self.break_target = old_break - self.continue_target = old_continue - - if any(node.orelse): - else_tail = self.add_block("for.else") - self.current_block = else_tail - self.visit(node.orelse) - post_else_tail = self.current_block - - tail = self.add_block("for.tail") - self.current_block = tail - - if any(node.orelse): - if not post_else_tail.is_terminated(): - post_else_tail.append(ir.Branch(tail)) - else: - else_tail = tail - - if node.trip_count is not None: - head.append(ir.Loop(node.trip_count, phi, cond, body, else_tail)) - else: - head.append(ir.BranchIf(cond, body, else_tail)) - if not post_body.is_terminated(): - post_body.append(ir.Branch(continue_block)) - break_block.append(ir.Branch(tail)) - - def visit_Break(self, node): - self.append(ir.Branch(self.break_target)) - - def visit_Continue(self, node): - self.append(ir.Branch(self.continue_target)) - - def raise_exn(self, exn=None, loc=None): - if self.final_branch is not None: - raise_proxy = self.add_block("try.raise") - self.final_branch(raise_proxy, self.current_block) - self.current_block = raise_proxy - - if exn is not None: - assert loc is not None - loc_file = ir.Constant(loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(loc.line(), builtins.TInt32()) - loc_column = ir.Constant(loc.column(), builtins.TInt32()) - loc_function = ir.Constant(".".join(self.name), builtins.TStr()) - - self.append(ir.SetAttr(exn, "__file__", loc_file)) - self.append(ir.SetAttr(exn, "__line__", loc_line)) - self.append(ir.SetAttr(exn, "__col__", loc_column)) - self.append(ir.SetAttr(exn, "__func__", loc_function)) - - if self.unwind_target is not None: - self.append(ir.Raise(exn, self.unwind_target)) - else: - self.append(ir.Raise(exn)) - else: - if self.unwind_target is not None: - self.append(ir.Reraise(self.unwind_target)) - else: - self.append(ir.Reraise()) - - def visit_Raise(self, node): - if node.exc is not None and types.is_exn_constructor(node.exc.type): - self.raise_exn(self.alloc_exn(node.exc.type.instance), loc=self.current_loc) - else: - self.raise_exn(self.visit(node.exc), loc=self.current_loc) - - def visit_Try(self, node): - dispatcher = self.add_block("try.dispatch") - - if any(node.finalbody): - # k for continuation - final_suffix = ".try@{}:{}".format(node.loc.line(), node.loc.column()) - final_env_type = ir.TEnvironment(name=self.current_function.name + final_suffix, - vars={ "$cont": ir.TBasicBlock() }) - final_state = self.append(ir.Alloc([], final_env_type)) - final_targets = [] - final_paths = [] - - def final_branch(target, block): - block.append(ir.SetLocal(final_state, "$cont", target)) - final_targets.append(target) - final_paths.append(block) - - if self.break_target is not None: - break_proxy = self.add_block("try.break") - old_break, self.break_target = self.break_target, break_proxy - final_branch(old_break, break_proxy) - - if self.continue_target is not None: - continue_proxy = self.add_block("try.continue") - old_continue, self.continue_target = self.continue_target, continue_proxy - final_branch(old_continue, continue_proxy) - - return_proxy = self.add_block("try.return") - old_return, self.return_target = self.return_target, return_proxy - if old_return is not None: - final_branch(old_return, return_proxy) - else: - return_action = self.add_block("try.doreturn") - value = return_action.append(ir.GetLocal(self.current_private_env, "$return")) - return_action.append(ir.Return(value)) - final_branch(return_action, return_proxy) - - body = self.add_block("try.body") - self.append(ir.Branch(body)) - self.current_block = body - - try: - old_unwind, self.unwind_target = self.unwind_target, dispatcher - self.visit(node.body) - finally: - self.unwind_target = old_unwind - - if not self.current_block.is_terminated(): - self.visit(node.orelse) - elif any(node.orelse): - self.warn_unreachable(node.orelse[0]) - body = self.current_block - - if any(node.finalbody): - if self.break_target: - self.break_target = old_break - if self.continue_target: - self.continue_target = old_continue - self.return_target = old_return - - old_final_branch, self.final_branch = self.final_branch, final_branch - - cleanup = self.add_block('handler.cleanup') - landingpad = dispatcher.append(ir.LandingPad(cleanup)) - - handlers = [] - for handler_node in node.handlers: - exn_type = handler_node.name_type.find() - if handler_node.filter is not None and \ - not builtins.is_exception(exn_type, 'Exception'): - handler = self.add_block("handler." + exn_type.name) - landingpad.add_clause(handler, exn_type) - else: - handler = self.add_block("handler.catchall") - landingpad.add_clause(handler, None) - - self.current_block = handler - if handler_node.name is not None: - exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type)) - self._set_local(handler_node.name, exn) - self.visit(handler_node.body) - post_handler = self.current_block - - handlers.append((handler, post_handler)) - - if any(node.finalbody): - # Finalize and continue after try statement. - self.final_branch = old_final_branch - - finalizer = self.add_block("finally") - self.current_block = finalizer - - self.visit(node.finalbody) - post_finalizer = self.current_block - - # Finalize and reraise. Separate from previous case to expose flow - # to LocalAccessValidator. - finalizer_reraise = self.add_block("finally.reraise") - self.current_block = finalizer_reraise - - self.visit(node.finalbody) - self.terminate(ir.Reraise(self.unwind_target)) - - self.current_block = tail = self.add_block("try.tail") - if any(node.finalbody): - final_targets.append(tail) - - for block in final_paths: - block.append(ir.Branch(finalizer)) - - if not body.is_terminated(): - body.append(ir.SetLocal(final_state, "$cont", tail)) - body.append(ir.Branch(finalizer)) - - cleanup.append(ir.Branch(finalizer_reraise)) - - for handler, post_handler in handlers: - if not post_handler.is_terminated(): - post_handler.append(ir.SetLocal(final_state, "$cont", tail)) - post_handler.append(ir.Branch(finalizer)) - - if not post_finalizer.is_terminated(): - dest = post_finalizer.append(ir.GetLocal(final_state, "$cont")) - post_finalizer.append(ir.IndirectBranch(dest, final_targets)) - else: - if not body.is_terminated(): - body.append(ir.Branch(tail)) - - cleanup.append(ir.Reraise(self.unwind_target)) - - for handler, post_handler in handlers: - if not post_handler.is_terminated(): - post_handler.append(ir.Branch(tail)) - - def _try_finally(self, body_gen, finally_gen, name): - dispatcher = self.add_block("{}.dispatch".format(name)) - - try: - old_unwind, self.unwind_target = self.unwind_target, dispatcher - body_gen() - finally: - self.unwind_target = old_unwind - - if not self.current_block.is_terminated(): - finally_gen() - - self.post_body = self.current_block - - self.current_block = self.add_block("{}.cleanup".format(name)) - dispatcher.append(ir.LandingPad(self.current_block)) - finally_gen() - self.raise_exn() - - self.current_block = self.post_body - - def visit_With(self, node): - context_expr_node = node.items[0].context_expr - optional_vars_node = node.items[0].optional_vars - - if types.is_builtin(context_expr_node.type, "sequential"): - self.visit(node.body) - return - elif types.is_builtin(context_expr_node.type, "interleave"): - interleave = self.append(ir.Interleave([])) - - heads, tails = [], [] - for stmt in node.body: - self.current_block = self.add_block("interleave.branch") - heads.append(self.current_block) - self.visit(stmt) - tails.append(self.current_block) - - for head in heads: - interleave.add_destination(head) - - self.current_block = self.add_block("interleave.tail") - for tail in tails: - if not tail.is_terminated(): - tail.append(ir.Branch(self.current_block)) - return - elif types.is_builtin(context_expr_node.type, "parallel"): - start_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt64())) - end_mu = start_mu - - for stmt in node.body: - self.append(ir.Builtin("at_mu", [start_mu], builtins.TNone())) - - block = self.add_block("parallel.branch") - if self.current_block.is_terminated(): - self.warn_unreachable(stmt[0]) - else: - self.append(ir.Branch(block)) - self.current_block = block - - self.visit(stmt) - - mid_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt64())) - gt_mu = self.append(ir.Compare(ast.Gt(loc=None), mid_mu, end_mu)) - end_mu = self.append(ir.Select(gt_mu, mid_mu, end_mu)) - - self.append(ir.Builtin("at_mu", [end_mu], builtins.TNone())) - return - - cleanup = [] - for item_node in node.items: - # user-defined context manager - context_expr_node = item_node.context_expr - optional_vars_node = item_node.optional_vars - context_mgr = self.visit(context_expr_node) - enter_fn = self.append(ir.GetAttr(context_mgr, '__enter__')) - exit_fn = self.append(ir.GetAttr(context_mgr, '__exit__')) - - try: - self.current_assign = self._user_call(enter_fn, [], {}) - if optional_vars_node is not None: - self.visit(optional_vars_node) - finally: - self.current_assign = None - - none = self.append(ir.Alloc([], builtins.TNone())) - cleanup.append(lambda: - self._user_call(exit_fn, [none, none, none], {})) - - self._try_finally( - body_gen=lambda: self.visit(node.body), - finally_gen=lambda: [thunk() for thunk in cleanup], - name="with") - - # Expression visitors - # These visitors return a node in addition to mutating - # the IR. - - def visit_LambdaT(self, node): - return self.visit_function(node, is_lambda=True, is_internal=True) - - def visit_IfExpT(self, node): - cond = self.visit(node.test) - head = self.current_block - - if_true = self.add_block("ifexp.body") - self.current_block = if_true - true_result = self.visit(node.body) - post_if_true = self.current_block - - if_false = self.add_block("ifexp.else") - self.current_block = if_false - false_result = self.visit(node.orelse) - post_if_false = self.current_block - - tail = self.add_block("ifexp.tail") - self.current_block = tail - - if not post_if_true.is_terminated(): - post_if_true.append(ir.Branch(tail)) - if not post_if_false.is_terminated(): - post_if_false.append(ir.Branch(tail)) - head.append(ir.BranchIf(cond, if_true, if_false)) - - phi = self.append(ir.Phi(node.type)) - phi.add_incoming(true_result, post_if_true) - phi.add_incoming(false_result, post_if_false) - return phi - - def visit_NumT(self, node): - return ir.Constant(node.n, node.type) - - def visit_StrT(self, node): - return ir.Constant(node.s, node.type) - - def visit_NameConstantT(self, node): - return ir.Constant(node.value, node.type) - - def _env_for(self, name): - if name in self.current_globals: - return self.append(ir.Builtin("globalenv", [self.current_env], - self.current_env.type.outermost())) - else: - return self.current_env - - def _get_local(self, name): - if self.current_class is not None and \ - name in self.current_class.type.attributes: - return self.append(ir.GetAttr(self.current_class, name, - name="FLD." + name)) - - return self.append(ir.GetLocal(self._env_for(name), name, - name="LOC." + name)) - - def _set_local(self, name, value): - if self.current_class is not None and \ - name in self.current_class.type.attributes: - return self.append(ir.SetAttr(self.current_class, name, value)) - - self.append(ir.SetLocal(self._env_for(name), name, value)) - - def visit_NameT(self, node): - if self.current_assign is None: - insn = self._get_local(node.id) - self.variable_map[node] = insn - return insn - else: - return self._set_local(node.id, self.current_assign) - - def visit_AttributeT(self, node): - try: - old_assign, self.current_assign = self.current_assign, None - obj = self.visit(node.value) - finally: - self.current_assign = old_assign - - if self.current_assign is None: - return self.append(ir.GetAttr(obj, node.attr, - name="{}.FLD.{}".format(_readable_name(obj), node.attr))) - else: - return self.append(ir.SetAttr(obj, node.attr, self.current_assign)) - - def _make_check(self, cond, exn_gen, loc=None, params=[]): - if loc is None: - loc = self.current_loc - - try: - name = "check:{}:{}".format(loc.line(), loc.column()) - args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] + \ - [ir.Argument(param.type, "ARG.{}".format(index)) - for index, param in enumerate(params)] - typ = types.TFunction(OrderedDict([("arg{}".format(index), param.type) - for index, param in enumerate(params)]), - OrderedDict(), - builtins.TNone()) - func = ir.Function(typ, ".".join(self.name + [name]), args, loc=loc) - func.is_internal = True - func.is_cold = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_final_branch, self.final_branch = self.final_branch, None - old_unwind, self.unwind_target = self.unwind_target, None - self.raise_exn(exn_gen(*args[1:]), loc=loc) - finally: - self.current_function = old_func - self.current_block = old_block - self.final_branch = old_final_branch - self.unwind_target = old_unwind - - # cond: bool Value, condition - # exn_gen: lambda()->exn Value, exception if condition not true - cond_block = self.current_block - - self.current_block = body_block = self.add_block("check.body") - self._invoke_raising_func(func, params, "check") - - self.current_block = tail_block = self.add_block("check.tail") - cond_block.append(ir.BranchIf(cond, tail_block, body_block)) - - def _invoke_raising_func(self, func, params, block_name): - """Emit a call/invoke instruction as appropriate to terminte the current - basic block with a call to a helper function that always raises an - exception. - - (This is done for compiler-inserted checks and assertions to keep the - generated code tight for the normal case.) - """ - closure = self.append(ir.Closure(func, - ir.Constant(None, ir.TEnvironment("raise", {})))) - if self.unwind_target is None: - insn = self.append(ir.Call(closure, params, {})) - else: - after_invoke = self.add_block(block_name + ".invoke") - insn = self.append(ir.Invoke(closure, params, {}, after_invoke, self.unwind_target)) - self.current_block = after_invoke - insn.is_cold = True - self.append(ir.Unreachable()) - - def _map_index(self, length, index, one_past_the_end=False, loc=None): - lt_0 = self.append(ir.Compare(ast.Lt(loc=None), - index, ir.Constant(0, index.type))) - from_end = self.append(ir.Arith(ast.Add(loc=None), length, index)) - mapped_index = self.append(ir.Select(lt_0, from_end, index)) - mapped_ge_0 = self.append(ir.Compare(ast.GtE(loc=None), - mapped_index, ir.Constant(0, mapped_index.type))) - end_cmpop = ast.LtE(loc=None) if one_past_the_end else ast.Lt(loc=None) - mapped_lt_len = self.append(ir.Compare(end_cmpop, mapped_index, length)) - in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len, - ir.Constant(False, builtins.TBool()))) - head = self.current_block - - self._make_check( - in_bounds, - lambda index, length: self.alloc_exn(builtins.TException("IndexError"), - ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), - index, length), - params=[index, length], - loc=loc) - - return mapped_index - - def _make_loop(self, init, cond_gen, body_gen, name="loop"): - # init: 'iter Value, initial loop variable value - # cond_gen: lambda('iter Value)->bool Value, loop condition - # body_gen: lambda('iter Value)->'iter Value, loop body, - # returns next loop variable value - init_block = self.current_block - - self.current_block = head_block = self.add_block("{}.head".format(name)) - init_block.append(ir.Branch(head_block)) - phi = self.append(ir.Phi(init.type)) - phi.add_incoming(init, init_block) - cond = cond_gen(phi) - - self.current_block = body_block = self.add_block("{}.body".format(name)) - body = body_gen(phi) - self.append(ir.Branch(head_block)) - phi.add_incoming(body, self.current_block) - - self.current_block = tail_block = self.add_block("{}.tail".format(name)) - head_block.append(ir.BranchIf(cond, body_block, tail_block)) - - return head_block, body_block, tail_block - - def visit_SubscriptT(self, node): - try: - old_assign, self.current_assign = self.current_assign, None - value = self.visit(node.value) - finally: - self.current_assign = old_assign - - if isinstance(node.slice, ast.Index): - try: - old_assign, self.current_assign = self.current_assign, None - index = self.visit(node.slice.value) - finally: - self.current_assign = old_assign - - # For multi-dimensional indexes, just apply them sequentially. This - # works, as they are only supported for types where we do not - # immediately need to distinguish between the Get and Set cases - # (i.e. arrays, which are reference types). - if types.is_tuple(index.type): - num_idxs = len(index.type.find().elts) - indices = [ - self.append(ir.GetAttr(index, i)) for i in range(num_idxs) - ] - else: - indices = [index] - indexed = value - for i, idx in enumerate(indices): - length = self.iterable_len(indexed, idx.type) - mapped_index = self._map_index(length, idx, loc=node.begin_loc) - if self.current_assign is None or i < len(indices) - 1: - indexed = self.iterable_get(indexed, mapped_index) - indexed.set_name("{}.at.{}".format(indexed.name, - _readable_name(idx))) - else: - self.append(ir.SetElem(indexed, mapped_index, self.current_assign, - name="{}.at.{}".format(value.name, - _readable_name(index)))) - if self.current_assign is None: - return indexed - else: - # This is a slice. The endpoint checking logic is the same for both lists - # and NumPy arrays, but the actual implementations differ – while slices of - # built-in lists are always copies in Python, they are views sharing the - # same backing storage in NumPy. - length = self.iterable_len(value, node.slice.type) - - if node.slice.lower is not None: - try: - old_assign, self.current_assign = self.current_assign, None - start_index = self.visit(node.slice.lower) - finally: - self.current_assign = old_assign - else: - start_index = ir.Constant(0, node.slice.type) - mapped_start_index = self._map_index(length, start_index, - loc=node.begin_loc) - - if node.slice.upper is not None: - try: - old_assign, self.current_assign = self.current_assign, None - stop_index = self.visit(node.slice.upper) - finally: - self.current_assign = old_assign - else: - stop_index = length - mapped_stop_index = self._map_index(length, stop_index, one_past_the_end=True, - loc=node.begin_loc) - - if builtins.is_array(node.type): - # To implement strided slicing with the proper NumPy reference - # semantics, the pointer/length array representation will need to be - # extended by another field to hold a variable stride. - assert node.slice.step is None, ( - "array slices with non-trivial step " - "should have been disallowed during type inference") - - # One-dimensionally slicing an array only affects the outermost - # dimension. - shape = self.append(ir.GetAttr(value, "shape")) - lengths = [ - self.append(ir.GetAttr(shape, i)) - for i in range(len(shape.type.elts)) - ] - - # Compute outermost length – zero for "backwards" indices. - raw_len = self.append( - ir.Arith(ast.Sub(loc=None), mapped_stop_index, mapped_start_index)) - is_neg_len = self.append( - ir.Compare(ast.Lt(loc=None), raw_len, ir.Constant(0, raw_len.type))) - outer_len = self.append( - ir.Select(is_neg_len, ir.Constant(0, raw_len.type), raw_len)) - new_shape = self._make_array_shape([outer_len] + lengths[1:]) - - # Offset buffer pointer by start index (times stride for inner dims). - stride = reduce( - lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], ir.Constant(1, lengths[0].type)) - offset = self.append( - ir.Arith(ast.Mult(loc=None), stride, mapped_start_index)) - buffer = self.append(ir.GetAttr(value, "buffer")) - new_buffer = self.append(ir.Offset(buffer, offset)) - - return self.append(ir.Alloc([new_buffer, new_shape], node.type)) - else: - if node.slice.step is not None: - try: - old_assign, self.current_assign = self.current_assign, None - step = self.visit(node.slice.step) - finally: - self.current_assign = old_assign - - self._make_check( - self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), - lambda: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("step cannot be zero", builtins.TStr())), - loc=node.slice.step.loc) - else: - step = ir.Constant(1, node.slice.type) - counting_up = self.append(ir.Compare(ast.Gt(loc=None), step, - ir.Constant(0, step.type))) - - unstepped_size = self.append(ir.Arith(ast.Sub(loc=None), - mapped_stop_index, mapped_start_index)) - slice_size_a = self.append(ir.Arith(ast.FloorDiv(loc=None), unstepped_size, step)) - slice_size_b = self.append(ir.Arith(ast.Mod(loc=None), unstepped_size, step)) - rem_not_empty = self.append(ir.Compare(ast.NotEq(loc=None), slice_size_b, - ir.Constant(0, slice_size_b.type))) - slice_size_c = self.append(ir.Arith(ast.Add(loc=None), slice_size_a, - ir.Constant(1, slice_size_a.type))) - slice_size = self.append(ir.Select(rem_not_empty, - slice_size_c, slice_size_a, - name="slice.size")) - self._make_check( - self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), - lambda slice_size, length: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("slice size {0} is larger than iterable length {1}", - builtins.TStr()), - slice_size, length), - params=[slice_size, length], - loc=node.slice.loc) - - if self.current_assign is None: - is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), - slice_size, ir.Constant(0, slice_size.type))) - abs_slice_size = self.append(ir.Select(is_neg_size, - ir.Constant(0, slice_size.type), slice_size)) - other_value = self.append(ir.Alloc([abs_slice_size], value.type, - name="slice.result")) - else: - other_value = self.current_assign - - prehead = self.current_block - - head = self.current_block = self.add_block("slice.head") - prehead.append(ir.Branch(head)) - - index = self.append(ir.Phi(node.slice.type, - name="slice.index")) - index.add_incoming(mapped_start_index, prehead) - other_index = self.append(ir.Phi(node.slice.type, - name="slice.resindex")) - other_index.add_incoming(ir.Constant(0, node.slice.type), prehead) - - # Still within bounds? - bounded_up = self.append(ir.Compare(ast.Lt(loc=None), index, mapped_stop_index)) - bounded_down = self.append(ir.Compare(ast.Gt(loc=None), index, mapped_stop_index)) - within_bounds = self.append(ir.Select(counting_up, bounded_up, bounded_down)) - - body = self.current_block = self.add_block("slice.body") - - if self.current_assign is None: - elem = self.iterable_get(value, index) - self.append(ir.SetElem(other_value, other_index, elem)) - else: - elem = self.append(ir.GetElem(self.current_assign, other_index)) - self.append(ir.SetElem(value, index, elem)) - - next_index = self.append(ir.Arith(ast.Add(loc=None), index, step)) - index.add_incoming(next_index, body) - next_other_index = self.append(ir.Arith(ast.Add(loc=None), other_index, - ir.Constant(1, node.slice.type))) - other_index.add_incoming(next_other_index, body) - self.append(ir.Branch(head)) - - tail = self.current_block = self.add_block("slice.tail") - head.append(ir.BranchIf(within_bounds, body, tail)) - - if self.current_assign is None: - return other_value - - def visit_TupleT(self, node): - if self.current_assign is None: - return self.append(ir.Alloc([self.visit(elt) for elt in node.elts], node.type)) - else: - try: - old_assign = self.current_assign - for index, elt_node in enumerate(node.elts): - self.current_assign = \ - self.append(ir.GetAttr(old_assign, index, - name="{}.e{}".format(old_assign.name, index)), - loc=elt_node.loc) - self.visit(elt_node) - finally: - self.current_assign = old_assign - - def visit_ListT(self, node): - if self.current_assign is None: - elts = [self.visit(elt_node) for elt_node in node.elts] - lst = self.append(ir.Alloc([ir.Constant(len(node.elts), self._size_type)], - node.type)) - for index, elt_node in enumerate(elts): - self.append(ir.SetElem(lst, ir.Constant(index, self._size_type), elt_node)) - return lst - else: - length = self.iterable_len(self.current_assign) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), length, - ir.Constant(len(node.elts), self._size_type))), - lambda length: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("list must be {0} elements long to decompose", builtins.TStr()), - length), - params=[length]) - - for index, elt_node in enumerate(node.elts): - elt = self.append(ir.GetElem(self.current_assign, - ir.Constant(index, self._size_type))) - try: - old_assign, self.current_assign = self.current_assign, elt - self.visit(elt_node) - finally: - self.current_assign = old_assign - - def visit_ListCompT(self, node): - assert len(node.generators) == 1 - comprehension = node.generators[0] - assert comprehension.ifs == [] - - iterable = self.visit(comprehension.iter) - length = self.iterable_len(iterable) - result = self.append(ir.Alloc([length], node.type)) - - try: - gen_suffix = ".gen@{}:{}".format(node.loc.line(), node.loc.column()) - env_type = ir.TEnvironment(name=self.current_function.name + gen_suffix, - vars=node.typing_env, outer=self.current_env.type) - env = self.append(ir.Alloc([], env_type, name="env.gen")) - old_env, self.current_env = self.current_env, env - - self.append(ir.SetLocal(env, "$outer", old_env)) - - def body_gen(index): - elt = self.iterable_get(iterable, index) - try: - old_assign, self.current_assign = self.current_assign, elt - self.visit(comprehension.target) - finally: - self.current_assign = old_assign - - mapped_elt = self.visit(node.elt) - self.append(ir.SetElem(result, index, mapped_elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - return result - finally: - self.current_env = old_env - - def visit_BoolOpT(self, node): - blocks = [] - for value_node in node.values: - value_head = self.current_block - value = self.visit(value_node) - value_tail = self.current_block - - blocks.append((value, value_head, value_tail)) - self.current_block = self.add_block("boolop.seq") - - tail = self.current_block - phi = self.append(ir.Phi(node.type)) - for ((value, value_head, value_tail), (next_value_head, next_value_tail)) in \ - zip(blocks, [(h,t) for (v,h,t) in blocks[1:]] + [(tail, tail)]): - phi.add_incoming(value, value_tail) - if next_value_head != tail: - cond = self.coerce_to_bool(value, block=value_tail) - if isinstance(node.op, ast.And): - value_tail.append(ir.BranchIf(cond, next_value_head, tail)) - else: - value_tail.append(ir.BranchIf(cond, tail, next_value_head)) - else: - value_tail.append(ir.Branch(tail)) - return phi - - def _make_array_unaryop(self, name, make_op, result_type, arg_type): - try: - result = ir.Argument(result_type, "result") - arg = ir.Argument(arg_type, "arg") - - # TODO: We'd like to use a "C function" here to be able to supply - # specialised implementations in a library in the future (and e.g. avoid - # passing around the context argument), but the code generator currently - # doesn't allow emitting them. - args = [result, arg] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env_args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] - - old_loc, self.current_loc = self.current_loc, None - func = ir.Function(typ, name, env_args + args) - func.is_internal = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_final_branch, self.final_branch = self.final_branch, None - old_unwind, self.unwind_target = self.unwind_target, None - - shape = self.append(ir.GetAttr(arg, "shape")) - - result_buffer = self.append(ir.GetAttr(result, "buffer")) - arg_buffer = self.append(ir.GetAttr(arg, "buffer")) - num_total_elts = self._get_total_array_len(shape) - - def body_gen(index): - a = self.append(ir.GetElem(arg_buffer, index)) - self.append( - ir.SetElem(result_buffer, index, make_op(a))) - return self.append( - ir.Arith(ast.Add(loc=None), index, ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_total_elts)), body_gen) - - self.append(ir.Return(ir.Constant(None, builtins.TNone()))) - return func - finally: - self.current_loc = old_loc - self.current_function = old_func - self.current_block = old_block - self.final_branch = old_final_branch - self.unwind_target = old_unwind - - def _get_array_unaryop(self, name, make_op, result_type, arg_type): - name = "_array_{}_{}".format( - name, self._mangle_arrayop_types([result_type, arg_type])) - if name not in self.array_op_funcs: - self.array_op_funcs[name] = self._make_array_unaryop( - name, make_op, result_type, arg_type) - return self.array_op_funcs[name] - - def visit_UnaryOpT(self, node): - if isinstance(node.op, ast.Not): - cond = self.coerce_to_bool(self.visit(node.operand)) - return self.append(ir.Select(cond, - ir.Constant(False, builtins.TBool()), - ir.Constant(True, builtins.TBool()))) - elif isinstance(node.op, ast.Invert): - operand = self.visit(node.operand) - return self.append(ir.Arith(ast.BitXor(loc=None), - ir.Constant(-1, operand.type), operand)) - elif isinstance(node.op, ast.USub): - def make_sub(val): - return self.append(ir.Arith(ast.Sub(loc=None), - ir.Constant(0, val.type), val)) - operand = self.visit(node.operand) - if builtins.is_array(operand.type): - shape = self.append(ir.GetAttr(operand, "shape")) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - func = self._get_array_unaryop("USub", make_sub, node.type, operand.type) - self._invoke_arrayop(func, [result, operand]) - return result - else: - return make_sub(operand) - elif isinstance(node.op, ast.UAdd): - # No-op. - return self.visit(node.operand) - else: - assert False - - def visit_CoerceT(self, node): - value = self.visit(node.value) - if node.type.find() == value.type: - return value - else: - if builtins.is_array(node.type): - result_elt = node.type.find()["elt"] - shape = self.append(ir.GetAttr(value, "shape")) - result, _ = self._allocate_new_array(result_elt, shape) - func = self._get_array_unaryop( - "Coerce", lambda v: self.append(ir.Coerce(v, result_elt)), - node.type, value.type) - self._invoke_arrayop(func, [result, value]) - return result - else: - return self.append( - ir.Coerce(value, - node.type, - name="{}.{}".format(_readable_name(value), - node.type.name))) - - def _get_total_array_len(self, shape): - lengths = [ - self.append(ir.GetAttr(shape, i)) for i in range(len(shape.type.elts)) - ] - return reduce(lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], lengths[0]) - - def _allocate_new_array(self, elt, shape): - total_length = self._get_total_array_len(shape) - buffer = self.append(ir.Alloc([total_length], types._TPointer(elt=elt))) - result_type = builtins.TArray(elt, types.TValue(len(shape.type.elts))) - return self.append(ir.Alloc([buffer, shape], result_type)), total_length - - def _make_array_binop(self, name, result_type, lhs_type, rhs_type, body_gen): - try: - result = ir.Argument(result_type, "result") - lhs = ir.Argument(lhs_type, "lhs") - rhs = ir.Argument(rhs_type, "rhs") - - # TODO: We'd like to use a "C function" here to be able to supply - # specialised implementations in a library in the future (and e.g. avoid - # passing around the context argument), but the code generator currently - # doesn't allow emitting them. - args = [result, lhs, rhs] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env_args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] - - old_loc, self.current_loc = self.current_loc, None - func = ir.Function(typ, name, env_args + args) - func.is_internal = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_final_branch, self.final_branch = self.final_branch, None - old_unwind, self.unwind_target = self.unwind_target, None - - body_gen(result, lhs, rhs) - - self.append(ir.Return(ir.Constant(None, builtins.TNone()))) - return func - finally: - self.current_loc = old_loc - self.current_function = old_func - self.current_block = old_block - self.final_branch = old_final_branch - self.unwind_target = old_unwind - - def _make_array_elementwise_binop(self, name, result_type, lhs_type, - rhs_type, make_op): - def body_gen(result, lhs, rhs): - # At this point, shapes are assumed to match; could just pass buffer - # pointer for two of the three arrays as well. - result_buffer = self.append(ir.GetAttr(result, "buffer")) - shape = self.append(ir.GetAttr(result, "shape")) - num_total_elts = self._get_total_array_len(shape) - - if builtins.is_array(lhs.type): - lhs_buffer = self.append(ir.GetAttr(lhs, "buffer")) - def get_left(index): - return self.append(ir.GetElem(lhs_buffer, index)) - else: - def get_left(index): - return lhs - - if builtins.is_array(rhs.type): - rhs_buffer = self.append(ir.GetAttr(rhs, "buffer")) - def get_right(index): - return self.append(ir.GetElem(rhs_buffer, index)) - else: - def get_right(index): - return rhs - - def loop_gen(index): - l = get_left(index) - r = get_right(index) - result = make_op(l, r) - self.append(ir.SetElem(result_buffer, index, result)) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_total_elts)), - loop_gen) - - return self._make_array_binop(name, result_type, lhs_type, rhs_type, - body_gen) - - def _mangle_arrayop_types(self, types): - def name_error(typ): - assert False, "Internal compiler error: No RPC tag for {}".format(typ) - - def mangle_name(typ): - typ = typ.find() - # rpc_tag is used to turn element types into mangled names for no - # particularly good reason apart from not having to invent yet another - # string representation. - if builtins.is_array(typ): - return mangle_name(typ["elt"]) + str(typ["num_dims"].find().value) - return ir.rpc_tag(typ, name_error).decode() - - return "_".join(mangle_name(t) for t in types) - - def _get_array_elementwise_binop(self, name, make_op, result_type, lhs_type, rhs_type): - # Currently, we always have any type coercions resolved explicitly in the AST. - # In the future, this might no longer be true and the three types might all - # differ. - name = "_array_{}_{}".format( - name, - self._mangle_arrayop_types([result_type, lhs_type, rhs_type])) - if name not in self.array_op_funcs: - self.array_op_funcs[name] = self._make_array_elementwise_binop( - name, result_type, lhs_type, rhs_type, make_op) - return self.array_op_funcs[name] - - def _invoke_arrayop(self, func, params): - closure = self.append( - ir.Closure(func, ir.Constant(None, ir.TEnvironment("arrayop", {})))) - if self.unwind_target is None: - self.append(ir.Call(closure, params, {})) - else: - after_invoke = self.add_block("arrayop.invoke") - self.append(ir.Invoke(func, params, {}, after_invoke, self.unwind_target)) - self.current_block = after_invoke - - def _get_array_offset(self, shape, indices): - result = indices[0] - for dim, index in zip(shape[1:], indices[1:]): - result = self.append(ir.Arith(ast.Mult(loc=None), result, dim)) - result = self.append(ir.Arith(ast.Add(loc=None), result, index)) - return result - - def _get_matmult(self, result_type, lhs_type, rhs_type): - name = "_array_MatMult_" + self._mangle_arrayop_types( - [result_type, lhs_type, rhs_type]) - if name not in self.array_op_funcs: - - def body_gen(result, lhs, rhs): - assert builtins.is_array(result.type), \ - "vec @ vec should have been normalised into array result" - - # We assume result has correct shape; could just pass buffer pointer - # as well. - result_buffer = self.append(ir.GetAttr(result, "buffer")) - lhs_buffer = self.append(ir.GetAttr(lhs, "buffer")) - rhs_buffer = self.append(ir.GetAttr(rhs, "buffer")) - - num_rows, num_summands, _, num_cols = self._get_matmult_shapes(lhs, rhs) - - elt = result.type["elt"].find() - env_type = ir.TEnvironment(name + ".loop", {"$total": elt}) - env = self.append(ir.Alloc([], env_type)) - - def row_loop(row_idx): - lhs_base_offset = self.append( - ir.Arith(ast.Mult(loc=None), row_idx, num_summands)) - lhs_base = self.append(ir.Offset(lhs_buffer, lhs_base_offset)) - result_base_offset = self.append( - ir.Arith(ast.Mult(loc=None), row_idx, num_cols)) - result_base = self.append( - ir.Offset(result_buffer, result_base_offset)) - - def col_loop(col_idx): - rhs_base = self.append(ir.Offset(rhs_buffer, col_idx)) - - self.append( - ir.SetLocal(env, "$total", ir.Constant(elt.zero(), elt))) - - def sum_loop(sum_idx): - lhs_elem = self.append(ir.GetElem(lhs_base, sum_idx)) - rhs_offset = self.append( - ir.Arith(ast.Mult(loc=None), sum_idx, num_cols)) - rhs_elem = self.append(ir.GetElem(rhs_base, rhs_offset)) - product = self.append( - ir.Arith(ast.Mult(loc=None), lhs_elem, rhs_elem)) - prev_total = self.append(ir.GetLocal(env, "$total")) - total = self.append( - ir.Arith(ast.Add(loc=None), prev_total, product)) - self.append(ir.SetLocal(env, "$total", total)) - return self.append( - ir.Arith(ast.Add(loc=None), sum_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_summands)), - sum_loop) - - total = self.append(ir.GetLocal(env, "$total")) - self.append(ir.SetElem(result_base, col_idx, total)) - - return self.append( - ir.Arith(ast.Add(loc=None), col_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_cols)), col_loop) - return self.append( - ir.Arith(ast.Add(loc=None), row_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_rows)), row_loop) - - self.array_op_funcs[name] = self._make_array_binop( - name, result_type, lhs_type, rhs_type, body_gen) - return self.array_op_funcs[name] - - def _get_matmult_shapes(self, lhs, rhs): - lhs_shape = self.append(ir.GetAttr(lhs, "shape")) - if lhs.type["num_dims"].value == 1: - lhs_shape_outer = ir.Constant(1, self._size_type) - lhs_shape_inner = self.append(ir.GetAttr(lhs_shape, 0)) - else: - lhs_shape_outer = self.append(ir.GetAttr(lhs_shape, 0)) - lhs_shape_inner = self.append(ir.GetAttr(lhs_shape, 1)) - - rhs_shape = self.append(ir.GetAttr(rhs, "shape")) - if rhs.type["num_dims"].value == 1: - rhs_shape_inner = self.append(ir.GetAttr(rhs_shape, 0)) - rhs_shape_outer = ir.Constant(1, self._size_type) - else: - rhs_shape_inner = self.append(ir.GetAttr(rhs_shape, 0)) - rhs_shape_outer = self.append(ir.GetAttr(rhs_shape, 1)) - - return lhs_shape_outer, lhs_shape_inner, rhs_shape_inner, rhs_shape_outer - - def _make_array_shape(self, dims): - return self.append(ir.Alloc(dims, types.TTuple([self._size_type] * len(dims)))) - - def _emit_matmult(self, node, left, right): - # TODO: Also expose as numpy.dot. - lhs = self.visit(left) - rhs = self.visit(right) - - num_rows, lhs_inner, rhs_inner, num_cols = self._get_matmult_shapes(lhs, rhs) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), lhs_inner, rhs_inner)), - lambda lhs_inner, rhs_inner: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant( - "inner dimensions for matrix multiplication do not match ({0} vs. {1})", - builtins.TStr()), lhs_inner, rhs_inner), - params=[lhs_inner, rhs_inner], - loc=node.loc) - result_shape = self._make_array_shape([num_rows, num_cols]) - - final_type = node.type.find() - if not builtins.is_array(final_type): - elt = node.type - result_dims = 0 - else: - elt = final_type["elt"] - result_dims = final_type["num_dims"].value - - result, _ = self._allocate_new_array(elt, result_shape) - func = self._get_matmult(result.type, left.type, right.type) - self._invoke_arrayop(func, [result, lhs, rhs]) - - if result_dims == 2: - return result - result_buffer = self.append(ir.GetAttr(result, "buffer")) - if result_dims == 1: - shape = self._make_array_shape( - [num_cols if lhs.type["num_dims"].value == 1 else num_rows]) - return self.append(ir.Alloc([result_buffer, shape], node.type)) - return self.append(ir.GetElem(result_buffer, ir.Constant(0, self._size_type))) - - def _broadcast_binop(self, name, make_op, result_type, lhs, rhs, assign_to_lhs): - # Broadcast scalars (broadcasting higher dimensions is not yet allowed in the - # language). - broadcast = False - array_arg = lhs - if not builtins.is_array(lhs.type): - broadcast = True - array_arg = rhs - elif not builtins.is_array(rhs.type): - broadcast = True - - shape = self.append(ir.GetAttr(array_arg, "shape")) - - if not broadcast: - rhs_shape = self.append(ir.GetAttr(rhs, "shape")) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), shape, rhs_shape)), - lambda: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant("operands could not be broadcast together", - builtins.TStr()))) - if assign_to_lhs: - result = lhs - else: - elt = result_type.find()["elt"] - result, _ = self._allocate_new_array(elt, shape) - func = self._get_array_elementwise_binop(name, make_op, result_type, lhs.type, - rhs.type) - self._invoke_arrayop(func, [result, lhs, rhs]) - return result - - def visit_BinOpT(self, node): - if isinstance(node.op, ast.MatMult): - return self._emit_matmult(node, node.left, node.right) - elif builtins.is_array(node.type): - lhs = self.visit(node.left) - rhs = self.visit(node.right) - name = type(node.op).__name__ - def make_op(l, r): - return self.append(ir.Arith(node.op, l, r)) - return self._broadcast_binop(name, make_op, node.type, lhs, rhs, - assign_to_lhs=False) - elif builtins.is_numeric(node.type): - lhs = self.visit(node.left) - rhs = self.visit(node.right) - if isinstance(node.op, (ast.LShift, ast.RShift)): - # Check for negative shift amount. - self._make_check( - self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))), - lambda: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("shift amount must be nonnegative", builtins.TStr())), - loc=node.right.loc) - elif isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod)): - self._make_check( - self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), - lambda: self.alloc_exn(builtins.TException("ZeroDivisionError"), - ir.Constant("cannot divide by zero", builtins.TStr())), - loc=node.right.loc) - - return self.append(ir.Arith(node.op, lhs, rhs)) - elif isinstance(node.op, ast.Add): # list + list, tuple + tuple, str + str - lhs, rhs = self.visit(node.left), self.visit(node.right) - if types.is_tuple(node.left.type) and types.is_tuple(node.right.type): - elts = [] - for index, elt in enumerate(node.left.type.elts): - elts.append(self.append(ir.GetAttr(lhs, index))) - for index, elt in enumerate(node.right.type.elts): - elts.append(self.append(ir.GetAttr(rhs, index))) - return self.append(ir.Alloc(elts, node.type)) - elif builtins.is_listish(node.left.type) and builtins.is_listish(node.right.type): - lhs_length = self.iterable_len(lhs) - rhs_length = self.iterable_len(rhs) - - result_length = self.append(ir.Arith(ast.Add(loc=None), lhs_length, rhs_length)) - result = self.append(ir.Alloc([result_length], node.type)) - - # Copy lhs - def body_gen(index): - elt = self.append(ir.GetElem(lhs, index)) - self.append(ir.SetElem(result, index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, lhs_length)), - body_gen) - - # Copy rhs - def body_gen(index): - elt = self.append(ir.GetElem(rhs, index)) - result_index = self.append(ir.Arith(ast.Add(loc=None), index, lhs_length)) - self.append(ir.SetElem(result, result_index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, rhs_length)), - body_gen) - - return result - else: - assert False - elif isinstance(node.op, ast.Mult): # list * int, int * list - lhs, rhs = self.visit(node.left), self.visit(node.right) - if builtins.is_listish(lhs.type) and builtins.is_int(rhs.type): - lst, num = lhs, rhs - elif builtins.is_int(lhs.type) and builtins.is_listish(rhs.type): - lst, num = rhs, lhs - else: - assert False - - lst_length = self.iterable_len(lst) - - result_length = self.append(ir.Arith(ast.Mult(loc=None), lst_length, num)) - result = self.append(ir.Alloc([result_length], node.type)) - - # num times... - def body_gen(num_index): - # ... copy the list - def body_gen(lst_index): - elt = self.append(ir.GetElem(lst, lst_index)) - base_index = self.append(ir.Arith(ast.Mult(loc=None), - num_index, lst_length)) - result_index = self.append(ir.Arith(ast.Add(loc=None), - base_index, lst_index)) - self.append(ir.SetElem(result, base_index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), lst_index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, lst_length)), - body_gen) - - return self.append(ir.Arith(ast.Add(loc=None), num_index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, num)), - body_gen) - - return result - else: - assert False - - def polymorphic_compare_pair_order(self, op, lhs, rhs): - if builtins.is_none(lhs.type) and builtins.is_none(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif builtins.is_numeric(lhs.type) and builtins.is_numeric(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif builtins.is_bool(lhs.type) and builtins.is_bool(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif types.is_tuple(lhs.type) and types.is_tuple(rhs.type): - result = None - for index in range(len(lhs.type.elts)): - lhs_elt = self.append(ir.GetAttr(lhs, index)) - rhs_elt = self.append(ir.GetAttr(rhs, index)) - elt_result = self.polymorphic_compare_pair(op, lhs_elt, rhs_elt) - if result is None: - result = elt_result - else: - result = self.append(ir.Select(result, elt_result, - ir.Constant(False, builtins.TBool()))) - return result - elif builtins.is_listish(lhs.type) and builtins.is_listish(rhs.type): - head = self.current_block - lhs_length = self.iterable_len(lhs) - rhs_length = self.iterable_len(rhs) - compare_length = self.append(ir.Compare(op, lhs_length, rhs_length)) - eq_length = self.append(ir.Compare(ast.Eq(loc=None), lhs_length, rhs_length)) - - # If the length is the same, compare element-by-element - # and break when the comparison result is false - loop_head = self.add_block("compare.head") - self.current_block = loop_head - index_phi = self.append(ir.Phi(self._size_type)) - index_phi.add_incoming(ir.Constant(0, self._size_type), head) - loop_cond = self.append(ir.Compare(ast.Lt(loc=None), index_phi, lhs_length)) - - loop_body = self.add_block("compare.body") - self.current_block = loop_body - lhs_elt = self.append(ir.GetElem(lhs, index_phi)) - rhs_elt = self.append(ir.GetElem(rhs, index_phi)) - body_result = self.polymorphic_compare_pair(op, lhs_elt, rhs_elt) - body_end = self.current_block - - loop_body2 = self.add_block("compare.body2") - self.current_block = loop_body2 - index_next = self.append(ir.Arith(ast.Add(loc=None), index_phi, - ir.Constant(1, self._size_type))) - self.append(ir.Branch(loop_head)) - index_phi.add_incoming(index_next, loop_body2) - - tail = self.add_block("compare.tail") - self.current_block = tail - phi = self.append(ir.Phi(builtins.TBool())) - head.append(ir.BranchIf(eq_length, loop_head, tail)) - phi.add_incoming(compare_length, head) - loop_head.append(ir.BranchIf(loop_cond, loop_body, tail)) - phi.add_incoming(ir.Constant(True, builtins.TBool()), loop_head) - body_end.append(ir.BranchIf(body_result, loop_body2, tail)) - phi.add_incoming(body_result, body_end) - - if isinstance(op, ast.NotEq): - result = self.append(ir.Select(phi, - ir.Constant(False, builtins.TBool()), ir.Constant(True, builtins.TBool()))) - else: - result = phi - - return result - else: - loc = lhs.loc - loc.end = rhs.loc.end - diag = diagnostic.Diagnostic("error", - "Custom object comparison is not supported", - {}, - loc) - self.engine.process(diag) - - def polymorphic_compare_pair_inclusion(self, needle, haystack): - if builtins.is_range(haystack.type): - # Optimized range `in` operator - start = self.append(ir.GetAttr(haystack, "start")) - stop = self.append(ir.GetAttr(haystack, "stop")) - step = self.append(ir.GetAttr(haystack, "step")) - after_start = self.append(ir.Compare(ast.GtE(loc=None), needle, start)) - after_stop = self.append(ir.Compare(ast.Lt(loc=None), needle, stop)) - from_start = self.append(ir.Arith(ast.Sub(loc=None), needle, start)) - mod_step = self.append(ir.Arith(ast.Mod(loc=None), from_start, step)) - on_step = self.append(ir.Compare(ast.Eq(loc=None), mod_step, - ir.Constant(0, mod_step.type))) - result = self.append(ir.Select(after_start, after_stop, - ir.Constant(False, builtins.TBool()))) - result = self.append(ir.Select(result, on_step, - ir.Constant(False, builtins.TBool()))) - elif builtins.is_iterable(haystack.type): - length = self.iterable_len(haystack) - - cmp_result = loop_body2 = None - def body_gen(index): - nonlocal cmp_result, loop_body2 - - elt = self.iterable_get(haystack, index) - cmp_result = self.polymorphic_compare_pair(ast.Eq(loc=None), needle, elt) - - loop_body2 = self.add_block("compare.body") - self.current_block = loop_body2 - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - loop_head, loop_body, loop_tail = \ - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen, name="compare") - - loop_body.append(ir.BranchIf(cmp_result, loop_tail, loop_body2)) - phi = loop_tail.prepend(ir.Phi(builtins.TBool())) - phi.add_incoming(ir.Constant(False, builtins.TBool()), loop_head) - phi.add_incoming(ir.Constant(True, builtins.TBool()), loop_body) - - result = phi - else: - loc = needle.loc - loc.end = haystack.loc.end - diag = diagnostic.Diagnostic("error", - "Custom object inclusion test is not supported", - {}, - loc) - self.engine.process(diag) - - return result - - def invert(self, value): - return self.append(ir.Select(value, - ir.Constant(False, builtins.TBool()), - ir.Constant(True, builtins.TBool()))) - - def polymorphic_compare_pair(self, op, lhs, rhs): - if isinstance(op, (ast.Is, ast.IsNot)): - # The backend will handle equality of aggregates. - return self.append(ir.Compare(op, lhs, rhs)) - elif isinstance(op, ast.In): - return self.polymorphic_compare_pair_inclusion(lhs, rhs) - elif isinstance(op, ast.NotIn): - result = self.polymorphic_compare_pair_inclusion(lhs, rhs) - return self.invert(result) - elif isinstance(op, (ast.Eq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): - return self.polymorphic_compare_pair_order(op, lhs, rhs) - elif isinstance(op, ast.NotEq): - result = self.polymorphic_compare_pair_order(ast.Eq(loc=op.loc), lhs, rhs) - return self.invert(result) - else: - assert False - - def visit_CompareT(self, node): - # Essentially a sequence of `and`s performed over results - # of comparisons. - blocks = [] - lhs = self.visit(node.left) - for op, rhs_node in zip(node.ops, node.comparators): - result_head = self.current_block - rhs = self.visit(rhs_node) - result = self.polymorphic_compare_pair(op, lhs, rhs) - result_tail = self.current_block - - blocks.append((result, result_head, result_tail)) - self.current_block = self.add_block("compare.seq") - lhs = rhs - - tail = self.current_block - phi = self.append(ir.Phi(node.type)) - for ((result, result_head, result_tail), (next_result_head, next_result_tail)) in \ - zip(blocks, [(h,t) for (v,h,t) in blocks[1:]] + [(tail, tail)]): - phi.add_incoming(result, result_tail) - if next_result_head != tail: - result_tail.append(ir.BranchIf(result, next_result_head, tail)) - else: - result_tail.append(ir.Branch(tail)) - return phi - - # Keep this function with builtins.TException.attributes. - def alloc_exn(self, typ, message=None, param0=None, param1=None, param2=None): - typ = typ.find() - name = "{}:{}".format(typ.id, typ.name) - attributes = [ - ir.Constant(name, builtins.TStr()), # typeinfo - ir.Constant("", builtins.TStr()), # file - ir.Constant(0, builtins.TInt32()), # line - ir.Constant(0, builtins.TInt32()), # column - ir.Constant("", builtins.TStr()), # function - ] - - if message is None: - attributes.append(ir.Constant(typ.name, builtins.TStr())) - else: - attributes.append(message) # message - - param_type = builtins.TInt64() - for param in [param0, param1, param2]: - if param is None: - attributes.append(ir.Constant(0, builtins.TInt64())) - else: - if param.type != param_type: - param = self.append(ir.Coerce(param, param_type)) - attributes.append(param) # paramN, N=0:2 - - return self.append(ir.Alloc(attributes, typ)) - - def visit_builtin_call(self, node): - # A builtin by any other name... Ignore node.func, just use the type. - typ = node.func.type - if types.is_builtin(typ, "bool"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(False, builtins.TBool()) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.coerce_to_bool(arg) - else: - assert False - elif types.is_builtin(typ, "int") or \ - types.is_builtin(typ, "int32") or types.is_builtin(typ, "int64"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(0, node.type) - elif len(node.args) == 1 and \ - (len(node.keywords) == 0 or \ - len(node.keywords) == 1 and node.keywords[0].arg == 'width'): - # The width argument is purely type-level - arg = self.visit(node.args[0]) - return self.append(ir.Coerce(arg, node.type)) - else: - assert False - elif types.is_builtin(typ, "float"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(0.0, builtins.TFloat()) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.append(ir.Coerce(arg, node.type)) - else: - assert False - elif (types.is_builtin(typ, "list") or - types.is_builtin(typ, "bytearray") or types.is_builtin(typ, "bytes")): - if len(node.args) == 0 and len(node.keywords) == 0: - length = ir.Constant(0, builtins.TInt32()) - return self.append(ir.Alloc([length], node.type)) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - length = self.iterable_len(arg) - result = self.append(ir.Alloc([length], node.type)) - - def body_gen(index): - elt = self.iterable_get(arg, index) - elt = self.append(ir.Coerce(elt, builtins.get_iterable_elt(node.type))) - self.append(ir.SetElem(result, index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - return result - else: - assert False - elif types.is_builtin(typ, "array"): - if len(node.args) == 1 and len(node.keywords) in (0, 1): - result_type = node.type.find() - arg = self.visit(node.args[0]) - - result_elt = result_type["elt"].find() - num_dims = result_type["num_dims"].value - - # Derive shape from first element on each level (and fail later if the - # array is in fact jagged). - first_elt = None - lengths = [] - for dim_idx in range(num_dims): - if first_elt is None: - first_elt = arg - else: - first_elt = self.iterable_get(first_elt, - ir.Constant(0, self._size_type)) - lengths.append(self.iterable_len(first_elt)) - - shape = self.append(ir.Alloc(lengths, result_type.attributes["shape"])) - num_total_elts = self._get_total_array_len(shape) - - # Assign buffer from nested iterables. - buffer = self.append( - ir.Alloc([num_total_elts], result_type.attributes["buffer"])) - - def assign_elems(outer_indices, indexed_arg): - if len(outer_indices) == num_dims: - dest_idx = self._get_array_offset(lengths, outer_indices) - coerced = self.append(ir.Coerce(indexed_arg, result_elt)) - self.append(ir.SetElem(buffer, dest_idx, coerced)) - else: - this_level_len = self.iterable_len(indexed_arg) - dim_idx = len(outer_indices) - if dim_idx > 0: - # Check for rectangularity (outermost index is never jagged, - # by definition). - result_len = self.append(ir.GetAttr(shape, dim_idx)) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), this_level_len, result_len)), - lambda a, b: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant( - "arrays must be rectangular (lengths were {0} vs. {1})", - builtins.TStr()), a, b), - params=[this_level_len, result_len], - loc=node.loc) - - def body_gen(index): - elem = self.iterable_get(indexed_arg, index) - assign_elems(outer_indices + [index], elem) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, this_level_len)), body_gen) - assign_elems([], arg) - return self.append(ir.Alloc([buffer, shape], node.type)) - else: - assert False - elif types.is_builtin(typ, "range"): - elt_typ = builtins.get_iterable_elt(node.type) - if len(node.args) == 1 and len(node.keywords) == 0: - max_arg = self.visit(node.args[0]) - return self.append(ir.Alloc([ - ir.Constant(elt_typ.zero(), elt_typ), - max_arg, - ir.Constant(elt_typ.one(), elt_typ), - ], node.type)) - elif len(node.args) == 2 and len(node.keywords) == 0: - min_arg = self.visit(node.args[0]) - max_arg = self.visit(node.args[1]) - return self.append(ir.Alloc([ - min_arg, - max_arg, - ir.Constant(elt_typ.one(), elt_typ), - ], node.type)) - elif len(node.args) == 3 and len(node.keywords) == 0: - min_arg = self.visit(node.args[0]) - max_arg = self.visit(node.args[1]) - step_arg = self.visit(node.args[2]) - return self.append(ir.Alloc([ - min_arg, - max_arg, - step_arg, - ], node.type)) - else: - assert False - elif types.is_builtin(typ, "len"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.iterable_len(arg) - else: - assert False - elif types.is_builtin(typ, "round"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.append(ir.Builtin("round", [arg], node.type)) - else: - assert False - elif types.is_builtin(typ, "abs"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - neg = self.append( - ir.Arith(ast.Sub(loc=None), ir.Constant(0, arg.type), arg)) - cond = self.append( - ir.Compare(ast.Lt(loc=None), arg, ir.Constant(0, arg.type))) - return self.append(ir.Select(cond, neg, arg)) - else: - assert False - elif types.is_builtin(typ, "min"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - cond = self.append(ir.Compare(ast.Lt(loc=None), arg0, arg1)) - return self.append(ir.Select(cond, arg0, arg1)) - else: - assert False - elif types.is_builtin(typ, "max"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - cond = self.append(ir.Compare(ast.Gt(loc=None), arg0, arg1)) - return self.append(ir.Select(cond, arg0, arg1)) - else: - assert False - elif types.is_builtin(typ, "make_array"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - - num_dims = node.type.find()["num_dims"].value - if types.is_tuple(arg0.type): - lens = [self.append(ir.GetAttr(arg0, i)) for i in range(num_dims)] - else: - assert num_dims == 1 - lens = [arg0] - - shape = self._make_array_shape(lens) - result, total_len = self._allocate_new_array(node.type.find()["elt"], - shape) - - def body_gen(index): - self.append(ir.SetElem(result, index, arg1)) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, total_len)), body_gen) - return result - else: - assert False - elif types.is_builtin(typ, "numpy.transpose"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg, = map(self.visit, node.args) - - num_dims = arg.type.find()["num_dims"].value - if num_dims == 1: - # No-op as per NumPy semantics. - return arg - assert num_dims == 2 - arg_shape = self.append(ir.GetAttr(arg, "shape")) - dim0 = self.append(ir.GetAttr(arg_shape, 0)) - dim1 = self.append(ir.GetAttr(arg_shape, 1)) - shape = self._make_array_shape([dim1, dim0]) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - arg_buffer = self.append(ir.GetAttr(arg, "buffer")) - result_buffer = self.append(ir.GetAttr(result, "buffer")) - - def outer_gen(idx1): - arg_base = self.append(ir.Offset(arg_buffer, idx1)) - result_offset = self.append(ir.Arith(ast.Mult(loc=None), idx1, - dim0)) - result_base = self.append(ir.Offset(result_buffer, result_offset)) - - def inner_gen(idx0): - arg_offset = self.append( - ir.Arith(ast.Mult(loc=None), idx0, dim1)) - val = self.append(ir.GetElem(arg_base, arg_offset)) - self.append(ir.SetElem(result_base, idx0, val)) - return self.append( - ir.Arith(ast.Add(loc=None), idx0, ir.Constant(1, - idx0.type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda idx0: self.append( - ir.Compare(ast.Lt(loc=None), idx0, dim0)), inner_gen) - return self.append( - ir.Arith(ast.Add(loc=None), idx1, ir.Constant(1, idx1.type))) - - self._make_loop( - ir.Constant(0, self._size_type), - lambda idx1: self.append(ir.Compare(ast.Lt(loc=None), idx1, dim1)), - outer_gen) - return result - else: - assert False - elif types.is_builtin(typ, "print"): - self.polymorphic_print([self.visit(arg) for arg in node.args], - separator=" ", suffix="\n") - return ir.Constant(None, builtins.TNone()) - elif types.is_builtin(typ, "rtio_log"): - prefix, *args = node.args - self.polymorphic_print([self.visit(prefix)], - separator=" ", suffix="\x1E", as_rtio=True) - self.polymorphic_print([self.visit(arg) for arg in args], - separator=" ", suffix="\x1D", as_rtio=True) - return ir.Constant(None, builtins.TNone()) - elif types.is_builtin(typ, "delay"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - arg_mu_float = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period)) - arg_mu = self.append(ir.Builtin("round", [arg_mu_float], builtins.TInt64())) - return self.append(ir.Builtin("delay_mu", [arg_mu], builtins.TNone())) - else: - assert False - elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "delay_mu") \ - or types.is_builtin(typ, "at_mu"): - return self.append(ir.Builtin(typ.name, - [self.visit(arg) for arg in node.args], node.type)) - elif types.is_exn_constructor(typ): - return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args]) - elif types.is_constructor(typ): - return self.append(ir.Alloc([], typ.instance)) - else: - diag = diagnostic.Diagnostic("error", - "builtin function '{name}' cannot be used in this context", - {"name": typ.find().name}, - node.loc) - self.engine.process(diag) - - def _user_call(self, callee, positional, keywords, arg_exprs={}): - if types.is_function(callee.type) or types.is_rpc(callee.type): - func = callee - self_arg = None - fn_typ = callee.type - offset = 0 - elif types.is_method(callee.type): - func = self.append(ir.GetAttr(callee, "__func__", - name="{}.ENV".format(callee.name))) - self_arg = self.append(ir.GetAttr(callee, "__self__", - name="{}.SLF".format(callee.name))) - fn_typ = types.get_method_function(callee.type) - offset = 1 - else: - assert False - - if types.is_rpc(fn_typ): - if self_arg is None: - args = positional - else: - args = [self_arg] + positional - - for keyword in keywords: - arg = keywords[keyword] - args.append(self.append(ir.Alloc([ir.Constant(keyword, builtins.TStr()), arg], - ir.TKeyword(arg.type)))) - else: - args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) - - for index, arg in enumerate(positional): - if index + offset < len(fn_typ.args): - args[index + offset] = arg - else: - args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) - - for keyword in keywords: - arg = keywords[keyword] - if keyword in fn_typ.args: - for index, arg_name in enumerate(fn_typ.args): - if keyword == arg_name: - assert args[index] is None - args[index] = arg - break - elif keyword in fn_typ.optargs: - for index, optarg_name in enumerate(fn_typ.optargs): - if keyword == optarg_name: - assert args[len(fn_typ.args) + index] is None - args[len(fn_typ.args) + index] = \ - self.append(ir.Alloc([arg], ir.TOption(arg.type))) - break - - for index, optarg_name in enumerate(fn_typ.optargs): - if args[len(fn_typ.args) + index] is None: - args[len(fn_typ.args) + index] = \ - self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name]))) - - if self_arg is not None: - assert args[0] is None - args[0] = self_arg - - assert None not in args - - if self.unwind_target is None or \ - types.is_external_function(callee.type) and "nounwind" in callee.type.flags: - insn = self.append(ir.Call(func, args, arg_exprs)) - else: - after_invoke = self.add_block("invoke") - insn = self.append(ir.Invoke(func, args, arg_exprs, - after_invoke, self.unwind_target)) - self.current_block = after_invoke - - return insn - - def visit_CallT(self, node): - if not types.is_builtin(node.func.type): - callee = self.visit(node.func) - args = [self.visit(arg_node) for arg_node in node.args] - keywords = {kw_node.arg: self.visit(kw_node.value) for kw_node in node.keywords} - - if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): - before_delay = self.current_block - during_delay = self.add_block("delay.head") - before_delay.append(ir.Branch(during_delay)) - self.current_block = during_delay - - if types.is_builtin(node.func.type): - insn = self.visit_builtin_call(node) - elif (types.is_broadcast_across_arrays(node.func.type) and len(args) >= 1 - and any(builtins.is_array(arg.type) for arg in args)): - # The iodelay machinery set up in the surrounding code was - # deprecated/a relic from the past when array broadcasting support - # was added, so no attempt to keep the delay tracking intact is - # made. - def make_call(*args): - return self._user_call(ir.Constant(None, callee.type), args, {}, - node.arg_exprs) - # TODO: Generate more generically if non-externals are allowed. - name = node.func.type.find().name - - if len(args) == 1: - shape = self.append(ir.GetAttr(args[0], "shape")) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - func = self._get_array_unaryop(name, make_call, node.type, args[0].type) - self._invoke_arrayop(func, [result, args[0]]) - insn = result - elif len(args) == 2: - insn = self._broadcast_binop(name, make_call, node.type, *args, - assign_to_lhs=False) - else: - assert False, "Broadcasting for {} arguments not implemented".format(len) - else: - insn = self._user_call(callee, args, keywords, node.arg_exprs) - if isinstance(node.func, asttyped.AttributeT): - attr_node = node.func - self.method_map[(attr_node.value.type.find(), - attr_node.attr)].append(insn) - - if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): - after_delay = self.add_block("delay.tail") - self.append(ir.Delay(node.iodelay, insn, after_delay)) - self.current_block = after_delay - - return insn - - def visit_QuoteT(self, node): - return self.append(ir.Quote(node.value, node.type)) - - def _get_raise_assert_func(self): - """Emit the helper function that constructs AssertionErrors and raises - them, if it does not already exist in the current module. - - A separate function is used for code size reasons. (This could also be - compiled into a stand-alone support library instead.) - """ - if self.raise_assert_func: - return self.raise_assert_func - try: - msg = ir.Argument(builtins.TStr(), "msg") - file = ir.Argument(builtins.TStr(), "file") - line = ir.Argument(builtins.TInt32(), "line") - col = ir.Argument(builtins.TInt32(), "col") - function = ir.Argument(builtins.TStr(), "function") - - args = [msg, file, line, col, function] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env = ir.TEnvironment(name="raise", vars={}) - env_arg = ir.EnvironmentArgument(env, "ARG.ENV") - func = ir.Function(typ, "_artiq_raise_assert", [env_arg] + args) - func.is_internal = True - func.is_cold = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - old_final_branch, self.final_branch = self.final_branch, None - old_unwind, self.unwind_target = self.unwind_target, None - - exn = self.alloc_exn(builtins.TException("AssertionError"), message=msg) - self.append(ir.SetAttr(exn, "__file__", file)) - self.append(ir.SetAttr(exn, "__line__", line)) - self.append(ir.SetAttr(exn, "__col__", col)) - self.append(ir.SetAttr(exn, "__func__", function)) - self.append(ir.Raise(exn)) - finally: - self.current_function = old_func - self.current_block = old_block - self.final_branch = old_final_branch - self.unwind_target = old_unwind - - self.raise_assert_func = func - return self.raise_assert_func - - def visit_Assert(self, node): - cond = self.visit(node.test) - head = self.current_block - - if_failed = self.current_block = self.add_block("assert.fail") - text = str(node.msg.s) if node.msg else "AssertionError" - msg = ir.Constant(text, builtins.TStr()) - loc_file = ir.Constant(node.loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(node.loc.line(), builtins.TInt32()) - loc_column = ir.Constant(node.loc.column(), builtins.TInt32()) - loc_function = ir.Constant(".".join(self.name), builtins.TStr()) - self._invoke_raising_func(self._get_raise_assert_func(), [ - msg, loc_file, loc_line, loc_column, loc_function - ], "assert.fail") - - tail = self.current_block = self.add_block("assert.tail") - self.append(ir.BranchIf(cond, tail, if_failed), block=head) - - def polymorphic_print(self, values, separator, suffix="", as_repr=False, as_rtio=False): - def printf(format_string, *args): - format = ir.Constant(format_string, builtins.TStr()) - if as_rtio: - self.append(ir.Builtin("rtio_log", [format, *args], builtins.TNone())) - else: - self.append(ir.Builtin("printf", [format, *args], builtins.TNone())) - - format_string = "" - args = [] - def flush(): - nonlocal format_string, args - if format_string != "": - printf(format_string + "\x00", *args) - format_string = "" - args = [] - - for value in values: - if format_string != "": - format_string += separator - - if types.is_tuple(value.type): - format_string += "("; flush() - self.polymorphic_print([self.append(ir.GetAttr(value, index)) - for index in range(len(value.type.elts))], - separator=", ", as_repr=True, as_rtio=as_rtio) - if len(value.type.elts) == 1: - format_string += ",)" - else: - format_string += ")" - elif types.is_function(value.type): - format_string += "" - args.append(self.append(ir.GetAttr(value, '__code__'))) - args.append(self.append(ir.GetAttr(value, '__closure__'))) - elif builtins.is_none(value.type): - format_string += "None" - elif builtins.is_bool(value.type): - format_string += "%.*s" - args.append(self.append(ir.Select(value, - ir.Constant("True", builtins.TStr()), - ir.Constant("False", builtins.TStr())))) - elif builtins.is_int(value.type): - width = builtins.get_int_width(value.type) - if width <= 32: - format_string += "%d" - elif width <= 64: - format_string += "%lld" - else: - assert False - args.append(value) - elif builtins.is_float(value.type): - format_string += "%g" - args.append(value) - elif builtins.is_str(value.type): - if as_repr: - format_string += "\"%.*s\"" - else: - format_string += "%.*s" - args.append(value) - elif builtins.is_listish(value.type): - if builtins.is_list(value.type): - format_string += "["; flush() - elif builtins.is_bytes(value.type): - format_string += "bytes(["; flush() - elif builtins.is_bytearray(value.type): - format_string += "bytearray(["; flush() - elif builtins.is_array(value.type): - format_string += "array(["; flush() - else: - assert False - - length = self.iterable_len(value) - last = self.append(ir.Arith(ast.Sub(loc=None), length, ir.Constant(1, length.type))) - def body_gen(index): - elt = self.iterable_get(value, index) - self.polymorphic_print([elt], separator="", as_repr=True, as_rtio=as_rtio) - is_last = self.append(ir.Compare(ast.Lt(loc=None), index, last)) - head = self.current_block - - if_last = self.current_block = self.add_block("print.comma") - printf(", \x00") - - tail = self.current_block = self.add_block("print.tail") - if_last.append(ir.Branch(tail)) - head.append(ir.BranchIf(is_last, if_last, tail)) - - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - if builtins.is_list(value.type): - format_string += "]" - elif (builtins.is_bytes(value.type) or builtins.is_bytearray(value.type) or - builtins.is_array(value.type)): - format_string += "])" - elif builtins.is_range(value.type): - format_string += "range("; flush() - - start = self.append(ir.GetAttr(value, "start")) - stop = self.append(ir.GetAttr(value, "stop")) - step = self.append(ir.GetAttr(value, "step")) - self.polymorphic_print([start, stop, step], separator=", ", as_rtio=as_rtio) - - format_string += ")" - elif builtins.is_exception(value.type): - name = self.append(ir.GetAttr(value, "__name__")) - message = self.append(ir.GetAttr(value, "__message__")) - param1 = self.append(ir.GetAttr(value, "__param0__")) - param2 = self.append(ir.GetAttr(value, "__param1__")) - param3 = self.append(ir.GetAttr(value, "__param2__")) - - format_string += "%.*s(%.*s, %lld, %lld, %lld)" - args += [name, message, param1, param2, param3] - else: - assert False - - format_string += suffix - flush() diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py deleted file mode 100644 index 4c3112be6..000000000 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ /dev/null @@ -1,526 +0,0 @@ -""" -:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`) -to a typedtree (:mod:`..asttyped`). -""" - -from collections import OrderedDict -from pythonparser import ast, algorithm, diagnostic -from .. import asttyped, types, builtins - -# This visitor will be called for every node with a scope, -# i.e.: class, function, comprehension, lambda -class LocalExtractor(algorithm.Visitor): - def __init__(self, env_stack, engine): - super().__init__() - self.env_stack = env_stack - self.engine = engine - - self.in_root = False - self.in_assign = False - self.typing_env = OrderedDict() - - # which names are global have to be recorded in the current scope - self.global_ = set() - - # which names are nonlocal only affects whether the current scope - # gets a new binding or not, so we throw this away - self.nonlocal_ = set() - - # parameters can't be declared as global or nonlocal - self.params = set() - - def visit_in_assign(self, node, in_assign): - try: - old_in_assign, self.in_assign = self.in_assign, in_assign - return self.visit(node) - finally: - self.in_assign = old_in_assign - - def visit_Assign(self, node): - self.visit(node.value) - self.visit_in_assign(node.targets, in_assign=True) - - def visit_For(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target, in_assign=True) - self.visit(node.body) - self.visit(node.orelse) - - def visit_withitem(self, node): - self.visit(node.context_expr) - self.visit_in_assign(node.optional_vars, in_assign=True) - - def visit_comprehension(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target, in_assign=True) - self.visit(node.ifs) - - def visit_generator(self, node): - if self.in_root: - return - self.in_root = True - self.visit(list(reversed(node.generators))) - self.visit(node.elt) - - visit_ListComp = visit_generator - visit_SetComp = visit_generator - visit_GeneratorExp = visit_generator - - def visit_DictComp(self, node): - if self.in_root: - return - self.in_root = True - self.visit(list(reversed(node.generators))) - self.visit(node.key) - self.visit(node.value) - - def visit_root(self, node): - if self.in_root: - return - self.in_root = True - self.generic_visit(node) - - visit_Module = visit_root # don't look at inner scopes - visit_ClassDef = visit_root - visit_Lambda = visit_root - - def visit_FunctionDef(self, node): - if self.in_root: - self._assignable(node.name) - self.visit_root(node) - - def _assignable(self, name): - assert name is not None - if name not in self.typing_env and name not in self.nonlocal_: - self.typing_env[name] = types.TVar() - - def visit_arg(self, node): - if node.arg in self.params: - diag = diagnostic.Diagnostic("error", - "duplicate parameter '{name}'", {"name": node.arg}, - node.loc) - self.engine.process(diag) - return - self._assignable(node.arg) - self.params.add(node.arg) - - def visit_Name(self, node): - if self.in_assign: - # code like: - # x = 1 - # def f(): - # x = 1 - # creates a new binding for x in f's scope - self._assignable(node.id) - - def visit_Attribute(self, node): - self.visit_in_assign(node.value, in_assign=False) - - def visit_Subscript(self, node): - self.visit_in_assign(node.value, in_assign=False) - self.visit_in_assign(node.slice, in_assign=False) - - def _check_not_in(self, name, names, curkind, newkind, loc): - if name in names: - diag = diagnostic.Diagnostic("error", - "name '{name}' cannot be {curkind} and {newkind} simultaneously", - {"name": name, "curkind": curkind, "newkind": newkind}, loc) - self.engine.process(diag) - return True - return False - - def visit_Global(self, node): - for name, loc in zip(node.names, node.name_locs): - if self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) or \ - self._check_not_in(name, self.params, "a parameter", "global", loc): - continue - - self.global_.add(name) - if len(self.env_stack) == 1: - self._assignable(name) # already in global scope - else: - if name not in self.env_stack[1]: - self.env_stack[1][name] = types.TVar() - self.typing_env[name] = self.env_stack[1][name] - - def visit_Nonlocal(self, node): - for name, loc in zip(node.names, node.name_locs): - if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \ - self._check_not_in(name, self.params, "a parameter", "nonlocal", loc): - continue - - # nonlocal does not search prelude and global scopes - found = False - for outer_env in reversed(self.env_stack[2:]): - if name in outer_env: - found = True - break - if not found: - diag = diagnostic.Diagnostic("error", - "cannot declare name '{name}' as nonlocal: it is not bound in any outer scope", - {"name": name}, - loc, [node.keyword_loc]) - self.engine.process(diag) - continue - - self.nonlocal_.add(name) - - def visit_ExceptHandler(self, node): - self.visit(node.type) - if node.name is not None: - self._assignable(node.name) - for stmt in node.body: - self.visit(stmt) - - -class ASTTypedRewriter(algorithm.Transformer): - """ - :class:`ASTTypedRewriter` converts an untyped AST to a typed AST - where all type fields of non-literals are filled with fresh type variables, - and type fields of literals are filled with corresponding types. - - :class:`ASTTypedRewriter` also discovers the scope of variable bindings - via :class:`LocalExtractor`. - """ - - def __init__(self, engine, prelude): - self.engine = engine - self.globals = None - self.env_stack = [prelude] - self.in_class = None - - def _try_find_name(self, name): - if self.in_class is not None: - typ = self.in_class.constructor_type.attributes.get(name) - if typ is not None: - return typ - - for typing_env in reversed(self.env_stack): - if name in typing_env: - return typing_env[name] - - def _find_name(self, name, loc): - typ = self._try_find_name(name) - if typ is not None: - return typ - - diag = diagnostic.Diagnostic("fatal", - "undefined variable '{name}'", {"name":name}, loc) - self.engine.process(diag) - - # Visitors that replace node with a typed node - # - def visit_Module(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.ModuleT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - body=node.body, loc=node.loc) - self.globals = node.typing_env - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_FunctionDef(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - signature_type = self._find_name(node.name, node.name_loc) - - node = asttyped.FunctionDefT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - signature_type=signature_type, return_type=types.TVar(), - name=node.name, args=node.args, returns=node.returns, - body=node.body, decorator_list=node.decorator_list, - keyword_loc=node.keyword_loc, name_loc=node.name_loc, - arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs, - loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_ClassDef(self, node): - if any(node.bases) or any(node.keywords) or \ - node.starargs is not None or node.kwargs is not None: - diag = diagnostic.Diagnostic("error", - "inheritance is not supported", {}, - node.lparen_loc.join(node.rparen_loc)) - self.engine.process(diag) - - for child in node.body: - if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)): - continue - - diag = diagnostic.Diagnostic("fatal", - "class body must contain only assignments and function definitions", {}, - child.loc) - self.engine.process(diag) - - if node.name in self.env_stack[-1]: - diag = diagnostic.Diagnostic("fatal", - "variable '{name}' is already defined", {"name":node.name}, node.name_loc) - self.engine.process(diag) - - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - # Now we create two types. - # The first type is the type of instances created by the constructor. - # Its attributes are those of the class environment, but wrapped - # appropriately so that they are linked to the class from which they - # originate. - instance_type = types.TInstance(node.name, OrderedDict()) - - # The second type is the type of the constructor itself (in other words, - # the class object): it is simply a singleton type that has the class - # environment as attributes. - constructor_type = types.TConstructor(instance_type) - constructor_type.attributes = extractor.typing_env - instance_type.constructor = constructor_type - - self.env_stack[-1][node.name] = constructor_type - - node = asttyped.ClassDefT( - constructor_type=constructor_type, - name=node.name, - bases=self.visit(node.bases), keywords=self.visit(node.keywords), - starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs), - body=node.body, - decorator_list=self.visit(node.decorator_list), - keyword_loc=node.keyword_loc, name_loc=node.name_loc, - lparen_loc=node.lparen_loc, star_loc=node.star_loc, - dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc, - colon_loc=node.colon_loc, at_locs=node.at_locs, - loc=node.loc) - - try: - old_in_class, self.in_class = self.in_class, node - return self.generic_visit(node) - finally: - self.in_class = old_in_class - - def visit_arg(self, node): - if node.annotation is not None: - diag = diagnostic.Diagnostic("fatal", - "type annotations are not supported here", {}, - node.annotation.loc) - self.engine.process(diag) - - return asttyped.argT(type=self._find_name(node.arg, node.loc), - arg=node.arg, annotation=None, - arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) - - def visit_Num(self, node): - if isinstance(node.n, int): - typ = builtins.TInt() - elif isinstance(node.n, float): - typ = builtins.TFloat() - else: - diag = diagnostic.Diagnostic("fatal", - "numeric type {type} is not supported", {"type": node.n.__class__.__name__}, - node.loc) - self.engine.process(diag) - return asttyped.NumT(type=typ, - n=node.n, loc=node.loc) - - def visit_Str(self, node): - if isinstance(node.s, str): - typ = builtins.TStr() - elif isinstance(node.s, bytes): - typ = builtins.TBytes() - else: - assert False - return asttyped.StrT(type=typ, s=node.s, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - - def visit_Name(self, node): - return asttyped.NameT(type=self._find_name(node.id, node.loc), - id=node.id, ctx=node.ctx, loc=node.loc) - - def visit_NameConstant(self, node): - if node.value is True or node.value is False: - typ = builtins.TBool() - elif node.value is None: - typ = builtins.TNone() - return asttyped.NameConstantT(type=typ, value=node.value, loc=node.loc) - - def visit_Tuple(self, node): - node = self.generic_visit(node) - return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]), - elts=node.elts, ctx=node.ctx, loc=node.loc) - - def visit_List(self, node): - node = self.generic_visit(node) - node = asttyped.ListT(type=builtins.TList(), - elts=node.elts, ctx=node.ctx, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return self.visit(node) - - def visit_Attribute(self, node): - node = self.generic_visit(node) - node = asttyped.AttributeT(type=types.TVar(), - value=node.value, attr=node.attr, ctx=node.ctx, - dot_loc=node.dot_loc, attr_loc=node.attr_loc, loc=node.loc) - return self.visit(node) - - def visit_Slice(self, node): - node = self.generic_visit(node) - node = asttyped.SliceT(type=types.TVar(), - lower=node.lower, upper=node.upper, step=node.step, - bound_colon_loc=node.bound_colon_loc, - step_colon_loc=node.step_colon_loc, - loc=node.loc) - return self.visit(node) - - def visit_Subscript(self, node): - node = self.generic_visit(node) - node = asttyped.SubscriptT(type=types.TVar(), - value=node.value, slice=node.slice, ctx=node.ctx, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return self.visit(node) - - def visit_BoolOp(self, node): - node = self.generic_visit(node) - node = asttyped.BoolOpT(type=types.TVar(), - op=node.op, values=node.values, - op_locs=node.op_locs, loc=node.loc) - return self.visit(node) - - def visit_UnaryOp(self, node): - node = self.generic_visit(node) - node = asttyped.UnaryOpT(type=types.TVar(), - op=node.op, operand=node.operand, - loc=node.loc) - return self.visit(node) - - def visit_BinOp(self, node): - node = self.generic_visit(node) - node = asttyped.BinOpT(type=types.TVar(), - left=node.left, op=node.op, right=node.right, - loc=node.loc) - return self.visit(node) - - def visit_Compare(self, node): - node = self.generic_visit(node) - node = asttyped.CompareT(type=types.TVar(), - left=node.left, ops=node.ops, comparators=node.comparators, - loc=node.loc) - return self.visit(node) - - def visit_IfExp(self, node): - node = self.generic_visit(node) - node = asttyped.IfExpT(type=types.TVar(), - test=node.test, body=node.body, orelse=node.orelse, - if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc) - return self.visit(node) - - def visit_ListComp(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.ListCompT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - type=types.TVar(), - elt=node.elt, generators=node.generators, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_Call(self, node): - node = self.generic_visit(node) - node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={}, - func=node.func, args=node.args, keywords=node.keywords, - starargs=node.starargs, kwargs=node.kwargs, - star_loc=node.star_loc, dstar_loc=node.dstar_loc, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return node - - def visit_Lambda(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.LambdaT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - type=types.TVar(), - args=node.args, body=node.body, - lambda_loc=node.lambda_loc, colon_loc=node.colon_loc, loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_ExceptHandler(self, node): - node = self.generic_visit(node) - if node.name is not None: - name_type = self._find_name(node.name, node.name_loc) - else: - name_type = types.TVar() - node = asttyped.ExceptHandlerT( - name_type=name_type, - filter=node.type, name=node.name, body=node.body, - except_loc=node.except_loc, as_loc=node.as_loc, name_loc=node.name_loc, - colon_loc=node.colon_loc, loc=node.loc) - return node - - def visit_Raise(self, node): - node = self.generic_visit(node) - if node.cause: - diag = diagnostic.Diagnostic("error", - "'raise from' syntax is not supported", {}, - node.from_loc) - self.engine.process(diag) - return node - - def visit_For(self, node): - node = self.generic_visit(node) - node = asttyped.ForT( - target=node.target, iter=node.iter, body=node.body, orelse=node.orelse, - trip_count=None, trip_interval=None, - keyword_loc=node.keyword_loc, in_loc=node.in_loc, for_colon_loc=node.for_colon_loc, - else_loc=node.else_loc, else_colon_loc=node.else_colon_loc, loc=node.loc) - return node - - def visit_withitem(self, node): - node = self.generic_visit(node) - node = asttyped.withitemT( - context_expr=node.context_expr, optional_vars=node.optional_vars, - enter_type=types.TVar(), exit_type=types.TVar(), - as_loc=node.as_loc, loc=node.loc) - return node - - # Unsupported visitors - # - def visit_unsupported(self, node): - diag = diagnostic.Diagnostic("fatal", - "this syntax is not supported", {}, - node.loc) - self.engine.process(diag) - - # expr - visit_Dict = visit_unsupported - visit_DictComp = visit_unsupported - visit_Ellipsis = visit_unsupported - visit_GeneratorExp = visit_unsupported - # visit_Set = visit_unsupported - visit_SetComp = visit_unsupported - visit_Starred = visit_unsupported - visit_Yield = visit_unsupported - visit_YieldFrom = visit_unsupported - - # stmt - visit_Delete = visit_unsupported - visit_Import = visit_unsupported - visit_ImportFrom = visit_unsupported diff --git a/artiq/compiler/transforms/cast_monomorphizer.py b/artiq/compiler/transforms/cast_monomorphizer.py deleted file mode 100644 index c0935ff1f..000000000 --- a/artiq/compiler/transforms/cast_monomorphizer.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -:class:`CastMonomorphizer` uses explicit casts to monomorphize -expressions of undetermined integer type to either 32 or 64 bits. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins, asttyped - -class CastMonomorphizer(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_CallT(self, node): - if (types.is_builtin(node.func.type, "int") or - types.is_builtin(node.func.type, "int32") or - types.is_builtin(node.func.type, "int64")): - typ = node.type.find() - if (not types.is_var(typ["width"]) and - len(node.args) == 1 and - builtins.is_int(node.args[0].type) and - types.is_var(node.args[0].type.find()["width"])): - if isinstance(node.args[0], asttyped.BinOpT): - # Binary operations are a bit special: they can widen, and so their - # return type is indeterminate until both argument types are fully known. - # In case we first monomorphize the return type, and then some argument type, - # and argument type is wider than return type, we'll introduce a conflict. - return - - node.args[0].type.unify(typ) - - if types.is_builtin(node.func.type, "int") or \ - types.is_builtin(node.func.type, "round"): - typ = node.type.find() - if types.is_var(typ["width"]): - typ["width"].unify(types.TValue(32)) - - self.generic_visit(node) - - def visit_CoerceT(self, node): - if isinstance(node.value, asttyped.NumT) and \ - builtins.is_int(node.type) and \ - builtins.is_int(node.value.type) and \ - not types.is_var(node.type["width"]) and \ - types.is_var(node.value.type["width"]): - node.value.type.unify(node.type) - - self.generic_visit(node) diff --git a/artiq/compiler/transforms/constant_hoister.py b/artiq/compiler/transforms/constant_hoister.py deleted file mode 100644 index ea51046aa..000000000 --- a/artiq/compiler/transforms/constant_hoister.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -:class:`ConstantHoister` is a code motion transform: -it moves any invariant loads to the earliest point where -they may be executed. -""" - -from .. import types, ir - -class ConstantHoister: - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - entry = func.entry() - worklist = set(func.instructions()) - moved = set() - while len(worklist) > 0: - insn = worklist.pop() - - if (isinstance(insn, ir.GetAttr) and insn not in moved and - types.is_instance(insn.object().type) and - insn.attr in insn.object().type.constant_attributes): - has_variant_operands = False - index_in_entry = 0 - for operand in insn.operands: - if isinstance(operand, ir.Argument): - pass - elif isinstance(operand, ir.Instruction) and operand.basic_block == entry: - index_in_entry = entry.index(operand) + 1 - else: - has_variant_operands = True - break - - if has_variant_operands: - continue - - insn.remove_from_parent() - entry.instructions.insert(index_in_entry, insn) - moved.add(insn) - - for use in insn.uses: - worklist.add(use) diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py deleted file mode 100644 index 608a46d55..000000000 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -:class:`DeadCodeEliminator` is a dead code elimination transform: -it only basic blocks with no predecessors as well as unused -instructions without side effects. -""" - -from .. import ir - -class DeadCodeEliminator: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - modified = True - while modified: - modified = False - for block in list(func.basic_blocks): - if not any(block.predecessors()) and block != func.entry(): - self.remove_block(block) - modified = True - - modified = True - while modified: - modified = False - for insn in func.instructions(): - # Note that GetLocal is treated as an impure operation: - # the local access validator has to observe it to emit - # a diagnostic for reads of uninitialized locals, and - # it also has to run after the interleaver, but interleaver - # doesn't like to work with IR before DCE. - if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce, - ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure, - ir.Offset)) \ - and not any(insn.uses): - insn.erase() - modified = True - - def remove_block(self, block): - # block.uses are updated while iterating - for use in set(block.uses): - if isinstance(use, ir.Phi): - use.remove_incoming_block(block) - if not any(use.operands): - self.remove_instruction(use) - elif isinstance(use, ir.SetLocal): - # setlocal %env, %block is only used for lowering finally - use.erase() - else: - assert False - - block.erase() - - def remove_instruction(self, insn): - for use in set(insn.uses): - if isinstance(use, ir.Phi): - use.remove_incoming_value(insn) - if not any(use.operands): - self.remove_instruction(use) - else: - assert False - - insn.erase() diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py deleted file mode 100644 index fdc268d4f..000000000 --- a/artiq/compiler/transforms/inferencer.py +++ /dev/null @@ -1,1777 +0,0 @@ -""" -:class:`Inferencer` performs unification-based inference on a typedtree. -""" - -from collections import OrderedDict -from pythonparser import algorithm, diagnostic, ast -from .. import asttyped, types, builtins -from .typedtree_printer import TypedtreePrinter -from artiq.experiment import kernel - - -def is_nested_empty_list(node): - """If the passed AST node is an empty list, or a regularly nested list thereof, - returns the number of nesting layers, or ``None`` otherwise. - - For instance, ``is_nested_empty_list([]) == 1`` and - ``is_nested_empty_list([[], []]) == 2``, but - ``is_nested_empty_list([[[]], []]) == None`` as the number of nesting layers doesn't - match. - """ - if not isinstance(node, ast.List): - return None - if not node.elts: - return 1 - result = is_nested_empty_list(node.elts[0]) - if result is None: - return None - for elt in node.elts[:1]: - if result != is_nested_empty_list(elt): - return None - return result + 1 - - -class Inferencer(algorithm.Visitor): - """ - :class:`Inferencer` infers types by recursively applying the unification - algorithm. It does not treat inability to infer a concrete type as an error; - the result can still contain type variables. - - :class:`Inferencer` is idempotent, but does not guarantee that it will - perform all possible inference in a single pass. - """ - - def __init__(self, engine): - self.engine = engine - self.function = None # currently visited function, for Return inference - self.in_loop = False - self.has_return = False - - def _unify(self, typea, typeb, loca, locb, makenotes=None, when=""): - try: - typea.unify(typeb) - except types.UnificationError as e: - printer = types.TypePrinter() - - if makenotes: - notes = makenotes(printer, typea, typeb, loca, locb) - else: - notes = [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca) - ] - if locb: - notes.append( - diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb)) - - highlights = [locb] if locb else [] - if e.typea.find() == typea.find() and e.typeb.find() == typeb.find() or \ - e.typeb.find() == typea.find() and e.typea.find() == typeb.find(): - diag = diagnostic.Diagnostic("error", - "cannot unify {typea} with {typeb}{when}", - {"typea": printer.name(typea), "typeb": printer.name(typeb), - "when": when}, - loca, highlights, notes) - else: # give more detail - diag = diagnostic.Diagnostic("error", - "cannot unify {typea} with {typeb}{when}: {fraga} is incompatible with {fragb}", - {"typea": printer.name(typea), "typeb": printer.name(typeb), - "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb), - "when": when}, - loca, highlights, notes) - self.engine.process(diag) - - # makenotes for the case where types of multiple elements are unified - # with the type of parent expression - def _makenotes_elts(self, elts, kind): - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "{kind} of type {typea}", - {"kind": kind, "typea": printer.name(elts[0].type)}, - elts[0].loc), - diagnostic.Diagnostic("note", - "{kind} of type {typeb}", - {"kind": kind, "typeb": printer.name(typeb)}, - locb) - ] - return makenotes - - def visit_ListT(self, node): - self.generic_visit(node) - elt_type_loc = node.loc - for elt in node.elts: - self._unify(node.type["elt"], elt.type, - elt_type_loc, elt.loc, - self._makenotes_elts(node.elts, "a list element")) - elt_type_loc = elt.loc - - def visit_AttributeT(self, node): - self.generic_visit(node) - self._unify_attribute(result_type=node.type, value_node=node.value, - attr_name=node.attr, attr_loc=node.attr_loc, - loc=node.loc) - - def _unify_method_self(self, method_type, attr_name, attr_loc, loc, self_loc): - self_type = types.get_method_self(method_type) - function_type = types.get_method_function(method_type) - - if len(function_type.args) < 1: - diag = diagnostic.Diagnostic("error", - "function '{attr}{type}' of class '{class}' cannot accept a self argument", - {"attr": attr_name, "type": types.TypePrinter().name(function_type), - "class": self_type.name}, - loc) - self.engine.process(diag) - else: - def makenotes(printer, typea, typeb, loca, locb): - if attr_loc is None: - msgb = "reference to an instance with a method '{attr}{typeb}'" - else: - msgb = "reference to a method '{attr}{typeb}'" - - return [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - msgb, - {"attr": attr_name, - "typeb": printer.name(function_type)}, - locb) - ] - - self._unify(self_type, list(function_type.args.values())[0], - self_loc, loc, - makenotes=makenotes, - when=" while inferring the type for self argument") - - def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc): - object_type = value_node.type.find() - if not types.is_var(object_type): - if attr_name in object_type.attributes: - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(object_type)}, - value_node.loc) - ] - - attr_type = object_type.attributes[attr_name] - self._unify(result_type, attr_type, loc, None, - makenotes=makenotes, when=" for attribute '{}'".format(attr_name)) - elif types.is_instance(object_type) and \ - attr_name in object_type.constructor.attributes: - attr_type = object_type.constructor.attributes[attr_name].find() - if types.is_function(attr_type): - # Convert to a method. - attr_type = types.TMethod(object_type, attr_type) - self._unify_method_self(attr_type, attr_name, attr_loc, loc, value_node.loc) - elif types.is_rpc(attr_type): - # Convert to a method. We don't have to bother typechecking - # the self argument, since for RPCs anything goes. - attr_type = types.TMethod(object_type, attr_type) - - if not types.is_var(attr_type): - self._unify(result_type, attr_type, - loc, None) - else: - if attr_loc.source_buffer == value_node.loc.source_buffer: - highlights, notes = [value_node.loc], [] - else: - # This happens when the object being accessed is embedded - # from the host program. - note = diagnostic.Diagnostic("note", - "object being accessed", {}, - value_node.loc) - highlights, notes = [], [note] - - diag = diagnostic.Diagnostic("error", - "type {type} does not have an attribute '{attr}'", - {"type": types.TypePrinter().name(object_type), "attr": attr_name}, - attr_loc, highlights, notes) - self.engine.process(diag) - - def _unify_iterable(self, element, collection): - if builtins.is_bytes(collection.type) or builtins.is_bytearray(collection.type): - self._unify(element.type, builtins.get_iterable_elt(collection.type), - element.loc, None) - elif builtins.is_array(collection.type): - array_type = collection.type.find() - elem_dims = array_type["num_dims"].value - 1 - if elem_dims > 0: - elem_type = builtins.TArray(array_type["elt"], types.TValue(elem_dims)) - else: - elem_type = array_type["elt"] - self._unify(element.type, elem_type, element.loc, collection.loc) - elif builtins.is_iterable(collection.type) and not builtins.is_str(collection.type): - rhs_type = collection.type.find() - rhs_wrapped_lhs_type = types.TMono(rhs_type.name, {"elt": element.type}) - self._unify(rhs_wrapped_lhs_type, rhs_type, - element.loc, collection.loc) - elif not types.is_var(collection.type): - diag = diagnostic.Diagnostic("error", - "type {type} is not iterable", - {"type": types.TypePrinter().name(collection.type)}, - collection.loc, []) - self.engine.process(diag) - - def visit_Index(self, node): - self.generic_visit(node) - value = node.value - if types.is_tuple(value.type): - for elt in value.type.find().elts: - self._unify(elt, builtins.TInt(), - value.loc, None) - else: - self._unify(value.type, builtins.TInt(), - value.loc, None) - - def visit_SliceT(self, node): - self.generic_visit(node) - if (node.lower, node.upper, node.step) == (None, None, None): - self._unify(node.type, builtins.TInt32(), - node.loc, None) - else: - self._unify(node.type, builtins.TInt(), - node.loc, None) - for operand in (node.lower, node.upper, node.step): - if operand is not None: - self._unify(operand.type, node.type, - operand.loc, None) - - def visit_ExtSlice(self, node): - diag = diagnostic.Diagnostic("error", - "multi-dimensional slices are not supported", {}, - node.loc, []) - self.engine.process(diag) - - def visit_SubscriptT(self, node): - self.generic_visit(node) - if isinstance(node.slice, ast.Index): - if types.is_tuple(node.slice.value.type): - if types.is_var(node.value.type): - return - if not builtins.is_array(node.value.type): - diag = diagnostic.Diagnostic( - "error", - "multi-dimensional indexing only supported for arrays, not {type}", - {"type": types.TypePrinter().name(node.value.type)}, - node.loc, []) - self.engine.process(diag) - return - num_idxs = len(node.slice.value.type.find().elts) - array_type = node.value.type.find() - num_dims = array_type["num_dims"].value - remaining_dims = num_dims - num_idxs - if remaining_dims < 0: - diag = diagnostic.Diagnostic( - "error", - "too many indices for array of dimension {num_dims}", - {"num_dims": num_dims}, node.slice.loc, []) - self.engine.process(diag) - return - if remaining_dims == 0: - self._unify(node.type, array_type["elt"], node.loc, - node.value.loc) - else: - self._unify( - node.type, - builtins.TArray(array_type["elt"], remaining_dims)) - else: - self._unify_iterable(element=node, collection=node.value) - elif isinstance(node.slice, ast.Slice): - if builtins.is_array(node.value.type): - if node.slice.step is not None: - diag = diagnostic.Diagnostic( - "error", - "strided slicing not yet supported for NumPy arrays", {}, - node.slice.step.loc, []) - self.engine.process(diag) - return - self._unify(node.type, node.value.type, node.loc, node.value.loc) - else: # ExtSlice - pass # error emitted above - - def visit_IfExpT(self, node): - self.generic_visit(node) - self._unify(node.test.type, builtins.TBool(), node.test.loc, None) - self._unify(node.body.type, node.orelse.type, - node.body.loc, node.orelse.loc) - self._unify(node.type, node.body.type, - node.loc, None) - - def visit_BoolOpT(self, node): - self.generic_visit(node) - for value in node.values: - self._unify(node.type, value.type, - node.loc, value.loc, self._makenotes_elts(node.values, "an operand")) - - def visit_UnaryOpT(self, node): - self.generic_visit(node) - operand_type = node.operand.type.find() - if isinstance(node.op, ast.Not): - self._unify(node.type, builtins.TBool(), - node.loc, None) - elif isinstance(node.op, ast.Invert): - if builtins.is_int(operand_type): - self._unify(node.type, operand_type, - node.loc, None) - elif not types.is_var(operand_type): - diag = diagnostic.Diagnostic("error", - "expected '~' operand to be of integer type, not {type}", - {"type": types.TypePrinter().name(operand_type)}, - node.operand.loc) - self.engine.process(diag) - else: # UAdd, USub - if types.is_var(operand_type): - return - - if builtins.is_numeric(operand_type): - self._unify(node.type, operand_type, node.loc, None) - return - - if builtins.is_array(operand_type): - elt = operand_type.find()["elt"] - if builtins.is_numeric(elt): - self._unify(node.type, operand_type, node.loc, None) - return - if types.is_var(elt): - return - - diag = diagnostic.Diagnostic("error", - "expected unary '{op}' operand to be of numeric type, not {type}", - {"op": node.op.loc.source(), - "type": types.TypePrinter().name(operand_type)}, - node.operand.loc) - self.engine.process(diag) - - def visit_CoerceT(self, node): - self.generic_visit(node) - if builtins.is_numeric(node.type) and builtins.is_numeric(node.value.type): - pass - elif (builtins.is_array(node.type) and builtins.is_array(node.value.type) - and builtins.is_numeric(node.type.find()["elt"]) - and builtins.is_numeric(node.value.type.find()["elt"])): - pass - else: - printer = types.TypePrinter() - note = diagnostic.Diagnostic("note", - "expression that required coercion to {typeb}", - {"typeb": printer.name(node.type)}, - node.other_value.loc) - diag = diagnostic.Diagnostic("error", - "cannot coerce {typea} to {typeb}", - {"typea": printer.name(node.value.type), "typeb": printer.name(node.type)}, - node.loc, notes=[note]) - self.engine.process(diag) - - def _coerce_one(self, typ, coerced_node, other_node): - if coerced_node.type.find() == typ.find(): - return coerced_node - elif isinstance(coerced_node, asttyped.CoerceT): - node = coerced_node - node.type.unify(typ) - node.other_value = other_node - else: - node = asttyped.CoerceT(type=typ, value=coerced_node, other_value=other_node, - loc=coerced_node.loc) - self.visit(node) - return node - - def _coerce_numeric(self, nodes, map_return=lambda typ: typ, map_node_type =lambda typ:typ): - # See https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex. - node_types = [] - for node in nodes: - if isinstance(node, asttyped.CoerceT): - # If we already know exactly what we coerce this value to, use that type, - # or we'll get an unification error in case the coerced type is not the same - # as the type of the coerced value. - # Otherwise, use the potentially more specific subtype when considering possible - # coercions, or we may get stuck. - if node.type.fold(False, lambda acc, ty: acc or types.is_var(ty)): - node_types.append(node.value.type) - else: - node_types.append(node.type) - else: - node_types.append(node.type) - node_types = [map_node_type(typ) for typ in node_types] - if any(map(types.is_var, node_types)): # not enough info yet - return - elif not all(map(builtins.is_numeric, node_types)): - err_node = next(filter(lambda node: not builtins.is_numeric(node.type), nodes)) - diag = diagnostic.Diagnostic("error", - "cannot coerce {type} to a numeric type", - {"type": types.TypePrinter().name(err_node.type)}, - err_node.loc, []) - self.engine.process(diag) - return - elif any(map(builtins.is_float, node_types)): - typ = builtins.TFloat() - elif any(map(builtins.is_int, node_types)): - widths = list(map(builtins.get_int_width, node_types)) - if all(widths): - typ = builtins.TInt(types.TValue(max(widths))) - else: - typ = builtins.TInt() - else: - assert False - - return map_return(typ) - - def _order_by_pred(self, pred, left, right): - if pred(left.type): - return left, right - elif pred(right.type): - return right, left - else: - assert False - - def _coerce_binary_broadcast_op(self, left, right, map_return_elt, op_loc): - def num_dims(typ): - if builtins.is_array(typ): - # TODO: If number of dimensions is ever made a non-fixed parameter, - # need to acutally unify num_dims in _coerce_binop/…. - return typ.find()["num_dims"].value - return 0 - - left_dims = num_dims(left.type) - right_dims = num_dims(right.type) - if left_dims != right_dims and left_dims != 0 and right_dims != 0: - # Mismatch (only scalar broadcast supported for now). - note1 = diagnostic.Diagnostic("note", "operand of dimension {num_dims}", - {"num_dims": left_dims}, left.loc) - note2 = diagnostic.Diagnostic("note", "operand of dimension {num_dims}", - {"num_dims": right_dims}, right.loc) - diag = diagnostic.Diagnostic( - "error", "dimensions of '{op}' array operands must match", - {"op": op_loc.source()}, op_loc, [left.loc, right.loc], [note1, note2]) - self.engine.process(diag) - return - - def map_node_type(typ): - if not builtins.is_array(typ): - # This is a single value broadcast across the array. - return typ - return typ.find()["elt"] - - # Figure out result type, handling broadcasts. - result_dims = left_dims if left_dims else right_dims - def map_return(typ): - elt = map_return_elt(typ) - result = builtins.TArray(elt=elt, num_dims=result_dims) - left = builtins.TArray(elt=elt, num_dims=left_dims) if left_dims else elt - right = builtins.TArray(elt=elt, num_dims=right_dims) if right_dims else elt - return (result, left, right) - - return self._coerce_numeric((left, right), - map_return=map_return, - map_node_type=map_node_type) - - def _coerce_binop(self, op, left, right): - if isinstance(op, ast.MatMult): - if types.is_var(left.type) or types.is_var(right.type): - return - - def num_dims(operand): - if not builtins.is_array(operand.type): - diag = diagnostic.Diagnostic( - "error", - "expected matrix multiplication operand to be of array type, not {type}", - { - "op": op.loc.source(), - "type": types.TypePrinter().name(operand.type) - }, op.loc, [operand.loc]) - self.engine.process(diag) - return - num_dims = operand.type.find()["num_dims"].value - if num_dims not in (1, 2): - diag = diagnostic.Diagnostic( - "error", - "expected matrix multiplication operand to be 1- or 2-dimensional, not {type}", - { - "op": op.loc.source(), - "type": types.TypePrinter().name(operand.type) - }, op.loc, [operand.loc]) - self.engine.process(diag) - return - return num_dims - - left_dims = num_dims(left) - if not left_dims: - return - right_dims = num_dims(right) - if not right_dims: - return - - def map_node_type(typ): - return typ.find()["elt"] - - def map_return(typ): - if left_dims == 1: - if right_dims == 1: - result_dims = 0 - else: - result_dims = 1 - elif right_dims == 1: - result_dims = 1 - else: - result_dims = 2 - result = typ if result_dims == 0 else builtins.TArray( - typ, result_dims) - return (result, builtins.TArray(typ, left_dims), - builtins.TArray(typ, right_dims)) - - return self._coerce_numeric((left, right), - map_return=map_return, - map_node_type=map_node_type) - elif builtins.is_array(left.type) or builtins.is_array(right.type): - # Operations on arrays are element-wise (possibly using broadcasting). - - # TODO: Allow only for integer arrays. - # allowed_int_array_ops = (ast.BitAnd, ast.BitOr, ast.BitXor, ast.LShift, - # ast.RShift) - allowed_array_ops = (ast.Add, ast.Mult, ast.FloorDiv, ast.Mod, - ast.Pow, ast.Sub, ast.Div) - if not isinstance(op, allowed_array_ops): - diag = diagnostic.Diagnostic( - "error", "operator '{op}' not valid for array types", - {"op": op.loc.source()}, op.loc) - self.engine.process(diag) - return - - def map_result(typ): - if isinstance(op, ast.Div): - return builtins.TFloat() - return typ - return self._coerce_binary_broadcast_op(left, right, map_result, op.loc) - elif isinstance(op, (ast.BitAnd, ast.BitOr, ast.BitXor, - ast.LShift, ast.RShift)): - # bitwise operators require integers - for operand in (left, right): - if not types.is_var(operand.type) and not builtins.is_int(operand.type): - diag = diagnostic.Diagnostic("error", - "expected '{op}' operand to be of integer type, not {type}", - {"op": op.loc.source(), - "type": types.TypePrinter().name(operand.type)}, - op.loc, [operand.loc]) - self.engine.process(diag) - return - - return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) - elif isinstance(op, ast.Add): - # add works on numbers and also collections - if builtins.is_collection(left.type) or builtins.is_collection(right.type): - collection, other = \ - self._order_by_pred(builtins.is_collection, left, right) - if types.is_tuple(collection.type): - pred, kind = types.is_tuple, "tuple" - elif builtins.is_list(collection.type): - pred, kind = builtins.is_list, "list" - else: - assert False - - if types.is_var(other.type): - return - - if not pred(other.type): - printer = types.TypePrinter() - note1 = diagnostic.Diagnostic("note", - "{kind} of type {typea}", - {"typea": printer.name(collection.type), "kind": kind}, - collection.loc) - note2 = diagnostic.Diagnostic("note", - "{typeb}, which cannot be added to a {kind}", - {"typeb": printer.name(other.type), "kind": kind}, - other.loc) - diag = diagnostic.Diagnostic("error", - "expected every '+' operand to be a {kind} in this context", - {"kind": kind}, - op.loc, [other.loc, collection.loc], - [note1, note2]) - self.engine.process(diag) - return - - if types.is_tuple(collection.type): - return types.TTuple(left.type.find().elts + - right.type.find().elts), left.type, right.type - elif builtins.is_list(collection.type): - self._unify(left.type, right.type, - left.loc, right.loc) - return left.type, left.type, right.type - elif (builtins.is_str(left.type) or builtins.is_str(right.type) or - builtins.is_bytes(left.type) or builtins.is_bytes(right.type)): - self._unify(left.type, right.type, - left.loc, right.loc) - return left.type, left.type, right.type - else: - return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) - elif isinstance(op, ast.Mult): - # mult works on numbers and also number & collection - if types.is_tuple(left.type) or types.is_tuple(right.type): - tuple_, other = self._order_by_pred(types.is_tuple, left, right) - diag = diagnostic.Diagnostic("error", - "passing tuples to '*' is not supported", {}, - op.loc, [tuple_.loc]) - self.engine.process(diag) - return - elif builtins.is_list(left.type) or builtins.is_list(right.type): - list_, other = self._order_by_pred(builtins.is_list, left, right) - if not builtins.is_int(other.type) and not types.is_var(other.type): - printer = types.TypePrinter() - note1 = diagnostic.Diagnostic("note", - "list operand of type {typea}", - {"typea": printer.name(list_.type)}, - list_.loc) - note2 = diagnostic.Diagnostic("note", - "operand of type {typeb}, which is not a valid repetition amount", - {"typeb": printer.name(other.type)}, - other.loc) - diag = diagnostic.Diagnostic("error", - "expected '*' operands to be a list and an integer in this context", {}, - op.loc, [list_.loc, other.loc], - [note1, note2]) - self.engine.process(diag) - return - - return list_.type, left.type, right.type - else: - return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) - elif isinstance(op, (ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): - # numeric operators work on any kind of number - return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) - elif isinstance(op, ast.Div): - # division always returns a float - return self._coerce_numeric((left, right), - lambda typ: (builtins.TFloat(), builtins.TFloat(), builtins.TFloat())) - else: - diag = diagnostic.Diagnostic("error", - "operator '{op}' is not supported", {"op": op.loc.source()}, - op.loc) - self.engine.process(diag) - return - - def visit_BinOpT(self, node): - self.generic_visit(node) - coerced = self._coerce_binop(node.op, node.left, node.right) - if coerced: - return_type, left_type, right_type = coerced - node.left = self._coerce_one(left_type, node.left, other_node=node.right) - node.right = self._coerce_one(right_type, node.right, other_node=node.left) - - def makenotes(printer, typea, typeb, loca, locb): - def makenote(typ, coerced, loc): - if typ == coerced: - return diagnostic.Diagnostic("note", - "expression of type {type}", - {"type": printer.name(typ)}, - loc) - else: - return diagnostic.Diagnostic("note", - "expression of type {typea} (coerced to {typeb})", - {"typea": printer.name(typ), - "typeb": printer.name(coerced)}, - loc) - - if node.type == return_type: - note = diagnostic.Diagnostic("note", - "expression of type {type}", - {"type": printer.name(typea)}, - loca) - else: - note = diagnostic.Diagnostic("note", - "expression of type {typea} (but {typeb} was expected)", - {"typea": printer.name(typea), - "typeb": printer.name(typeb)}, - loca) - - return [ - makenote(node.left.type, left_type, node.left.loc), - makenote(node.right.type, right_type, node.right.loc), - note - ] - - self._unify(node.type, return_type, - node.loc, None, - makenotes=makenotes) - - def visit_CompareT(self, node): - self.generic_visit(node) - pairs = zip([node.left] + node.comparators, node.comparators) - if all(map(lambda op: isinstance(op, (ast.Is, ast.IsNot)), node.ops)): - for left, right in pairs: - self._unify(left.type, right.type, - left.loc, right.loc) - elif all(map(lambda op: isinstance(op, (ast.In, ast.NotIn)), node.ops)): - for left, right in pairs: - self._unify_iterable(element=left, collection=right) - else: # Eq, NotEq, Lt, LtE, Gt, GtE - operands = [node.left] + node.comparators - operand_types = [operand.type for operand in operands] - if any(map(builtins.is_collection, operand_types)): - for left, right in pairs: - self._unify(left.type, right.type, - left.loc, right.loc) - elif any(map(builtins.is_numeric, operand_types)): - typ = self._coerce_numeric(operands) - if typ: - try: - other_node = next(filter(lambda operand: operand.type.find() == typ.find(), - operands)) - except StopIteration: - # can't find an argument with an exact type, meaning - # the return value is more generic than any of the inputs, meaning - # the type is known (typ is not None), but its width is not - def wide_enough(opreand): - return types.is_mono(opreand.type) and \ - opreand.type.find().name == typ.find().name - other_node = next(filter(wide_enough, operands)) - node.left, *node.comparators = \ - [self._coerce_one(typ, operand, other_node) for operand in operands] - else: - pass # No coercion required. - self._unify(node.type, builtins.TBool(), - node.loc, None) - - def visit_ListCompT(self, node): - if len(node.generators) > 1: - diag = diagnostic.Diagnostic("error", - "multiple for clauses in comprehensions are not supported", {}, - node.generators[1].for_loc) - self.engine.process(diag) - - self.generic_visit(node) - self._unify(node.type, builtins.TList(node.elt.type), - node.loc, None) - - def visit_comprehension(self, node): - if any(node.ifs): - diag = diagnostic.Diagnostic("error", - "if clauses in comprehensions are not supported", {}, - node.if_locs[0]) - self.engine.process(diag) - - self.generic_visit(node) - self._unify_iterable(element=node.target, collection=node.iter) - - def visit_builtin_call(self, node): - typ = node.func.type.find() - - def valid_form(signature): - return diagnostic.Diagnostic("note", - "{func} can be invoked as: {signature}", - {"func": typ.name, "signature": signature}, - node.func.loc) - - def diagnose(valid_forms): - printer = types.TypePrinter() - args = [printer.name(arg.type) for arg in node.args] - args += ["%s=%s" % (kw.arg, printer.name(kw.value.type)) for kw in node.keywords] - - diag = diagnostic.Diagnostic("error", - "{func} cannot be invoked with the arguments ({args})", - {"func": typ.name, "args": ", ".join(args)}, - node.func.loc, notes=valid_forms) - self.engine.process(diag) - - def simple_form(info, arg_types=[], return_type=builtins.TNone()): - self._unify(node.type, return_type, - node.loc, None) - - if len(node.args) == len(arg_types) and len(node.keywords) == 0: - for index, arg_type in enumerate(arg_types): - self._unify(node.args[index].type, arg_type, - node.args[index].loc, None) - else: - diagnose([ valid_form(info) ]) - - if types.is_exn_constructor(typ): - valid_forms = lambda: [ - valid_form("{exn}() -> {exn}".format(exn=typ.name)), - valid_form("{exn}(message:str) -> {exn}".format(exn=typ.name)), - valid_form("{exn}(message:str, param1:numpy.int64) -> {exn}".format(exn=typ.name)), - valid_form("{exn}(message:str, param1:numpy.int64, " - "param2:numpy.int64) -> {exn}".format(exn=typ.name)), - valid_form("{exn}(message:str, param1:numpy.int64, " - "param2:numpy.int64, param3:numpy.int64) " - "-> {exn}".format(exn=typ.name)), - ] - - if len(node.args) == 0 and len(node.keywords) == 0: - pass # Default message, zeroes as parameters - elif len(node.args) >= 1 and len(node.args) <= 4 and len(node.keywords) == 0: - message, *params = node.args - - self._unify(message.type, builtins.TStr(), - message.loc, None) - for param in params: - self._unify(param.type, builtins.TInt64(), - param.loc, None) - else: - diagnose(valid_forms()) - - self._unify(node.type, typ.instance, - node.loc, None) - elif types.is_builtin(typ, "bool"): - valid_forms = lambda: [ - valid_form("bool() -> bool"), - valid_form("bool(x:'a) -> bool") - ] - - if len(node.args) == 0 and len(node.keywords) == 0: - pass # False - elif len(node.args) == 1 and len(node.keywords) == 0: - arg, = node.args - pass # anything goes - else: - diagnose(valid_forms()) - - self._unify(node.type, builtins.TBool(), - node.loc, None) - elif types.is_builtin(typ, "int") or \ - types.is_builtin(typ, "int32") or types.is_builtin(typ, "int64"): - if types.is_builtin(typ, "int"): - valid_forms = lambda: [ - valid_form("int() -> numpy.int?"), - valid_form("int(x:'a) -> numpy.int? where 'a is numeric") - ] - result_typ = builtins.TInt() - elif types.is_builtin(typ, "int32"): - valid_forms = lambda: [ - valid_form("numpy.int32() -> numpy.int32"), - valid_form("numpy.int32(x:'a) -> numpy.int32 where 'a is numeric") - ] - result_typ = builtins.TInt32() - elif types.is_builtin(typ, "int64"): - valid_forms = lambda: [ - valid_form("numpy.int64() -> numpy.int64"), - valid_form("numpy.int64(x:'a) -> numpy.int64 where 'a is numeric") - ] - result_typ = builtins.TInt64() - - self._unify(node.type, result_typ, - node.loc, None) - - if len(node.args) == 0 and len(node.keywords) == 0: - pass # 0 - elif len(node.args) == 1 and len(node.keywords) == 0 and \ - types.is_var(node.args[0].type): - pass # undetermined yet - elif len(node.args) == 1 and len(node.keywords) == 0 and \ - builtins.is_numeric(node.args[0].type): - self._unify(node.type, result_typ, - node.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "float"): - valid_forms = lambda: [ - valid_form("float() -> float"), - valid_form("float(x:'a) -> float where 'a is numeric") - ] - - self._unify(node.type, builtins.TFloat(), - node.loc, None) - - if len(node.args) == 0 and len(node.keywords) == 0: - pass # 0.0 - elif len(node.args) == 1 and len(node.keywords) == 0 and \ - types.is_var(node.args[0].type): - pass # undetermined yet - elif len(node.args) == 1 and len(node.keywords) == 0 and \ - builtins.is_numeric(node.args[0].type): - pass - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "str"): - diag = diagnostic.Diagnostic("error", - "strings currently cannot be constructed", {}, - node.loc) - self.engine.process(diag) - elif types.is_builtin(typ, "array"): - valid_forms = lambda: [ - valid_form("array(x:'a) -> array(elt='b) where 'a is iterable"), - valid_form("array(x:'a, dtype:'b) -> array(elt='b) where 'a is iterable") - ] - - explicit_dtype = None - keywords_acceptable = False - if len(node.keywords) == 0: - keywords_acceptable = True - elif len(node.keywords) == 1: - if node.keywords[0].arg == "dtype": - keywords_acceptable = True - explicit_dtype = node.keywords[0].value - if len(node.args) == 1 and keywords_acceptable: - arg, = node.args - - num_empty_dims = is_nested_empty_list(arg) - if num_empty_dims is not None: - # As a special case, following the behaviour of numpy.array (and - # repr() on ndarrays), consider empty lists to be exactly of the - # number of dimensions given, instead of potentially containing an - # unknown number of extra dimensions. - num_dims = num_empty_dims - - # The ultimate element type will be TVar initially, but we might be - # able to resolve it from context. - elt = arg.type - for _ in range(num_dims): - assert builtins.is_list(elt) - elt = elt.find()["elt"] - else: - # In the absence of any other information (there currently isn't a way - # to specify any), assume that all iterables are expandable into a - # (runtime-checked) rectangular array of the innermost element type. - elt = arg.type - num_dims = 0 - expected_dims = (node.type.find()["num_dims"].value - if builtins.is_array(node.type) else -1) - while True: - if num_dims == expected_dims: - # If we already know the number of dimensions of the result, - # stop so we can disambiguate the (innermost) element type of - # the argument if it is still unknown. - break - if types.is_var(elt): - # Can't make progress here because we don't know how many more - # dimensions might be "hidden" inside. - return - if not builtins.is_iterable(elt) or builtins.is_str(elt): - break - if builtins.is_array(elt): - num_dims += elt.find()["num_dims"].value - else: - num_dims += 1 - elt = builtins.get_iterable_elt(elt) - - if explicit_dtype is not None: - # TODO: Factor out type detection; support quoted type constructors - # (TList(TInt32), …)? - typ = explicit_dtype.type - if types.is_builtin(typ, "int32"): - elt = builtins.TInt32() - elif types.is_builtin(typ, "int64"): - elt = builtins.TInt64() - elif types.is_constructor(typ): - elt = typ.find().instance - else: - diag = diagnostic.Diagnostic( - "error", - "dtype argument of {builtin}() must be a valid constructor", - {"builtin": typ.find().name}, - node.func.loc, - notes=[note]) - self.engine.process(diag) - return - - if num_dims == 0: - note = diagnostic.Diagnostic( - "note", "this expression has type {type}", - {"type": types.TypePrinter().name(arg.type)}, arg.loc) - diag = diagnostic.Diagnostic( - "error", - "the argument of {builtin}() must be of an iterable type", - {"builtin": typ.find().name}, - node.func.loc, - notes=[note]) - self.engine.process(diag) - return - - self._unify(node.type, - builtins.TArray(elt, types.TValue(num_dims)), - node.loc, arg.loc) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "list"): - valid_forms = lambda: [ - valid_form("list() -> list(elt='a)"), - valid_form("list(x:'a) -> list(elt='b) where 'a is iterable") - ] - - self._unify(node.type, builtins.TList(), node.loc, None) - - if len(node.args) == 0 and len(node.keywords) == 0: - pass # [] - elif len(node.args) == 1 and len(node.keywords) == 0: - arg, = node.args - - if builtins.is_iterable(arg.type): - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "iterator returning elements of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "iterator returning elements of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - self._unify(node.type.find().params["elt"], - arg.type.find().params["elt"], - node.loc, arg.loc, makenotes=makenotes) - elif types.is_var(arg.type): - pass # undetermined yet - else: - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(arg.type)}, - arg.loc) - diag = diagnostic.Diagnostic("error", - "the argument of {builtin}() must be of an iterable type", - {"builtin": typ.find().name}, - node.func.loc, notes=[note]) - self.engine.process(diag) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "range"): - valid_forms = lambda: [ - valid_form("range(max:numpy.int?) -> range(elt=numpy.int?)"), - valid_form("range(min:numpy.int?, max:numpy.int?) " - "-> range(elt=numpy.int?)"), - valid_form("range(min:numpy.int?, max:numpy.int?, " - "step:numpy.int?) -> range(elt=numpy.int?)"), - ] - - range_elt = builtins.TInt(types.TVar()) - self._unify(node.type, builtins.TRange(range_elt), - node.loc, None) - - if len(node.args) in (1, 2, 3) and len(node.keywords) == 0: - for arg in node.args: - self._unify(arg.type, range_elt, - arg.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "len"): - valid_forms = lambda: [ - valid_form("len(x:'a) -> numpy.int?"), - ] - - if len(node.args) == 1 and len(node.keywords) == 0: - arg, = node.args - - if builtins.is_range(arg.type): - self._unify(node.type, builtins.get_iterable_elt(arg.type), - node.loc, None) - elif builtins.is_listish(arg.type): - # TODO: should be ssize_t-sized - self._unify(node.type, builtins.TInt32(), - node.loc, None) - elif types.is_var(arg.type): - pass # undetermined yet - else: - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(arg.type)}, - arg.loc) - diag = diagnostic.Diagnostic("error", - "the argument of len() must be of an iterable type", {}, - node.func.loc, notes=[note]) - self.engine.process(diag) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "round"): - valid_forms = lambda: [ - valid_form("round(x:float) -> numpy.int?"), - ] - - self._unify(node.type, builtins.TInt(), - node.loc, None) - - if len(node.args) == 1 and len(node.keywords) == 0: - arg, = node.args - - self._unify(arg.type, builtins.TFloat(), - arg.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "abs"): - fn = typ.name - - valid_forms = lambda: [ - valid_form("abs(x:numpy.int?) -> numpy.int?"), - valid_form("abs(x:float) -> float") - ] - - if len(node.args) == 1 and len(node.keywords) == 0: - (arg,) = node.args - if builtins.is_int(arg.type) or builtins.is_float(arg.type): - self._unify(arg.type, node.type, - arg.loc, node.loc) - elif types.is_var(arg.type): - pass # undetermined yet - else: - diag = diagnostic.Diagnostic("error", - "the arguments of abs() must be of a numeric type", {}, - node.func.loc) - self.engine.process(diag) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "min") or types.is_builtin(typ, "max"): - fn = typ.name - - valid_forms = lambda: [ - valid_form("{}(x:numpy.int?, y:numpy.int?) -> numpy.int?".format(fn)), - valid_form("{}(x:float, y:float) -> float".format(fn)) - ] - - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = node.args - - self._unify(arg0.type, arg1.type, - arg0.loc, arg1.loc) - - if builtins.is_int(arg0.type) or builtins.is_float(arg0.type): - self._unify(arg0.type, node.type, - arg0.loc, node.loc) - elif types.is_var(arg0.type): - pass # undetermined yet - else: - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(arg0.type)}, - arg0.loc) - diag = diagnostic.Diagnostic("error", - "the arguments of {fn}() must be of a numeric type", - {"fn": fn}, - node.func.loc, notes=[note]) - self.engine.process(diag) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "print"): - valid_forms = lambda: [ - valid_form("print(args...) -> None"), - ] - - self._unify(node.type, builtins.TNone(), - node.loc, None) - - if len(node.keywords) == 0: - # We can print any arguments. - pass - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "make_array"): - valid_forms = lambda: [ - valid_form("numpy.full(count:int32, value:'a) -> array(elt='a, num_dims=1)"), - valid_form("numpy.full(shape:(int32,)*'b, value:'a) -> array(elt='a, num_dims='b)"), - ] - - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = node.args - - if types.is_var(arg0.type): - return # undetermined yet - elif types.is_tuple(arg0.type): - num_dims = len(arg0.type.find().elts) - self._unify(arg0.type, types.TTuple([builtins.TInt32()] * num_dims), - arg0.loc, None) - else: - num_dims = 1 - self._unify(arg0.type, builtins.TInt32(), - arg0.loc, None) - - self._unify(node.type, builtins.TArray(num_dims=num_dims), - node.loc, None) - self._unify(arg1.type, node.type.find()["elt"], - arg1.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "numpy.transpose"): - valid_forms = lambda: [ - valid_form("transpose(x: array(elt='a, num_dims=1)) -> array(elt='a, num_dims=1)"), - valid_form("transpose(x: array(elt='a, num_dims=2)) -> array(elt='a, num_dims=2)") - ] - - if len(node.args) == 1 and len(node.keywords) == 0: - arg, = node.args - - if types.is_var(arg.type): - pass # undetermined yet - elif not builtins.is_array(arg.type): - note = diagnostic.Diagnostic( - "note", "this expression has type {type}", - {"type": types.TypePrinter().name(arg.type)}, arg.loc) - diag = diagnostic.Diagnostic( - "error", - "the argument of {builtin}() must be an array", - {"builtin": typ.find().name}, - node.func.loc, - notes=[note]) - self.engine.process(diag) - else: - num_dims = arg.type.find()["num_dims"].value - if num_dims not in (1, 2): - note = diagnostic.Diagnostic( - "note", "argument is {num_dims}-dimensional", - {"num_dims": num_dims}, arg.loc) - diag = diagnostic.Diagnostic( - "error", - "{builtin}() is currently only supported for up to " - "two-dimensional arrays", {"builtin": typ.find().name}, - node.func.loc, - notes=[note]) - self.engine.process(diag) - else: - self._unify(node.type, arg.type, node.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "rtio_log"): - valid_forms = lambda: [ - valid_form("rtio_log(channel:str, args...) -> None"), - ] - - self._unify(node.type, builtins.TNone(), - node.loc, None) - - if len(node.args) >= 1 and len(node.keywords) == 0: - arg = node.args[0] - - self._unify(arg.type, builtins.TStr(), - arg.loc, None) - else: - diagnose(valid_forms()) - elif types.is_builtin(typ, "now"): - simple_form("now() -> float", - [], builtins.TFloat()) - elif types.is_builtin(typ, "delay"): - simple_form("delay(time:float) -> None", - [builtins.TFloat()]) - elif types.is_builtin(typ, "at"): - simple_form("at(time:float) -> None", - [builtins.TFloat()]) - elif types.is_builtin(typ, "now_mu"): - simple_form("now_mu() -> numpy.int64", - [], builtins.TInt64()) - elif types.is_builtin(typ, "delay_mu"): - simple_form("delay_mu(time_mu:numpy.int64) -> None", - [builtins.TInt64()]) - elif types.is_builtin(typ, "at_mu"): - simple_form("at_mu(time_mu:numpy.int64) -> None", - [builtins.TInt64()]) - elif types.is_constructor(typ): - # An user-defined class. - self._unify(node.type, typ.find().instance, - node.loc, None) - elif types.is_builtin(typ, "kernel"): - # Ignored. - self._unify(node.type, builtins.TNone(), - node.loc, None) - else: - assert False - - def visit_CallT(self, node): - self.generic_visit(node) - - for (sigil_loc, vararg) in ((node.star_loc, node.starargs), - (node.dstar_loc, node.kwargs)): - if vararg: - diag = diagnostic.Diagnostic("error", - "variadic arguments are not supported", {}, - sigil_loc, [vararg.loc]) - self.engine.process(diag) - return - - typ = node.func.type.find() - - if types.is_var(typ): - return # not enough info yet - elif types.is_builtin(typ): - return self.visit_builtin_call(node) - elif types.is_rpc(typ): - self._unify(node.type, typ.ret, - node.loc, None) - return - elif not (types.is_function(typ) or types.is_method(typ)): - diag = diagnostic.Diagnostic("error", - "cannot call this expression of type {type}", - {"type": types.TypePrinter().name(typ)}, - node.func.loc, []) - self.engine.process(diag) - return - - if types.is_function(typ): - typ_arity = typ.arity() - typ_args = typ.args - typ_optargs = typ.optargs - typ_ret = typ.ret - else: - typ_self = types.get_method_self(typ) - typ_func = types.get_method_function(typ) - if types.is_var(typ_func): - return # not enough info yet - elif types.is_rpc(typ_func): - self._unify(node.type, typ_func.ret, - node.loc, None) - return - elif typ_func.arity() == 0: - return # error elsewhere - - method_args = list(typ_func.args.items()) - - self_arg_name, self_arg_type = method_args[0] - self._unify(self_arg_type, typ_self, - node.loc, None) - - typ_arity = typ_func.arity() - 1 - typ_args = OrderedDict(method_args[1:]) - typ_optargs = typ_func.optargs - typ_ret = typ_func.ret - - passed_args = dict() - - if len(node.args) > typ_arity: - note = diagnostic.Diagnostic("note", - "extraneous argument(s)", {}, - node.args[typ_arity].loc.join(node.args[-1].loc)) - diag = diagnostic.Diagnostic("error", - "this function of type {type} accepts at most {num} arguments", - {"type": types.TypePrinter().name(node.func.type), - "num": typ_arity}, - node.func.loc, [], [note]) - self.engine.process(diag) - return - - # Array broadcasting for functions explicitly marked as such. - if len(node.args) == typ_arity and types.is_broadcast_across_arrays(typ): - if typ_arity == 1: - arg_type = node.args[0].type.find() - if builtins.is_array(arg_type): - typ_arg, = typ_args.values() - self._unify(typ_arg, arg_type["elt"], node.args[0].loc, None) - self._unify(node.type, builtins.TArray(typ_ret, arg_type["num_dims"]), - node.loc, None) - return - elif typ_arity == 2: - if any(builtins.is_array(arg.type) for arg in node.args): - ret, arg0, arg1 = self._coerce_binary_broadcast_op( - node.args[0], node.args[1], lambda t: typ_ret, node.loc) - node.args[0] = self._coerce_one(arg0, node.args[0], - other_node=node.args[1]) - node.args[1] = self._coerce_one(arg1, node.args[1], - other_node=node.args[0]) - self._unify(node.type, ret, node.loc, None) - return - - for actualarg, (formalname, formaltyp) in \ - zip(node.args, list(typ_args.items()) + list(typ_optargs.items())): - self._unify(actualarg.type, formaltyp, - actualarg.loc, None) - passed_args[formalname] = actualarg.loc - - for keyword in node.keywords: - if keyword.arg in passed_args: - diag = diagnostic.Diagnostic("error", - "the argument '{name}' has been passed earlier as positional", - {"name": keyword.arg}, - keyword.arg_loc, [passed_args[keyword.arg]]) - self.engine.process(diag) - return - - if keyword.arg in typ_args: - self._unify(keyword.value.type, typ_args[keyword.arg], - keyword.value.loc, None) - elif keyword.arg in typ_optargs: - self._unify(keyword.value.type, typ_optargs[keyword.arg], - keyword.value.loc, None) - else: - note = diagnostic.Diagnostic("note", - "extraneous argument", {}, - keyword.loc) - diag = diagnostic.Diagnostic("error", - "this function of type {type} does not accept argument '{name}'", - {"type": types.TypePrinter().name(node.func.type), - "name": keyword.arg}, - node.func.loc, [], [note]) - self.engine.process(diag) - return - passed_args[keyword.arg] = keyword.arg_loc - - for formalname in typ_args: - if formalname not in passed_args: - note = diagnostic.Diagnostic("note", - "the called function is of type {type}", - {"type": types.TypePrinter().name(node.func.type)}, - node.func.loc) - diag = diagnostic.Diagnostic("error", - "mandatory argument '{name}' is not passed", - {"name": formalname}, - node.begin_loc.join(node.end_loc), [], [note]) - self.engine.process(diag) - return - - self._unify(node.type, typ_ret, - node.loc, None) - - def visit_LambdaT(self, node): - self.generic_visit(node) - signature_type = self._type_from_arguments(node.args, node.body.type) - if signature_type: - self._unify(node.type, signature_type, - node.loc, None) - - def visit_Assign(self, node): - self.generic_visit(node) - for target in node.targets: - self._unify(target.type, node.value.type, - target.loc, node.value.loc) - - def visit_AugAssign(self, node): - self.generic_visit(node) - coerced = self._coerce_binop(node.op, node.target, node.value) - if coerced: - return_type, target_type, value_type = coerced - - if isinstance(node.value, asttyped.CoerceT): - orig_value_type = node.value.value.type - else: - orig_value_type = node.value.type - - try: - node.target.type.unify(return_type) - except types.UnificationError as e: - printer = types.TypePrinter() - note = diagnostic.Diagnostic("note", - "expression of type {typec}", - {"typec": printer.name(orig_value_type)}, - node.value.loc) - diag = diagnostic.Diagnostic("error", - "the result of this operation has type {typeb}, " - "which cannot be assigned to a left-hand side of type {typea}", - {"typea": printer.name(node.target.type), - "typeb": printer.name(return_type)}, - node.op.loc, [node.target.loc], [note]) - self.engine.process(diag) - return - - try: - node.target.type.unify(target_type) - except types.UnificationError as e: - printer = types.TypePrinter() - note = diagnostic.Diagnostic("note", - "expression of type {typec}", - {"typec": printer.name(orig_value_type)}, - node.value.loc) - diag = diagnostic.Diagnostic("error", - "this operation requires the left-hand side of type {typea} " - "to be coerced to {typeb}, which cannot be done", - {"typea": printer.name(node.target.type), - "typeb": printer.name(target_type)}, - node.op.loc, [node.target.loc], [note]) - self.engine.process(diag) - return - - node.value = self._coerce_one(value_type, node.value, other_node=node.target) - - def visit_ForT(self, node): - old_in_loop, self.in_loop = self.in_loop, True - self.generic_visit(node) - self.in_loop = old_in_loop - self._unify_iterable(node.target, node.iter) - - def visit_While(self, node): - old_in_loop, self.in_loop = self.in_loop, True - self.generic_visit(node) - self.in_loop = old_in_loop - - def visit_Break(self, node): - if not self.in_loop: - diag = diagnostic.Diagnostic("error", - "break statement outside of a loop", {}, - node.keyword_loc) - self.engine.process(diag) - - def visit_Continue(self, node): - if not self.in_loop: - diag = diagnostic.Diagnostic("error", - "continue statement outside of a loop", {}, - node.keyword_loc) - self.engine.process(diag) - - def visit_withitemT(self, node): - self.generic_visit(node) - - typ = node.context_expr.type - if (types.is_builtin(typ, "interleave") or types.is_builtin(typ, "sequential") or - types.is_builtin(typ, "parallel")): - # builtin context managers - if node.optional_vars is not None: - self._unify(node.optional_vars.type, builtins.TNone(), - node.optional_vars.loc, None) - elif types.is_instance(typ) or types.is_constructor(typ): - # user-defined context managers - self._unify_attribute(result_type=node.enter_type, value_node=node.context_expr, - attr_name='__enter__', attr_loc=None, loc=node.loc) - self._unify_attribute(result_type=node.exit_type, value_node=node.context_expr, - attr_name='__exit__', attr_loc=None, loc=node.loc) - - printer = types.TypePrinter() - - def check_callback(attr_name, typ, arity): - if types.is_var(typ): - return - - if not (types.is_method(typ) or types.is_function(typ)): - diag = diagnostic.Diagnostic("error", - "attribute '{attr}' of type {manager_type} must be a function", - {"attr": attr_name, - "manager_type": printer.name(node.context_expr.type)}, - node.context_expr.loc) - self.engine.process(diag) - return - - if types.is_method(typ): - typ = types.get_method_function(typ).find() - else: - typ = typ.find() - - if not (len(typ.args) == arity and len(typ.optargs) == 0): - diag = diagnostic.Diagnostic("error", - "function '{attr}{attr_type}' must accept " - "{arity} positional argument{s} and no optional arguments", - {"attr": attr_name, - "attr_type": printer.name(typ), - "arity": arity, "s": "s" if arity > 1 else ""}, - node.context_expr.loc) - self.engine.process(diag) - - for formal_arg_name in list(typ.args)[1:]: - formal_arg_type = typ.args[formal_arg_name] - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "exception handling via context managers is not supported; " - "the argument '{arg}' of function '{attr}{attr_type}' " - "will always be None", - {"arg": formal_arg_name, - "attr": attr_name, - "attr_type": printer.name(typ)}, - loca), - ] - - self._unify(formal_arg_type, builtins.TNone(), - node.context_expr.loc, None, - makenotes=makenotes) - - check_callback('__enter__', node.enter_type, 1) - check_callback('__exit__', node.exit_type, 4) - - if node.optional_vars is not None: - if types.is_method(node.exit_type): - var_type = types.get_method_function(node.exit_type).find().ret - else: - var_type = node.exit_type.find().ret - - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "context manager with an '__enter__' method returning {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - - self._unify(node.optional_vars.type, var_type, - node.optional_vars.loc, node.context_expr.loc, - makenotes=makenotes) - - elif not types.is_var(typ): - diag = diagnostic.Diagnostic("error", - "value of type {type} cannot act as a context manager", - {"type": types.TypePrinter().name(typ)}, - node.context_expr.loc) - self.engine.process(diag) - - def visit_With(self, node): - self.generic_visit(node) - - for item_node in node.items: - typ = item_node.context_expr.type.find() - if (types.is_builtin(typ, "parallel") or types.is_builtin(typ, "interleave") or - types.is_builtin(typ, "sequential")) and len(node.items) != 1: - diag = diagnostic.Diagnostic("error", - "the '{kind}' context manager must be the only one in a 'with' statement", - {"kind": typ.name}, - node.keyword_loc.join(node.colon_loc)) - self.engine.process(diag) - - def visit_ExceptHandlerT(self, node): - self.generic_visit(node) - - if node.filter is not None: - if not types.is_exn_constructor(node.filter.type): - diag = diagnostic.Diagnostic("error", - "this expression must refer to an exception constructor", - {"type": types.TypePrinter().name(node.filter.type)}, - node.filter.loc) - self.engine.process(diag) - else: - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "constructor of an exception of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - self._unify(node.name_type, node.filter.type.instance, - node.name_loc, node.filter.loc, makenotes) - - def _type_from_arguments(self, node, ret): - self.generic_visit(node) - - for (sigil_loc, vararg) in ((node.star_loc, node.vararg), - (node.dstar_loc, node.kwarg)): - if vararg: - diag = diagnostic.Diagnostic("error", - "variadic arguments are not supported", {}, - sigil_loc, [vararg.loc]) - self.engine.process(diag) - return - - def extract_args(arg_nodes): - args = [(arg_node.arg, arg_node.type) for arg_node in arg_nodes] - return OrderedDict(args) - - return types.TFunction(extract_args(node.args[:len(node.args) - len(node.defaults)]), - extract_args(node.args[len(node.args) - len(node.defaults):]), - ret) - - def visit_arguments(self, node): - self.generic_visit(node) - for arg, default in zip(node.args[len(node.args) - len(node.defaults):], node.defaults): - self._unify(arg.type, default.type, - arg.loc, default.loc) - - def visit_FunctionDefT(self, node): - for index, decorator in enumerate(node.decorator_list): - def eval_attr(attr): - if isinstance(attr.value, asttyped.QuoteT): - return getattr(attr.value.value, attr.attr) - return getattr(eval_attr(attr.value), attr.attr) - if isinstance(decorator, asttyped.AttributeT): - decorator = eval_attr(decorator) - if id(decorator) == id(kernel) or \ - types.is_builtin(decorator.type, "kernel") or \ - isinstance(decorator, asttyped.CallT) and \ - types.is_builtin(decorator.func.type, "kernel"): - continue - - diag = diagnostic.Diagnostic("error", - "decorators are not supported", {}, - node.at_locs[index], []) - self.engine.process(diag) - - try: - old_function, self.function = self.function, node - old_in_loop, self.in_loop = self.in_loop, False - old_has_return, self.has_return = self.has_return, False - - self.generic_visit(node) - - # Lack of return statements is not the only case where the return - # type cannot be inferred. The other one is infinite (possibly mutual) - # recursion. Since Python functions don't have to return a value, - # we ignore that one. - if not self.has_return: - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "function with return type {typea}", - {"typea": printer.name(typea)}, - node.name_loc), - ] - self._unify(node.return_type, builtins.TNone(), - node.name_loc, None, makenotes) - finally: - self.function = old_function - self.in_loop = old_in_loop - self.has_return = old_has_return - - signature_type = self._type_from_arguments(node.args, node.return_type) - if signature_type: - self._unify(node.signature_type, signature_type, - node.name_loc, None) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def visit_ClassDefT(self, node): - if any(node.decorator_list): - diag = diagnostic.Diagnostic("error", - "decorators are not supported", {}, - node.at_locs[0], [node.decorator_list[0].loc]) - self.engine.process(diag) - - self.generic_visit(node) - - def visit_Return(self, node): - if not self.function: - diag = diagnostic.Diagnostic("error", - "return statement outside of a function", {}, - node.keyword_loc) - self.engine.process(diag) - return - - self.has_return = True - - self.generic_visit(node) - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "function with return type {typea}", - {"typea": printer.name(typea)}, - self.function.name_loc), - diagnostic.Diagnostic("note", - "a statement returning {typeb}", - {"typeb": printer.name(typeb)}, - node.loc) - ] - if node.value is None: - self._unify(self.function.return_type, builtins.TNone(), - self.function.name_loc, node.loc, makenotes) - else: - self._unify(self.function.return_type, node.value.type, - self.function.name_loc, node.value.loc, makenotes) - - def visit_Raise(self, node): - self.generic_visit(node) - - if node.exc is not None: - exc_type = node.exc.type - if types.is_exn_constructor(exc_type): - pass # short form - elif not types.is_var(exc_type) and not builtins.is_exception(exc_type): - diag = diagnostic.Diagnostic("error", - "cannot raise a value of type {type}, which is not an exception", - {"type": types.TypePrinter().name(exc_type)}, - node.loc) - self.engine.process(diag) - - def visit_Assert(self, node): - self.generic_visit(node) - self._unify(node.test.type, builtins.TBool(), - node.test.loc, None) - if node.msg is not None: - if not isinstance(node.msg, asttyped.StrT): - diag = diagnostic.Diagnostic("error", - "assertion message must be a string literal", {}, - node.msg.loc) - self.engine.process(diag) diff --git a/artiq/compiler/transforms/int_monomorphizer.py b/artiq/compiler/transforms/int_monomorphizer.py deleted file mode 100644 index adab3b165..000000000 --- a/artiq/compiler/transforms/int_monomorphizer.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -:class:`IntMonomorphizer` collapses the integer literals of undetermined -width to 32 bits, assuming they fit into 32 bits, or 64 bits if they -do not. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins, asttyped - -class IntMonomorphizer(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_NumT(self, node): - if builtins.is_int(node.type): - if types.is_var(node.type["width"]): - if -2**31 < node.n < 2**31-1: - width = 32 - elif -2**63 < node.n < 2**63-1: - width = 64 - else: - diag = diagnostic.Diagnostic("error", - "integer literal out of range for a signed 64-bit value", {}, - node.loc) - self.engine.process(diag) - return - - node.type["width"].unify(types.TValue(width)) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py deleted file mode 100644 index 1bf9cf5a5..000000000 --- a/artiq/compiler/transforms/interleaver.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -:class:`Interleaver` reorders requests to the RTIO core so that -the timestamp would always monotonically nondecrease. -""" - -from pythonparser import diagnostic - -from .. import types, builtins, ir, iodelay -from ..analyses import domination -from ..algorithms import inline, unroll - -def delay_free_subgraph(root, limit): - visited = set() - queue = root.successors() - while len(queue) > 0: - block = queue.pop() - visited.add(block) - - if block is limit: - continue - - if isinstance(block.terminator(), ir.Delay): - return False - - for successor in block.successors(): - if successor not in visited: - queue.append(successor) - - return True - -def iodelay_of_block(block): - terminator = block.terminator() - if isinstance(terminator, ir.Delay): - # We should be able to fold everything without free variables. - folded_expr = terminator.interval.fold() - assert iodelay.is_const(folded_expr) - return folded_expr.value - else: - return 0 - -def is_pure_delay(insn): - return isinstance(insn, ir.Builtin) and insn.op in ("delay", "delay_mu") - -def is_impure_delay_block(block): - terminator = block.terminator() - return isinstance(terminator, ir.Delay) and \ - not is_pure_delay(terminator.decomposition()) - -class Interleaver: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - for insn in func.instructions(): - if isinstance(insn, ir.Delay): - if any(insn.interval.free_vars()): - # If a function has free variables in delay expressions, - # that means its IO delay depends on arguments. - # Do not change such functions in any way so that it will - # be successfully inlined and then removed by DCE. - return - - postdom_tree = None - for insn in func.instructions(): - if not isinstance(insn, ir.Interleave): - continue - - # Lazily compute dominators. - if postdom_tree is None: - postdom_tree = domination.PostDominatorTree(func) - - target_block = insn.basic_block - target_time = 0 - source_blocks = insn.basic_block.successors() - source_times = [0 for _ in source_blocks] - - if len(source_blocks) == 1: - # Immediate dominator for a interleave instruction with one successor - # is the first instruction in the body of the statement which created - # it, but below we expect that it would be the first instruction after - # the statement itself. - insn.replace_with(ir.Branch(source_blocks[0])) - continue - - interleave_until = postdom_tree.immediate_dominator(insn.basic_block) - assert interleave_until is not None # no nonlocal flow in `with interleave` - assert interleave_until not in source_blocks - - while len(source_blocks) > 0: - def time_after_block(pair): - index, block = pair - return source_times[index] + iodelay_of_block(block) - - # Always prefer impure blocks (with calls) to pure blocks, because - # impure blocks may expand with smaller delays appearing, and in - # case of a tie, if a pure block is preferred, this would violate - # the timeline monotonicity. - available_source_blocks = list(filter(is_impure_delay_block, source_blocks)) - if not any(available_source_blocks): - available_source_blocks = source_blocks - - index, source_block = min(enumerate(available_source_blocks), key=time_after_block) - source_block_delay = iodelay_of_block(source_block) - - new_target_time = source_times[index] + source_block_delay - target_time_delta = new_target_time - target_time - assert target_time_delta >= 0 - - target_terminator = target_block.terminator() - if isinstance(target_terminator, ir.Interleave): - target_terminator.replace_with(ir.Branch(source_block)) - elif isinstance(target_terminator, (ir.Delay, ir.Branch)): - target_terminator.set_target(source_block) - else: - assert False - - source_terminator = source_block.terminator() - if isinstance(source_terminator, ir.Interleave): - source_terminator.replace_with(ir.Branch(source_terminator.target())) - elif isinstance(source_terminator, ir.Branch): - pass - elif isinstance(source_terminator, ir.BranchIf): - # Skip a delay-free loop/conditional - source_block = postdom_tree.immediate_dominator(source_block) - assert (source_block is not None) - elif isinstance(source_terminator, ir.Return): - break - elif isinstance(source_terminator, ir.Delay): - old_decomp = source_terminator.decomposition() - if is_pure_delay(old_decomp): - if target_time_delta > 0: - new_decomp_expr = ir.Constant(int(target_time_delta), builtins.TInt64()) - new_decomp = ir.Builtin("delay_mu", [new_decomp_expr], builtins.TNone()) - new_decomp.loc = old_decomp.loc - - source_terminator.basic_block.insert(new_decomp, before=source_terminator) - source_terminator.interval = iodelay.Const(target_time_delta) - source_terminator.set_decomposition(new_decomp) - else: - source_terminator.replace_with(ir.Branch(source_terminator.target())) - old_decomp.erase() - else: # It's a call. - need_to_inline = len(source_blocks) > 1 - if need_to_inline: - if old_decomp.static_target_function is None: - diag = diagnostic.Diagnostic("fatal", - "it is not possible to interleave this function call within " - "a 'with interleave:' statement because the compiler could not " - "prove that the same function would always be called", {}, - old_decomp.loc) - self.engine.process(diag) - - inline(old_decomp) - postdom_tree = domination.PostDominatorTree(func) - continue - elif target_time_delta > 0: - source_terminator.interval = iodelay.Const(target_time_delta) - else: - source_terminator.replace_with(ir.Branch(source_terminator.target())) - elif isinstance(source_terminator, ir.Loop): - unroll(source_terminator) - - postdom_tree = domination.PostDominatorTree(func) - continue - else: - assert False - - target_block = source_block - target_time = new_target_time - - new_source_block = postdom_tree.immediate_dominator(source_block) - assert (new_source_block is not None) - assert delay_free_subgraph(source_block, new_source_block) - - if new_source_block == interleave_until: - # We're finished with this branch. - del source_blocks[index] - del source_times[index] - else: - source_blocks[index] = new_source_block - source_times[index] = new_target_time diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py deleted file mode 100644 index 90bfefdb3..000000000 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ /dev/null @@ -1,327 +0,0 @@ -""" -:class:`IODelayEstimator` calculates the amount of time -elapsed from the point of view of the RTIO core for -every function. -""" - -from pythonparser import ast, algorithm, diagnostic -from .. import types, iodelay, builtins, asttyped - -class _UnknownDelay(Exception): - pass - -class _IndeterminateDelay(Exception): - def __init__(self, cause): - self.cause = cause - -class IODelayEstimator(algorithm.Visitor): - def __init__(self, engine, ref_period): - self.engine = engine - self.ref_period = ref_period - self.changed = False - self.current_delay = iodelay.Const(0) - self.current_args = None - self.current_goto = None - self.current_return = None - - def evaluate(self, node, abort, context): - if isinstance(node, asttyped.NumT): - return iodelay.Const(node.n) - elif isinstance(node, asttyped.CoerceT): - return self.evaluate(node.value, abort, context) - elif isinstance(node, asttyped.NameT): - if self.current_args is None: - note = diagnostic.Diagnostic("note", - "this variable is not an argument", {}, - node.loc) - abort([note]) - elif node.id in [arg.arg for arg in self.current_args.args]: - return iodelay.Var(node.id) - else: - notes = [ - diagnostic.Diagnostic("note", - "this variable is not an argument of the innermost function", {}, - node.loc), - diagnostic.Diagnostic("note", - "only these arguments are in scope of analysis", {}, - self.current_args.loc) - ] - abort(notes) - elif isinstance(node, asttyped.BinOpT): - lhs = self.evaluate(node.left, abort, context) - rhs = self.evaluate(node.right, abort, context) - if isinstance(node.op, ast.Add): - return lhs + rhs - elif isinstance(node.op, ast.Sub): - return lhs - rhs - elif isinstance(node.op, ast.Mult): - return lhs * rhs - elif isinstance(node.op, ast.Div): - return lhs / rhs - elif isinstance(node.op, ast.FloorDiv): - return lhs // rhs - else: - note = diagnostic.Diagnostic("note", - "this operator is not supported {context}", - {"context": context}, - node.op.loc) - abort([note]) - else: - note = diagnostic.Diagnostic("note", - "this expression is not supported {context}", - {"context": context}, - node.loc) - abort([note]) - - def abort(self, message, loc, notes=[]): - diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes) - raise _IndeterminateDelay(diag) - - def visit_fixpoint(self, node): - while True: - self.changed = False - self.visit(node) - if not self.changed: - return - - def visit_ModuleT(self, node): - try: - for stmt in node.body: - try: - self.visit(stmt) - except _UnknownDelay: - pass # more luck next time? - except _IndeterminateDelay: - pass # we don't care; module-level code is never interleaved - - def visit_function(self, args, body, typ, loc): - old_args, self.current_args = self.current_args, args - old_return, self.current_return = self.current_return, None - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - try: - self.visit(body) - if not iodelay.is_zero(self.current_delay) and self.current_return is not None: - self.abort("only return statement at the end of the function " - "can be interleaved", self.current_return.loc) - - delay = types.TFixedDelay(self.current_delay.fold()) - except _IndeterminateDelay as error: - delay = types.TIndeterminateDelay(error.cause) - self.current_delay = old_delay - self.current_return = old_return - self.current_args = old_args - - if types.is_indeterminate_delay(delay) and types.is_indeterminate_delay(typ.delay): - # Both delays indeterminate; no point in unifying since that will - # replace the lazy and more specific error with an eager and more generic - # error (unification error of delay(?) with delay(?), which is useless). - return - - try: - old_delay = typ.delay.find() - typ.delay.unify(delay) - if typ.delay.find() != old_delay: - self.changed = True - except types.UnificationError as e: - printer = types.TypePrinter() - diag = diagnostic.Diagnostic("fatal", - "delay {delaya} was inferred for this function, but its delay is already " - "constrained externally to {delayb}", - {"delaya": printer.name(delay), "delayb": printer.name(typ.delay)}, - loc) - self.engine.process(diag) - - def visit_FunctionDefT(self, node): - self.visit(node.args.defaults) - self.visit(node.args.kw_defaults) - - # We can only handle return in tail position. - if isinstance(node.body[-1], ast.Return): - body = node.body[:-1] - else: - body = node.body - self.visit_function(node.args, body, node.signature_type.find(), node.loc) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def visit_LambdaT(self, node): - self.visit_function(node.args, node.body, node.type.find(), node.loc) - - def get_iterable_length(self, node, context): - def abort(notes): - self.abort("for statement cannot be interleaved because " - "iteration count is indeterminate", - node.loc, notes) - - def evaluate(node): - return self.evaluate(node, abort, context) - - if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"): - range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1) - if len(node.args) == 3: - range_min, range_max, range_step = map(evaluate, node.args) - elif len(node.args) == 2: - range_min, range_max = map(evaluate, node.args) - elif len(node.args) == 1: - range_max, = map(evaluate, node.args) - return (range_max - range_min) // range_step - else: - note = diagnostic.Diagnostic("note", - "this value is not a constant range literal", {}, - node.loc) - abort([note]) - - def visit_ForT(self, node): - self.visit(node.iter) - - old_goto, self.current_goto = self.current_goto, None - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.visit(node.body) - if iodelay.is_zero(self.current_delay): - self.current_delay = old_delay - else: - if self.current_goto is not None: - self.abort("loop iteration count is indeterminate because of control flow", - self.current_goto.loc) - - context = "in an iterable used in a for loop that is being interleaved" - node.trip_count = self.get_iterable_length(node.iter, context).fold() - node.trip_interval = self.current_delay.fold() - self.current_delay = old_delay + node.trip_interval * node.trip_count - self.current_goto = old_goto - - self.visit(node.orelse) - - def visit_goto(self, node): - self.current_goto = node - - visit_Break = visit_goto - visit_Continue = visit_goto - - def visit_control_flow(self, kind, node): - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.generic_visit(node) - if not iodelay.is_zero(self.current_delay): - self.abort("{} cannot be interleaved".format(kind), node.loc) - self.current_delay = old_delay - - visit_If = lambda self, node: self.visit_control_flow("if statement", node) - visit_IfExpT = lambda self, node: self.visit_control_flow("if expression", node) - visit_Try = lambda self, node: self.visit_control_flow("try statement", node) - - def visit_While(self, node): - old_goto, self.current_goto = self.current_goto, None - self.visit_control_flow("while statement", node) - self.current_goto = old_goto - - def visit_Return(self, node): - self.current_return = node - - def visit_With(self, node): - self.visit(node.items) - - context_expr = node.items[0].context_expr - if len(node.items) == 1 and types.is_builtin(context_expr.type, "interleave"): - try: - delays = [] - for stmt in node.body: - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.visit(stmt) - delays.append(self.current_delay) - self.current_delay = old_delay - - if any(delays): - self.current_delay += iodelay.Max(delays) - except _IndeterminateDelay as error: - # Interleave failures inside `with` statements are hard failures, - # since there's no chance that the code will never actually execute - # inside a `with` statement after all. - note = diagnostic.Diagnostic("note", - "while interleaving this 'with interleave:' statement", {}, - node.loc) - error.cause.notes += [note] - self.engine.process(error.cause) - - flow_stmt = None - if self.current_goto is not None: - flow_stmt = self.current_goto - elif self.current_return is not None: - flow_stmt = self.current_return - - if flow_stmt is not None: - note = diagnostic.Diagnostic("note", - "this '{kind}' statement transfers control out of " - "the 'with interleave:' statement", - {"kind": flow_stmt.keyword_loc.source()}, - flow_stmt.loc) - diag = diagnostic.Diagnostic("error", - "cannot interleave this 'with interleave:' statement", {}, - node.keyword_loc.join(node.colon_loc), notes=[note]) - self.engine.process(diag) - - elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"): - self.visit(node.body) - else: - self.abort("with statement cannot be interleaved", node.loc) - - def visit_CallT(self, node): - typ = node.func.type.find() - def abort(notes): - self.abort("call cannot be interleaved because " - "an argument cannot be statically evaluated", - node.loc, notes) - - if types.is_builtin(typ, "delay"): - value = self.evaluate(node.args[0], abort=abort, - context="as an argument for delay()") - call_delay = iodelay.SToMU(value, ref_period=self.ref_period) - elif types.is_builtin(typ, "delay_mu"): - value = self.evaluate(node.args[0], abort=abort, - context="as an argument for delay_mu()") - call_delay = value - elif not types.is_builtin(typ): - if types.is_function(typ) or types.is_rpc(typ): - offset = 0 - elif types.is_method(typ): - offset = 1 - typ = types.get_method_function(typ) - else: - assert False - - if types.is_rpc(typ): - call_delay = iodelay.Const(0) - else: - delay = typ.find().delay.find() - if types.is_var(delay): - raise _UnknownDelay() - elif delay.is_indeterminate(): - note = diagnostic.Diagnostic("note", - "function called here", {}, - node.loc) - cause = delay.cause - cause = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments, - cause.location, cause.highlights, - cause.notes + [note]) - raise _IndeterminateDelay(cause) - elif delay.is_fixed(): - args = {} - for kw_node in node.keywords: - args[kw_node.arg] = kw_node.value - for arg_name, arg_node in zip(list(typ.args)[offset:], node.args): - args[arg_name] = arg_node - - free_vars = delay.duration.free_vars() - node.arg_exprs = { - arg: self.evaluate(args[arg], abort=abort, - context="in the expression for argument '{}' " - "that affects I/O delay".format(arg)) - for arg in free_vars - } - call_delay = delay.duration.fold(node.arg_exprs) - else: - assert False - else: - call_delay = iodelay.Const(0) - - self.current_delay += call_delay - node.iodelay = call_delay diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py deleted file mode 100644 index 61f66599c..000000000 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ /dev/null @@ -1,1656 +0,0 @@ -""" -:class:`LLVMIRGenerator` transforms ARTIQ intermediate representation -into LLVM intermediate representation. -""" - -import os, re, types as pytypes, numpy -from collections import defaultdict -from pythonparser import ast, diagnostic -from llvmlite import ir as ll, binding as llvm -from ...language import core as language_core -from .. import types, builtins, ir -from ..embedding import SpecializedFunction - - -llvoid = ll.VoidType() -llunit = ll.LiteralStructType([]) -lli1 = ll.IntType(1) -lli8 = ll.IntType(8) -lli32 = ll.IntType(32) -lli64 = ll.IntType(64) -lldouble = ll.DoubleType() -llptr = ll.IntType(8).as_pointer() -llptrptr = ll.IntType(8).as_pointer().as_pointer() -llslice = ll.LiteralStructType([llptr, lli32]) -llsliceptr = ll.LiteralStructType([llptr, lli32]).as_pointer() -llmetadata = ll.MetaDataType() - - -def memoize(generator): - def memoized(self, *args): - key = (generator,) + args - try: - return self.cache[key] - except KeyError: - result = generator(self, *args) - self.cache[key] = result - return result - return memoized - - -class ABILayoutInfo: - """Caches DataLayout size/alignment lookup results. - - llvmlite's Type.get_abi_{size, alignment}() are implemented in a very - inefficient way, in particular _get_ll_pointer_type() used to construct the - corresponding llvm::Type is. We thus cache the results, optionally directly - using the compiler type as a key. - - (This is a separate class for use with @memoize.) - """ - - def __init__(self, lldatalayout, llcontext, llty_of_type): - self.cache = {} - self.lldatalayout = lldatalayout - self.llcontext = llcontext - self.llty_of_type = llty_of_type - - @memoize - def get_size_align(self, llty): - lowered = llty._get_ll_pointer_type(self.lldatalayout, self.llcontext) - return (self.lldatalayout.get_pointee_abi_size(lowered), - self.lldatalayout.get_pointee_abi_alignment(lowered)) - - @memoize - def get_size_align_for_type(self, typ): - return self.get_size_align(self.llty_of_type(typ)) - - -class LLVMIRGenerator: - def __init__(self, engine, module_name, target, embedding_map): - self.engine = engine - self.target = target - self.embedding_map = embedding_map - self.llcontext = target.llcontext - self.llmodule = ll.Module(context=self.llcontext, name=module_name) - self.llmodule.triple = target.triple - self.llmodule.data_layout = target.data_layout - self.lldatalayout = llvm.create_target_data(self.llmodule.data_layout) - self.abi_layout_info = ABILayoutInfo(self.lldatalayout, self.llcontext, - self.llty_of_type) - self.function_flags = None - self.llfunction = None - self.llmap = {} - self.llobject_map = {} - self.phis = [] - self.empty_metadata = self.llmodule.add_metadata([]) - self.quote_fail_msg = None - - def needs_sret(self, lltyp, may_be_large=True): - if isinstance(lltyp, ll.VoidType): - return False - elif isinstance(lltyp, ll.IntType): - return False - elif isinstance(lltyp, ll.PointerType): - return False - elif may_be_large and isinstance(lltyp, ll.DoubleType): - return False - elif may_be_large and isinstance(lltyp, ll.LiteralStructType) \ - and len(lltyp.elements) <= 2: - return not any([self.needs_sret(elt, may_be_large=False) for elt in lltyp.elements]) - else: - assert isinstance(lltyp, ll.Type) - return True - - def has_sret(self, functy): - llretty = self.llty_of_type(functy.ret, for_return=True) - return self.needs_sret(llretty) - - def llty_of_type(self, typ, bare=False, for_return=False): - typ = typ.find() - if types.is_tuple(typ): - return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) - elif types.is_rpc(typ) or types.is_external_function(typ): - if for_return: - return llvoid - else: - return llunit - elif types._is_pointer(typ): - return ll.PointerType(self.llty_of_type(typ["elt"])) - elif types.is_function(typ): - sretarg = [] - llretty = self.llty_of_type(typ.ret, for_return=True) - if self.needs_sret(llretty): - sretarg = [llretty.as_pointer()] - llretty = llvoid - - envarg = llptr - llty = ll.FunctionType(args=sretarg + [envarg] + - [self.llty_of_type(typ.args[arg]) - for arg in typ.args] + - [self.llty_of_type(ir.TOption(typ.optargs[arg])) - for arg in typ.optargs], - return_type=llretty) - - if bare: - return llty - else: - return ll.LiteralStructType([envarg, llty.as_pointer()]) - elif types.is_method(typ): - llfunty = self.llty_of_type(types.get_method_function(typ)) - llselfty = self.llty_of_type(types.get_method_self(typ)) - return ll.LiteralStructType([llfunty, llselfty]) - elif builtins.is_none(typ): - if for_return: - return llvoid - else: - return llunit - elif builtins.is_bool(typ): - return lli1 - elif builtins.is_int(typ): - return ll.IntType(builtins.get_int_width(typ)) - elif builtins.is_float(typ): - return lldouble - elif builtins.is_array(typ): - llshapety = self.llty_of_type(typ.attributes["shape"]) - llbufferty = self.llty_of_type(typ.attributes["buffer"]) - return ll.LiteralStructType([llbufferty, llshapety]) - elif builtins.is_listish(typ): - lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) - return ll.LiteralStructType([lleltty.as_pointer(), lli32]) - elif builtins.is_range(typ): - lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) - return ll.LiteralStructType([lleltty, lleltty, lleltty]) - elif ir.is_basic_block(typ): - return llptr - elif ir.is_option(typ): - return ll.LiteralStructType([lli1, self.llty_of_type(typ.params["value"])]) - elif ir.is_keyword(typ): - return ll.LiteralStructType([llslice, self.llty_of_type(typ.params["value"])]) - elif ir.is_environment(typ): - llty = self.llcontext.get_identified_type("env.{}".format(typ.env_name)) - if llty.elements is None: - llty.elements = [self.llty_of_type(typ.params[name]) for name in typ.params] - - if bare: - return llty - else: - return llty.as_pointer() - else: # Catch-all for exceptions and custom classes - if builtins.is_exception(typ): - name = "C.Exception" # they all share layout - elif types.is_constructor(typ): - name = "C.{}".format(typ.name) - else: - name = "I.{}".format(typ.name) - - llty = self.llcontext.get_identified_type(name) - if llty.elements is None: - # First setting elements to [] will allow us to handle - # self-referential types. - llty.elements = [] - llty.elements = [self.llty_of_type(attrtyp) - for attrtyp in typ.attributes.values()] - - if bare or not builtins.is_allocated(typ): - return llty - else: - return llty.as_pointer() - - def llstr_of_str(self, value, name=None, linkage="private", unnamed_addr=True): - if isinstance(value, str): - as_bytes = value.encode("utf-8") - else: - as_bytes = value - - if name is None: - sanitized_str = re.sub(rb"[^a-zA-Z0-9_.]", b"", as_bytes[:20]).decode('ascii') - name = self.llmodule.get_unique_name("S.{}".format(sanitized_str)) - - llstr = self.llmodule.globals.get(name) - if llstr is None: - llstrty = ll.ArrayType(lli8, len(as_bytes)) - llstr = ll.GlobalVariable(self.llmodule, llstrty, name) - llstr.global_constant = True - llstr.initializer = ll.Constant(llstrty, bytearray(as_bytes)) - llstr.linkage = linkage - llstr.unnamed_addr = unnamed_addr - return llstr.bitcast(llptr) - - def llconst_of_const(self, const): - llty = self.llty_of_type(const.type) - if const.value is None: - if isinstance(llty, ll.PointerType): - return ll.Constant(llty, None) - else: - return ll.Constant(llty, []) - elif const.value is True: - return ll.Constant(llty, True) - elif const.value is False: - return ll.Constant(llty, False) - elif isinstance(const.value, (int, float)): - return ll.Constant(llty, const.value) - elif isinstance(const.value, (str, bytes)): - if isinstance(const.value, str): - value = const.value.encode('utf-8') - else: - value = const.value - - llptr = self.llstr_of_str(const.value, linkage="private", unnamed_addr=True) - lllen = ll.Constant(lli32, len(const.value)) - return ll.Constant(llty, (llptr, lllen)) - else: - assert False - - def llbuiltin(self, name): - llglobal = self.llmodule.globals.get(name) - if llglobal is not None: - return llglobal - - if name in "llvm.donothing": - llty = ll.FunctionType(llvoid, []) - elif name == "llvm.floor.f64": - llty = ll.FunctionType(lldouble, [lldouble]) - elif name == "llvm.round.f64": - llty = ll.FunctionType(lldouble, [lldouble]) - elif name == "llvm.pow.f64": - llty = ll.FunctionType(lldouble, [lldouble, lldouble]) - elif name == "llvm.powi.f64": - llty = ll.FunctionType(lldouble, [lldouble, lli32]) - elif name == "llvm.copysign.f64": - llty = ll.FunctionType(lldouble, [lldouble, lldouble]) - elif name == "llvm.stacksave": - llty = ll.FunctionType(llptr, []) - elif name == "llvm.stackrestore": - llty = ll.FunctionType(llvoid, [llptr]) - elif name == "__py_modsi3": - llty = ll.FunctionType(lli32, [lli32, lli32]) - elif name == "__py_moddi3": - llty = ll.FunctionType(lli64, [lli64, lli64]) - elif name == "__py_moddf3": - llty = ll.FunctionType(lldouble, [lldouble, lldouble]) - elif name == self.target.print_function: - llty = ll.FunctionType(llvoid, [llptr], var_arg=True) - elif name == "rtio_log": - llty = ll.FunctionType(llvoid, [llptr], var_arg=True) - elif name == "__artiq_personality": - llty = ll.FunctionType(lli32, [], var_arg=True) - elif name == "__artiq_raise": - llty = ll.FunctionType(llvoid, [self.llty_of_type(builtins.TException())]) - elif name == "__artiq_reraise": - llty = ll.FunctionType(llvoid, []) - elif name == "memcmp": - llty = ll.FunctionType(lli32, [llptr, llptr, lli32]) - elif name == "rpc_send": - llty = ll.FunctionType(llvoid, [lli32, llsliceptr, llptrptr]) - elif name == "rpc_send_async": - llty = ll.FunctionType(llvoid, [lli32, llsliceptr, llptrptr]) - elif name == "rpc_recv": - llty = ll.FunctionType(lli32, [llptr]) - - # with now-pinning - elif name == "now": - llty = lli64 - - # without now-pinning - elif name == "now_mu": - llty = ll.FunctionType(lli64, []) - elif name == "at_mu": - llty = ll.FunctionType(llvoid, [lli64]) - elif name == "delay_mu": - llty = ll.FunctionType(llvoid, [lli64]) - - else: - assert False - - if isinstance(llty, ll.FunctionType): - llglobal = ll.Function(self.llmodule, llty, name) - if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): - llglobal.attributes.add("noreturn") - if name in ("rtio_log", "rpc_send", "rpc_send_async", - self.target.print_function): - llglobal.attributes.add("nounwind") - if name.find("__py_") == 0: - llglobal.linkage = 'linkonce_odr' - self.emit_intrinsic(name, llglobal) - else: - llglobal = ll.GlobalVariable(self.llmodule, llty, name) - - return llglobal - - def emit_intrinsic(self, name, llfun): - llbuilder = ll.IRBuilder() - llbuilder.position_at_end(llfun.append_basic_block("entry")) - - if name == "__py_modsi3" or name == "__py_moddi3": - if name == "__py_modsi3": - llty = lli32 - elif name == "__py_moddi3": - llty = lli64 - else: - assert False - - """ - Reference Objects/intobject.c - xdivy = x / y; - xmody = (long)(x - (unsigned long)xdivy * y); - /* If the signs of x and y differ, and the remainder is non-0, - * C89 doesn't define whether xdivy is now the floor or the - * ceiling of the infinitely precise quotient. We want the floor, - * and we have it iff the remainder's sign matches y's. - */ - if (xmody && ((y ^ xmody) < 0) /* i.e. and signs differ */) { - xmody += y; - // ... - } - """ - llx, lly = llfun.args - llxdivy = llbuilder.sdiv(llx, lly) - llxremy = llbuilder.srem(llx, lly) - - llxmodynonzero = llbuilder.icmp_signed('!=', llxremy, ll.Constant(llty, 0)) - lldiffsign = llbuilder.icmp_signed('<', llbuilder.xor(llx, lly), ll.Constant(llty, 0)) - - llcond = llbuilder.and_(llxmodynonzero, lldiffsign) - with llbuilder.if_then(llcond): - llbuilder.ret(llbuilder.add(llxremy, lly)) - llbuilder.ret(llxremy) - elif name == "__py_moddf3": - """ - Reference Objects/floatobject.c - mod = fmod(vx, wx); - /* fmod is typically exact, so vx-mod is *mathematically* an - exact multiple of wx. But this is fp arithmetic, and fp - vx - mod is an approximation; the result is that div may - not be an exact integral value after the division, although - it will always be very close to one. - */ - // ... - if (mod) { - /* ensure the remainder has the same sign as the denominator */ - if ((wx < 0) != (mod < 0)) { - mod += wx; - // ... - } - } - else { - /* the remainder is zero, and in the presence of signed zeroes - fmod returns different results across platforms; ensure - it has the same sign as the denominator; we'd like to do - "mod = wx * 0.0", but that may get optimized away */ - mod *= mod; /* hide "mod = +0" from optimizer */ - if (wx < 0.0) - mod = -mod; - } - """ - llv, llw = llfun.args - llrem = llbuilder.frem(llv, llw) - - llremnonzero = llbuilder.fcmp_unordered('!=', llrem, ll.Constant(lldouble, 0.0)) - llwltzero = llbuilder.fcmp_ordered('<', llw, ll.Constant(lldouble, 0.0)) - llremltzero = llbuilder.fcmp_ordered('<', llrem, ll.Constant(lldouble, 0.0)) - lldiffsign = llbuilder.icmp_unsigned('!=', llwltzero, llremltzero) - - llcond = llbuilder.and_(llremnonzero, lldiffsign) - with llbuilder.if_then(llcond): - llbuilder.ret(llbuilder.fadd(llrem, llw)) - llbuilder.ret(llrem) - else: - assert False - - def get_function(self, typ, name): - llfun = self.llmodule.globals.get(name) - if llfun is None: - llfunty = self.llty_of_type(typ, bare=True) - llfun = ll.Function(self.llmodule, llfunty, name) - - llretty = self.llty_of_type(typ.find().ret, for_return=True) - if self.needs_sret(llretty): - llfun.args[0].add_attribute('sret') - return llfun - - def get_function_with_undef_env(self, typ, name): - llfun = self.get_function(typ, name) - llclosure = ll.Constant(self.llty_of_type(typ), [ - ll.Constant(llptr, ll.Undefined), - llfun - ]) - return llclosure - - def map(self, value): - if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): - return self.llmap[value] - elif isinstance(value, ir.Constant): - return self.llconst_of_const(value) - elif isinstance(value, ir.Function): - return self.get_function(value.type, value.name) - else: - assert False - - def process(self, functions, attribute_writeback): - for func in functions: - self.process_function(func) - - if attribute_writeback and self.embedding_map is not None: - self.emit_attribute_writeback() - - return self.llmodule - - def emit_attribute_writeback(self): - llobjects = defaultdict(lambda: []) - - for obj_id, obj_ref, obj_typ in self.embedding_map.iter_objects(): - llobject = self.llmodule.globals.get("O.{}".format(obj_id)) - if llobject is not None: - llobjects[obj_typ].append(llobject.bitcast(llptr)) - - llrpcattrty = self.llcontext.get_identified_type("A") - llrpcattrty.elements = [lli32, llslice, llslice] - - lldescty = self.llcontext.get_identified_type("D") - lldescty.elements = [llrpcattrty.as_pointer().as_pointer(), llptr.as_pointer()] - - lldescs = [] - for typ in llobjects: - if "__objectid__" not in typ.attributes: - continue - - if types.is_constructor(typ): - type_name = "C.{}".format(typ.name) - else: - type_name = "I.{}".format(typ.name) - - def llrpcattr_of_attr(offset, name, typ): - def rpc_tag_error(typ): - print(typ) - assert False - - if name == "__objectid__": - rpctag = b"" - else: - rpctag = b"Os" + ir.rpc_tag(typ, error_handler=rpc_tag_error) + b":n" - - llrpcattrinit = ll.Constant(llrpcattrty, [ - ll.Constant(lli32, offset), - self.llconst_of_const(ir.Constant(rpctag, builtins.TStr())), - self.llconst_of_const(ir.Constant(name, builtins.TStr())) - ]) - - if name == "__objectid__": - return self.get_or_define_global(name, llrpcattrty, llrpcattrinit) - - llrpcattr = ll.GlobalVariable(self.llmodule, llrpcattrty, - name="A.{}.{}".format(type_name, name)) - llrpcattr.initializer = llrpcattrinit - llrpcattr.global_constant = True - llrpcattr.unnamed_addr = True - llrpcattr.linkage = 'private' - - return llrpcattr - - offset = 0 - llrpcattrs = [] - for attr in typ.attributes: - attrtyp = typ.attributes[attr] - size, alignment = self.abi_layout_info.get_size_align_for_type(attrtyp) - - if offset % alignment != 0: - offset += alignment - (offset % alignment) - - if types.is_instance(typ) and attr not in typ.constant_attributes: - try: - llrpcattrs.append(llrpcattr_of_attr(offset, attr, attrtyp)) - except ValueError: - pass - - offset += size - - if len(llrpcattrs) == 1: - # Don't bother serializing objects that only have __objectid__ - # since there's nothing to writeback anyway. - continue - - llrpcattraryty = ll.ArrayType(llrpcattrty.as_pointer(), len(llrpcattrs) + 1) - llrpcattrary = ll.GlobalVariable(self.llmodule, llrpcattraryty, - name="Ax.{}".format(type_name)) - llrpcattrary.initializer = ll.Constant(llrpcattraryty, - llrpcattrs + [ll.Constant(llrpcattrty.as_pointer(), None)]) - llrpcattrary.global_constant = True - llrpcattrary.unnamed_addr = True - llrpcattrary.linkage = 'private' - - llobjectaryty = ll.ArrayType(llptr, len(llobjects[typ]) + 1) - llobjectary = ll.GlobalVariable(self.llmodule, llobjectaryty, - name="Ox.{}".format(type_name)) - llobjectary.initializer = ll.Constant(llobjectaryty, - llobjects[typ] + [ll.Constant(llptr, None)]) - llobjectary.linkage = 'private' - - lldesc = ll.GlobalVariable(self.llmodule, lldescty, - name="D.{}".format(type_name)) - lldesc.initializer = ll.Constant(lldescty, [ - llrpcattrary.bitcast(llrpcattrty.as_pointer().as_pointer()), - llobjectary.bitcast(llptr.as_pointer()) - ]) - lldesc.global_constant = True - lldesc.linkage = 'private' - lldescs.append(lldesc) - - llglobaldescty = ll.ArrayType(lldescty.as_pointer(), len(lldescs) + 1) - llglobaldesc = ll.GlobalVariable(self.llmodule, llglobaldescty, - name="typeinfo") - llglobaldesc.initializer = ll.Constant(llglobaldescty, - lldescs + [ll.Constant(lldescty.as_pointer(), None)]) - - def process_function(self, func): - try: - self.function_flags = func.flags - self.llfunction = self.map(func) - - if func.is_internal: - self.llfunction.linkage = 'private' - if func.is_cold: - self.llfunction.attributes.add('cold') - self.llfunction.attributes.add('noinline') - if 'inline' in func.flags: - self.llfunction.attributes.add('inlinehint') - if 'forceinline' in func.flags: - self.llfunction.attributes.add('alwaysinline') - - self.llfunction.attributes.add('uwtable') - self.llfunction.attributes.personality = self.llbuiltin("__artiq_personality") - - self.llbuilder = ll.IRBuilder() - llblock_map = {} - - # First, map arguments. - if self.has_sret(func.type): - llactualargs = self.llfunction.args[1:] - else: - llactualargs = self.llfunction.args - - for arg, llarg in zip(func.arguments, llactualargs): - llarg.name = arg.name - self.llmap[arg] = llarg - - # Second, create all basic blocks. - for block in func.basic_blocks: - llblock = self.llfunction.append_basic_block(block.name) - self.llmap[block] = llblock - - # Third, translate all instructions. - for block in func.basic_blocks: - self.llbuilder.position_at_end(self.llmap[block]) - for insn in block.instructions: - llinsn = getattr(self, "process_" + type(insn).__name__)(insn) - assert llinsn is not None - self.llmap[insn] = llinsn - - # There is no 1:1 correspondence between ARTIQ and LLVM - # basic blocks, because sometimes we expand a single ARTIQ - # instruction so that the result spans several LLVM basic - # blocks. This only really matters for phis, which are thus - # using a different map (the following one). - llblock_map[block] = self.llbuilder.basic_block - - # Fourth, add incoming values to phis. - for phi, llphi in self.phis: - for value, block in phi.incoming(): - llphi.add_incoming(self.map(value), llblock_map[block]) - finally: - self.function_flags = None - self.llfunction = None - self.llmap = {} - self.phis = [] - - def process_Phi(self, insn): - llinsn = self.llbuilder.phi(self.llty_of_type(insn.type), name=insn.name) - self.phis.append((insn, llinsn)) - return llinsn - - def llindex(self, index): - return ll.Constant(lli32, index) - - def process_Alloc(self, insn): - if ir.is_environment(insn.type): - return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True), - name=insn.name) - elif ir.is_option(insn.type): - if len(insn.operands) == 0: # empty - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - return self.llbuilder.insert_value(llvalue, ll.Constant(lli1, False), 0, - name=insn.name) - elif len(insn.operands) == 1: # full - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - llvalue = self.llbuilder.insert_value(llvalue, ll.Constant(lli1, True), 0) - return self.llbuilder.insert_value(llvalue, self.map(insn.operands[0]), 1, - name=insn.name) - else: - assert False - elif types._is_pointer(insn.type) or (builtins.is_listish(insn.type) - and not builtins.is_array(insn.type)): - llsize = self.map(insn.operands[0]) - lleltty = self.llty_of_type(builtins.get_iterable_elt(insn.type)) - llalloc = self.llbuilder.alloca(lleltty, size=llsize) - if types._is_pointer(insn.type): - return llalloc - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - llvalue = self.llbuilder.insert_value(llvalue, llalloc, 0, name=insn.name) - llvalue = self.llbuilder.insert_value(llvalue, llsize, 1) - return llvalue - elif (not builtins.is_allocated(insn.type) or ir.is_keyword(insn.type) - or builtins.is_array(insn.type)): - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - for index, elt in enumerate(insn.operands): - llvalue = self.llbuilder.insert_value(llvalue, self.map(elt), index) - llvalue.name = insn.name - return llvalue - elif types.is_constructor(insn.type): - return self.get_class(insn.type) - else: # catchall for exceptions and custom (allocated) classes - llalloc = self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True)) - for index, operand in enumerate(insn.operands): - lloperand = self.map(operand) - llfieldptr = self.llbuilder.gep(llalloc, [self.llindex(0), self.llindex(index)], - inbounds=True) - self.llbuilder.store(lloperand, llfieldptr) - return llalloc - - def llptr_to_var(self, llenv, env_ty, var_name): - if var_name in env_ty.params: - var_index = list(env_ty.params.keys()).index(var_name) - return self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(var_index)], - inbounds=True) - else: - outer_index = list(env_ty.params.keys()).index("$outer") - llptr = self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(outer_index)], - inbounds=True) - llouterenv = self.llbuilder.load(llptr) - llouterenv.set_metadata('invariant.load', self.empty_metadata) - llouterenv.set_metadata('nonnull', self.empty_metadata) - return self.llptr_to_var(llouterenv, env_ty.params["$outer"], var_name) - - def mark_dereferenceable(self, load): - assert isinstance(load, ll.LoadInstr) and isinstance(load.type, ll.PointerType) - pointee_size, _ = self.abi_layout_info.get_size_align(load.type.pointee) - metadata = self.llmodule.add_metadata([ll.Constant(lli64, pointee_size)]) - load.set_metadata('dereferenceable', metadata) - - def process_GetLocal(self, insn): - env = insn.environment() - llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) - llptr.name = "ptr.{}.{}".format(env.name, insn.var_name) - llvalue = self.llbuilder.load(llptr, name="val.{}.{}".format(env.name, insn.var_name)) - if isinstance(llvalue.type, ll.PointerType): - self.mark_dereferenceable(llvalue) - return llvalue - - def process_SetLocal(self, insn): - env = insn.environment() - llvalue = self.map(insn.value()) - if isinstance(llvalue.type, ll.VoidType): - # We store NoneType as {} but return it as void. So, bail out here. - return ll.Constant(ll.LiteralStructType([]), []) - llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) - llptr.name = "ptr.{}.{}".format(env.name, insn.var_name) - if isinstance(llvalue, ll.Block): - llvalue = ll.BlockAddress(self.llfunction, llvalue) - if llptr.type.pointee != llvalue.type: - # The environment argument is an i8*, so that all closures can - # unify with each other regardless of environment type or size. - # We fixup the type on assignment into the "$outer" slot. - assert insn.var_name == '$outer' - llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee) - return self.llbuilder.store(llvalue, llptr) - - def attr_index(self, typ, attr): - return list(typ.attributes.keys()).index(attr) - - def get_or_define_global(self, name, llty, llvalue=None): - if llvalue is None: - llvalue = ll.Constant(llty, ll.Undefined) - - if name in self.llmodule.globals: - llglobal = self.llmodule.get_global(name) - else: - llglobal = ll.GlobalVariable(self.llmodule, llty, name) - llglobal.linkage = "private" - if llvalue is not None: - llglobal.initializer = llvalue - return llglobal - - def get_class(self, typ): - assert types.is_constructor(typ) - llty = self.llty_of_type(typ).pointee - return self.get_or_define_global("C.{}".format(typ.name), llty) - - def get_global_closure_ptr(self, typ, attr): - closure_type = typ.attributes[attr] - assert types.is_constructor(typ) - assert types.is_function(closure_type) or types.is_rpc(closure_type) - if types.is_external_function(closure_type) or types.is_rpc(closure_type): - return None - - llty = self.llty_of_type(typ.attributes[attr]) - return self.get_or_define_global("F.{}.{}".format(typ.name, attr), llty) - - def get_global_closure(self, typ, attr): - llclosureptr = self.get_global_closure_ptr(typ, attr) - if llclosureptr is None: - return None - - # LLVM's GlobalOpt pass only considers for SROA the globals that - # are used only by GEPs, so we have to do this stupid hack. - llenvptr = self.llbuilder.gep(llclosureptr, [self.llindex(0), self.llindex(0)]) - llfunptr = self.llbuilder.gep(llclosureptr, [self.llindex(0), self.llindex(1)]) - return [llenvptr, llfunptr] - - def load_closure(self, typ, attr): - llclosureparts = self.get_global_closure(typ, attr) - if llclosureparts is None: - return ll.Constant(llunit, []) - - # See above. - llenvptr, llfunptr = llclosureparts - llenv = self.llbuilder.load(llenvptr) - llfun = self.llbuilder.load(llfunptr) - llclosure = ll.Constant(ll.LiteralStructType([llenv.type, llfun.type]), ll.Undefined) - llclosure = self.llbuilder.insert_value(llclosure, llenv, 0) - llclosure = self.llbuilder.insert_value(llclosure, llfun, 1) - return llclosure - - def store_closure(self, llclosure, typ, attr): - llclosureparts = self.get_global_closure(typ, attr) - assert llclosureparts is not None - - llenvptr, llfunptr = llclosureparts - llenv = self.llbuilder.extract_value(llclosure, 0) - llfun = self.llbuilder.extract_value(llclosure, 1) - self.llbuilder.store(llenv, llenvptr) - return self.llbuilder.store(llfun, llfunptr) - - def process_GetAttr(self, insn): - typ, attr = insn.object().type, insn.attr - if types.is_tuple(typ): - return self.llbuilder.extract_value(self.map(insn.object()), attr, - name=insn.name) - elif builtins.is_array(typ) or not builtins.is_allocated(typ): - return self.llbuilder.extract_value(self.map(insn.object()), - self.attr_index(typ, attr), - name=insn.name) - else: - if attr in typ.attributes: - index = self.attr_index(typ, attr) - obj = self.map(insn.object()) - elif attr in typ.constructor.attributes: - index = self.attr_index(typ.constructor, attr) - obj = self.get_class(typ.constructor) - else: - assert False - - if types.is_method(insn.type) and attr not in typ.attributes: - llfun = self.load_closure(typ.constructor, attr) - llfun.name = "met.{}.{}".format(typ.constructor.name, attr) - llself = self.map(insn.object()) - - llmethodty = self.llty_of_type(insn.type) - llmethod = ll.Constant(llmethodty, ll.Undefined) - llmethod = self.llbuilder.insert_value(llmethod, llfun, - self.attr_index(insn.type, '__func__')) - llmethod = self.llbuilder.insert_value(llmethod, llself, - self.attr_index(insn.type, '__self__')) - return llmethod - elif types.is_function(insn.type) and attr in typ.attributes and \ - types.is_constructor(typ): - llfun = self.load_closure(typ, attr) - llfun.name = "fun.{}".format(insn.name) - return llfun - else: - llptr = self.llbuilder.gep(obj, [self.llindex(0), self.llindex(index)], - inbounds=True, name="ptr.{}".format(insn.name)) - llvalue = self.llbuilder.load(llptr, name="val.{}".format(insn.name)) - if types.is_instance(typ) and attr in typ.constant_attributes: - llvalue.set_metadata('invariant.load', self.empty_metadata) - if isinstance(llvalue.type, ll.PointerType): - self.mark_dereferenceable(llvalue) - return llvalue - - def process_SetAttr(self, insn): - typ, attr = insn.object().type, insn.attr - assert builtins.is_allocated(typ) - - if attr in typ.attributes: - obj = self.map(insn.object()) - elif attr in typ.constructor.attributes: - typ = typ.constructor - obj = self.get_class(typ) - else: - assert False - - llvalue = self.map(insn.value()) - if types.is_function(insn.value().type) and attr in typ.attributes and \ - types.is_constructor(typ): - return self.store_closure(llvalue, typ, attr) - else: - llptr = self.llbuilder.gep(obj, [self.llindex(0), - self.llindex(self.attr_index(typ, attr))], - inbounds=True, name=insn.name) - return self.llbuilder.store(llvalue, llptr) - - def process_Offset(self, insn): - base, idx = insn.base(), insn.index() - llelts, llidx = map(self.map, (base, idx)) - if not types._is_pointer(base.type): - # This is list-ish. - llelts = self.llbuilder.extract_value(llelts, 0) - llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) - return llelt - - def process_GetElem(self, insn): - llelt = self.process_Offset(insn) - llvalue = self.llbuilder.load(llelt) - if isinstance(llvalue.type, ll.PointerType): - self.mark_dereferenceable(llvalue) - return llvalue - - def process_SetElem(self, insn): - base, idx = insn.base(), insn.index() - llelts, llidx = map(self.map, (base, idx)) - if not types._is_pointer(base.type): - # This is list-ish. - llelts = self.llbuilder.extract_value(llelts, 0) - llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) - return self.llbuilder.store(self.map(insn.value()), llelt) - - def process_Coerce(self, insn): - typ, value_typ = insn.type, insn.value().type - if typ == value_typ: - return self.map(insn.value()) - if builtins.is_int(typ) and builtins.is_float(value_typ): - return self.llbuilder.fptosi(self.map(insn.value()), self.llty_of_type(typ), - name=insn.name) - elif builtins.is_float(typ) and builtins.is_int(value_typ): - return self.llbuilder.sitofp(self.map(insn.value()), self.llty_of_type(typ), - name=insn.name) - elif builtins.is_int(typ) and builtins.is_int(value_typ): - if builtins.get_int_width(typ) > builtins.get_int_width(value_typ): - return self.llbuilder.sext(self.map(insn.value()), self.llty_of_type(typ), - name=insn.name) - else: # builtins.get_int_width(typ) <= builtins.get_int_width(value_typ): - return self.llbuilder.trunc(self.map(insn.value()), self.llty_of_type(typ), - name=insn.name) - else: - assert False - - def add_fast_math_flags(self, llvalue): - if 'fast-math' in self.function_flags: - llvalue.opname = llvalue.opname + ' fast' - - def process_Arith(self, insn): - if isinstance(insn.op, ast.Add): - if builtins.is_float(insn.type): - llvalue = self.llbuilder.fadd(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - self.add_fast_math_flags(llvalue) - return llvalue - else: - return self.llbuilder.add(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.Sub): - if builtins.is_float(insn.type): - llvalue = self.llbuilder.fsub(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - self.add_fast_math_flags(llvalue) - return llvalue - else: - return self.llbuilder.sub(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.Mult): - if builtins.is_float(insn.type): - llvalue = self.llbuilder.fmul(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - self.add_fast_math_flags(llvalue) - return llvalue - else: - return self.llbuilder.mul(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.Div): - if builtins.is_float(insn.lhs().type): - llvalue = self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - self.add_fast_math_flags(llvalue) - return llvalue - else: - lllhs = self.llbuilder.sitofp(self.map(insn.lhs()), self.llty_of_type(insn.type)) - llrhs = self.llbuilder.sitofp(self.map(insn.rhs()), self.llty_of_type(insn.type)) - llvalue = self.llbuilder.fdiv(lllhs, llrhs, - name=insn.name) - self.add_fast_math_flags(llvalue) - return llvalue - elif isinstance(insn.op, ast.FloorDiv): - if builtins.is_float(insn.type): - llvalue = self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs())) - self.add_fast_math_flags(llvalue) - return self.llbuilder.call(self.llbuiltin("llvm.floor.f64"), [llvalue], - name=insn.name) - else: - return self.llbuilder.sdiv(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.Mod): - lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs())) - if builtins.is_float(insn.type): - intrinsic = "__py_moddf3" - elif builtins.is_int32(insn.type): - intrinsic = "__py_modsi3" - elif builtins.is_int64(insn.type): - intrinsic = "__py_moddi3" - return self.llbuilder.call(self.llbuiltin(intrinsic), [lllhs, llrhs], - name=insn.name) - elif isinstance(insn.op, ast.Pow): - if builtins.is_float(insn.type): - return self.llbuilder.call(self.llbuiltin("llvm.pow.f64"), - [self.map(insn.lhs()), self.map(insn.rhs())], - name=insn.name) - else: - lllhs = self.llbuilder.sitofp(self.map(insn.lhs()), lldouble) - llrhs = self.llbuilder.trunc(self.map(insn.rhs()), lli32) - llvalue = self.llbuilder.call(self.llbuiltin("llvm.powi.f64"), [lllhs, llrhs]) - return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type), - name=insn.name) - elif isinstance(insn.op, ast.LShift): - lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs())) - llrhs_max = ll.Constant(llrhs.type, builtins.get_int_width(insn.lhs().type)) - llrhs_overflow = self.llbuilder.icmp_signed('>=', llrhs, llrhs_max) - llvalue_zero = ll.Constant(lllhs.type, 0) - llvalue = self.llbuilder.shl(lllhs, llrhs) - return self.llbuilder.select(llrhs_overflow, llvalue_zero, llvalue, - name=insn.name) - elif isinstance(insn.op, ast.RShift): - lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs())) - llrhs_max = ll.Constant(llrhs.type, builtins.get_int_width(insn.lhs().type) - 1) - llrhs_overflow = self.llbuilder.icmp_signed('>', llrhs, llrhs_max) - llvalue = self.llbuilder.ashr(lllhs, llrhs) - llvalue_max = self.llbuilder.ashr(lllhs, llrhs_max) # preserve sign bit - return self.llbuilder.select(llrhs_overflow, llvalue_max, llvalue, - name=insn.name) - elif isinstance(insn.op, ast.BitAnd): - return self.llbuilder.and_(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.BitOr): - return self.llbuilder.or_(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - elif isinstance(insn.op, ast.BitXor): - return self.llbuilder.xor(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) - else: - assert False - - def process_Compare(self, insn): - if isinstance(insn.op, (ast.Eq, ast.Is)): - op = '==' - elif isinstance(insn.op, (ast.NotEq, ast.IsNot)): - op = '!=' - elif isinstance(insn.op, ast.Gt): - op = '>' - elif isinstance(insn.op, ast.GtE): - op = '>=' - elif isinstance(insn.op, ast.Lt): - op = '<' - elif isinstance(insn.op, ast.LtE): - op = '<=' - else: - assert False - - lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs())) - assert lllhs.type == llrhs.type - - if isinstance(lllhs.type, ll.IntType): - return self.llbuilder.icmp_signed(op, lllhs, llrhs, - name=insn.name) - elif isinstance(lllhs.type, ll.PointerType): - return self.llbuilder.icmp_unsigned(op, lllhs, llrhs, - name=insn.name) - elif isinstance(lllhs.type, ll.DoubleType): - llresult = self.llbuilder.fcmp_ordered(op, lllhs, llrhs, - name=insn.name) - self.add_fast_math_flags(llresult) - return llresult - elif isinstance(lllhs.type, ll.LiteralStructType): - # Compare aggregates (such as lists or ranges) element-by-element. - llvalue = ll.Constant(lli1, True) - for index in range(len(lllhs.type.elements)): - lllhselt = self.llbuilder.extract_value(lllhs, index) - llrhselt = self.llbuilder.extract_value(llrhs, index) - llresult = self.llbuilder.icmp_unsigned('==', lllhselt, llrhselt) - llvalue = self.llbuilder.select(llresult, llvalue, - ll.Constant(lli1, False)) - return self.llbuilder.icmp_unsigned(op, llvalue, ll.Constant(lli1, True), - name=insn.name) - else: - print(lllhs, llrhs) - assert False - - def process_Builtin(self, insn): - if insn.op == "nop": - return self.llbuilder.call(self.llbuiltin("llvm.donothing"), []) - elif insn.op == "is_some": - lloptarg = self.map(insn.operands[0]) - return self.llbuilder.extract_value(lloptarg, 0, - name=insn.name) - elif insn.op == "unwrap": - lloptarg = self.map(insn.operands[0]) - return self.llbuilder.extract_value(lloptarg, 1, - name=insn.name) - elif insn.op == "unwrap_or": - lloptarg, lldefault = map(self.map, insn.operands) - llhas_arg = self.llbuilder.extract_value(lloptarg, 0, name="opt.has") - llarg = self.llbuilder.extract_value(lloptarg, 1, name="opt.val") - return self.llbuilder.select(llhas_arg, llarg, lldefault, - name=insn.name) - elif insn.op == "round": - llarg = self.map(insn.operands[0]) - llvalue = self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llarg]) - return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type), - name=insn.name) - elif insn.op == "globalenv": - def get_outer(llenv, env_ty): - if "$outer" in env_ty.params: - outer_index = list(env_ty.params.keys()).index("$outer") - llptr = self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(outer_index)], - inbounds=True) - llouterenv = self.llbuilder.load(llptr) - llouterenv.set_metadata('invariant.load', self.empty_metadata) - llouterenv.set_metadata('nonnull', self.empty_metadata) - return self.llptr_to_var(llouterenv, env_ty.params["$outer"], var_name) - else: - return llenv - - env, = insn.operands - return get_outer(self.map(env), env.type) - elif insn.op == "len": - collection, = insn.operands - if builtins.is_array(collection.type): - # Return length of outermost dimension. - shape = self.llbuilder.extract_value(self.map(collection), - self.attr_index(collection.type, "shape")) - return self.llbuilder.extract_value(shape, 0) - return self.llbuilder.extract_value(self.map(collection), 1) - elif insn.op in ("printf", "rtio_log"): - # We only get integers, floats, pointers and strings here. - lloperands = [] - for i, operand in enumerate(insn.operands): - lloperand = self.map(operand) - if i == 0 and (insn.op == "printf" or insn.op == "rtio_log"): - lloperands.append(self.llbuilder.extract_value(lloperand, 0)) - elif builtins.is_str(operand.type) or builtins.is_bytes(operand.type): - lloperands.append(self.llbuilder.extract_value(lloperand, 1)) - lloperands.append(self.llbuilder.extract_value(lloperand, 0)) - else: - lloperands.append(lloperand) - func_name = self.target.print_function if insn.op == "printf" else insn.op - return self.llbuilder.call(self.llbuiltin(func_name), lloperands, - name=insn.name) - elif insn.op == "exncast": - # This is an identity cast at LLVM IR level. - return self.map(insn.operands[0]) - elif insn.op == "now_mu": - if self.target.now_pinning: - # Word swap now.old as CPU is little endian - # Most significant word is stored in lower address (see generated csr.rs) - llnow_raw = self.llbuilder.load(self.llbuiltin("now"), name=insn.name) - llnow_lo = self.llbuilder.shl(llnow_raw, ll.Constant(lli64, 32)) - llnow_hi = self.llbuilder.lshr(llnow_raw, ll.Constant(lli64, 32)) - return self.llbuilder.or_(llnow_lo, llnow_hi) - else: - return self.llbuilder.call(self.llbuiltin("now_mu"), []) - elif insn.op == "at_mu": - time, = insn.operands - lltime = self.map(time) - if self.target.now_pinning: - lltime_hi = self.llbuilder.trunc(self.llbuilder.lshr(lltime, ll.Constant(lli64, 32)), lli32) - lltime_lo = self.llbuilder.trunc(lltime, lli32) - llnow_hiptr = self.llbuilder.bitcast(self.llbuiltin("now"), lli32.as_pointer()) - llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(1)]) - llstore_hi = self.llbuilder.store_atomic(lltime_hi, llnow_hiptr, ordering="seq_cst", align=4) - llstore_lo = self.llbuilder.store_atomic(lltime_lo, llnow_loptr, ordering="seq_cst", align=4) - return llstore_lo - else: - return self.llbuilder.call(self.llbuiltin("at_mu"), [lltime]) - elif insn.op == "delay_mu": - interval, = insn.operands - llinterval = self.map(interval) - if self.target.now_pinning: - llnowptr = self.llbuiltin("now") - llnow = self.llbuilder.load(llnowptr, name="now.old") - - # Word swap now.old as CPU is little endian - # Most significant word is stored in lower address (see generated csr.rs) - llnow_lo = self.llbuilder.shl(llnow, ll.Constant(lli64, 32)) - llnow_hi = self.llbuilder.lshr(llnow, ll.Constant(lli64, 32)) - llnow = self.llbuilder.or_(llnow_lo, llnow_hi) - - lladjusted = self.llbuilder.add(llnow, llinterval, name="now.new") - lladjusted_hi = self.llbuilder.trunc(self.llbuilder.lshr(lladjusted, ll.Constant(lli64, 32)), lli32) - lladjusted_lo = self.llbuilder.trunc(lladjusted, lli32) - llnow_hiptr = self.llbuilder.bitcast(llnowptr, lli32.as_pointer()) - llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(1)]) - llstore_hi = self.llbuilder.store_atomic(lladjusted_hi, llnow_hiptr, ordering="seq_cst", align=4) - llstore_lo = self.llbuilder.store_atomic(lladjusted_lo, llnow_loptr, ordering="seq_cst", align=4) - return llstore_lo - else: - return self.llbuilder.call(self.llbuiltin("delay_mu"), [llinterval]) - else: - assert False - - def process_Closure(self, insn): - llenv = self.map(insn.environment()) - llenv = self.llbuilder.bitcast(llenv, llptr) - llfun = self.map(insn.target_function) - llvalue = ll.Constant(self.llty_of_type(insn.target_function.type), ll.Undefined) - llvalue = self.llbuilder.insert_value(llvalue, llenv, 0) - llvalue = self.llbuilder.insert_value(llvalue, llfun, 1, name=insn.name) - return llvalue - - def _prepare_closure_call(self, insn): - llargs = [self.map(arg) for arg in insn.arguments()] - llclosure = self.map(insn.target_function()) - if insn.static_target_function is None: - if isinstance(llclosure, ll.Constant): - name = "fun.{}".format(llclosure.constant[1].name) - else: - name = "fun.{}".format(llclosure.name) - llfun = self.llbuilder.extract_value(llclosure, 1, name=name) - else: - llfun = self.map(insn.static_target_function) - llenv = self.llbuilder.extract_value(llclosure, 0, name="env.fun") - return llfun, [llenv] + list(llargs) - - def _prepare_ffi_call(self, insn): - llargs = [] - byvals = [] - for i, arg in enumerate(insn.arguments()): - llarg = self.map(arg) - if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)): - llslot = self.llbuilder.alloca(llarg.type) - self.llbuilder.store(llarg, llslot) - llargs.append(llslot) - byvals.append(i) - else: - llargs.append(llarg) - - llfunname = insn.target_function().type.name - llfun = self.llmodule.globals.get(llfunname) - if llfun is None: - llretty = self.llty_of_type(insn.type, for_return=True) - if self.needs_sret(llretty): - llfunty = ll.FunctionType(llvoid, [llretty.as_pointer()] + - [llarg.type for llarg in llargs]) - else: - llfunty = ll.FunctionType(llretty, [llarg.type for llarg in llargs]) - - llfun = ll.Function(self.llmodule, llfunty, - insn.target_function().type.name) - if self.needs_sret(llretty): - llfun.args[0].add_attribute('sret') - byvals = [i + 1 for i in byvals] - for i in byvals: - llfun.args[i].add_attribute('byval') - if 'nounwind' in insn.target_function().type.flags: - llfun.attributes.add('nounwind') - if 'nowrite' in insn.target_function().type.flags: - llfun.attributes.add('inaccessiblememonly') - - return llfun, list(llargs) - - def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock): - llservice = ll.Constant(lli32, fun_type.service) - - tag = b"" - - for arg in args: - def arg_error_handler(typ): - printer = types.TypePrinter() - note = diagnostic.Diagnostic("note", - "value of type {type}", - {"type": printer.name(typ)}, - arg.loc) - diag = diagnostic.Diagnostic("error", - "type {type} is not supported in remote procedure calls", - {"type": printer.name(arg.type)}, - arg.loc, notes=[note]) - self.engine.process(diag) - tag += ir.rpc_tag(arg.type, arg_error_handler) - tag += b":" - - def ret_error_handler(typ): - printer = types.TypePrinter() - note = diagnostic.Diagnostic("note", - "value of type {type}", - {"type": printer.name(typ)}, - fun_loc) - diag = diagnostic.Diagnostic("error", - "return type {type} is not supported in remote procedure calls", - {"type": printer.name(fun_type.ret)}, - fun_loc, notes=[note]) - self.engine.process(diag) - tag += ir.rpc_tag(fun_type.ret, ret_error_handler) - - lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr())) - lltagptr = self.llbuilder.alloca(lltag.type) - self.llbuilder.store(lltag, lltagptr) - - llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [], - name="rpc.stack") - - llargs = self.llbuilder.alloca(llptr, ll.Constant(lli32, len(args)), - name="rpc.args") - for index, arg in enumerate(args): - if builtins.is_none(arg.type): - llargslot = self.llbuilder.alloca(llunit, - name="rpc.arg{}".format(index)) - else: - llarg = self.map(arg) - llargslot = self.llbuilder.alloca(llarg.type, - name="rpc.arg{}".format(index)) - self.llbuilder.store(llarg, llargslot) - llargslot = self.llbuilder.bitcast(llargslot, llptr) - - llargptr = self.llbuilder.gep(llargs, [ll.Constant(lli32, index)]) - self.llbuilder.store(llargslot, llargptr) - - if fun_type.is_async: - self.llbuilder.call(self.llbuiltin("rpc_send_async"), - [llservice, lltagptr, llargs]) - else: - self.llbuilder.call(self.llbuiltin("rpc_send"), - [llservice, lltagptr, llargs]) - - # Don't waste stack space on saved arguments. - self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) - - if fun_type.is_async: - # If this RPC is called using an `invoke` ARTIQ IR instruction, there will be - # no other instructions in this basic block. Since this RPC is async, it cannot - # possibly raise an exception, so add an explicit jump to the normal successor. - if llunwindblock: - self.llbuilder.branch(llnormalblock) - - return ll.Undefined - - # T result = { - # void *ret_ptr = alloca(sizeof(T)); - # void *ptr = ret_ptr; - # loop: int size = rpc_recv(ptr); - # // Non-zero: Provide `size` bytes of extra storage for variable-length data. - # if(size) { ptr = alloca(size); goto loop; } - # else *(T*)ret_ptr - # } - llprehead = self.llbuilder.basic_block - llhead = self.llbuilder.append_basic_block(name="rpc.head") - if llunwindblock: - llheadu = self.llbuilder.append_basic_block(name="rpc.head.unwind") - llalloc = self.llbuilder.append_basic_block(name="rpc.continue") - lltail = self.llbuilder.append_basic_block(name="rpc.tail") - - llretty = self.llty_of_type(fun_type.ret) - llslot = self.llbuilder.alloca(llretty, name="rpc.ret.alloc") - llslotgen = self.llbuilder.bitcast(llslot, llptr, name="rpc.ret.ptr") - self.llbuilder.branch(llhead) - - self.llbuilder.position_at_end(llhead) - llphi = self.llbuilder.phi(llslotgen.type, name="rpc.ptr") - llphi.add_incoming(llslotgen, llprehead) - if llunwindblock: - llsize = self.llbuilder.invoke(self.llbuiltin("rpc_recv"), [llphi], - llheadu, llunwindblock, - name="rpc.size.next") - self.llbuilder.position_at_end(llheadu) - else: - llsize = self.llbuilder.call(self.llbuiltin("rpc_recv"), [llphi], - name="rpc.size.next") - lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0), - name="rpc.done") - self.llbuilder.cbranch(lldone, lltail, llalloc) - - self.llbuilder.position_at_end(llalloc) - llalloca = self.llbuilder.alloca(lli8, llsize, name="rpc.alloc") - llalloca.align = 4 # maximum alignment required by OR1K ABI - llphi.add_incoming(llalloca, llalloc) - self.llbuilder.branch(llhead) - - self.llbuilder.position_at_end(lltail) - llret = self.llbuilder.load(llslot, name="rpc.ret") - if not fun_type.ret.fold(False, lambda r, t: r or builtins.is_allocated(t)): - # We didn't allocate anything except the slot for the value itself. - # Don't waste stack space. - self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) - if llnormalblock: - self.llbuilder.branch(llnormalblock) - return llret - - def process_Call(self, insn): - functiontyp = insn.target_function().type - if types.is_rpc(functiontyp): - return self._build_rpc(insn.target_function().loc, - functiontyp, - insn.arguments(), - llnormalblock=None, llunwindblock=None) - elif types.is_external_function(functiontyp): - llfun, llargs = self._prepare_ffi_call(insn) - else: - llfun, llargs = self._prepare_closure_call(insn) - - if self.has_sret(functiontyp): - llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) - - llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) - llcall = self.llbuilder.call(llfun, [llresultslot] + llargs) - llresult = self.llbuilder.load(llresultslot) - - self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) - else: - llcall = llresult = self.llbuilder.call(llfun, llargs, name=insn.name) - - if isinstance(llresult.type, ll.VoidType): - # We have NoneType-returning functions return void, but None is - # {} elsewhere. - llresult = ll.Constant(llunit, []) - - return llresult - - def process_Invoke(self, insn): - functiontyp = insn.target_function().type - llnormalblock = self.map(insn.normal_target()) - llunwindblock = self.map(insn.exception_target()) - if types.is_rpc(functiontyp): - return self._build_rpc(insn.target_function().loc, - functiontyp, - insn.arguments(), - llnormalblock, llunwindblock) - elif types.is_external_function(functiontyp): - llfun, llargs = self._prepare_ffi_call(insn) - else: - llfun, llargs = self._prepare_closure_call(insn) - - if self.has_sret(functiontyp): - llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) - - llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) - llcall = self.llbuilder.invoke(llfun, [llresultslot] + llargs, - llnormalblock, llunwindblock, name=insn.name) - - self.llbuilder.position_at_start(llnormalblock) - llresult = self.llbuilder.load(llresultslot) - - self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) - else: - llcall = self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, - name=insn.name) - llresult = llcall - - # The !tbaa metadata is not legal to use with the invoke instruction, - # so unlike process_Call, we do not set it here. - - return llresult - - def _quote_listish_to_llglobal(self, value, elt_type, path, kind_name): - fail_msg = "at " + ".".join(path()) - if len(value) > 0: - if builtins.is_int(elt_type): - int_typ = (int, numpy.int32, numpy.int64) - for v in value: - assert isinstance(v, int_typ), fail_msg - llty = self.llty_of_type(elt_type) - llelts = [ll.Constant(llty, int(v)) for v in value] - elif builtins.is_float(elt_type): - for v in value: - assert isinstance(v, float), fail_msg - llty = self.llty_of_type(elt_type) - llelts = [ll.Constant(llty, v) for v in value] - else: - llelts = [self._quote(value[i], elt_type, lambda: path() + [str(i)]) - for i in range(len(value))] - else: - llelts = [] - lleltsary = ll.Constant(ll.ArrayType(self.llty_of_type(elt_type), len(llelts)), - list(llelts)) - name = self.llmodule.scope.deduplicate("quoted.{}".format(kind_name)) - llglobal = ll.GlobalVariable(self.llmodule, lleltsary.type, name) - llglobal.initializer = lleltsary - llglobal.linkage = "private" - return llglobal.bitcast(lleltsary.type.element.as_pointer()) - - def _quote_attributes(self, value, typ, path, value_id, llty): - llglobal = None - llfields = [] - emit_as_constant = True - for attr in typ.attributes: - if attr == "__objectid__": - objectid = self.embedding_map.store_object(value) - llfields.append(ll.Constant(lli32, objectid)) - - assert llglobal is None - if types.is_constructor(typ): - llglobal = self.get_class(typ) - else: - llglobal = ll.GlobalVariable(self.llmodule, llty.pointee, - name="O.{}".format(objectid)) - - self.llobject_map[value_id] = llglobal - else: - attrvalue = getattr(value, attr) - is_class_function = (types.is_constructor(typ) and - types.is_function(typ.attributes[attr]) and - not types.is_external_function(typ.attributes[attr])) - if is_class_function: - attrvalue = self.embedding_map.specialize_function(typ.instance, attrvalue) - if not (types.is_instance(typ) and attr in typ.constant_attributes): - emit_as_constant = False - llattrvalue = self._quote(attrvalue, typ.attributes[attr], - lambda: path() + [attr]) - llfields.append(llattrvalue) - if is_class_function: - llclosureptr = self.get_global_closure_ptr(typ, attr) - llclosureptr.initializer = llattrvalue - - llglobal.global_constant = emit_as_constant - llglobal.initializer = ll.Constant(llty.pointee, llfields) - llglobal.linkage = "private" - return llglobal - - def _quote(self, value, typ, path): - value_id = id(value) - if value_id in self.llobject_map: - return self.llobject_map[value_id] - llty = self.llty_of_type(typ) - - fail_msg = self.quote_fail_msg - if fail_msg == None: - self.quote_fail_msg = fail_msg = "at " + ".".join(path()) - - if types.is_constructor(typ) or types.is_instance(typ): - if types.is_instance(typ): - # Make sure the class functions are quoted, as this has the side effect of - # initializing the global closures. - self._quote(type(value), typ.constructor, - lambda: path() + ['__class__']) - return self._quote_attributes(value, typ, path, value_id, llty) - elif types.is_module(typ): - return self._quote_attributes(value, typ, path, value_id, llty) - elif builtins.is_none(typ): - assert value is None, fail_msg - return ll.Constant.literal_struct([]) - elif builtins.is_bool(typ): - assert value in (True, False), fail_msg - # Explicitly cast to bool to handle numpy.bool_. - return ll.Constant(llty, bool(value)) - elif builtins.is_int(typ): - assert isinstance(value, (int, numpy.int32, numpy.int64)), fail_msg - return ll.Constant(llty, int(value)) - elif builtins.is_float(typ): - assert isinstance(value, float), fail_msg - return ll.Constant(llty, value) - elif builtins.is_str(typ) or builtins.is_bytes(typ) or builtins.is_bytearray(typ): - assert isinstance(value, (str, bytes, bytearray)), fail_msg - if isinstance(value, str): - as_bytes = value.encode("utf-8") - else: - as_bytes = value - - llstr = self.llstr_of_str(as_bytes) - llconst = ll.Constant(llty, [llstr, ll.Constant(lli32, len(as_bytes))]) - return llconst - elif builtins.is_array(typ): - assert isinstance(value, numpy.ndarray), fail_msg - typ = typ.find() - assert len(value.shape) == typ["num_dims"].find().value - flattened = value.reshape((-1,)) - lleltsptr = self._quote_listish_to_llglobal(flattened, typ["elt"], path, "array") - llshape = ll.Constant.literal_struct([ll.Constant(lli32, s) for s in value.shape]) - return ll.Constant(llty, [lleltsptr, llshape]) - elif builtins.is_listish(typ): - assert isinstance(value, (list, numpy.ndarray)), fail_msg - elt_type = builtins.get_iterable_elt(typ) - lleltsptr = self._quote_listish_to_llglobal(value, elt_type, path, typ.find().name) - llconst = ll.Constant(llty, [lleltsptr, ll.Constant(lli32, len(value))]) - return llconst - elif types.is_tuple(typ): - assert isinstance(value, tuple), fail_msg - llelts = [self._quote(v, t, lambda: path() + [str(i)]) - for i, (v, t) in enumerate(zip(value, typ.elts))] - return ll.Constant(llty, llelts) - elif types.is_rpc(typ) or types.is_external_function(typ) or types.is_builtin_function(typ): - # RPC, C and builtin functions have no runtime representation. - return ll.Constant(llty, ll.Undefined) - elif types.is_function(typ): - try: - func = self.embedding_map.retrieve_function(value) - except KeyError: - # If a class function was embedded directly (e.g. by a `C.f(...)` call), - # but it also appears in a class hierarchy, we might need to fall back - # to the non-specialized one, since direct invocations do not cause - # monomorphization. - assert isinstance(value, SpecializedFunction) - func = self.embedding_map.retrieve_function(value.host_function) - return self.get_function_with_undef_env(typ.find(), func) - elif types.is_method(typ): - llclosure = self._quote(value.__func__, types.get_method_function(typ), - lambda: path() + ['__func__']) - llself = self._quote(value.__self__, types.get_method_self(typ), - lambda: path() + ['__self__']) - return ll.Constant(llty, [llclosure, llself]) - else: - print(typ) - assert False, fail_msg - - def process_Quote(self, insn): - assert self.embedding_map is not None - return self._quote(insn.value, insn.type, lambda: [repr(insn.value)]) - - def process_Select(self, insn): - return self.llbuilder.select(self.map(insn.condition()), - self.map(insn.if_true()), self.map(insn.if_false())) - - def process_Branch(self, insn): - return self.llbuilder.branch(self.map(insn.target())) - - process_Delay = process_Branch - - def process_BranchIf(self, insn): - return self.llbuilder.cbranch(self.map(insn.condition()), - self.map(insn.if_true()), self.map(insn.if_false())) - - process_Loop = process_BranchIf - - def process_IndirectBranch(self, insn): - llinsn = self.llbuilder.branch_indirect(self.map(insn.target())) - for dest in insn.destinations(): - llinsn.add_destination(self.map(dest)) - return llinsn - - def process_Return(self, insn): - if builtins.is_none(insn.value().type): - return self.llbuilder.ret_void() - else: - llvalue = self.map(insn.value()) - if self.needs_sret(llvalue.type): - self.llbuilder.store(llvalue, self.llfunction.args[0]) - return self.llbuilder.ret_void() - else: - return self.llbuilder.ret(llvalue) - - def process_Unreachable(self, insn): - return self.llbuilder.unreachable() - - def _gen_raise(self, insn, func, args): - if insn.exception_target() is not None: - llnormalblock = self.llfunction.append_basic_block("unreachable") - llnormalblock.terminator = ll.Unreachable(llnormalblock) - llnormalblock.instructions.append(llnormalblock.terminator) - - llunwindblock = self.map(insn.exception_target()) - llinsn = self.llbuilder.invoke(func, args, - llnormalblock, llunwindblock, - name=insn.name) - else: - llinsn = self.llbuilder.call(func, args, - name=insn.name) - self.llbuilder.unreachable() - llinsn.attributes.add('noreturn') - return llinsn - - def process_Raise(self, insn): - llexn = self.map(insn.value()) - return self._gen_raise(insn, self.llbuiltin("__artiq_raise"), [llexn]) - - def process_Reraise(self, insn): - return self._gen_raise(insn, self.llbuiltin("__artiq_reraise"), []) - - def process_LandingPad(self, insn): - # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} - lllandingpadty = ll.LiteralStructType([llptr, llptr]) - lllandingpad = self.llbuilder.landingpad(lllandingpadty, cleanup=True) - llrawexn = self.llbuilder.extract_value(lllandingpad, 1) - llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type)) - llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)], - inbounds=True) - llexnname = self.llbuilder.load(llexnnameptr) - - for target, typ in insn.clauses(): - if typ is None: - exnname = "" # see the comment in ksupport/eh.rs - else: - exnname = "{}:{}".format(typ.id, typ.name) - - llclauseexnname = self.llconst_of_const( - ir.Constant(exnname, builtins.TStr())) - llclauseexnnameptr = self.llmodule.globals.get("exn.{}".format(exnname)) - if llclauseexnnameptr is None: - llclauseexnnameptr = ll.GlobalVariable(self.llmodule, llclauseexnname.type, - name="exn.{}".format(exnname)) - llclauseexnnameptr.global_constant = True - llclauseexnnameptr.initializer = llclauseexnname - llclauseexnnameptr.linkage = "private" - llclauseexnnameptr.unnamed_addr = True - lllandingpad.add_clause(ll.CatchClause(llclauseexnnameptr)) - - if typ is None: - self.llbuilder.branch(self.map(target)) - else: - llexnlen = self.llbuilder.extract_value(llexnname, 1) - llclauseexnlen = self.llbuilder.extract_value(llclauseexnname, 1) - llmatchinglen = self.llbuilder.icmp_unsigned('==', llexnlen, llclauseexnlen) - with self.llbuilder.if_then(llmatchinglen): - llexnptr = self.llbuilder.extract_value(llexnname, 0) - llclauseexnptr = self.llbuilder.extract_value(llclauseexnname, 0) - llcomparedata = self.llbuilder.call(self.llbuiltin("memcmp"), - [llexnptr, llclauseexnptr, llexnlen]) - llmatchingdata = self.llbuilder.icmp_unsigned('==', llcomparedata, - ll.Constant(lli32, 0)) - with self.llbuilder.if_then(llmatchingdata): - self.llbuilder.branch(self.map(target)) - - if self.llbuilder.basic_block.terminator is None: - self.llbuilder.branch(self.map(insn.cleanup())) - - return llexn diff --git a/artiq/compiler/transforms/local_demoter.py b/artiq/compiler/transforms/local_demoter.py deleted file mode 100644 index 4701e7a7c..000000000 --- a/artiq/compiler/transforms/local_demoter.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -:class:`LocalDemoter` is a constant propagation transform: -it replaces reads of any local variable with only one write -in a function without closures with the value that was written. - -:class:`LocalAccessValidator` must be run before this transform -to ensure that the transformation it performs is sound. -""" - -from collections import defaultdict -from .. import ir - -class LocalDemoter: - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - env_safe = {} - env_gets = defaultdict(lambda: set()) - env_sets = defaultdict(lambda: set()) - - for insn in func.instructions(): - if isinstance(insn, (ir.GetLocal, ir.SetLocal)): - if "$" in insn.var_name: - continue - - env = insn.environment() - - if env not in env_safe: - for use in env.uses: - if not isinstance(use, (ir.GetLocal, ir.SetLocal)): - env_safe[env] = False - break - else: - env_safe[env] = True - - if not env_safe[env]: - continue - - if isinstance(insn, ir.SetLocal): - env_sets[(env, insn.var_name)].add(insn) - else: - env_gets[(env, insn.var_name)].add(insn) - - for (env, var_name) in env_sets: - if len(env_sets[(env, var_name)]) == 1: - set_insn = next(iter(env_sets[(env, var_name)])) - for get_insn in env_gets[(env, var_name)]: - get_insn.replace_all_uses_with(set_insn.value()) - get_insn.erase() diff --git a/artiq/compiler/transforms/typedtree_printer.py b/artiq/compiler/transforms/typedtree_printer.py deleted file mode 100644 index e0c296f42..000000000 --- a/artiq/compiler/transforms/typedtree_printer.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -:class:`TypedtreePrinter` prints a human-readable representation of typedtrees. -""" - -from pythonparser import algorithm, ast -from .. import types, asttyped - -class TypedtreePrinter(algorithm.Visitor): - def __init__(self): - self.str = None - self.level = None - self.last_nl = None - self.type_printer = None - - def print(self, node): - try: - self.str = "" - self.level = 0 - self.last_nl = 0 - self.type_printer = types.TypePrinter() - self.visit(node) - self._nl() - return self.str - finally: - self.str = None - self.level = None - self.last_nl = 0 - self.type_printer = None - - def _nl(self): - # self.str += "·" - if len(self.str) != self.last_nl: - self.str += "\n" + (" " * self.level) - self.last_nl = len(self.str) - - def _indent(self): - self.level += 1 - self._nl() - - def _dedent(self): - self._nl() - self.level -= 1 - self.str = self.str[:-2] - self.last_nl -= 2 - - def visit(self, obj): - if isinstance(obj, ast.AST): - attrs = set(obj._fields) - {'ctx'} - if isinstance(obj, asttyped.commontyped): - attrs.update(set(obj._types)) - - for attr in set(attrs): - if not getattr(obj, attr): - attrs.remove(attr) # omit falsey stuff - - self.str += obj.__class__.__name__ + "(" - if len(attrs) > 1: - self._indent() - - for attr in attrs: - if len(attrs) > 1: - self._nl() - self.str += attr + "=" - self.visit(getattr(obj, attr)) - if len(attrs) > 1: - self._nl() - - if len(attrs) > 1: - self._dedent() - self.str += ")" - elif isinstance(obj, types.Type): - self.str += self.type_printer.name(obj, max_depth=0) - elif isinstance(obj, list): - self.str += "[" - if len(obj) > 1: - self._indent() - - for elem in obj: - if len(obj) > 1: - self._nl() - self.visit(elem) - if len(obj) > 1: - self._nl() - - if len(obj) > 1: - self._dedent() - self.str += "]" - else: - self.str += repr(obj) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py deleted file mode 100644 index 1d9336b4d..000000000 --- a/artiq/compiler/types.py +++ /dev/null @@ -1,839 +0,0 @@ -""" -The :mod:`types` module contains the classes describing the types -in :mod:`asttyped`. -""" - -import builtins -import string -from collections import OrderedDict -from . import iodelay - - -class UnificationError(Exception): - def __init__(self, typea, typeb): - self.typea, self.typeb = typea, typeb - - -def genalnum(): - ident = ["a"] - while True: - yield "".join(ident) - pos = len(ident) - 1 - while pos >= 0: - cur_n = string.ascii_lowercase.index(ident[pos]) - if cur_n < 25: - ident[pos] = string.ascii_lowercase[cur_n + 1] - break - else: - ident[pos] = "a" - pos -= 1 - if pos < 0: - ident = ["a"] + ident - -def _freeze(dict_): - return tuple((key, dict_[key]) for key in dict_) - -def _map_find(elts): - if isinstance(elts, list): - return [x.find() for x in elts] - elif isinstance(elts, dict): - return {k: elts[k].find() for k in elts} - else: - assert False - - -class Type(object): - def __str__(self): - return TypePrinter().name(self) - -class TVar(Type): - """ - A type variable. - - In effect, the classic union-find data structure is intrusively - folded into this class. - """ - - def __init__(self): - self.parent = self - self.rank = 0 - - def find(self): - parent = self.parent - if parent is self: - return self - else: - # The recursive find() invocation is turned into a loop - # because paths resulting from unification of large arrays - # can easily cause a stack overflow. - root = self - while parent.__class__ == TVar and root is not parent: - _, parent = root, root.parent = parent, parent.parent - return root.parent - - def unify(self, other): - if other is self: - return - x = other.find() - y = self.find() - if x is y: - return - if y.__class__ == TVar: - if x.__class__ == TVar: - if x.rank < y.rank: - x, y = y, x - y.parent = x - if x.rank == y.rank: - x.rank += 1 - else: - y.parent = x - else: - y.unify(x) - - def fold(self, accum, fn): - if self.parent is self: - return fn(accum, self) - else: - return self.find().fold(accum, fn) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - if self.parent is self: - return "" % id(self) - else: - return repr(self.find()) - - # __eq__ and __hash__ are not overridden and default to - # comparison by identity. Use .find() explicitly before - # any lookups or comparisons. - -class TMono(Type): - """ - A monomorphic type, possibly parametric. - - :class:`TMono` is supposed to be subclassed by builtin types, - unlike all other :class:`Type` descendants. Similarly, - instances of :class:`TMono` should never be allocated directly, - as that will break the type-sniffing code in :mod:`builtins`. - """ - - attributes = OrderedDict() - - def __init__(self, name, params={}): - assert isinstance(params, (dict, OrderedDict)) - self.name, self.params = name, OrderedDict(sorted(params.items())) - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TMono) and self.name == other.name: - assert self.params.keys() == other.params.keys() - for param in self.params: - self.params[param].unify(other.params[param]) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for param in self.params: - accum = self.params[param].fold(accum, fn) - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) - - def __getitem__(self, param): - return self.params[param] - - def __eq__(self, other): - return isinstance(other, TMono) and \ - self.name == other.name and \ - _map_find(self.params) == _map_find(other.params) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash((self.name, _freeze(self.params))) - -class TTuple(Type): - """ - A tuple type. - - :ivar elts: (list of :class:`Type`) elements - """ - - attributes = OrderedDict() - - def __init__(self, elts=[]): - self.elts = elts - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TTuple) and len(self.elts) == len(other.elts): - for selfelt, otherelt in zip(self.elts, other.elts): - selfelt.unify(otherelt) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for elt in self.elts: - accum = elt.fold(accum, fn) - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TTuple(%s)" % repr(self.elts) - - def __eq__(self, other): - return isinstance(other, TTuple) and \ - _map_find(self.elts) == _map_find(other.elts) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(tuple(self.elts)) - -class _TPointer(TMono): - def __init__(self, elt=None): - if elt is None: - elt = TMono("int", {"width": 8}) # i8* - super().__init__("pointer", params={"elt": elt}) - -class TFunction(Type): - """ - A function type. - - :ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`) - mandatory arguments - :ivar optargs: (:class:`collections.OrderedDict` of string to :class:`Type`) - optional arguments - :ivar ret: (:class:`Type`) - return type - :ivar delay: (:class:`Type`) - RTIO delay - """ - - attributes = OrderedDict([ - ('__closure__', _TPointer()), - ('__code__', _TPointer()), - ]) - - def __init__(self, args, optargs, ret): - assert isinstance(args, OrderedDict) - assert isinstance(optargs, OrderedDict) - assert isinstance(ret, Type) - self.args, self.optargs, self.ret = args, optargs, ret - self.delay = TVar() - - def arity(self): - return len(self.args) + len(self.optargs) - - def arg_names(self): - return list(self.args.keys()) + list(self.optargs.keys()) - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TFunction) and \ - self.args.keys() == other.args.keys() and \ - self.optargs.keys() == other.optargs.keys(): - for selfarg, otherarg in zip(list(self.args.values()) + list(self.optargs.values()), - list(other.args.values()) + list(other.optargs.values())): - selfarg.unify(otherarg) - self.ret.unify(other.ret) - self.delay.unify(other.delay) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for arg in self.args: - accum = self.args[arg].fold(accum, fn) - for optarg in self.optargs: - accum = self.optargs[optarg].fold(accum, fn) - accum = self.ret.fold(accum, fn) - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TFunction({}, {}, {})".format( - repr(self.args), repr(self.optargs), repr(self.ret)) - - def __eq__(self, other): - return isinstance(other, TFunction) and \ - _map_find(self.args) == _map_find(other.args) and \ - _map_find(self.optargs) == _map_find(other.optargs) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash((_freeze(self.args), _freeze(self.optargs), self.ret)) - -class TExternalFunction(TFunction): - """ - A type of an externally-provided function. - - This can be any function following the C ABI, such as provided by the - C/Rust runtime, or a compiler backend intrinsic. The mangled name to link - against is encoded as part of the type. - - :ivar name: (str) external symbol name. - This will be the symbol linked against (following any extra C name - mangling rules). - :ivar flags: (set of str) function flags. - Flag ``nounwind`` means the function never raises an exception. - Flag ``nowrite`` means the function never accesses any memory - that the ARTIQ Python code can observe. - :ivar broadcast_across_arrays: (bool) - If True, the function is transparently applied element-wise when called - with TArray arguments. - """ - - attributes = OrderedDict() - - def __init__(self, args, ret, name, flags=set(), broadcast_across_arrays=False): - assert isinstance(flags, set) - for flag in flags: - assert flag in {'nounwind', 'nowrite'} - super().__init__(args, OrderedDict(), ret) - self.name = name - self.delay = TFixedDelay(iodelay.Const(0)) - self.flags = flags - self.broadcast_across_arrays = broadcast_across_arrays - - def unify(self, other): - if other is self: - return - if isinstance(other, TExternalFunction) and \ - self.name == other.name: - super().unify(other) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - -class TRPC(Type): - """ - A type of a remote call. - - :ivar ret: (:class:`Type`) - return type - :ivar service: (int) RPC service number - :ivar is_async: (bool) whether the RPC blocks until return - """ - - attributes = OrderedDict() - - def __init__(self, ret, service, is_async=False): - assert isinstance(ret, Type) - self.ret, self.service, self.is_async = ret, service, is_async - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TRPC) and \ - self.service == other.service and \ - self.is_async == other.is_async: - self.ret.unify(other.ret) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - accum = self.ret.fold(accum, fn) - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TRPC({})".format(repr(self.ret)) - - def __eq__(self, other): - return isinstance(other, TRPC) and \ - self.service == other.service and \ - self.is_async == other.is_async - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.service) - -class TBuiltin(Type): - """ - An instance of builtin type. Every instance of a builtin - type is treated specially according to its name. - """ - - def __init__(self, name): - assert isinstance(name, str) - self.name = name - self.attributes = OrderedDict() - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if self != other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name)) - - def __eq__(self, other): - return isinstance(other, TBuiltin) and \ - self.name == other.name - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.name) - -class TBuiltinFunction(TBuiltin): - """ - A type of a builtin function. - - Builtin functions are treated specially throughout all stages of the - compilation process according to their name (e.g. calls may not actually - lower to a function call). See :class:`TExternalFunction` for externally - defined functions that are otherwise regular. - """ - -class TConstructor(TBuiltin): - """ - A type of a constructor of a class, e.g. ``list``. - Note that this is not the same as the type of an instance of - the class, which is ``TMono("list", ...)`` (or a descendant). - - :ivar instance: (:class:`Type`) - the type of the instance created by this constructor - """ - - def __init__(self, instance): - assert isinstance(instance, TMono) - super().__init__(instance.name) - self.instance = instance - -class TExceptionConstructor(TConstructor): - """ - A type of a constructor of an exception, e.g. ``Exception``. - Note that this is not the same as the type of an instance of - the class, which is ``TMono("Exception", ...)``. - """ - -class TInstance(TMono): - """ - A type of an instance of a user-defined class. - - :ivar constructor: (:class:`TConstructor`) - the type of the constructor with which this instance - was created - """ - - def __init__(self, name, attributes): - assert isinstance(attributes, OrderedDict) - super().__init__(name) - self.attributes = attributes - self.constant_attributes = set() - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TInstance({}, {})".format( - repr(self.name), repr(self.attributes)) - -class TModule(TMono): - """ - A type of a module. - """ - - def __init__(self, name, attributes): - assert isinstance(attributes, OrderedDict) - super().__init__(name) - self.attributes = attributes - self.constant_attributes = set() - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TModule({}, {})".format( - repr(self.name), repr(self.attributes)) - -class TMethod(TMono): - """ - A type of a method. - """ - - def __init__(self, self_type, function_type): - super().__init__("method", {"self": self_type, "fn": function_type}) - self.attributes = OrderedDict([ - ("__func__", function_type), - ("__self__", self_type), - ]) - -class TValue(Type): - """ - A type-level value (such as the integer denoting width of - a generic integer type. - """ - - def __init__(self, value): - self.value = value - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TVar): - other.unify(self) - elif self != other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - return fn(accum, self) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TValue(%s)" % repr(self.value) - - def __eq__(self, other): - return isinstance(other, TValue) and \ - self.value == other.value - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.value) - -class TDelay(Type): - """ - The type-level representation of IO delay. - """ - - def __init__(self, duration, cause): - # Avoid pulling in too many dependencies with `artiq.language`. - from pythonparser import diagnostic - assert duration is None or isinstance(duration, iodelay.Expr) - assert cause is None or isinstance(cause, diagnostic.Diagnostic) - assert (not (duration and cause)) and (duration or cause) - self.duration, self.cause = duration, cause - - def is_fixed(self): - return self.duration is not None - - def is_indeterminate(self): - return self.cause is not None - - def find(self): - return self - - def unify(self, other): - other = other.find() - - if isinstance(other, TVar): - other.unify(self) - elif self.is_fixed() and other.is_fixed() and \ - self.duration.fold() == other.duration.fold(): - pass - elif self is not other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - # delay types do not participate in folding - pass - - def __eq__(self, other): - return isinstance(other, TDelay) and \ - (self.duration == other.duration and \ - self.cause == other.cause) - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - if self.duration is None: - return "<{}.TIndeterminateDelay>".format(__name__) - elif self.cause is None: - return "{}.TFixedDelay({})".format(__name__, self.duration) - else: - assert False - -def TIndeterminateDelay(cause): - return TDelay(None, cause) - -def TFixedDelay(duration): - return TDelay(duration, None) - - -def is_var(typ): - return isinstance(typ.find(), TVar) - -def is_mono(typ, name=None, **params): - typ = typ.find() - - if not isinstance(typ, TMono): - return False - - if name is not None and typ.name != name: - return False - - for param in params: - if param not in typ.params: - return False - if typ.params[param].find() != params[param].find(): - return False - return True - -def is_polymorphic(typ): - return typ.fold(False, lambda accum, typ: accum or is_var(typ)) - -def is_tuple(typ, elts=None): - typ = typ.find() - if elts: - return isinstance(typ, TTuple) and \ - elts == typ.elts - else: - return isinstance(typ, TTuple) - -def _is_pointer(typ): - return isinstance(typ.find(), _TPointer) - -def is_function(typ): - return isinstance(typ.find(), TFunction) - -def is_rpc(typ): - return isinstance(typ.find(), TRPC) - -def is_external_function(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TExternalFunction) - else: - return isinstance(typ, TExternalFunction) and \ - typ.name == name - -def is_builtin(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TBuiltin) - else: - return isinstance(typ, TBuiltin) and \ - typ.name == name - -def is_builtin_function(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TBuiltinFunction) - else: - return isinstance(typ, TBuiltinFunction) and \ - typ.name == name - -def is_broadcast_across_arrays(typ): - # For now, broadcasting is only exposed to predefined external functions, and - # statically selected. Might be extended to user-defined functions if the design - # pans out. - typ = typ.find() - if not isinstance(typ, TExternalFunction): - return False - return typ.broadcast_across_arrays - -def is_constructor(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TConstructor) and \ - typ.name == name - else: - return isinstance(typ, TConstructor) - -def is_exn_constructor(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TExceptionConstructor) and \ - typ.name == name - else: - return isinstance(typ, TExceptionConstructor) - -def is_instance(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TInstance) and \ - typ.name == name - else: - return isinstance(typ, TInstance) - -def is_module(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TModule) and \ - typ.name == name - else: - return isinstance(typ, TModule) - -def is_method(typ): - return isinstance(typ.find(), TMethod) - -def get_method_self(typ): - if is_method(typ): - return typ.find().params["self"].find() - -def get_method_function(typ): - if is_method(typ): - return typ.find().params["fn"].find() - -def is_value(typ): - return isinstance(typ.find(), TValue) - -def get_value(typ): - typ = typ.find() - if isinstance(typ, TVar): - return None - elif isinstance(typ, TValue): - return typ.value - else: - assert False - -def is_delay(typ): - return isinstance(typ.find(), TDelay) - -def is_fixed_delay(typ): - return is_delay(typ) and typ.find().is_fixed() - -def is_indeterminate_delay(typ): - return is_delay(typ) and typ.find().is_indeterminate() - - -class TypePrinter(object): - """ - A class that prints types using Python-like syntax and gives - type variables sequential alphabetic names. - """ - - custom_printers = {} - - def __init__(self): - self.gen = genalnum() - self.map = {} - self.recurse_guard = set() - - def name(self, typ, depth=0, max_depth=1): - typ = typ.find() - if isinstance(typ, TVar): - if typ not in self.map: - self.map[typ] = "'%s" % next(self.gen) - return self.map[typ] - elif isinstance(typ, TInstance): - if typ in self.recurse_guard or depth >= max_depth: - return "".format(typ.name) - elif len(typ.attributes) > 0: - self.recurse_guard.add(typ) - attrs = ",\n\t\t".join(["{}: {}".format(attr, self.name(typ.attributes[attr], - depth + 1)) - for attr in typ.attributes]) - return "".format(typ.name, attrs) - else: - self.recurse_guard.add(typ) - return "".format(typ.name) - elif isinstance(typ, TMono): - if typ.name in self.custom_printers: - return self.custom_printers[typ.name](typ, self, depth + 1, max_depth) - elif typ.params == {}: - return typ.name - else: - return "%s(%s)" % (typ.name, ", ".join( - ["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params])) - elif isinstance(typ, _TPointer): - return "{}*".format(self.name(typ["elt"], depth + 1)) - elif isinstance(typ, TTuple): - if len(typ.elts) == 1: - return "(%s,)" % self.name(typ.elts[0], depth + 1) - else: - return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts]) - elif isinstance(typ, (TFunction, TExternalFunction)): - args = [] - args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1)) - for arg in typ.args] - args += ["?%s:%s" % (arg, self.name(typ.optargs[arg], depth + 1)) - for arg in typ.optargs] - signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret, depth + 1)) - - delay = typ.delay.find() - if isinstance(delay, TVar): - signature += " delay({})".format(self.name(delay, depth + 1)) - elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)): - signature += " " + self.name(delay, depth + 1) - - if isinstance(typ, TExternalFunction): - return "[ffi {}]{}".format(repr(typ.name), signature) - elif isinstance(typ, TFunction): - return signature - elif isinstance(typ, TRPC): - return "[rpc{} #{}](...)->{}".format(typ.service, - " async" if typ.is_async else "", - self.name(typ.ret, depth + 1)) - elif isinstance(typ, TBuiltinFunction): - return "".format(typ.name) - elif isinstance(typ, (TConstructor, TExceptionConstructor)): - if typ in self.recurse_guard or depth >= max_depth: - return "".format(typ.name) - elif len(typ.attributes) > 0: - self.recurse_guard.add(typ) - attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr], - depth + 1)) - for attr in typ.attributes]) - return "".format(typ.name, attrs) - else: - self.recurse_guard.add(typ) - return "".format(typ.name) - elif isinstance(typ, TBuiltin): - return "".format(typ.name) - elif isinstance(typ, TValue): - return repr(typ.value) - elif isinstance(typ, TDelay): - if typ.is_fixed(): - return "delay({} mu)".format(typ.duration) - elif typ.is_indeterminate(): - return "delay(?)" - else: - assert False - else: - assert False diff --git a/artiq/compiler/validators/__init__.py b/artiq/compiler/validators/__init__.py deleted file mode 100644 index ccbaa8827..000000000 --- a/artiq/compiler/validators/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .monomorphism import MonomorphismValidator -from .escape import EscapeValidator -from .local_access import LocalAccessValidator -from .constness import ConstnessValidator diff --git a/artiq/compiler/validators/constness.py b/artiq/compiler/validators/constness.py deleted file mode 100644 index fb1123c49..000000000 --- a/artiq/compiler/validators/constness.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -:class:`ConstnessValidator` checks that no attribute marked -as constant is ever set. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins - -class ConstnessValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - self.in_assign = False - - def visit_Assign(self, node): - self.visit(node.value) - self.in_assign = True - self.visit(node.targets) - self.in_assign = False - - def visit_AugAssign(self, node): - self.visit(node.value) - self.in_assign = True - self.visit(node.target) - self.in_assign = False - - def visit_SubscriptT(self, node): - old_in_assign, self.in_assign = self.in_assign, False - self.visit(node.value) - self.visit(node.slice) - self.in_assign = old_in_assign - - if self.in_assign and builtins.is_bytes(node.value.type): - diag = diagnostic.Diagnostic("error", - "type {typ} is not mutable", - {"typ": "bytes"}, - node.loc) - self.engine.process(diag) - - def visit_AttributeT(self, node): - old_in_assign, self.in_assign = self.in_assign, False - self.visit(node.value) - self.in_assign = old_in_assign - - if self.in_assign: - typ = node.value.type.find() - if types.is_instance(typ) and node.attr in typ.constant_attributes: - diag = diagnostic.Diagnostic("error", - "cannot assign to constant attribute '{attr}' of class '{class}'", - {"attr": node.attr, "class": typ.name}, - node.loc) - self.engine.process(diag) - return - if builtins.is_array(typ): - diag = diagnostic.Diagnostic("error", - "array attributes cannot be assigned to", - {}, node.loc) - self.engine.process(diag) - return diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py deleted file mode 100644 index c6ae59704..000000000 --- a/artiq/compiler/validators/escape.py +++ /dev/null @@ -1,360 +0,0 @@ -""" -:class:`EscapeValidator` verifies that no mutable data escapes -the region of its allocation. -""" - -import functools -from pythonparser import algorithm, diagnostic -from .. import asttyped, types, builtins - -def has_region(typ): - return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ)) - -class Global: - def __repr__(self): - return "Global()" - -class Argument: - def __init__(self, loc): - self.loc = loc - - def __repr__(self): - return "Argument()" - -class Region: - """ - A last-in-first-out allocation region. Tied to lexical scoping - and is internally represented simply by a source range. - - :ivar range: (:class:`pythonparser.source.Range` or None) - """ - - def __init__(self, source_range=None): - self.range = source_range - - def present(self): - return bool(self.range) - - def includes(self, other): - assert self.range - assert self.range.source_buffer == other.range.source_buffer - - return self.range.begin_pos <= other.range.begin_pos and \ - self.range.end_pos >= other.range.end_pos - - def intersects(self, other): - assert self.range - assert self.range.source_buffer == other.range.source_buffer - - return (self.range.begin_pos <= other.range.begin_pos <= self.range.end_pos and \ - other.range.end_pos > self.range.end_pos) or \ - (other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \ - self.range.end_pos > other.range.end_pos) - - def outlives(lhs, rhs): - if not isinstance(lhs, Region): # lhs lives nonlexically - return True - elif not isinstance(rhs, Region): # rhs lives nonlexically, lhs does not - return False - else: - assert not lhs.intersects(rhs) - return lhs.includes(rhs) - - def __repr__(self): - return "Region({})".format(repr(self.range)) - -class RegionOf(algorithm.Visitor): - """ - Visit an expression and return the region that must be alive for the - expression to execute. - - For expressions involving multiple regions, the shortest-lived one is - returned. - """ - - def __init__(self, env_stack, youngest_region): - self.env_stack, self.youngest_region = env_stack, youngest_region - - # Liveness determined by assignments - def visit_NameT(self, node): - # First, look at stack regions - for region in reversed(self.env_stack[1:]): - if node.id in region: - return region[node.id] - - # Then, look at the global region of this module - if node.id in self.env_stack[0]: - return Global() - - assert False - - # Value lives as long as the current scope, if it's mutable, - # or else forever - def visit_sometimes_allocating(self, node): - if has_region(node.type): - return self.youngest_region - else: - return Global() - - visit_BinOpT = visit_sometimes_allocating - - def visit_CallT(self, node): - if types.is_external_function(node.func.type, "cache_get"): - # The cache is borrow checked dynamically - return Global() - else: - self.visit_sometimes_allocating(node) - - # Value lives as long as the object/container, if it's mutable, - # or else forever - def visit_accessor(self, node): - if has_region(node.type): - return self.visit(node.value) - else: - return Global() - - visit_AttributeT = visit_accessor - visit_SubscriptT = visit_accessor - - # Value lives as long as the shortest living operand - def visit_selecting(self, nodes): - regions = [self.visit(node) for node in nodes] - regions = list(filter(lambda x: x, regions)) - if any(regions): - regions.sort(key=functools.cmp_to_key(Region.outlives), reverse=True) - return regions[0] - else: - return Global() - - def visit_BoolOpT(self, node): - return self.visit_selecting(node.values) - - def visit_IfExpT(self, node): - return self.visit_selecting([node.body, node.orelse]) - - def visit_TupleT(self, node): - return self.visit_selecting(node.elts) - - # Value lives as long as the current scope - def visit_allocating(self, node): - return self.youngest_region - - visit_DictT = visit_allocating - visit_DictCompT = visit_allocating - visit_GeneratorExpT = visit_allocating - visit_LambdaT = visit_allocating - visit_ListT = visit_allocating - visit_ListCompT = visit_allocating - visit_SetT = visit_allocating - visit_SetCompT = visit_allocating - - # Value lives forever - def visit_immutable(self, node): - assert not has_region(node.type) - return Global() - - visit_NameConstantT = visit_immutable - visit_NumT = visit_immutable - visit_EllipsisT = visit_immutable - visit_UnaryOpT = visit_sometimes_allocating # possibly array op - visit_CompareT = visit_immutable - - # Value lives forever - def visit_global(self, node): - return Global() - - visit_StrT = visit_global - visit_QuoteT = visit_global - - # Not implemented - def visit_unimplemented(self, node): - assert False - - visit_StarredT = visit_unimplemented - visit_YieldT = visit_unimplemented - visit_YieldFromT = visit_unimplemented - - -class AssignedNamesOf(algorithm.Visitor): - """ - Visit an expression and return the list of names that appear - on the lhs of assignment, directly or through an accessor. - """ - - def visit_name(self, node): - return [node] - - visit_NameT = visit_name - visit_QuoteT = visit_name - - def visit_accessor(self, node): - return self.visit(node.value) - - visit_AttributeT = visit_accessor - visit_SubscriptT = visit_accessor - - def visit_sequence(self, node): - return functools.reduce(list.__add__, map(self.visit, node.elts)) - - visit_TupleT = visit_sequence - visit_ListT = visit_sequence - - def visit_StarredT(self, node): - assert False - - -class EscapeValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - self.youngest_region = Global() - self.env_stack = [] - self.youngest_env = None - - def _region_of(self, expr): - return RegionOf(self.env_stack, self.youngest_region).visit(expr) - - def _names_of(self, expr): - return AssignedNamesOf().visit(expr) - - def _diagnostics_for(self, region, loc, descr="the value of the expression"): - if isinstance(region, Region): - return [ - diagnostic.Diagnostic("note", - "{descr} is alive from this point...", {"descr": descr}, - region.range.begin()), - diagnostic.Diagnostic("note", - "... to this point", {}, - region.range.end()) - ] - elif isinstance(region, Global): - return [ - diagnostic.Diagnostic("note", - "{descr} is alive forever", {"descr": descr}, - loc) - ] - elif isinstance(region, Argument): - return [ - diagnostic.Diagnostic("note", - "{descr} is still alive after this function returns", {"descr": descr}, - loc), - diagnostic.Diagnostic("note", - "{descr} is introduced here as a formal argument", {"descr": descr}, - region.loc) - ] - else: - assert False - - def visit_in_region(self, node, region, typing_env, args=[]): - try: - old_youngest_region = self.youngest_region - self.youngest_region = region - - old_youngest_env = self.youngest_env - self.youngest_env = {} - - for name in typing_env: - if has_region(typing_env[name]): - if name in args: - self.youngest_env[name] = args[name] - else: - self.youngest_env[name] = Region(None) # not yet known - else: - self.youngest_env[name] = Global() - self.env_stack.append(self.youngest_env) - - self.generic_visit(node) - finally: - self.env_stack.pop() - self.youngest_env = old_youngest_env - self.youngest_region = old_youngest_region - - def visit_ModuleT(self, node): - self.visit_in_region(node, None, node.typing_env) - - def visit_FunctionDefT(self, node): - self.youngest_env[node.name] = self.youngest_region - self.visit_in_region(node, Region(node.loc), node.typing_env, - args={ arg.arg: Argument(arg.loc) for arg in node.args.args }) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def visit_ClassDefT(self, node): - self.youngest_env[node.name] = self.youngest_region - self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes) - - # Only three ways for a pointer to escape: - # * Assigning or op-assigning it (we ensure an outlives relationship) - # * Returning it (we only allow returning values that live forever) - # * Raising it (we forbid allocating exceptions that refer to mutable data)¹ - # - # Literals doesn't count: a constructed object is always - # outlived by all its constituents. - # Closures don't count: see above. - # Calling functions doesn't count: arguments never outlive - # the function body. - # - # ¹Strings are currently never allocated with a limited lifetime, - # and exceptions can only refer to strings, so we don't actually check - # this property. But we will need to, if string operations are ever added. - - def visit_assignment(self, target, value): - value_region = self._region_of(value) - - # If we assign to an attribute of a quoted value, there will be no names - # in the assignment lhs. - target_names = self._names_of(target) or [] - - # Adopt the value region for any variables declared on the lhs. - for name in target_names: - region = self._region_of(name) - if isinstance(region, Region) and not region.present(): - # Find the name's environment to overwrite the region. - for env in self.env_stack[::-1]: - if name.id in env: - env[name.id] = value_region - break - - # The assigned value should outlive the assignee - target_regions = [self._region_of(name) for name in target_names] - for target_region in target_regions: - if not Region.outlives(value_region, target_region): - diag = diagnostic.Diagnostic("error", - "the assigned value does not outlive the assignment target", {}, - value.loc, [target.loc], - notes=self._diagnostics_for(target_region, target.loc, - "the assignment target") + - self._diagnostics_for(value_region, value.loc, - "the assigned value")) - self.engine.process(diag) - - def visit_Assign(self, node): - for target in node.targets: - self.visit_assignment(target, node.value) - - def visit_AugAssign(self, node): - if builtins.is_list(node.target.type): - note = diagnostic.Diagnostic("note", - "try using `{lhs} = {lhs} {op} {rhs}` instead", - {"lhs": node.target.loc.source(), - "rhs": node.value.loc.source(), - "op": node.op.loc.source()[:-1]}, - node.loc) - diag = diagnostic.Diagnostic("error", - "lists cannot be mutated in-place", {}, - node.op.loc, [node.target.loc], - notes=[note]) - self.engine.process(diag) - - self.visit_assignment(node.target, node.value) - - def visit_Return(self, node): - region = self._region_of(node.value) - if isinstance(region, Region): - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(node.value.type)}, - node.value.loc) - diag = diagnostic.Diagnostic("error", - "cannot return an allocated value that does not live forever", {}, - node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) - self.engine.process(diag) diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py deleted file mode 100644 index 55ed7bd0d..000000000 --- a/artiq/compiler/validators/local_access.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -:class:`LocalAccessValidator` verifies that local variables -are not accessed before being used. -""" - -from functools import reduce -from pythonparser import diagnostic -from .. import ir, analyses - -def is_special_variable(name): - return "$" in name - -class LocalAccessValidator: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - # Find all environments and closures allocated in this func. - environments, closures = [], [] - for insn in func.instructions(): - if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type): - environments.append(insn) - elif isinstance(insn, ir.Closure): - closures.append(insn) - - # Compute initial state of interesting environments. - # Environments consisting only of internal variables (containing a ".") - # are ignored. - initial_state = {} - for env in environments: - env_state = {var: False for var in env.type.params if "." not in var} - if any(env_state): - initial_state[env] = env_state - - # Traverse the acyclic graph made of basic blocks and forward edges only, - # while updating the environment state. - domtree = analyses.DominatorTree(func) - state = {} - def traverse(block): - # Have we computed the state of this block already? - if block in state: - return state[block] - - # No! Which forward edges lead to this block? - # If we dominate a predecessor, it's a back edge instead. - forward_edge_preds = [pred for pred in block.predecessors() - if block not in domtree.dominators(pred)] - - # Figure out what the state is before the leader - # instruction of this block. - pred_states = [traverse(pred) for pred in forward_edge_preds] - block_state = {} - if len(pred_states) > 1: - for env in initial_state: - # The variable has to be initialized in all predecessors - # in order to be initialized in this block. - def merge_state(a, b): - return {var: a[var] and b[var] for var in a} - block_state[env] = reduce(merge_state, - [state[env] for state in pred_states]) - elif len(pred_states) == 1: - # The state is the same as at the terminator of predecessor. - # We'll mutate it, so copy. - pred_state = pred_states[0] - for env in initial_state: - env_state = pred_state[env] - block_state[env] = {var: env_state[var] for var in env_state} - else: - # This is the entry block. - for env in initial_state: - env_state = initial_state[env] - block_state[env] = {var: env_state[var] for var in env_state} - - # Update the state based on block contents, while validating - # that no access to uninitialized variables will be done. - for insn in block.instructions: - def pred_at_fault(env, var_name): - # Find out where the uninitialized state comes from. - for pred, pred_state in zip(forward_edge_preds, pred_states): - if not pred_state[env][var_name]: - return pred - - # It's the entry block and it was never initialized. - return None - - set_local_in_this_frame = False - if (isinstance(insn, (ir.SetLocal, ir.GetLocal)) and - not is_special_variable(insn.var_name)): - env, var_name = insn.environment(), insn.var_name - - # Make sure that the variable is defined in the scope of this function. - if env in block_state and var_name in block_state[env]: - if isinstance(insn, ir.SetLocal): - # We've just initialized it. - block_state[env][var_name] = True - set_local_in_this_frame = True - else: # isinstance(insn, ir.GetLocal) - if not block_state[env][var_name]: - # Oops, accessing it uninitialized. - self._uninitialized_access(insn, var_name, - pred_at_fault(env, var_name)) - - closures_to_check = [] - - if (isinstance(insn, (ir.SetLocal, ir.SetAttr, ir.SetElem)) and - not set_local_in_this_frame): - # Closures may escape via these mechanisms and be invoked elsewhere. - if isinstance(insn.value(), ir.Closure): - closures_to_check.append(insn.value()) - - if isinstance(insn, (ir.Call, ir.Invoke)): - # We can't always trace the flow of closures from point of - # definition to point of call; however, we know that, by transitiveness - # of this analysis, only closures defined in this function can contain - # uninitialized variables. - # - # Thus, enumerate the closures, and check all of them during any operation - # that may eventually result in the closure being called. - closures_to_check = closures - - for closure in closures_to_check: - env = closure.environment() - # Make sure this environment has any interesting variables. - if env in block_state: - for var_name in block_state[env]: - if not block_state[env][var_name] and not is_special_variable(var_name): - # A closure would capture this variable while it is not always - # initialized. Note that this check is transitive. - self._uninitialized_access(closure, var_name, - pred_at_fault(env, var_name)) - - # Save the state. - state[block] = block_state - - return block_state - - for block in func.basic_blocks: - traverse(block) - - def _uninitialized_access(self, insn, var_name, pred_at_fault): - if pred_at_fault is not None: - visited = set() - possible_preds = [pred_at_fault] - - uninitialized_loc = None - while uninitialized_loc is None: - possible_pred = possible_preds.pop(0) - visited.add(possible_pred) - - for pred_insn in reversed(possible_pred.instructions): - if pred_insn.loc is not None: - uninitialized_loc = pred_insn.loc.begin() - break - - for block in possible_pred.predecessors(): - if block not in visited: - possible_preds.append(block) - - assert uninitialized_loc is not None - - note = diagnostic.Diagnostic("note", - "variable is not initialized when control flows from this point", {}, - uninitialized_loc) - else: - note = None - - if note is not None: - notes = [note] - else: - notes = [] - - if isinstance(insn, ir.Closure): - diag = diagnostic.Diagnostic("error", - "variable '{name}' can be captured in a closure uninitialized here", - {"name": var_name}, - insn.loc, notes=notes) - else: - diag = diagnostic.Diagnostic("error", - "variable '{name}' is not always initialized here", - {"name": var_name}, - insn.loc, notes=notes) - - self.engine.process(diag) diff --git a/artiq/compiler/validators/monomorphism.py b/artiq/compiler/validators/monomorphism.py deleted file mode 100644 index 0911deb40..000000000 --- a/artiq/compiler/validators/monomorphism.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -:class:`MonomorphismValidator` verifies that all type variables have been -elided, which is necessary for code generation. -""" - -from pythonparser import algorithm, diagnostic -from .. import asttyped, types, builtins - -class MonomorphismValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_FunctionDefT(self, node): - super().generic_visit(node) - - return_type = node.signature_type.find().ret - if types.is_polymorphic(return_type): - note = diagnostic.Diagnostic("note", - "the function has return type {type}", - {"type": types.TypePrinter().name(return_type)}, - node.name_loc) - diag = diagnostic.Diagnostic("error", - "the return type of this function cannot be fully inferred", {}, - node.name_loc, notes=[note]) - self.engine.process(diag) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def generic_visit(self, node): - super().generic_visit(node) - - if isinstance(node, asttyped.commontyped): - if types.is_polymorphic(node.type): - note = diagnostic.Diagnostic("note", - "the expression has type {type}", - {"type": types.TypePrinter().name(node.type)}, - node.loc) - diag = diagnostic.Diagnostic("error", - "the type of this expression cannot be fully inferred", {}, - node.loc, notes=[note]) - self.engine.process(diag) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index dc8207266..191a90de7 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,8 +1,6 @@ import os, sys import numpy -from pythonparser import diagnostic - from artiq import __artiq_dir__ as artiq_dir from artiq.language.core import * diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 21baf3f05..e29eef8d1 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -10,8 +10,6 @@ from collections import defaultdict import h5py -from llvmlite import binding as llvm - from sipyco import common_args from artiq import __version__ as artiq_version @@ -70,22 +68,6 @@ class ELFRunner(FileRunner): return f.read() -class LLVMIRRunner(FileRunner): - def compile(self): - with open(self.file, "r") as f: - llmodule = llvm.parse_assembly(f.read()) - llmodule.verify() - return self.target.link([self.target.assemble(llmodule)]) - - -class LLVMBitcodeRunner(FileRunner): - def compile(self): - with open(self.file, "rb") as f: - llmodule = llvm.parse_bitcode(f.read()) - llmodule.verify() - return self.target.link([self.target.assemble(llmodule)]) - - class DummyScheduler: def __init__(self): self.rid = 0 @@ -157,9 +139,7 @@ def _build_experiment(device_mgr, dataset_mgr, args): managers = (device_mgr, dataset_mgr, argument_mgr, {}) if hasattr(args, "file"): is_elf = args.file.endswith(".elf") - is_ll = args.file.endswith(".ll") - is_bc = args.file.endswith(".bc") - if is_elf or is_ll or is_bc: + if is_elf: if args.arguments: raise ValueError("arguments not supported for precompiled kernels") if args.class_name: @@ -167,10 +147,6 @@ def _build_experiment(device_mgr, dataset_mgr, args): "for precompiled kernels") if is_elf: return ELFRunner(managers, file=args.file) - elif is_ll: - return LLVMIRRunner(managers, file=args.file) - elif is_bc: - return LLVMBitcodeRunner(managers, file=args.file) else: import_cache.install_hook() module = file_import(args.file, prefix="artiq_run_") diff --git a/artiq/test/libartiq_support/lib.rs b/artiq/test/libartiq_support/lib.rs deleted file mode 100644 index 8f06852ba..000000000 --- a/artiq/test/libartiq_support/lib.rs +++ /dev/null @@ -1,78 +0,0 @@ -#![feature(libc, panic_unwind, unwind_attributes, rustc_private, int_bits_const)] -#![crate_name = "artiq_support"] -#![crate_type = "cdylib"] - -extern crate std as core; -extern crate libc; -extern crate unwind; - -// Note: this does *not* match the cslice crate! -// ARTIQ Python has the slice length field fixed at 32 bits, even on 64-bit platforms. -mod cslice { - use core::marker::PhantomData; - use core::convert::AsRef; - use core::slice; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct CSlice<'a, T> { - base: *const T, - len: u32, - phantom: PhantomData<&'a ()> - } - - impl<'a, T> CSlice<'a, T> { - pub fn len(&self) -> usize { - self.len as usize - } - } - - impl<'a, T> AsRef<[T]> for CSlice<'a, T> { - fn as_ref(&self) -> &[T] { - unsafe { - slice::from_raw_parts(self.base, self.len as usize) - } - } - } - - pub trait AsCSlice<'a, T> { - fn as_c_slice(&'a self) -> CSlice<'a, T>; - } - - impl<'a> AsCSlice<'a, u8> for str { - fn as_c_slice(&'a self) -> CSlice<'a, u8> { - CSlice { - base: self.as_ptr(), - len: self.len() as u32, - phantom: PhantomData - } - } - } -} - -#[path = "."] -pub mod eh { - #[path = "../../firmware/libeh/dwarf.rs"] - pub mod dwarf; -} -#[path = "../../firmware/ksupport/eh_artiq.rs"] -pub mod eh_artiq; - -use std::{str, process}; - -fn terminate(exception: &eh_artiq::Exception, mut _backtrace: &mut [usize]) -> ! { - println!("Uncaught {}: {} ({}, {}, {})", - str::from_utf8(exception.name.as_ref()).unwrap(), - str::from_utf8(exception.message.as_ref()).unwrap(), - exception.param[0], - exception.param[1], - exception.param[2]); - println!("at {}:{}:{}", - str::from_utf8(exception.file.as_ref()).unwrap(), - exception.line, - exception.column); - process::exit(1); -} - -#[export_name = "now"] -pub static mut NOW: i64 = 0; diff --git a/artiq/test/lit/codegen/assign_none.py b/artiq/test/lit/codegen/assign_none.py deleted file mode 100644 index 000e87ac7..000000000 --- a/artiq/test/lit/codegen/assign_none.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def f(): - pass -def g(): - a = f() diff --git a/artiq/test/lit/codegen/custom_comparison.py b/artiq/test/lit/codegen/custom_comparison.py deleted file mode 100644 index 8a7a1d32b..000000000 --- a/artiq/test/lit/codegen/custom_comparison.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class Foo: - def __init__(self): - pass - -a = Foo() -b = Foo() - -# CHECK-L: ${LINE:+1}: error: Custom object comparison is not supported -a > b - diff --git a/artiq/test/lit/codegen/custom_inclusion.py b/artiq/test/lit/codegen/custom_inclusion.py deleted file mode 100644 index 92cd1a772..000000000 --- a/artiq/test/lit/codegen/custom_inclusion.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class Foo: - def __init__(self): - pass - -a = Foo() -b = Foo() - -# CHECK-L: ${LINE:+1}: error: Custom object inclusion test is not supported -a in b - diff --git a/artiq/test/lit/codegen/error_illegal_return.py b/artiq/test/lit/codegen/error_illegal_return.py deleted file mode 100644 index 5e15cade7..000000000 --- a/artiq/test/lit/codegen/error_illegal_return.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: this function must return a value of type numpy.int32 explicitly -def foo(x): - if x: - return 1 - -foo(True) diff --git a/artiq/test/lit/codegen/none_retval.py b/artiq/test/lit/codegen/none_retval.py deleted file mode 100644 index ed2c9eb25..000000000 --- a/artiq/test/lit/codegen/none_retval.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def make_none(): - return None - -def take_arg(arg): - pass - -def run(): - retval = make_none() - take_arg(retval) diff --git a/artiq/test/lit/codegen/noop_coercion.py b/artiq/test/lit/codegen/noop_coercion.py deleted file mode 100644 index df4ce830b..000000000 --- a/artiq/test/lit/codegen/noop_coercion.py +++ /dev/null @@ -1,4 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def f(): - return float(1.0) diff --git a/artiq/test/lit/codegen/unreachable_implicit_return.py b/artiq/test/lit/codegen/unreachable_implicit_return.py deleted file mode 100644 index a434f8a28..000000000 --- a/artiq/test/lit/codegen/unreachable_implicit_return.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s - -def foo(x): - if x: - return 1 - else: - return 2 - -foo(True) diff --git a/artiq/test/lit/codegen/warning_useless_bool.py b/artiq/test/lit/codegen/warning_useless_bool.py deleted file mode 100644 index d81fa2941..000000000 --- a/artiq/test/lit/codegen/warning_useless_bool.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: warning: this expression, which is always truthful, is coerced to bool -bool(IndexError()) diff --git a/artiq/test/lit/constant_hoisting/device_db.py b/artiq/test/lit/constant_hoisting/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/constant_hoisting/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/constant_hoisting/invariant_load.py b/artiq/test/lit/constant_hoisting/invariant_load.py deleted file mode 100644 index 62fac4202..000000000 --- a/artiq/test/lit/constant_hoisting/invariant_load.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t ARTIQ_IR_NO_LOC=1 %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.txt - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: %LOC.self.FLD.foo = numpy.int32 getattr('foo') %ARG.self -# CHECK-L: for.head: - -class c: - kernel_invariants = {"foo"} - - def __init__(self): - self.foo = 1 - - @kernel - def run(self): - for _ in range(10): - core_log(1.0 * self.foo) - -i = c() - -@kernel -def entrypoint(): - i.run() diff --git a/artiq/test/lit/devirtualization/device_db.py b/artiq/test/lit/devirtualization/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/devirtualization/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/devirtualization/function.py b/artiq/test/lit/devirtualization/function.py deleted file mode 100644 index c8fe65691..000000000 --- a/artiq/test/lit/devirtualization/function.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.txt -# XFAIL: * - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: call ()->NoneType %local.testbench.entrypoint ; calls testbench.entrypoint - -@kernel -def baz(): - pass - -class foo: - @kernel - def bar(self): - # CHECK-L: call ()->NoneType %local.testbench.baz ; calls testbench.baz - baz() -x = foo() - -@kernel -def entrypoint(): - x.bar() diff --git a/artiq/test/lit/devirtualization/method.py b/artiq/test/lit/devirtualization/method.py deleted file mode 100644 index 8c258a41b..000000000 --- a/artiq/test/lit/devirtualization/method.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t %python -m artiq.compiler.testbench.embedding +compile %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t.txt -# XFAIL: * - -from artiq.language.core import * -from artiq.language.types import * - -class foo: - @kernel - def bar(self): - pass -x = foo() - -@kernel -def entrypoint(): - # CHECK-L: ; calls testbench.foo.bar - x.bar() diff --git a/artiq/test/lit/embedding/annotation.py b/artiq/test/lit/embedding/annotation.py deleted file mode 100644 index 21ae25332..000000000 --- a/artiq/test/lit/embedding/annotation.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK: i64 @_Z13testbench.foozz\(i64 %ARG.x, \{ i1, i64 \} %ARG.y\) - -@kernel -def foo(x: TInt64, y: TInt64 = 1) -> TInt64: - print(x+y) - return x+y - -@kernel -def bar(x: TInt64) -> None: - print(x) - -@kernel -def entrypoint(): - print(foo(0, 2)) - print(foo(1, 3)) - bar(3) diff --git a/artiq/test/lit/embedding/array_math_fns.py b/artiq/test/lit/embedding/array_math_fns.py deleted file mode 100644 index d23540b48..000000000 --- a/artiq/test/lit/embedding/array_math_fns.py +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -import numpy as np - -@kernel -def entrypoint(): - # Just make sure everything compiles. - - # LLVM intrinsic: - a = np.array([1.0, 2.0, 3.0]) - b = np.sin(a) - assert b.shape == a.shape - - # libm: - c = np.array([1.0, 2.0, 3.0]) - d = np.arctan(c) - assert d.shape == c.shape - - # libm, binary: - e = np.array([1.0, 2.0, 3.0]) - f = np.array([4.0, 5.0, 6.0]) - g = np.arctan2(e, f) - # g = np.arctan2(e, 0.0) - # g = np.arctan2(0.0, f) - assert g.shape == e.shape diff --git a/artiq/test/lit/embedding/array_transpose.py b/artiq/test/lit/embedding/array_transpose.py deleted file mode 100644 index 2ab44bd7d..000000000 --- a/artiq/test/lit/embedding/array_transpose.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -@kernel -def entrypoint(): - # FIXME: This needs to be a runtime test (but numpy.* integration is - # currently embedding-only). - a = np.array([1, 2, 3]) - b = np.transpose(a) - assert a.shape == b.shape - for i in range(len(a)): - assert a[i] == b[i] - - c = np.array([[1, 2, 3], [4, 5, 6]]) - d = np.transpose(c) - assert c.shape == d.shape - for i in range(2): - for j in range(3): - assert c[i][j] == d[j][i] diff --git a/artiq/test/lit/embedding/arrays.py b/artiq/test/lit/embedding/arrays.py deleted file mode 100644 index 63d846585..000000000 --- a/artiq/test/lit/embedding/arrays.py +++ /dev/null @@ -1,36 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -from numpy import array - -int_vec = array([1, 2, 3]) -float_vec = array([1.0, 2.0, 3.0]) -int_mat = array([[1, 2], [3, 4]]) -float_mat = array([[1.0, 2.0], [3.0, 4.0]]) - - -@kernel -def entrypoint(): - # TODO: These need to be runtime tests! - assert int_vec.shape == (3, ) - assert int_vec[0] == 1 - assert int_vec[1] == 2 - assert int_vec[2] == 3 - - assert float_vec.shape == (3, ) - assert float_vec[0] == 1.0 - assert float_vec[1] == 2.0 - assert float_vec[2] == 3.0 - - assert int_mat.shape == (2, 2) - assert int_mat[0][0] == 1 - assert int_mat[0][1] == 2 - assert int_mat[1][0] == 3 - assert int_mat[1][1] == 4 - - assert float_mat.shape == (2, 2) - assert float_mat[0][0] == 1.0 - assert float_mat[0][1] == 2.0 - assert float_mat[1][0] == 3.0 - assert float_mat[1][1] == 4.0 diff --git a/artiq/test/lit/embedding/async_rpc.py b/artiq/test/lit/embedding/async_rpc.py deleted file mode 100644 index 47c3041a5..000000000 --- a/artiq/test/lit/embedding/async_rpc.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK: call void @rpc_send_async - -@rpc(flags={"async"}) -def foo(): - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/bug_477.py b/artiq/test/lit/embedding/bug_477.py deleted file mode 100644 index 19d496ca6..000000000 --- a/artiq/test/lit/embedding/bug_477.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.experiment import * - -class MyClass: - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) - - -sl = [MyClass(x=1), MyClass(x=2)] - -@kernel -def bug(l): - for c in l: - print(c.x) - -@kernel -def entrypoint(): - bug(sl) diff --git a/artiq/test/lit/embedding/class_fn_direct_call.py b/artiq/test/lit/embedding/class_fn_direct_call.py deleted file mode 100644 index 91bdac519..000000000 --- a/artiq/test/lit/embedding/class_fn_direct_call.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class C: - @kernel - def f(self): - pass - -class D(C): - @kernel - def f(self): - # super().f() # super() not bound - C.f(self) # KeyError in compile - -di = D() -@kernel -def entrypoint(): - di.f() diff --git a/artiq/test/lit/embedding/device_db.py b/artiq/test/lit/embedding/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/embedding/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/embedding/error_attr_absent.py b/artiq/test/lit/embedding/error_attr_absent.py deleted file mode 100644 index 986c5d06a..000000000 --- a/artiq/test/lit/embedding/error_attr_absent.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object does not have an attribute 'x' - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: attribute accessed here - a.x diff --git a/artiq/test/lit/embedding/error_attr_absent_suggest.py b/artiq/test/lit/embedding/error_attr_absent_suggest.py deleted file mode 100644 index 474a5791a..000000000 --- a/artiq/test/lit/embedding/error_attr_absent_suggest.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - xx = 1 - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object does not have an attribute 'x'; did you mean 'xx'? - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: attribute accessed here - a.x diff --git a/artiq/test/lit/embedding/error_attr_conflict.py b/artiq/test/lit/embedding/error_attr_conflict.py deleted file mode 100644 index a8556dd2c..000000000 --- a/artiq/test/lit/embedding/error_attr_conflict.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass - -i1 = c() -i1.x = 1 - -i2 = c() -i2.x = 1.0 - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object has an attribute 'x' of type float, which is different from previously inferred type numpy.int32 for the same attribute - i1.x - # CHECK-L: ${LINE:+1}: note: expanded from here - i2.x diff --git a/artiq/test/lit/embedding/error_attr_constant.py b/artiq/test/lit/embedding/error_attr_constant.py deleted file mode 100644 index d78d4a65c..000000000 --- a/artiq/test/lit/embedding/error_attr_constant.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - kernel_invariants = {"a"} - - def __init__(self): - self.a = 1 - -i = c() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: error: cannot assign to constant attribute 'a' of class 'testbench.c' - i.a = 1 diff --git a/artiq/test/lit/embedding/error_attr_unify.py b/artiq/test/lit/embedding/error_attr_unify.py deleted file mode 100644 index c8f6321a8..000000000 --- a/artiq/test/lit/embedding/error_attr_unify.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - x = [1, "x"] - -@kernel -def entrypoint(): - # CHECK-L: :1: error: cannot unify numpy.int? with str - # CHECK-NEXT-L: [1, 'x'] - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: while inferring a type for an attribute 'x' of a host object - a.x diff --git a/artiq/test/lit/embedding/error_host_only.py b/artiq/test/lit/embedding/error_host_only.py deleted file mode 100644 index 2e2eba6db..000000000 --- a/artiq/test/lit/embedding/error_host_only.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class foo: - # CHECK-L: ${LINE:+2}: fatal: this function cannot be called as an RPC - @host_only - def pause(self): - pass - -x = foo() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+2}: note: in function called remotely here - # CHECK-L: ${LINE:+1}: note: while inferring a type for an attribute 'pause' of a host object - x.pause() diff --git a/artiq/test/lit/embedding/error_kernel_method_no_self.py b/artiq/test/lit/embedding/error_kernel_method_no_self.py deleted file mode 100644 index ed129fc39..000000000 --- a/artiq/test/lit/embedding/error_kernel_method_no_self.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass -@kernel -def f(): - pass -c.f = f -x = c().f - -@kernel -def entrypoint(): - # CHECK-L: :1: error: function 'f()->NoneType delay('a)' of class 'testbench.c' cannot accept a self argument - # CHECK-L: ${LINE:+1}: note: expanded from here - x diff --git a/artiq/test/lit/embedding/error_kernel_method_self_unify.py b/artiq/test/lit/embedding/error_kernel_method_self_unify.py deleted file mode 100644 index 925ee1d20..000000000 --- a/artiq/test/lit/embedding/error_kernel_method_self_unify.py +++ /dev/null @@ -1,24 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def f(self): - core_log(self.x) -class c: - a = f - x = 1 -class d: - b = f - x = 2 -xa = c().a -xb = d().b - -@kernel -def entrypoint(): - xa() - # CHECK-L: :1: error: cannot unify with %t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -def foo(): - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: fatal: name 'fo0' is not bound to anything; did you mean 'foo'? - fo0() diff --git a/artiq/test/lit/embedding/error_rpc_annot_return.py b/artiq/test/lit/embedding/error_rpc_annot_return.py deleted file mode 100644 index b60709f7c..000000000 --- a/artiq/test/lit/embedding/error_rpc_annot_return.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+1}: error: type annotation for return type, '1', is not an ARTIQ type -def foo() -> 1: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in function called remotely here - foo() diff --git a/artiq/test/lit/embedding/error_rpc_async_return.py b/artiq/test/lit/embedding/error_rpc_async_return.py deleted file mode 100644 index 72f3c79c3..000000000 --- a/artiq/test/lit/embedding/error_rpc_async_return.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: fatal: functions that return a value cannot be defined as async RPCs -@rpc(flags={"async"}) -def foo() -> TInt32: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: function called here - foo() diff --git a/artiq/test/lit/embedding/error_specialized_annot.py b/artiq/test/lit/embedding/error_specialized_annot.py deleted file mode 100644 index 2f5955043..000000000 --- a/artiq/test/lit/embedding/error_specialized_annot.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -class c(): -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '', is not an ARTIQ type - @kernel - def hello(self, x: float): - pass - - @kernel - def run(self): - self.hello(2) - -i = c() -@kernel -def entrypoint(): - i.run() diff --git a/artiq/test/lit/embedding/error_syscall_annot.py b/artiq/test/lit/embedding/error_syscall_annot.py deleted file mode 100644 index 4849910b3..000000000 --- a/artiq/test/lit/embedding/error_syscall_annot.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '1', is not an ARTIQ type -@syscall -def foo(x: 1) -> TNone: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_annot_return.py b/artiq/test/lit/embedding/error_syscall_annot_return.py deleted file mode 100644 index dfc24db9f..000000000 --- a/artiq/test/lit/embedding/error_syscall_annot_return.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for return type, '1', is not an ARTIQ type -@syscall -def foo() -> 1: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_arg.py b/artiq/test/lit/embedding/error_syscall_arg.py deleted file mode 100644 index a077e7d57..000000000 --- a/artiq/test/lit/embedding/error_syscall_arg.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call argument 'x' must have a type annotation -@syscall -def foo(x) -> TNone: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_default_arg.py b/artiq/test/lit/embedding/error_syscall_default_arg.py deleted file mode 100644 index 0bfdce88a..000000000 --- a/artiq/test/lit/embedding/error_syscall_default_arg.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call argument 'x' must not have a default value -@syscall -def foo(x=1) -> TNone: - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/error_syscall_return.py b/artiq/test/lit/embedding/error_syscall_return.py deleted file mode 100644 index bad7b20d1..000000000 --- a/artiq/test/lit/embedding/error_syscall_return.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call must have a return type annotation -@syscall -def foo(): - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/eval.py b/artiq/test/lit/embedding/eval.py deleted file mode 100644 index d2f91cbf9..000000000 --- a/artiq/test/lit/embedding/eval.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * - - -def make_incrementer(increment): - return kernel_from_string(["a"], "return a + {}".format(increment), - portable) - - -foo = make_incrementer(1) -bar = make_incrementer(2) - - -@kernel -def entrypoint(): - assert foo(4) == 5 - assert bar(4) == 6 diff --git a/artiq/test/lit/embedding/exception.py b/artiq/test/lit/embedding/exception.py deleted file mode 100644 index 0a0830de4..000000000 --- a/artiq/test/lit/embedding/exception.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -from artiq.coredevice.exceptions import RTIOUnderflow - -@kernel -def entrypoint(): - try: - pass - except RTIOUnderflow: - pass diff --git a/artiq/test/lit/embedding/fast_math_flags.py b/artiq/test/lit/embedding/fast_math_flags.py deleted file mode 100644 index 3ed40f70d..000000000 --- a/artiq/test/lit/embedding/fast_math_flags.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: env ARTIQ_DUMP_UNOPT_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t_unopt.ll - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: fmul fast double 1.000000e+00, 0.000000e+00 -@kernel(flags=["fast-math"]) -def foo(): - core_log(1.0 * 0.0) - -# CHECK-L: fmul fast double 2.000000e+00, 0.000000e+00 -@portable(flags=["fast-math"]) -def bar(): - core_log(2.0 * 0.0) - -@kernel -def entrypoint(): - foo() - bar() diff --git a/artiq/test/lit/embedding/fn_ptr_list.py b/artiq/test/lit/embedding/fn_ptr_list.py deleted file mode 100644 index 73e6ad3be..000000000 --- a/artiq/test/lit/embedding/fn_ptr_list.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def a(): - pass - -fns = [a, a] - -@kernel -def entrypoint(): - fns[0]() - fns[1]() diff --git a/artiq/test/lit/embedding/function_polymorphism.py b/artiq/test/lit/embedding/function_polymorphism.py deleted file mode 100644 index 52df5ee4f..000000000 --- a/artiq/test/lit/embedding/function_polymorphism.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -def f(x): - print(x) - -@kernel -def entrypoint(): - f("foo") - f(42) diff --git a/artiq/test/lit/embedding/inheritance.py b/artiq/test/lit/embedding/inheritance.py deleted file mode 100644 index 0863d9163..000000000 --- a/artiq/test/lit/embedding/inheritance.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class a: - @kernel - def f(self): - print(self.x) - return None - -class b(a): - x = 1 -class c(a): - x = 2 - -bi = b() -ci = c() -@kernel -def entrypoint(): - bi.f() - ci.f() diff --git a/artiq/test/lit/embedding/invariant_nested.py b/artiq/test/lit/embedding/invariant_nested.py deleted file mode 100644 index 5957dcb3d..000000000 --- a/artiq/test/lit/embedding/invariant_nested.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - - -class ClassA: - def __init__(self): - self.foo = False - - -class ClassB: - kernel_invariants = {"bar"} - - def __init__(self): - self.bar = ClassA() - -obj = ClassB() - -@kernel -def entrypoint(): - obj.bar.foo = True diff --git a/artiq/test/lit/embedding/invariant_propagation.py b/artiq/test/lit/embedding/invariant_propagation.py deleted file mode 100644 index e1af5c9c1..000000000 --- a/artiq/test/lit/embedding/invariant_propagation.py +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * - -class Class: - kernel_invariants = {"foo"} - - def __init__(self): - self.foo = True - - @kernel - def run(self): - if self.foo: - print("bar") - else: - # Make sure all the code for this branch will be completely elided: - # CHECK-NOT: baz - print("baz") - -obj = Class() - -@kernel -def entrypoint(): - obj.run() diff --git a/artiq/test/lit/embedding/math_fns.py b/artiq/test/lit/embedding/math_fns.py deleted file mode 100644 index 6f9416c8d..000000000 --- a/artiq/test/lit/embedding/math_fns.py +++ /dev/null @@ -1,31 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * -import numpy - -@kernel -def entrypoint(): - # LLVM's constant folding for transcendental functions is good enough that - # we can do a basic smoke test by just making sure the module compiles and - # all assertions are statically eliminated. - - # CHECK-NOT: assert - assert numpy.sin(0.0) == 0.0 - assert numpy.cos(0.0) == 1.0 - assert numpy.exp(0.0) == 1.0 - assert numpy.exp2(1.0) == 2.0 - assert numpy.log(numpy.exp(1.0)) == 1.0 - assert numpy.log10(10.0) == 1.0 - assert numpy.log2(2.0) == 1.0 - assert numpy.fabs(-1.0) == 1.0 - assert numpy.floor(42.5) == 42.0 - assert numpy.ceil(42.5) == 43.0 - assert numpy.trunc(41.5) == 41.0 - assert numpy.rint(41.5) == 42.0 - assert numpy.tan(0.0) == 0.0 - assert numpy.arcsin(0.0) == 0.0 - assert numpy.arccos(1.0) == 0.0 - assert numpy.arctan(0.0) == 0.0 - assert numpy.arctan2(0.0, 1.0) == 0.0 diff --git a/artiq/test/lit/embedding/method_on_instance.py b/artiq/test/lit/embedding/method_on_instance.py deleted file mode 100644 index 018545f6b..000000000 --- a/artiq/test/lit/embedding/method_on_instance.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class a: - def foo(self, x): - print(x) - -class b: - def __init__(self): - self.obj = a() - self.meth = self.obj.foo - - @kernel - def run(self): - self.meth(1) - -bi = b() -@kernel -def entrypoint(): - bi.run() diff --git a/artiq/test/lit/embedding/module.py b/artiq/test/lit/embedding/module.py deleted file mode 100644 index d25475b6d..000000000 --- a/artiq/test/lit/embedding/module.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -import time, os - -@kernel -def entrypoint(): - time.sleep(10) - os.mkdir("foo") diff --git a/artiq/test/lit/embedding/rpc_method_polymorphism.py b/artiq/test/lit/embedding/rpc_method_polymorphism.py deleted file mode 100644 index b7ca9f525..000000000 --- a/artiq/test/lit/embedding/rpc_method_polymorphism.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class c: - def p(self, foo): - print(foo) -i = c() - -@kernel -def entrypoint(): - i.p("foo") - i.p(42) diff --git a/artiq/test/lit/embedding/syscall_flags.py b/artiq/test/lit/embedding/syscall_flags.py deleted file mode 100644 index f8c618c3f..000000000 --- a/artiq/test/lit/embedding/syscall_flags.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK: call void @foo\(\)(, !dbg !\d+)? - -# CHECK-L: ; Function Attrs: inaccessiblememonly nounwind -# CHECK-NEXT-L: declare void @foo() - -@syscall(flags={"nounwind", "nowrite"}) -def foo() -> TNone: - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/tuple.py b/artiq/test/lit/embedding/tuple.py deleted file mode 100644 index 6f9a14a32..000000000 --- a/artiq/test/lit/embedding/tuple.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * - -values = (1, 2) - -@kernel -def entrypoint(): - assert values == (1, 2) diff --git a/artiq/test/lit/embedding/warning_invariant_1.py b/artiq/test/lit/embedding/warning_invariant_1.py deleted file mode 100644 index 5a0b8bdd1..000000000 --- a/artiq/test/lit/embedding/warning_invariant_1.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - a = b = 0 - def __init__(self, invariants): - self.kernel_invariants = invariants - - def __repr__(self): - return "" - -i1 = c({'a'}) -i2 = c({'a', 'b'}) - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type declares attribute(s) 'b' as kernel invariant, but other objects of the same type do not; the invariant annotation on this object will be ignored - # CHECK-L: ${LINE:+1}: note: expanded from here - [i1, i2] diff --git a/artiq/test/lit/embedding/warning_invariant_2.py b/artiq/test/lit/embedding/warning_invariant_2.py deleted file mode 100644 index b630b21d8..000000000 --- a/artiq/test/lit/embedding/warning_invariant_2.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - a = b = 0 - def __init__(self, invariants): - self.kernel_invariants = invariants - - def __repr__(self): - return "" - -i1 = c({"a", "b"}) -i2 = c({"a"}) - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type does not declare attribute(s) 'b' as kernel invariant, but other objects of the same type do; the invariant annotation on other objects will be ignored - # CHECK-L: ${LINE:+1}: note: expanded from here - [i1, i2] diff --git a/artiq/test/lit/embedding/warning_invariant_missing.py b/artiq/test/lit/embedding/warning_invariant_missing.py deleted file mode 100644 index 6fc91028f..000000000 --- a/artiq/test/lit/embedding/warning_invariant_missing.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - kernel_invariants = {"a", "b"} - a = 0 - - def __repr__(self): - return "" - -i = c() - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type declares attribute 'b' as kernel invariant, but the instance referenced here does not have this attribute - # CHECK-L: ${LINE:+1}: note: expanded from here - i diff --git a/artiq/test/lit/escape/const_string.py b/artiq/test/lit/escape/const_string.py deleted file mode 100644 index f5c844c05..000000000 --- a/artiq/test/lit/escape/const_string.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.experiment import * - -@kernel -def foo(): - return "x" - -@kernel -def entrypoint(): - foo() - - # Test reassigning strings. - a = "a" - b = a diff --git a/artiq/test/lit/escape/device_db.py b/artiq/test/lit/escape/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/escape/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/escape/error_list_aug_asgn.py b/artiq/test/lit/escape/error_list_aug_asgn.py deleted file mode 100644 index 1cdbd8838..000000000 --- a/artiq/test/lit/escape/error_list_aug_asgn.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -@kernel -def entrypoint(): - a = [1,2] - # CHECK-L: ${LINE:+2}: error: lists cannot be mutated in-place - # CHECK-L: ${LINE:+1}: note: try using `a = a + [3,4]` - a += [3,4] - diff --git a/artiq/test/lit/escape/error_mutable_attr.py b/artiq/test/lit/escape/error_mutable_attr.py deleted file mode 100644 index 2d5bc885a..000000000 --- a/artiq/test/lit/escape/error_mutable_attr.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -class c: - x = [] - -cc = c() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: error: the assigned value does not outlive the assignment target - cc.x = [1] diff --git a/artiq/test/lit/escape/error_string.py b/artiq/test/lit/escape/error_string.py deleted file mode 100644 index 33fbb71a6..000000000 --- a/artiq/test/lit/escape/error_string.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -@kernel -def foo(): - # CHECK-NOT-L: ${LINE:+1}: error: - return "x" - -@kernel -def bar(): - # CHECK-L: ${LINE:+1}: error: cannot return an allocated value that does not live forever - return "x" + "y" - -@kernel -def entrypoint(): - foo() - bar() diff --git a/artiq/test/lit/exceptions/catch.py b/artiq/test/lit/exceptions/catch.py deleted file mode 100644 index d6c2866c1..000000000 --- a/artiq/test/lit/exceptions/catch.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -try: - 1/0 -except ZeroDivisionError: - # CHECK-L: OK - print("OK") diff --git a/artiq/test/lit/exceptions/catch_all.py b/artiq/test/lit/exceptions/catch_all.py deleted file mode 100644 index 1417f5f31..000000000 --- a/artiq/test/lit/exceptions/catch_all.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def catch(f): - try: - f() - except Exception as e: - print(e) - -# CHECK-L: ZeroDivisionError -catch(lambda: 1/0) -# CHECK-L: IndexError -catch(lambda: [1.0][10]) diff --git a/artiq/test/lit/exceptions/catch_multi.py b/artiq/test/lit/exceptions/catch_multi.py deleted file mode 100644 index 472086660..000000000 --- a/artiq/test/lit/exceptions/catch_multi.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def catch(f): - try: - f() - except ZeroDivisionError as zde: - print(zde) - except IndexError as ie: - print(ie) - -# CHECK-L: ZeroDivisionError -catch(lambda: 1/0) -# CHECK-L: IndexError -catch(lambda: [1.0][10]) diff --git a/artiq/test/lit/exceptions/catch_outer.py b/artiq/test/lit/exceptions/catch_outer.py deleted file mode 100644 index de7253eaf..000000000 --- a/artiq/test/lit/exceptions/catch_outer.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - except ValueError: - # CHECK-NOT-L: FAIL - print("FAIL") - -try: - f() -except ZeroDivisionError: - # CHECK-L: OK - print("OK") diff --git a/artiq/test/lit/exceptions/finally.py b/artiq/test/lit/exceptions/finally.py deleted file mode 100644 index 17304fc15..000000000 --- a/artiq/test/lit/exceptions/finally.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - print("f-out") - -def g(): - try: - f() - except: - print("g-except") - -# CHECK-L: f-fin -# CHECK-NOT-L: f-out -# CHECK-L: g-except -g() diff --git a/artiq/test/lit/exceptions/finally_catch.py b/artiq/test/lit/exceptions/finally_catch.py deleted file mode 100644 index 23bc39730..000000000 --- a/artiq/test/lit/exceptions/finally_catch.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - except: - print("f-except") - finally: - print("f-fin") - print("f-out") - -# CHECK-L: f-except -# CHECK-L: f-fin -# CHECK-L: f-out -f() diff --git a/artiq/test/lit/exceptions/finally_raise.py b/artiq/test/lit/exceptions/finally_raise.py deleted file mode 100644 index 02c41ea7e..000000000 --- a/artiq/test/lit/exceptions/finally_raise.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - raise ValueError() - -def g(): - try: - f() - except ZeroDivisionError: - print("g-except-zde") - except ValueError: - print("g-except-ve") - -# CHECK-L: f-fin -# CHECK-L: g-except-ve -# CHECK-NOT-L: g-except-zde -g() diff --git a/artiq/test/lit/exceptions/finally_reraise.py b/artiq/test/lit/exceptions/finally_reraise.py deleted file mode 100644 index ef4da2af9..000000000 --- a/artiq/test/lit/exceptions/finally_reraise.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -x = 1 - -def doit(): - try: - if x > 0: - raise ZeroDivisionError - r = 0 - finally: - print('final') - return r - -try: - doit() -except ZeroDivisionError: - print('caught') - -# CHECK-L: final -# CHECK-L: caught diff --git a/artiq/test/lit/exceptions/finally_squash.py b/artiq/test/lit/exceptions/finally_squash.py deleted file mode 100644 index 8c7b58fc3..000000000 --- a/artiq/test/lit/exceptions/finally_squash.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - return - -def g(): - try: - f() - except: - print("g-except") - -# CHECK-L: f-fin -# CHECK-NOT-L: f-out -# CHECK-NOT-L: g-except -g() diff --git a/artiq/test/lit/exceptions/finally_uncaught.py b/artiq/test/lit/exceptions/finally_uncaught.py deleted file mode 100644 index 1eb211663..000000000 --- a/artiq/test/lit/exceptions/finally_uncaught.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - -# CHECK-L: f-fin -f() diff --git a/artiq/test/lit/exceptions/reraise.py b/artiq/test/lit/exceptions/reraise.py deleted file mode 100644 index 911b22322..000000000 --- a/artiq/test/lit/exceptions/reraise.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - # CHECK-L: Uncaught 0:ZeroDivisionError - # CHECK-L: at input.py:${LINE:+1}: - 1/0 - -def g(): - try: - f() - except: - raise - -g() diff --git a/artiq/test/lit/exceptions/reraise_update.py b/artiq/test/lit/exceptions/reraise_update.py deleted file mode 100644 index 11bd30639..000000000 --- a/artiq/test/lit/exceptions/reraise_update.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - 1/0 - -def g(): - try: - f() - except Exception as e: - # CHECK-L: Uncaught 0:ZeroDivisionError - # CHECK-L: at input.py:${LINE:+1}: - raise e - -g() diff --git a/artiq/test/lit/exceptions/uncaught.py b/artiq/test/lit/exceptions/uncaught.py deleted file mode 100644 index 13b83ec22..000000000 --- a/artiq/test/lit/exceptions/uncaught.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -# CHECK-L: Uncaught 0:ZeroDivisionError: cannot divide by zero (0, 0, 0) -# CHECK-L: at input.py:${LINE:+1}: -1/0 diff --git a/artiq/test/lit/inferencer/array_creation.py b/artiq/test/lit/inferencer/array_creation.py deleted file mode 100644 index 824150c22..000000000 --- a/artiq/test/lit/inferencer/array_creation.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: numpy.array(elt='a, num_dims=1) -array([]) -# CHECK-L: numpy.array(elt='b, num_dims=2) -array([[], []]) - -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=1) -array([1, 2, 3]) -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=2) -array([[1, 2, 3], [4, 5, 6]]) - -# Jagged arrays produce runtime failure: -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=2) -array([[1, 2, 3], [4, 5]]) diff --git a/artiq/test/lit/inferencer/builtin_calls.py b/artiq/test/lit/inferencer/builtin_calls.py deleted file mode 100644 index a4b2f81fe..000000000 --- a/artiq/test/lit/inferencer/builtin_calls.py +++ /dev/null @@ -1,38 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: bool:():bool -bool() - -# CHECK-L: bool:([]:list(elt='a)):bool -bool([]) - -# CHECK-L: int:():numpy.int? -int() - -# CHECK-L: int:(1.0:float):numpy.int? -int(1.0) - -# CHECK-L: int64:(1.0:float):numpy.int64 -int64(1.0) - -# CHECK-L: float:():float -float() - -# CHECK-L: float:(1:numpy.int?):float -float(1) - -# CHECK-L: list:():list(elt='b) -list() - -# CHECK-L: len:([]:list(elt='c)):numpy.int32 -len([]) - -# CHECK-L: round:(1.0:float):numpy.int? -round(1.0) - -# CHECK-L: abs:(1:numpy.int?):numpy.int? -abs(1) - -# CHECK-L: abs:(1.0:float):float -abs(1.0) diff --git a/artiq/test/lit/inferencer/cast.py b/artiq/test/lit/inferencer/cast.py deleted file mode 100644 index be2ddbf4a..000000000 --- a/artiq/test/lit/inferencer/cast.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: 2:numpy.int64 -int64(2)**32 - -# CHECK-L: round:(1.0:float):numpy.int64 -int64(round(1.0)) diff --git a/artiq/test/lit/inferencer/class.py b/artiq/test/lit/inferencer/class.py deleted file mode 100644 index 4e26b7c58..000000000 --- a/artiq/test/lit/inferencer/class.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - a = 1 - def f(): - pass - def m(self): - pass - -# CHECK-L: c:NoneType delay('a), m: (self:)->NoneType delay('b)}> -c -# CHECK-L: .a:numpy.int? -c.a -# CHECK-L: .f:()->NoneType delay('a) -c.f - -# CHECK-L: .m:method(fn=(self:)->NoneType delay('b), self=) -c().m() diff --git a/artiq/test/lit/inferencer/coerce.py b/artiq/test/lit/inferencer/coerce.py deleted file mode 100644 index 714bf6ad0..000000000 --- a/artiq/test/lit/inferencer/coerce.py +++ /dev/null @@ -1,41 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -1 | 2 -# CHECK-L: 1:numpy.int?:numpy.int? | 2:numpy.int?:numpy.int?:numpy.int? - -1 + 2 -# CHECK-L: 1:numpy.int?:numpy.int? + 2:numpy.int?:numpy.int?:numpy.int? - -(1,) + (2.0,) -# CHECK-L: (1:numpy.int?,):(numpy.int?,) + (2.0:float,):(float,):(numpy.int?, float) - -[1] + [2] -# CHECK-L: [1:numpy.int?]:list(elt=numpy.int?) + [2:numpy.int?]:list(elt=numpy.int?):list(elt=numpy.int?) - -1 * 2 -# CHECK-L: 1:numpy.int?:numpy.int? * 2:numpy.int?:numpy.int?:numpy.int? - -[1] * 2 -# CHECK-L: [1:numpy.int?]:list(elt=numpy.int?) * 2:numpy.int?:list(elt=numpy.int?) - -1 // 2 -# CHECK-L: 1:numpy.int?:numpy.int? // 2:numpy.int?:numpy.int?:numpy.int? - -1 + 1.0 -# CHECK-L: 1:numpy.int?:float + 1.0:float:float - -a = []; a += [1] -# CHECK-L: a:list(elt=numpy.int?) = []:list(elt=numpy.int?); a:list(elt=numpy.int?) += [1:numpy.int?]:list(elt=numpy.int?) - -[] is [1] -# CHECK-L: []:list(elt=numpy.int?) is [1:numpy.int?]:list(elt=numpy.int?):bool - -1 in [1] -# CHECK-L: 1:numpy.int? in [1:numpy.int?]:list(elt=numpy.int?):bool - -[] < [1] -# CHECK-L: []:list(elt=numpy.int?) < [1:numpy.int?]:list(elt=numpy.int?):bool - -1.0 < 1 -# CHECK-L: 1.0:float < 1:numpy.int?:float:bool diff --git a/artiq/test/lit/inferencer/coerce_explicit.py b/artiq/test/lit/inferencer/coerce_explicit.py deleted file mode 100644 index 4455c596c..000000000 --- a/artiq/test/lit/inferencer/coerce_explicit.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: n:numpy.int32 = -n = 0 -# CHECK-L: a:numpy.int32 = -a = n // 1 -# CHECK-L: b:numpy.int32 = -b = n // 10 -# CHECK-L: q:numpy.int64 = -q = (a << 0) + (b << 8) -core_log(int64(q)) diff --git a/artiq/test/lit/inferencer/error_array.py b/artiq/test/lit/inferencer/error_array.py deleted file mode 100644 index 8099ef9d8..000000000 --- a/artiq/test/lit/inferencer/error_array.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: array cannot be invoked with the arguments () -a = array() - -b = array([1, 2, 3]) - -# CHECK-L: ${LINE:+1}: error: too many indices for array of dimension 1 -b[1, 2] - -# CHECK-L: ${LINE:+1}: error: strided slicing not yet supported for NumPy arrays -b[::-1] - -# CHECK-L: ${LINE:+1}: error: array attributes cannot be assigned to -b.shape = (5, ) diff --git a/artiq/test/lit/inferencer/error_array_augassign.py b/artiq/test/lit/inferencer/error_array_augassign.py deleted file mode 100644 index c868e50c6..000000000 --- a/artiq/test/lit/inferencer/error_array_augassign.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = array([[1, 2], [3, 4]]) -b = 0.0 - -# CHECK-L: ${LINE:+1}: error: the result of this operation has type numpy.array(elt=float, num_dims=2), which cannot be assigned to a left-hand side of type float -b /= a - -# CHECK-L: ${LINE:+1}: error: the result of this operation has type numpy.array(elt=float, num_dims=2), which cannot be assigned to a left-hand side of type numpy.array(elt=numpy.int?, num_dims=2) -a /= a diff --git a/artiq/test/lit/inferencer/error_array_ops.py b/artiq/test/lit/inferencer/error_array_ops.py deleted file mode 100644 index 4f85290c1..000000000 --- a/artiq/test/lit/inferencer/error_array_ops.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = array([[1, 2], [3, 4]]) -b = array([7, 8]) - -# NumPy supports implicit broadcasting over axes, which we don't (yet). -# Make sure there is a nice error message. -# CHECK-L: ${LINE:+3}: error: dimensions of '+' array operands must match -# CHECK-L: ${LINE:+2}: note: operand of dimension 2 -# CHECK-L: ${LINE:+1}: note: operand of dimension 1 -a + b diff --git a/artiq/test/lit/inferencer/error_assert.py b/artiq/test/lit/inferencer/error_assert.py deleted file mode 100644 index 1e7c10284..000000000 --- a/artiq/test/lit/inferencer/error_assert.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = "A" -# CHECK-L: ${LINE:+1}: error: assertion message must be a string literal -assert True, x diff --git a/artiq/test/lit/inferencer/error_builtin_calls.py b/artiq/test/lit/inferencer/error_builtin_calls.py deleted file mode 100644 index 643011f2d..000000000 --- a/artiq/test/lit/inferencer/error_builtin_calls.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: the argument of len() must be of an iterable type -len(1) - -# CHECK-L: ${LINE:+1}: error: the argument of list() must be of an iterable type -list(1) - -# CHECK-L: ${LINE:+1}: error: the arguments of min() must be of a numeric type -min([1], [1]) - -# CHECK-L: ${LINE:+1}: error: the arguments of abs() must be of a numeric type -abs([1.0]) - -# CHECK-L: ${LINE:+1}: error: strings currently cannot be constructed -str(1) diff --git a/artiq/test/lit/inferencer/error_bytes_subscript_mut.py b/artiq/test/lit/inferencer/error_bytes_subscript_mut.py deleted file mode 100644 index 96abe780d..000000000 --- a/artiq/test/lit/inferencer/error_bytes_subscript_mut.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type bytes is not mutable -(b"a")[0] = 1 diff --git a/artiq/test/lit/inferencer/error_call.py b/artiq/test/lit/inferencer/error_call.py deleted file mode 100644 index 1497c7821..000000000 --- a/artiq/test/lit/inferencer/error_call.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: cannot call this expression of type numpy.int? -(1)() - -def f(x, y, z=1): - pass - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -f(*[]) - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -f(**[]) - -# CHECK-L: ${LINE:+1}: error: the argument 'x' has been passed earlier as positional -f(1, x=1) - -# CHECK-L: ${LINE:+1}: error: mandatory argument 'x' is not passed -f() - -# CHECK: ${LINE:+1}: error: this function of type .* does not accept argument 'q' -f(1, q=1) diff --git a/artiq/test/lit/inferencer/error_class.py b/artiq/test/lit/inferencer/error_class.py deleted file mode 100644 index 0cc075b05..000000000 --- a/artiq/test/lit/inferencer/error_class.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: inheritance is not supported -class a(1): - pass - -class b: - # CHECK-L: ${LINE:+1}: fatal: class body must contain only assignments and function definitions - x += 1 diff --git a/artiq/test/lit/inferencer/error_class_redefine.py b/artiq/test/lit/inferencer/error_class_redefine.py deleted file mode 100644 index d5556fd98..000000000 --- a/artiq/test/lit/inferencer/error_class_redefine.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - pass -# CHECK-L: ${LINE:+1}: fatal: variable 'c' is already defined -class c: - pass diff --git a/artiq/test/lit/inferencer/error_coerce.py b/artiq/test/lit/inferencer/error_coerce.py deleted file mode 100644 index c4f732e7e..000000000 --- a/artiq/test/lit/inferencer/error_coerce.py +++ /dev/null @@ -1,37 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: expected '<<' operand to be of integer type, not float -1 << 2.0 - -# CHECK-L: ${LINE:+3}: error: expected every '+' operand to be a list in this context -# CHECK-L: ${LINE:+2}: note: list of type list(elt=numpy.int?) -# CHECK-L: ${LINE:+1}: note: numpy.int?, which cannot be added to a list -[1] + 2 - -# CHECK-L: ${LINE:+1}: error: cannot unify list(elt=numpy.int?) with list(elt=float): numpy.int? is incompatible with float -[1] + [2.0] - -# CHECK-L: ${LINE:+3}: error: expected every '+' operand to be a tuple in this context -# CHECK-L: ${LINE:+2}: note: tuple of type (numpy.int?,) -# CHECK-L: ${LINE:+1}: note: numpy.int?, which cannot be added to a tuple -(1,) + 2 - -# CHECK-L: ${LINE:+1}: error: passing tuples to '*' is not supported -(1,) * 2 - -# CHECK-L: ${LINE:+3}: error: expected '*' operands to be a list and an integer in this context -# CHECK-L: ${LINE:+2}: note: list operand of type list(elt=numpy.int?) -# CHECK-L: ${LINE:+1}: note: operand of type list(elt='a), which is not a valid repetition amount -[1] * [] - -# CHECK-L: ${LINE:+1}: error: cannot coerce list(elt='a) to a numeric type -[] - 1.0 - -# CHECK-L: ${LINE:+2}: error: the result of this operation has type float, which cannot be assigned to a left-hand side of type numpy.int? -# CHECK-L: ${LINE:+1}: note: expression of type float -a = 1; a += 1.0 - -# CHECK-L: ${LINE:+2}: error: the result of this operation has type (numpy.int?, float), which cannot be assigned to a left-hand side of type (numpy.int?,) -# CHECK-L: ${LINE:+1}: note: expression of type (float,) -b = (1,); b += (1.0,) diff --git a/artiq/test/lit/inferencer/error_comprehension.py b/artiq/test/lit/inferencer/error_comprehension.py deleted file mode 100644 index d586dd657..000000000 --- a/artiq/test/lit/inferencer/error_comprehension.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: if clauses in comprehensions are not supported -[x for x in [] if x] - -# CHECK-L: ${LINE:+1}: error: multiple for clauses in comprehensions are not supported -[(x, y) for x in [] for y in []] diff --git a/artiq/test/lit/inferencer/error_control_flow.py b/artiq/test/lit/inferencer/error_control_flow.py deleted file mode 100644 index 65e300511..000000000 --- a/artiq/test/lit/inferencer/error_control_flow.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: return statement outside of a function -return - -# CHECK-L: ${LINE:+1}: error: break statement outside of a loop -break - -# CHECK-L: ${LINE:+1}: error: continue statement outside of a loop -continue - -while True: - def f(): - # CHECK-L: ${LINE:+1}: error: break statement outside of a loop - break - - # CHECK-L: ${LINE:+1}: error: continue statement outside of a loop - continue diff --git a/artiq/test/lit/inferencer/error_exception.py b/artiq/test/lit/inferencer/error_exception.py deleted file mode 100644 index ee049bb2a..000000000 --- a/artiq/test/lit/inferencer/error_exception.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -try: - pass -# CHECK-L: ${LINE:+1}: error: this expression must refer to an exception constructor -except 1: - pass - -try: - pass -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with Exception -except Exception as e: - e = 1 diff --git a/artiq/test/lit/inferencer/error_iterable.py b/artiq/test/lit/inferencer/error_iterable.py deleted file mode 100644 index 73afa2fcc..000000000 --- a/artiq/test/lit/inferencer/error_iterable.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type numpy.int? is not iterable -for x in 1: pass diff --git a/artiq/test/lit/inferencer/error_local_unbound.py b/artiq/test/lit/inferencer/error_local_unbound.py deleted file mode 100644 index 7327a8548..000000000 --- a/artiq/test/lit/inferencer/error_local_unbound.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: fatal: undefined variable 'x' -x diff --git a/artiq/test/lit/inferencer/error_locals.py b/artiq/test/lit/inferencer/error_locals.py deleted file mode 100644 index 3a5185ca8..000000000 --- a/artiq/test/lit/inferencer/error_locals.py +++ /dev/null @@ -1,35 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -def a(): - # CHECK-L: ${LINE:+1}: error: cannot declare name 'x' as nonlocal: it is not bound in any outer scope - nonlocal x - -def f(): - y = 1 - def b(): - nonlocal y - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be nonlocal and global simultaneously - global y - - def c(): - global y - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be global and nonlocal simultaneously - nonlocal y - - def d(y): - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be a parameter and global simultaneously - global y - - def e(y): - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be a parameter and nonlocal simultaneously - nonlocal y - -# CHECK-L: ${LINE:+1}: error: duplicate parameter 'x' -def f(x, x): - pass - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -def g(*x): - pass diff --git a/artiq/test/lit/inferencer/error_matmult.py b/artiq/test/lit/inferencer/error_matmult.py deleted file mode 100644 index 2586aec31..000000000 --- a/artiq/test/lit/inferencer/error_matmult.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be of array type -1 @ 2 - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be of array type -[1] @ [2] - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be 1- or 2-dimensional -array([[[0]]]) @ array([[[1]]]) diff --git a/artiq/test/lit/inferencer/error_method.py b/artiq/test/lit/inferencer/error_method.py deleted file mode 100644 index 2f4d1d3fe..000000000 --- a/artiq/test/lit/inferencer/error_method.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - def f(): - pass - - def g(self): - pass - -# CHECK-L: ${LINE:+1}: error: function 'f()->NoneType delay('a)' of class 'c' cannot accept a self argument -c().f() - -c.g(1) -# CHECK-L: ${LINE:+1}: error: cannot unify with numpy.int? while inferring the type for self argument -c().g() diff --git a/artiq/test/lit/inferencer/error_return.py b/artiq/test/lit/inferencer/error_return.py deleted file mode 100644 index 40e6fd889..000000000 --- a/artiq/test/lit/inferencer/error_return.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with NoneType -# CHECK-L: ${LINE:+1}: note: function with return type numpy.int? -def a(): - return 1 - # CHECK-L: ${LINE:+1}: note: a statement returning NoneType - return - -# CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with list(elt='a) -# CHECK-L: ${LINE:+1}: note: function with return type numpy.int? -def b(): - return 1 - # CHECK-L: ${LINE:+1}: note: a statement returning list(elt='a) - return [] diff --git a/artiq/test/lit/inferencer/error_str_iter.py b/artiq/test/lit/inferencer/error_str_iter.py deleted file mode 100644 index a79c52f83..000000000 --- a/artiq/test/lit/inferencer/error_str_iter.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type str is not iterable -("a")[0] diff --git a/artiq/test/lit/inferencer/error_subscript.py b/artiq/test/lit/inferencer/error_subscript.py deleted file mode 100644 index d8332ab09..000000000 --- a/artiq/test/lit/inferencer/error_subscript.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = [] - -# CHECK-L: ${LINE:+1}: error: multi-dimensional indexing only supported for arrays -x[1,2] - -# CHECK-L: ${LINE:+1}: error: multi-dimensional slices are not supported -x[1:2,3:4] diff --git a/artiq/test/lit/inferencer/error_unify.py b/artiq/test/lit/inferencer/error_unify.py deleted file mode 100644 index 636d4f85a..000000000 --- a/artiq/test/lit/inferencer/error_unify.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = 1 -b = [] - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with list(elt='a) -a = b - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with list(elt='a) -[1, []] -# CHECK-L: note: a list element of type numpy.int? -# CHECK-L: note: a list element of type list(elt='a) - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with bool -1 and False -# CHECK-L: note: an operand of type numpy.int? -# CHECK-L: note: an operand of type bool - -# CHECK-L: ${LINE:+1}: error: expected unary '+' operand to be of numeric type, not list(elt='a) -+[] - -# CHECK-L: ${LINE:+1}: error: expected '~' operand to be of integer type, not float -~1.0 - -# CHECK-L: ${LINE:+1}: error: type numpy.int? does not have an attribute 'x' -(1).x diff --git a/artiq/test/lit/inferencer/error_with_arity.py b/artiq/test/lit/inferencer/error_with_arity.py deleted file mode 100644 index c91a3996b..000000000 --- a/artiq/test/lit/inferencer/error_with_arity.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self, n1): - pass - - def __exit__(self, n1, n2): - pass - -def foo(): - # CHECK-L: ${LINE:+2}: error: function '__enter__(self:, n1:'a)->NoneType delay('b)' must accept 1 positional argument and no optional arguments - # CHECK-L: ${LINE:+1}: error: function '__exit__(self:, n1:'c, n2:'d)->NoneType delay('e)' must accept 4 positional arguments and no optional arguments - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_exn.py b/artiq/test/lit/inferencer/error_with_exn.py deleted file mode 100644 index 283dadb5a..000000000 --- a/artiq/test/lit/inferencer/error_with_exn.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - pass - - def __exit__(self, n1, n2, n3): - n3 = 1 - pass - -def foo(): - # CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with NoneType - # CHECK-L: ${LINE:+1}: note: exception handling via context managers is not supported; the argument 'n3' of function '__exit__(self:, n1:NoneType, n2:NoneType, n3:numpy.int?)->NoneType delay('a)' will always be None - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_many.py b/artiq/test/lit/inferencer/error_with_many.py deleted file mode 100644 index 81dc52017..000000000 --- a/artiq/test/lit/inferencer/error_with_many.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: the 'interleave' context manager must be the only one in a 'with' statement -with interleave, sequential: - pass diff --git a/artiq/test/lit/inferencer/error_with_self.py b/artiq/test/lit/inferencer/error_with_self.py deleted file mode 100644 index d22c5c085..000000000 --- a/artiq/test/lit/inferencer/error_with_self.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - pass - - def __exit__(self, n1, n2, n3): - pass - -def foo(): - contextmgr.__enter__(1) - # CHECK-L: ${LINE:+3}: error: cannot unify with numpy.int? while inferring the type for self argument - # CHECK-L: ${LINE:+2}: note: expression of type - # CHECK-L: ${LINE:+1}: note: reference to an instance with a method '__enter__(self:numpy.int?)->NoneType delay('a)' - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_var.py b/artiq/test/lit/inferencer/error_with_var.py deleted file mode 100644 index 97b1b345a..000000000 --- a/artiq/test/lit/inferencer/error_with_var.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - return 1 - - def __exit__(self, n1, n2, n3): - pass - -def foo(): - x = "x" - # CHECK-L: ${LINE:+3}: error: cannot unify str with NoneType - # CHECK-L: ${LINE:+2}: note: expression of type str - # CHECK-L: ${LINE:+1}: note: context manager with an '__enter__' method returning NoneType - with contextmgr() as x: - pass diff --git a/artiq/test/lit/inferencer/exception.py b/artiq/test/lit/inferencer/exception.py deleted file mode 100644 index e0e0f9645..000000000 --- a/artiq/test/lit/inferencer/exception.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: Exception: -Exception - -try: - pass -except Exception: - pass -except Exception as e: - # CHECK-L: e:Exception - e diff --git a/artiq/test/lit/inferencer/gcd.py b/artiq/test/lit/inferencer/gcd.py deleted file mode 100644 index 3137d7cc0..000000000 --- a/artiq/test/lit/inferencer/gcd.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t - -def _gcd(a, b): - if a < 0: - a = -a - while a: - c = a - a = b % a - b = c - return b - -# CHECK-L: _gcd:(a:numpy.int?, b:numpy.int?)->numpy.int?(10:numpy.int?, 25:numpy.int?):numpy.int? -_gcd(10, 25) diff --git a/artiq/test/lit/inferencer/if_expr.py b/artiq/test/lit/inferencer/if_expr.py deleted file mode 100644 index d955bc736..000000000 --- a/artiq/test/lit/inferencer/if_expr.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: def foo(val:bool)->numpy.int?: -def foo(val): - return 1 if val else 0 diff --git a/artiq/test/lit/inferencer/matmult.py b/artiq/test/lit/inferencer/matmult.py deleted file mode 100644 index e8e982c57..000000000 --- a/artiq/test/lit/inferencer/matmult.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -vec = array([0, 1]) -mat = array([[0, 1], [2, 3]]) - -# CHECK-L: ):numpy.int? -vec @ vec - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=1) -vec @ mat - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=1) -mat @ vec - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=2) -mat @ mat diff --git a/artiq/test/lit/inferencer/prelude.py b/artiq/test/lit/inferencer/prelude.py deleted file mode 100644 index 288cc78b3..000000000 --- a/artiq/test/lit/inferencer/prelude.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: x: -x = len - -def f(): - global len - # CHECK-L: len:numpy.int? = - len = 1 diff --git a/artiq/test/lit/inferencer/scoping.py b/artiq/test/lit/inferencer/scoping.py deleted file mode 100644 index 9847ef0ea..000000000 --- a/artiq/test/lit/inferencer/scoping.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: []:list(elt=numpy.int?) -x = [] - -def f(): - global x - x[0] = 1 diff --git a/artiq/test/lit/inferencer/slice.py b/artiq/test/lit/inferencer/slice.py deleted file mode 100644 index 610fa9dc3..000000000 --- a/artiq/test/lit/inferencer/slice.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = [0] -# CHECK-L: [::numpy.int32] -x[:] = [1] diff --git a/artiq/test/lit/inferencer/unify.py b/artiq/test/lit/inferencer/unify.py deleted file mode 100644 index dcff73487..000000000 --- a/artiq/test/lit/inferencer/unify.py +++ /dev/null @@ -1,82 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = 1 -# CHECK-L: a:numpy.int? - -b = a -# CHECK-L: b:numpy.int? - -c = True -# CHECK-L: c:bool - -d = False -# CHECK-L: d:bool - -e = None -# CHECK-L: e:NoneType - -f = 1.0 -# CHECK-L: f:float - -g = [] -# CHECK-L: g:list(elt='a) - -h = [1] -# CHECK-L: h:list(elt=numpy.int?) - -i = [] -i[0] = 1 -# CHECK-L: i:list(elt=numpy.int?) - -j = [] -j += [1.0] -# CHECK-L: j:list(elt=float) - -1 if c else 2 -# CHECK-L: 1:numpy.int? if c:bool else 2:numpy.int?:numpy.int? - -True and False -# CHECK-L: True:bool and False:bool:bool - -1 and 0 -# CHECK-L: 1:numpy.int? and 0:numpy.int?:numpy.int? - -~1 -# CHECK-L: 1:numpy.int?:numpy.int? - -not 1 -# CHECK-L: 1:numpy.int?:bool - -[x for x in [1]] -# CHECK-L: [x:numpy.int? for x:numpy.int? in [1:numpy.int?]:list(elt=numpy.int?)]:list(elt=numpy.int?) - -lambda x, y=1: x -# CHECK-L: lambda x:'b, y:numpy.int?=1:numpy.int?: x:'b:(x:'b, ?y:numpy.int?)->'b - -k = "x" -# CHECK-L: k:str - -ka = b"x" -# CHECK-L: ka:bytes - -kb = bytearray(b"x") -# CHECK-L: kb:bytearray - -l = array([1]) -# CHECK-L: l:numpy.array(elt=numpy.int?, num_dims=1) - -IndexError() -# CHECK-L: IndexError:():IndexError - -IndexError("x") -# CHECK-L: IndexError:("x":str):IndexError - -IndexError("x", 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64):IndexError - -IndexError("x", 1, 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64, 1:numpy.int64):IndexError - -IndexError("x", 1, 1, 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64, 1:numpy.int64, 1:numpy.int64):IndexError diff --git a/artiq/test/lit/inferencer/with.py b/artiq/test/lit/inferencer/with.py deleted file mode 100644 index c3128eeef..000000000 --- a/artiq/test/lit/inferencer/with.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: as x:NoneType -with interleave as x: pass diff --git a/artiq/test/lit/integration/abs.py b/artiq/test/lit/integration/abs.py deleted file mode 100644 index ba279ab4b..000000000 --- a/artiq/test/lit/integration/abs.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert abs(1234) == 1234 -assert abs(-1234) == 1234 -assert abs(1234.5) == 1234.5 -assert abs(-1234.5) == 1234.5 diff --git a/artiq/test/lit/integration/arithmetics.py b/artiq/test/lit/integration/arithmetics.py deleted file mode 100644 index 4fee4d27d..000000000 --- a/artiq/test/lit/integration/arithmetics.py +++ /dev/null @@ -1,64 +0,0 @@ -# RUN: %python %s -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: exceptions - -assert -(-1) == 1 -assert -(-1.0) == 1.0 -assert +1 == 1 -assert +1.0 == 1.0 -assert 1 + 1 == 2 -assert 1.0 + 1.0 == 2.0 -assert 1 - 1 == 0 -assert 1.0 - 1.0 == 0.0 -assert 2 * 2 == 4 -assert 2.0 * 2.0 == 4.0 -assert 3 / 2 == 1.5 -assert 3.0 / 2.0 == 1.5 -assert 3 // 2 == 1 -assert 3.0 // 2.0 == 1.0 -assert 3 % 2 == 1 -assert -3 % 2 == 1 -assert 3 % -2 == -1 -assert -3 % -2 == -1 -assert -1 % 8 == 7 -#ARTIQ#assert int64(3) % 2 == 1 -#ARTIQ#assert int64(-3) % 2 == 1 -#ARTIQ#assert int64(3) % -2 == -1 -#ARTIQ#assert int64(-3) % -2 == -1 -assert -1 % 8 == 7 -assert 3.0 % 2.0 == 1.0 -assert -3.0 % 2.0 == 1.0 -assert 3.0 % -2.0 == -1.0 -assert -3.0 % -2.0 == -1.0 -assert 3 ** 2 == 9 -assert 3.0 ** 2.0 == 9.0 -assert 9.0 ** 0.5 == 3.0 -assert 1 << 1 == 2 -assert 2 >> 1 == 1 -assert -2 >> 1 == -1 -#ARTIQ#assert 1 << 32 == 0 -assert -1 >> 32 == -1 -assert 0x18 & 0x0f == 0x08 -assert 0x18 | 0x0f == 0x1f -assert 0x18 ^ 0x0f == 0x17 -#ARTIQ#assert ~0x18 == -25 -#ARTIQ#assert ~int64(0x18) == -25 - -try: - 1 / 0 -except ZeroDivisionError: - pass -else: - assert False -try: - 1 // 0 -except ZeroDivisionError: - pass -else: - assert False -try: - 1 % 0 -except ZeroDivisionError: - pass -else: - assert False diff --git a/artiq/test/lit/integration/array_binops.py b/artiq/test/lit/integration/array_binops.py deleted file mode 100644 index cfca32d92..000000000 --- a/artiq/test/lit/integration/array_binops.py +++ /dev/null @@ -1,98 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2, 3]) -b = array([4, 5, 6]) - -c = a + b -assert c[0] == 5 -assert c[1] == 7 -assert c[2] == 9 - -c += a -assert c[0] == 6 -assert c[1] == 9 -assert c[2] == 12 - -c = b - a -assert c[0] == 3 -assert c[1] == 3 -assert c[2] == 3 - -c -= a -assert c[0] == 2 -assert c[1] == 1 -assert c[2] == 0 - -c = a * b -assert c[0] == 4 -assert c[1] == 10 -assert c[2] == 18 - -c *= a -assert c[0] == 4 -assert c[1] == 20 -assert c[2] == 54 - -c = b // a -assert c[0] == 4 -assert c[1] == 2 -assert c[2] == 2 - -c //= a -assert c[0] == 4 -assert c[1] == 1 -assert c[2] == 0 - -c = a ** b -assert c[0] == 1 -assert c[1] == 32 -assert c[2] == 729 - -c **= a -assert c[0] == 1 -assert c[1] == 1024 -assert c[2] == 387420489 - -c = b % a -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 0 - -c %= a -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 0 - -cf = b / a -assert cf[0] == 4.0 -assert cf[1] == 2.5 -assert cf[2] == 2.0 - -cf2 = cf + a -assert cf2[0] == 5.0 -assert cf2[1] == 4.5 -assert cf2[2] == 5.0 - -cf2 += a -assert cf2[0] == 6.0 -assert cf2[1] == 6.5 -assert cf2[2] == 8.0 - -cf /= a -assert cf[0] == 4.0 -assert cf[1] == 1.25 -assert cf[2] == 2.0 / 3.0 - -d = array([[1, 2], [3, 4]]) -e = array([[5, 6], [7, 8]]) -f = d + e -assert f[0][0] == 6 -assert f[0][1] == 8 -assert f[1][0] == 10 -assert f[1][1] == 12 - -f += d -assert f[0][0] == 7 -assert f[0][1] == 10 -assert f[1][0] == 13 -assert f[1][1] == 16 diff --git a/artiq/test/lit/integration/array_broadcast.py b/artiq/test/lit/integration/array_broadcast.py deleted file mode 100644 index d7cbc5998..000000000 --- a/artiq/test/lit/integration/array_broadcast.py +++ /dev/null @@ -1,55 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2, 3]) - -c = a + 1 -assert c[0] == 2 -assert c[1] == 3 -assert c[2] == 4 - -c = 1 - a -assert c[0] == 0 -assert c[1] == -1 -assert c[2] == -2 - -c = a * 1 -assert c[0] == 1 -assert c[1] == 2 -assert c[2] == 3 - -c = a // 2 -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 1 - -c = a ** 2 -assert c[0] == 1 -assert c[1] == 4 -assert c[2] == 9 - -c = 2 ** a -assert c[0] == 2 -assert c[1] == 4 -assert c[2] == 8 - -c = a % 2 -assert c[0] == 1 -assert c[1] == 0 -assert c[2] == 1 - -cf = a / 2 -assert cf[0] == 0.5 -assert cf[1] == 1.0 -assert cf[2] == 1.5 - -cf2 = 2 / array([1, 2, 4]) -assert cf2[0] == 2.0 -assert cf2[1] == 1.0 -assert cf2[2] == 0.5 - -d = array([[1, 2], [3, 4]]) -e = d + 1 -assert e[0][0] == 2 -assert e[0][1] == 3 -assert e[1][0] == 4 -assert e[1][1] == 5 diff --git a/artiq/test/lit/integration/array_creation.py b/artiq/test/lit/integration/array_creation.py deleted file mode 100644 index 512382cda..000000000 --- a/artiq/test/lit/integration/array_creation.py +++ /dev/null @@ -1,50 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: exceptions - -ary = array([1, 2, 3]) -assert len(ary) == 3 -assert ary.shape == (3,) -assert [x * x for x in ary] == [1, 4, 9] - -# Reassign to an existing value to disambiguate type of empty array. -empty_array = array([1]) -empty_array = array([]) -assert len(empty_array) == 0 -assert empty_array.shape == (0,) -assert [x * x for x in empty_array] == [] - -# Creating arrays from generic iterables, rectangularity is assumed (and ensured -# with runtime checks). -list_of_lists = [[1, 2], [3, 4]] -array_of_lists = array(list_of_lists) -assert array_of_lists.shape == (2, 2) -assert [[y for y in x] for x in array_of_lists] == list_of_lists - -matrix = array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) -assert len(matrix) == 2 -assert matrix.shape == (2, 3) -# FIXME: Need to decide on a solution for array comparisons — -# NumPy returns an array of bools! -# assert [x for x in matrix] == [array([1.0, 2.0, 3.0]), array([4.0, 5.0, 6.0])] -assert matrix[0, 0] == 1.0 -assert matrix[0, 1] == 2.0 -assert matrix[0, 2] == 3.0 -assert matrix[1, 0] == 4.0 -assert matrix[1, 1] == 5.0 -assert matrix[1, 2] == 6.0 - -matrix[0, 0] = 7.0 -matrix[1, 1] = 8.0 -assert matrix[0, 0] == 7.0 -assert matrix[0, 1] == 2.0 -assert matrix[0, 2] == 3.0 -assert matrix[1, 0] == 4.0 -assert matrix[1, 1] == 8.0 -assert matrix[1, 2] == 6.0 - -array_of_matrices = array([matrix, matrix]) -assert array_of_matrices.shape == (2, 2, 3) - -three_tensor = array([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]]) -assert len(three_tensor) == 1 -assert three_tensor.shape == (1, 2, 3) diff --git a/artiq/test/lit/integration/array_matmult.py b/artiq/test/lit/integration/array_matmult.py deleted file mode 100644 index 7519c10ff..000000000 --- a/artiq/test/lit/integration/array_matmult.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -mat23 = array([[1, 2, 3], [4, 5, 6]]) -mat32 = array([[1, 2], [3, 4], [5, 6]]) -vec2 = array([1, 2]) -vec3 = array([1, 2, 3]) - -assert vec3 @ vec3 == 14 - -a = mat23 @ mat32 -assert a.shape == (2, 2) -assert a[0][0] == 22 -assert a[0][1] == 28 -assert a[1][0] == 49 -assert a[1][1] == 64 - -b = mat23 @ vec3 -assert b.shape == (2,) -assert b[0] == 14 -assert b[1] == 32 - -b = vec3 @ mat32 -assert b.shape == (2,) -assert b[0] == 22 -assert b[1] == 28 diff --git a/artiq/test/lit/integration/array_slice.py b/artiq/test/lit/integration/array_slice.py deleted file mode 100644 index 6c37f8366..000000000 --- a/artiq/test/lit/integration/array_slice.py +++ /dev/null @@ -1,24 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([0, 1, 2, 3]) - -b = a[2:3] -assert b.shape == (1,) -assert b[0] == 2 -b[0] = 5 -assert a[2] == 5 - -b = a[3:2] -assert b.shape == (0,) - -c = array([[0, 1], [2, 3]]) - -d = c[:1] -assert d.shape == (1, 2) -assert d[0, 0] == 0 -assert d[0, 1] == 1 -d[0, 0] = 5 -assert c[0, 0] == 5 - -d = c[1:0] -assert d.shape == (0, 2) diff --git a/artiq/test/lit/integration/array_unaryops.py b/artiq/test/lit/integration/array_unaryops.py deleted file mode 100644 index e55b6f733..000000000 --- a/artiq/test/lit/integration/array_unaryops.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2]) - -b = +a -assert b[0] == 1 -assert b[1] == 2 - -b = -a -assert b[0] == -1 -assert b[1] == -2 diff --git a/artiq/test/lit/integration/attribute.py b/artiq/test/lit/integration/attribute.py deleted file mode 100644 index 301243b54..000000000 --- a/artiq/test/lit/integration/attribute.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -r = range(10) -assert r.start == 0 -assert r.stop == 10 -assert r.step == 1 diff --git a/artiq/test/lit/integration/bool.py b/artiq/test/lit/integration/bool.py deleted file mode 100644 index 1a68ebd1c..000000000 --- a/artiq/test/lit/integration/bool.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert (not 0) == True -assert (not 1) == False - -assert (0 and 0) is 0 -assert (1 and 0) is 0 -assert (0 and 1) is 0 -assert (1 and 2) is 2 - -assert (0 or 0) is 0 -assert (1 or 0) is 1 -assert (0 or 1) is 1 -assert (1 or 2) is 1 - -assert bool(False) is False and bool(False) is False -assert bool(0) is False and bool(1) is True -assert bool(0.0) is False and bool(1.0) is True -x = []; assert bool(x) is False; x = [1]; assert bool(x) is True -assert bool(range(0)) is False and bool(range(1)) is True diff --git a/artiq/test/lit/integration/builtin.py b/artiq/test/lit/integration/builtin.py deleted file mode 100644 index 1562edf57..000000000 --- a/artiq/test/lit/integration/builtin.py +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -assert bool() is False -# bool(x) is tested in bool.py - -assert int() is 0 -assert int(1.0) is 1 -#ARTIQ#assert int64(1) << 40 is 1099511627776 - -#ARTIQ#assert float() is 0.0 -#ARTIQ#assert float(1) is 1.0 - -x = list() -if False: x = [1] -assert x == [] - -#ARTIQ#assert range(10) is range(0, 10, 1) -#ARTIQ#assert range(1, 10) is range(1, 10, 1) - -assert len([1, 2, 3]) is 3 -assert len(range(10)) is 10 -assert len(range(0, 10, 2)) is 5 - -#ARTIQ#assert round(1.4) is 1 and round(1.6) is 2 diff --git a/artiq/test/lit/integration/bytes.py b/artiq/test/lit/integration/bytes.py deleted file mode 100644 index da68591e2..000000000 --- a/artiq/test/lit/integration/bytes.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert b"xy" == b"xy" -assert (b"x" + b"y") == b"xy" diff --git a/artiq/test/lit/integration/class.py b/artiq/test/lit/integration/class.py deleted file mode 100644 index 205210ea0..000000000 --- a/artiq/test/lit/integration/class.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class c: - a = 1 - def f(): - return 2 - def g(self): - return self.a + 5 - def h(self, x): - return self.a + x - -assert c.a == 1 -assert c.f() == 2 -assert c().g() == 6 -assert c().h(9) == 10 diff --git a/artiq/test/lit/integration/compare.py b/artiq/test/lit/integration/compare.py deleted file mode 100644 index 48a33cc09..000000000 --- a/artiq/test/lit/integration/compare.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert 1 < 2 and not (2 < 1) -assert 2 > 1 and not (1 > 2) -assert 1 == 1 and not (1 == 2) -assert 1 != 2 and not (1 != 1) -assert 1 <= 1 and 1 <= 2 and not (2 <= 1) -assert 1 >= 1 and 2 >= 1 and not (1 >= 2) -assert 1 is 1 and not (1 is 2) -assert 1 is not 2 and not (1 is not 1) - -x, y = [1], [1] -assert x is x and x is not y -#ARTIQ#assert range(10) is range(10) and range(10) is not range(11) - -lst = [1, 2, 3] -assert 1 in lst and 0 not in lst -assert 1 in range(10) and 11 not in range(10) and -1 not in range(10) diff --git a/artiq/test/lit/integration/finally.py b/artiq/test/lit/integration/finally.py deleted file mode 100644 index e629602ad..000000000 --- a/artiq/test/lit/integration/finally.py +++ /dev/null @@ -1,78 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t.1 -# RUN: OutputCheck %s --file-to-check=%t.1 -# RUN: %python %s >%t.2 -# RUN: OutputCheck %s --file-to-check=%t.2 -# REQUIRES: exceptions - -def f(): - while True: - try: - print("f-try") - break - finally: - print("f-finally") - print("f-out") - -def g(): - x = True - while x: - try: - print("g-try") - x = False - continue - finally: - print("g-finally") - print("g-out") - -def h(): - try: - print("h-try") - return 10 - finally: - print("h-finally") - print("h-out") - return 20 - -def i(): - try: - print("i-try") - return 10 - finally: - print("i-finally") - return 30 - print("i-out") - return 20 - -def j(): - try: - print("j-try") - finally: - print("j-finally") - print("j-out") - -# CHECK-L: f-try -# CHECK-L: f-finally -# CHECK-L: f-out -f() - -# CHECK-L: g-try -# CHECK-L: g-finally -# CHECK-L: g-out -g() - -# CHECK-L: h-try -# CHECK-L: h-finally -# CHECK-NOT-L: h-out -# CHECK-L: h 10 -print("h", h()) - -# CHECK-L: i-try -# CHECK-L: i-finally -# CHECK-NOT-L: i-out -# CHECK-L: i 30 -print("i", i()) - -# CHECK-L: j-try -# CHECK-L: j-finally -# CHECK-L: j-out -print("j", j()) diff --git a/artiq/test/lit/integration/for.py b/artiq/test/lit/integration/for.py deleted file mode 100644 index 7156bee5d..000000000 --- a/artiq/test/lit/integration/for.py +++ /dev/null @@ -1,53 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -count = 0 -for x in range(10): - count += 1 -assert count == 10 - -for x in range(10): - assert True -else: - assert True - -for x in range(0): - assert False -else: - assert True - -for x in range(10): - continue - assert False -else: - assert True - -for x in range(10): - break - assert False -else: - assert False - -# Verify continue target is reset in else block. -cond = False -while True: - if cond: - break - cond = True - for _ in range(1): - pass - else: - continue - assert False -else: - assert False - -# Verify for target is reset in else block. -while True: - for _ in range(1): - pass - else: - break - assert False -else: - assert False diff --git a/artiq/test/lit/integration/function.py b/artiq/test/lit/integration/function.py deleted file mode 100644 index bbaca2083..000000000 --- a/artiq/test/lit/integration/function.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -def fib(x): - if x == 1: - return x - else: - return x * fib(x - 1) -assert fib(5) == 120 - -# argument combinations handled in lambda.py diff --git a/artiq/test/lit/integration/if.py b/artiq/test/lit/integration/if.py deleted file mode 100644 index 8d8eec4f2..000000000 --- a/artiq/test/lit/integration/if.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -if True: - assert True - -if False: - assert False - -if True: - assert True -else: - assert False - -if False: - assert False -else: - assert True - -assert (0 if True else 1) == 0 -assert (0 if False else 1) == 1 - -if 0: - assert True - -if 1: - assert True diff --git a/artiq/test/lit/integration/instance.py b/artiq/test/lit/integration/instance.py deleted file mode 100644 index 5acea8721..000000000 --- a/artiq/test/lit/integration/instance.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class c: - a = 1 - -i = c() - -def f(): - c = None - assert i.a == 1 - -assert i.a == 1 -f() diff --git a/artiq/test/lit/integration/lambda.py b/artiq/test/lit/integration/lambda.py deleted file mode 100644 index a1f08763a..000000000 --- a/artiq/test/lit/integration/lambda.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert (lambda: 1)() == 1 -assert (lambda x: x)(1) == 1 -assert (lambda x, y: x + y)(1, 2) == 3 -assert (lambda x, y=1: x + y)(1) == 2 -assert (lambda x, y=1: x + y)(1, 2) == 3 -assert (lambda x, y=1: x + y)(x=3) == 4 -assert (lambda x, y=1: x + y)(y=2, x=3) == 5 diff --git a/artiq/test/lit/integration/list.py b/artiq/test/lit/integration/list.py deleted file mode 100644 index 97fad6a6c..000000000 --- a/artiq/test/lit/integration/list.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -[x, y] = [1, 2] -assert (x, y) == (1, 2) - -lst = [1, 2, 3] -assert [x*x for x in lst] == [1, 4, 9] - -assert [0] == [0] -assert [0] != [1] -assert [[0]] == [[0]] -assert [[0]] != [[1]] -assert [[[0]]] == [[[0]]] -assert [[[0]]] != [[[1]]] - -assert [1] + [2] == [1, 2] -assert [1] * 3 == [1, 1, 1] diff --git a/artiq/test/lit/integration/locals.py b/artiq/test/lit/integration/locals.py deleted file mode 100644 index 6ad9b0763..000000000 --- a/artiq/test/lit/integration/locals.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -x = 1 -assert x == 1 -x += 1 -assert x == 2 diff --git a/artiq/test/lit/integration/minmax.py b/artiq/test/lit/integration/minmax.py deleted file mode 100644 index 5f325be7b..000000000 --- a/artiq/test/lit/integration/minmax.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert min(1, 2) == 1 -assert max(1, 2) == 2 -assert min(1.0, 2.0) == 1.0 -assert max(1.0, 2.0) == 2.0 diff --git a/artiq/test/lit/integration/print.py b/artiq/test/lit/integration/print.py deleted file mode 100644 index 11887bf77..000000000 --- a/artiq/test/lit/integration/print.py +++ /dev/null @@ -1,41 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: None -print(None) - -# CHECK-L: True False -print(True, False) - -# CHECK-L: 1 -1 -print(1, -1) - -# CHECK-L: 10000000000 -print(10000000000) - -# CHECK-L: 1.5 -print(1.5) - -# CHECK-L: (True, 1) -print((True, 1)) - -# CHECK-L: (True,) -print((True,)) - -# CHECK-L: [1, 2, 3] -print([1, 2, 3]) - -# CHECK-L: [[1, 2], [3]] -print([[1, 2], [3]]) - -# CHECK-L: range(0, 10, 1) -print(range(10)) - -# CHECK-L: array([1, 2]) -print(array([1, 2])) - -# CHECK-L: bytes([97, 98]) -print(b"ab") - -# CHECK-L: bytearray([97, 98]) -print(bytearray(b"ab")) diff --git a/artiq/test/lit/integration/raise.py b/artiq/test/lit/integration/raise.py deleted file mode 100644 index 7b9d4fc70..000000000 --- a/artiq/test/lit/integration/raise.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -try: - raise ValueError -except ValueError: - pass diff --git a/artiq/test/lit/integration/str.py b/artiq/test/lit/integration/str.py deleted file mode 100644 index 9d75399e9..000000000 --- a/artiq/test/lit/integration/str.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert "xy" == "xy" -assert not ("xy" == "xz") - -assert "xy" != "xz" -assert not ("xy" != "xy") - -assert ("x" + "y") == "xy" diff --git a/artiq/test/lit/integration/subscript.py b/artiq/test/lit/integration/subscript.py deleted file mode 100644 index db50809e6..000000000 --- a/artiq/test/lit/integration/subscript.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -lst = list(range(10)) -assert lst[0] == 0 -assert lst[1] == 1 -assert lst[-1] == 9 -assert lst[0:1] == [0] -assert lst[0:2] == [0, 1] -assert lst[0:10] == lst -assert lst[1:-1] == lst[1:9] -assert lst[0:1:2] == [0] -assert lst[0:2:2] == [0] -assert lst[0:3:2] == [0, 2] - -lst = [0, 0, 0, 0, 0] -lst[0:5:2] = [1, 2, 3] -assert lst == [1, 0, 2, 0, 3] - -byt = b"abc" -assert byt[0] == 97 -assert byt[1] == 98 - -barr = bytearray(b"abc") -assert barr[0] == 97 -assert barr[1] == 98 diff --git a/artiq/test/lit/integration/tuple.py b/artiq/test/lit/integration/tuple.py deleted file mode 100644 index 44564c151..000000000 --- a/artiq/test/lit/integration/tuple.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -x, y = 2, 1 -x, y = y, x -assert x == 1 and y == 2 -assert (1, 2) + (3.0,) == (1, 2, 3.0) - -assert (0,) == (0,) -assert (0,) != (1,) - -assert ([0],) == ([0],) -assert ([0],) != ([1],) diff --git a/artiq/test/lit/integration/while.py b/artiq/test/lit/integration/while.py deleted file mode 100644 index 397055c9a..000000000 --- a/artiq/test/lit/integration/while.py +++ /dev/null @@ -1,62 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -cond, count = True, 0 -while cond: - count += 1 - cond = False -assert count == 1 - -while False: - pass -else: - assert True - -cond = True -while cond: - cond = False -else: - assert True - -while True: - break - assert False -else: - assert False - -cond = True -while cond: - cond = False - continue - assert False - -# Verify continue target is reset in else block. -cond = False -while True: - if cond: - break - cond = True - while False: - assert False - else: - continue - assert False -else: - assert False - -# Verify break target is reset in else block. -while True: - while False: - assert False - else: - break - assert False -else: - assert False - -while 0: - assert False - -while 1: - assert True - break diff --git a/artiq/test/lit/integration/with.py b/artiq/test/lit/integration/with.py deleted file mode 100644 index 8f2b9b9a8..000000000 --- a/artiq/test/lit/integration/with.py +++ /dev/null @@ -1,33 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class contextmgr: - def __enter__(self): - print(2) - - def __exit__(self, n1, n2, n3): - print(4) - -# CHECK-L: a 1 -# CHECK-L: 2 -# CHECK-L: a 3 -# CHECK-L: 4 -# CHECK-L: a 5 -print("a", 1) -with contextmgr(): - print("a", 3) -print("a", 5) - -# CHECK-L: b 1 -# CHECK-L: 2 -# CHECK-L: 4 -# CHECK-L: b 6 -try: - print("b", 1) - with contextmgr(): - [0][1] - print("b", 3) - print("b", 5) -except: - pass -print("b", 6) diff --git a/artiq/test/lit/interleaving/control_flow.py b/artiq/test/lit/interleaving/control_flow.py deleted file mode 100644 index c4f7356a3..000000000 --- a/artiq/test/lit/interleaving/control_flow.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - with interleave: - if True: - print(1) - else: - print(2) - while False: - print(3) - break - delay_mu(1) - print(4) - -# CHECK-L: 1 -# CHECK-L: 4 -f() diff --git a/artiq/test/lit/interleaving/error_inlining.py b/artiq/test/lit/interleaving/error_inlining.py deleted file mode 100644 index dbf237538..000000000 --- a/artiq/test/lit/interleaving/error_inlining.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - delay_mu(2) - -x = f if True else g - -def h(): - with interleave: - f() - # CHECK-L: ${LINE:+1}: fatal: it is not possible to interleave this function call within a 'with interleave:' statement because the compiler could not prove that the same function would always be called - x() diff --git a/artiq/test/lit/interleaving/error_toplevel_control_flow.py b/artiq/test/lit/interleaving/error_toplevel_control_flow.py deleted file mode 100644 index acbb7815e..000000000 --- a/artiq/test/lit/interleaving/error_toplevel_control_flow.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: cannot interleave this 'with interleave:' statement - with interleave: - # CHECK-L: ${LINE:+1}: note: this 'return' statement transfers control out of the 'with interleave:' statement - return - delay(1.0) - -def g(): - while True: - # CHECK-L: ${LINE:+1}: error: cannot interleave this 'with interleave:' statement - with interleave: - # CHECK-L: ${LINE:+1}: note: this 'break' statement transfers control out of the 'with interleave:' statement - break - delay(1.0) diff --git a/artiq/test/lit/interleaving/indirect.py b/artiq/test/lit/interleaving/indirect.py deleted file mode 100644 index 0f03f55ac..000000000 --- a/artiq/test/lit/interleaving/indirect.py +++ /dev/null @@ -1,28 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - f() - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - f() - # - print("D", now_mu()) - f() - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/indirect_arg.py b/artiq/test/lit/interleaving/indirect_arg.py deleted file mode 100644 index c8df81ba0..000000000 --- a/artiq/test/lit/interleaving/indirect_arg.py +++ /dev/null @@ -1,28 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(n): - delay_mu(n) - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - f(2) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - f(2) - # - print("D", now_mu()) - f(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/nonoverlapping.py b/artiq/test/lit/interleaving/nonoverlapping.py deleted file mode 100644 index ffc67326e..000000000 --- a/artiq/test/lit/interleaving/nonoverlapping.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - delay_mu(2) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - delay_mu(2) - # - print("D", now_mu()) - delay_mu(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/overlapping.py b/artiq/test/lit/interleaving/overlapping.py deleted file mode 100644 index 32914ed1c..000000000 --- a/artiq/test/lit/interleaving/overlapping.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - delay_mu(3) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - delay_mu(2) - # - print("D", now_mu()) - delay_mu(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: D 2 -# CHECK-L: B 3 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/pure_impure_tie.py b/artiq/test/lit/interleaving/pure_impure_tie.py deleted file mode 100644 index b6aca7b81..000000000 --- a/artiq/test/lit/interleaving/pure_impure_tie.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - with interleave: - f() - delay_mu(2) - print(now_mu()) - -# CHECK-L: 2 -g() diff --git a/artiq/test/lit/interleaving/unrolling.py b/artiq/test/lit/interleaving/unrolling.py deleted file mode 100644 index 824032f07..000000000 --- a/artiq/test/lit/interleaving/unrolling.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -with interleave: - for x in range(10): - delay_mu(1) - print("a", x) - with sequential: - delay_mu(5) - print("c") - with sequential: - delay_mu(3) - print("b") - -# CHECK-L: a 0 -# CHECK-L: a 1 -# CHECK-L: a 2 -# CHECK-L: b -# CHECK-L: a 3 -# CHECK-L: a 4 -# CHECK-L: c -# CHECK-L: a 5 -# CHECK-L: a 6 diff --git a/artiq/test/lit/iodelay/argument.py b/artiq/test/lit/iodelay/argument.py deleted file mode 100644 index 94da1c764..000000000 --- a/artiq/test/lit/iodelay/argument.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:float, b:numpy.int64)->NoneType delay(s->mu(a) + b mu) -def f(a, b): - delay(a) - delay_mu(b) diff --git a/artiq/test/lit/iodelay/arith.py b/artiq/test/lit/iodelay/arith.py deleted file mode 100644 index 05d42e666..000000000 --- a/artiq/test/lit/iodelay/arith.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int32, b:numpy.int32, c:numpy.int32, d:numpy.int32, e:numpy.int32)->NoneType delay(s->mu(a * b // c + d - 10 / e) mu) -def f(a, b, c, d, e): - delay(a * b // c + d - 10 / e) - -f(1,2,3,4,5) diff --git a/artiq/test/lit/iodelay/call.py b/artiq/test/lit/iodelay/call.py deleted file mode 100644 index e08446da1..000000000 --- a/artiq/test/lit/iodelay/call.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(1) - -# CHECK-L: g: ()->NoneType delay(2 mu) -def g(): - f() - f() diff --git a/artiq/test/lit/iodelay/call_subst.py b/artiq/test/lit/iodelay/call_subst.py deleted file mode 100644 index fddc007c6..000000000 --- a/artiq/test/lit/iodelay/call_subst.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def pulse(len): - # "on" - delay_mu(len) - # "off" - delay_mu(len) - -# CHECK-L: f: ()->NoneType delay(600 mu) -def f(): - pulse(100) - pulse(200) diff --git a/artiq/test/lit/iodelay/class.py b/artiq/test/lit/iodelay/class.py deleted file mode 100644 index 63fab1862..000000000 --- a/artiq/test/lit/iodelay/class.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: g: (i:)->NoneType delay(1000000 mu) -def g(i): - i.f(1.0) - -class c: - def f(self, x): - delay(x) - -g(c()) diff --git a/artiq/test/lit/iodelay/error_argument.py b/artiq/test/lit/iodelay/error_argument.py deleted file mode 100644 index f92dd559d..000000000 --- a/artiq/test/lit/iodelay/error_argument.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(x): - delay_mu(x) - -x = 1 - -def g(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved because an argument cannot be statically evaluated - # CHECK-L: ${LINE:+1}: note: this expression is not supported in the expression for argument 'x' that affects I/O delay - f(x if True else x) diff --git a/artiq/test/lit/iodelay/error_arith.py b/artiq/test/lit/iodelay/error_arith.py deleted file mode 100644 index 6a4c9f0c1..000000000 --- a/artiq/test/lit/iodelay/error_arith.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(a): - b = 1.0 - # CHECK-L: ${LINE:+3}: error: call cannot be interleaved - # CHECK-L: ${LINE:+2}: note: this variable is not an argument of the innermost function - # CHECK-L: ${LINE:-4}: note: only these arguments are in scope of analysis - delay(b) - -def g(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this operator is not supported as an argument for delay() - delay(2.0**2) - -def h(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this expression is not supported as an argument for delay_mu() - delay_mu(1 if False else 2) - -f(1) diff --git a/artiq/test/lit/iodelay/error_bad_interleave.py b/artiq/test/lit/iodelay/error_bad_interleave.py deleted file mode 100644 index b368c28f3..000000000 --- a/artiq/test/lit/iodelay/error_bad_interleave.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - with interleave: - # CHECK-L: ${LINE:+1}: error: while statement cannot be interleaved - while True: - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_builtinfn.py b/artiq/test/lit/iodelay/error_builtinfn.py deleted file mode 100644 index b1b89a78f..000000000 --- a/artiq/test/lit/iodelay/error_builtinfn.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - x = 1 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved because an argument cannot be statically evaluated - delay_mu(x) - -def g(): - x = 1.0 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - delay(x) - - diff --git a/artiq/test/lit/iodelay/error_call_nested.py b/artiq/test/lit/iodelay/error_call_nested.py deleted file mode 100644 index 2c12af9bd..000000000 --- a/artiq/test/lit/iodelay/error_call_nested.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - delay(1.0**2) - -def g(): - # CHECK-L: ${LINE:+1}: note: function called here - f() - f() diff --git a/artiq/test/lit/iodelay/error_call_subst.py b/artiq/test/lit/iodelay/error_call_subst.py deleted file mode 100644 index 0d1ba0843..000000000 --- a/artiq/test/lit/iodelay/error_call_subst.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def pulse(len): - # "on" - delay_mu(len) - # "off" - delay_mu(len) - -def f(): - a = 100 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - pulse(a) diff --git a/artiq/test/lit/iodelay/error_control_flow.py b/artiq/test/lit/iodelay/error_control_flow.py deleted file mode 100644 index c179c95b9..000000000 --- a/artiq/test/lit/iodelay/error_control_flow.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: while statement cannot be interleaved - while True: - delay_mu(1) - -def g(): - # CHECK-L: ${LINE:+1}: error: if statement cannot be interleaved - if True: - delay_mu(1) - -def h(): - # CHECK-L: ${LINE:+1}: error: if expression cannot be interleaved - delay_mu(1) if True else delay_mu(2) - -def i(): - # CHECK-L: ${LINE:+1}: error: try statement cannot be interleaved - try: - delay_mu(1) - finally: - pass diff --git a/artiq/test/lit/iodelay/error_for.py b/artiq/test/lit/iodelay/error_for.py deleted file mode 100644 index aae5d47f3..000000000 --- a/artiq/test/lit/iodelay/error_for.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - r = range(10) - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate - # CHECK-L: ${LINE:+1}: note: this value is not a constant range literal - for _ in r: - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_goto.py b/artiq/test/lit/iodelay/error_goto.py deleted file mode 100644 index 714db7610..000000000 --- a/artiq/test/lit/iodelay/error_goto.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - for _ in range(10): - delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow - break - -def g(): - for _ in range(10): - delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow - continue diff --git a/artiq/test/lit/iodelay/error_iterable.py b/artiq/test/lit/iodelay/error_iterable.py deleted file mode 100644 index 6429236db..000000000 --- a/artiq/test/lit/iodelay/error_iterable.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 - -def f(): - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate - # CHECK-L: ${LINE:+1}: note: this expression is not supported in an iterable used in a for loop that is being interleaved - for _ in range(x if True else x): - delay_mu(10) diff --git a/artiq/test/lit/iodelay/error_return.py b/artiq/test/lit/iodelay/error_return.py deleted file mode 100644 index c047de2b0..000000000 --- a/artiq/test/lit/iodelay/error_return.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - if True: - # CHECK-L: ${LINE:+1}: error: only return statement at the end of the function can be interleaved - return 1 - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_unify.py b/artiq/test/lit/iodelay/error_unify.py deleted file mode 100644 index 723b5e393..000000000 --- a/artiq/test/lit/iodelay/error_unify.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(10) - -# CHECK-L: ${LINE:+1}: fatal: delay delay(20 mu) was inferred for this function, but its delay is already constrained externally to delay(10 mu) -def g(): - delay_mu(20) - -x = f if True else g diff --git a/artiq/test/lit/iodelay/goto.py b/artiq/test/lit/iodelay/goto.py deleted file mode 100644 index d80de43f9..000000000 --- a/artiq/test/lit/iodelay/goto.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(10 mu) -def f(): - delay_mu(10) - for _ in range(10): - break - -# CHECK-L: g: ()->NoneType delay(10 mu) -def g(): - delay_mu(10) - for _ in range(10): - continue diff --git a/artiq/test/lit/iodelay/interleave.py b/artiq/test/lit/iodelay/interleave.py deleted file mode 100644 index 76e30b7a7..000000000 --- a/artiq/test/lit/iodelay/interleave.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int64, b:numpy.int64)->NoneType delay(max(a, b) mu) -def f(a, b): - with interleave: - delay_mu(a) - delay_mu(b) - -# CHECK-L: g: (a:numpy.int64)->NoneType delay(max(a, 200) mu) -def g(a): - with interleave: - delay_mu(100) - delay_mu(200) - delay_mu(a) diff --git a/artiq/test/lit/iodelay/linear.py b/artiq/test/lit/iodelay/linear.py deleted file mode 100644 index cb2bed204..000000000 --- a/artiq/test/lit/iodelay/linear.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(1001000 mu) -def f(): - delay(1.0) - delay_mu(1000) - -# CHECK-L: g: ()->NoneType delay(3 mu) -def g(): - delay_mu(1) - delay_mu(2) diff --git a/artiq/test/lit/iodelay/loop.py b/artiq/test/lit/iodelay/loop.py deleted file mode 100644 index 37bcb9e07..000000000 --- a/artiq/test/lit/iodelay/loop.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(30 mu) -def f(): - for _ in range(10): - delay_mu(3) - -# CHECK-L: g: ()->NoneType delay(60 mu) -def g(): - for _ in range(10): - for _ in range(2): - delay_mu(3) diff --git a/artiq/test/lit/iodelay/order_invariance.py b/artiq/test/lit/iodelay/order_invariance.py deleted file mode 100644 index e4deb96c5..000000000 --- a/artiq/test/lit/iodelay/order_invariance.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: g: ()->NoneType delay(2 mu) -def g(): - f() - f() - -def f(): - delay_mu(1) diff --git a/artiq/test/lit/iodelay/range.py b/artiq/test/lit/iodelay/range.py deleted file mode 100644 index c5592927e..000000000 --- a/artiq/test/lit/iodelay/range.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int32)->NoneType delay(3 * a mu) -def f(a): - for _ in range(a): - delay_mu(3) - -# CHECK-L: g: (a:numpy.int32, b:numpy.int32)->NoneType delay(3 * (b - a) mu) -def g(a, b): - for _ in range(a, b): - delay_mu(3) - -# CHECK-L: h: (a:numpy.int32, b:numpy.int32, c:numpy.int32)->NoneType delay(3 * (b - a) // c mu) -def h(a, b, c): - for _ in range(a, b, c): - delay_mu(3) - -f(1) -g(1,2) -h(1,2,3) diff --git a/artiq/test/lit/iodelay/return.py b/artiq/test/lit/iodelay/return.py deleted file mode 100644 index 9eefe1f54..000000000 --- a/artiq/test/lit/iodelay/return.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->numpy.int32 delay(30 mu) -def f(): - for _ in range(10): - delay_mu(3) - return 10 - -# CHECK-L: g: (x:float)->numpy.int32 -# CHECK-NOT-L: delay -def g(x): - if x > 1.0: - return 1 - return 0 - -g(1.0) diff --git a/artiq/test/lit/iodelay/sequential.py b/artiq/test/lit/iodelay/sequential.py deleted file mode 100644 index 5be751126..000000000 --- a/artiq/test/lit/iodelay/sequential.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int64, b:numpy.int64)->NoneType delay(a + b mu) -def f(a, b): - with sequential: - delay_mu(a) - delay_mu(b) diff --git a/artiq/test/lit/lit.cfg b/artiq/test/lit/lit.cfg deleted file mode 100644 index 693383297..000000000 --- a/artiq/test/lit/lit.cfg +++ /dev/null @@ -1,33 +0,0 @@ -# -*- python -*- - -import os -import sys -import lit.util -import lit.formats - -root = os.path.join(os.path.dirname(__file__), "..") - -config.name = "ARTIQ" -config.test_format = lit.formats.ShTest() -config.suffixes = [".py"] -config.excludes = ["not.py", "device_db.py"] - -if os.getenv("COVERAGE"): - config.environment["COVERAGE_FILE"] = os.path.join(root, "..", ".coverage") - python = "coverage run --parallel-mode --source=artiq" -else: - python = sys.executable -config.substitutions.append( ("%python", python) ) - -if os.getenv("PYTHONPATH"): - config.environment["PYTHONPATH"] = os.getenv("PYTHONPATH") - -not_ = "{} {}".format(sys.executable, os.path.join(root, "lit", "not.py")) -config.substitutions.append( ("%not", not_) ) - -if os.name == "posix": - config.environment["LIBARTIQ_SUPPORT"] = os.getenv("LIBARTIQ_SUPPORT") - config.environment["RUST_BACKTRACE"] = "1" - - config.available_features.add("exceptions") - config.available_features.add("time") diff --git a/artiq/test/lit/local_access/invalid_closure.py b/artiq/test/lit/local_access/invalid_closure.py deleted file mode 100644 index 75948fb57..000000000 --- a/artiq/test/lit/local_access/invalid_closure.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -if False: - t = 1 - -# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized -l = lambda: t - -# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized -def f(): - return t - -l() -f() diff --git a/artiq/test/lit/local_access/invalid_flow.py b/artiq/test/lit/local_access/invalid_flow.py deleted file mode 100644 index 7b99958b4..000000000 --- a/artiq/test/lit/local_access/invalid_flow.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -if x > 10: - y = 1 -# CHECK-L: ${LINE:+1}: error: variable 'y' is not always initialized -x + y - -for z in [1]: - pass -# CHECK-L: ${LINE:+1}: error: variable 'z' is not always initialized --z - -if True: - pass -else: - t = 1 -# CHECK-L: ${LINE:+1}: error: variable 't' is not always initialized --t diff --git a/artiq/test/lit/local_access/multiple_asgn.py b/artiq/test/lit/local_access/multiple_asgn.py deleted file mode 100644 index b316a2005..000000000 --- a/artiq/test/lit/local_access/multiple_asgn.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s - -def f(): - x, y = [0], [0] - x[0], y[0] diff --git a/artiq/test/lit/local_access/parallel.py b/artiq/test/lit/local_access/parallel.py deleted file mode 100644 index 001049117..000000000 --- a/artiq/test/lit/local_access/parallel.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t - -with interleave: - delay(1.0) - t0 = now_mu() -print(t0) diff --git a/artiq/test/lit/local_access/valid.py b/artiq/test/lit/local_access/valid.py deleted file mode 100644 index 3c5fd0208..000000000 --- a/artiq/test/lit/local_access/valid.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t - -if False: - x = 1 -else: - x = 2 --x diff --git a/artiq/test/lit/local_demotion/closure.py b/artiq/test/lit/local_demotion/closure.py deleted file mode 100644 index 5786eb20e..000000000 --- a/artiq/test/lit/local_demotion/closure.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def x(y): pass - -# CHECK-L: NoneType input.a(environment(...) %ARG.ENV, NoneType %ARG.self) { -# CHECK-L: setlocal('self') %ENV, NoneType %ARG.self -# CHECK-NOT-L: call (y:NoneType)->NoneType %LOC.x, NoneType %ARG.self - -def a(self): - def b(): - pass - x(self) - -a(None) diff --git a/artiq/test/lit/local_demotion/demotion.py b/artiq/test/lit/local_demotion/demotion.py deleted file mode 100644 index c9b4ed0c5..000000000 --- a/artiq/test/lit/local_demotion/demotion.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def x(y): pass - -# CHECK-L: NoneType input.a(environment(...) %ARG.ENV, NoneType %ARG.self) { -# CHECK-NOT-L: getlocal('self') %ENV -# CHECK-L: call (y:NoneType)->NoneType %LOC.x, NoneType %ARG.self - -def a(self): - x(self) - -a(None) diff --git a/artiq/test/lit/monomorphism/bug_1242.py b/artiq/test/lit/monomorphism/bug_1242.py deleted file mode 100644 index 8918a30cc..000000000 --- a/artiq/test/lit/monomorphism/bug_1242.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 0x100000000 -# CHECK-L: x: numpy.int64 - -y = int32(x) -# CHECK-L: y: numpy.int32 diff --git a/artiq/test/lit/monomorphism/bug_1252.py b/artiq/test/lit/monomorphism/bug_1252.py deleted file mode 100644 index e00a899ab..000000000 --- a/artiq/test/lit/monomorphism/bug_1252.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: %BLT.round = numpy.int64 builtin(round) float -def frequency_to_ftw(frequency): - return int64(round(1e-9*frequency)) - -frequency_to_ftw(1e9) diff --git a/artiq/test/lit/monomorphism/coercion.py b/artiq/test/lit/monomorphism/coercion.py deleted file mode 100644 index bbd67e936..000000000 --- a/artiq/test/lit/monomorphism/coercion.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(x): - x = int64(0) - return x - -# CHECK-L: g: ()->numpy.int64 -def g(): - return f(1 + 0) diff --git a/artiq/test/lit/monomorphism/integers.py b/artiq/test/lit/monomorphism/integers.py deleted file mode 100644 index e6760fffb..000000000 --- a/artiq/test/lit/monomorphism/integers.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -# CHECK-L: x: numpy.int32 - -y = int(1) -# CHECK-L: y: numpy.int32 diff --git a/artiq/test/lit/monomorphism/round.py b/artiq/test/lit/monomorphism/round.py deleted file mode 100644 index 74df18401..000000000 --- a/artiq/test/lit/monomorphism/round.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: round:(1.0:float):numpy.int32 -round(1.0) - -# CHECK-L: round:(2.0:float):numpy.int32 -int32(round(2.0)) - -# CHECK-L: round:(3.0:float):numpy.int64 -int64(round(3.0)) diff --git a/artiq/test/lit/not.py b/artiq/test/lit/not.py deleted file mode 100644 index 855a36416..000000000 --- a/artiq/test/lit/not.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys, subprocess - -def main(): - exit(not subprocess.call(sys.argv[1:])) - -if __name__ == "__main__": - main() diff --git a/artiq/test/lit/regression/bug_659.py b/artiq/test/lit/regression/bug_659.py deleted file mode 100644 index 096839725..000000000 --- a/artiq/test/lit/regression/bug_659.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -@kernel -def rotate(array): - '''Rotates an array, deleting the oldest value''' - length = len(array) - for i in range(np.int64(len(array)) - 1): - array[length - i - 1] = array[length - i - 2] - array[0] = 0 - -@kernel -def entrypoint(): - rotate([1,2,3,4]) diff --git a/artiq/test/lit/regression/device_db.py b/artiq/test/lit/regression/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/regression/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/regression/issue_1506.py b/artiq/test/lit/regression/issue_1506.py deleted file mode 100644 index 88d8831bd..000000000 --- a/artiq/test/lit/regression/issue_1506.py +++ /dev/null @@ -1,44 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -# -# Check various sret-ized return types integrate properly with try/finally, which lowers -# to `invoke` on the LLVM level (code adapted from GitHub #1506). -# - -LIST = [1, 2] - - -def get_tuple(): - return (1, 2) - - -def get_list(): - return LIST - - -def get_range(): - return range(10) - - -def main(): - try: - a, b = get_tuple() - assert a == 1 - assert b == 2 - finally: - pass - - try: - for _ in get_list(): - pass - finally: - pass - - try: - for _ in get_range(): - pass - finally: - pass - - -main() diff --git a/artiq/test/lit/regression/issue_1627.py b/artiq/test/lit/regression/issue_1627.py deleted file mode 100644 index 828ef3a9f..000000000 --- a/artiq/test/lit/regression/issue_1627.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -n = 2 -data = np.zeros((n, n)) - - -@kernel -def entrypoint(): - print(data[:n]) diff --git a/artiq/test/lit/regression/issue_1632.py b/artiq/test/lit/regression/issue_1632.py deleted file mode 100644 index c7f36f96b..000000000 --- a/artiq/test/lit/regression/issue_1632.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -class A: - def __init__(self): - self.n = 2 - - @kernel - def run(self): - print([1, 2, 3][:self.n]) - -a = A() - -@kernel -def entrypoint(): - a.run() diff --git a/artiq/test/lit/time/advance.py b/artiq/test/lit/time/advance.py deleted file mode 100644 index 7a6a894aa..000000000 --- a/artiq/test/lit/time/advance.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -assert now_mu() == 0 -delay(100.0) -assert now_mu() == 100000000 -at_mu(12345000000) -assert now_mu() == 12345000000 diff --git a/artiq/test/lit/time/advance_mu.py b/artiq/test/lit/time/advance_mu.py deleted file mode 100644 index d674afbec..000000000 --- a/artiq/test/lit/time/advance_mu.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -assert now_mu() == 0 -delay_mu(100) -assert now_mu() == 100 -at_mu(12345) -assert now_mu() == 12345 diff --git a/artiq/test/lit/time/parallel.py b/artiq/test/lit/time/parallel.py deleted file mode 100644 index c54261f48..000000000 --- a/artiq/test/lit/time/parallel.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -with parallel: - with sequential: - assert now_mu() == 0 - delay_mu(10) - assert now_mu() == 10 - with sequential: - assert now_mu() == 0 - delay_mu(20) - assert now_mu() == 20 - with sequential: - assert now_mu() == 0 - delay_mu(15) - assert now_mu() == 15 - assert now_mu() == 0 -assert now_mu() == 20 diff --git a/doc/manual/conf.py b/doc/manual/conf.py index bc092928f..ad0a85689 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -37,7 +37,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget", "qasync", "pyqtgraph", "matplotlib", "numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5", "h5py", "serial", "scipy", "scipy.interpolate", - "llvmlite", "Levenshtein", "pythonparser", + "nac3artiq", "sipyco", "sipyco.pc_rpc", "sipyco.sync_struct", "sipyco.asyncio_tools", "sipyco.logging_tools", "sipyco.broadcast", "sipyco.packed_exceptions"] diff --git a/flake.lock b/flake.lock index e97a38179..eefbb4239 100644 --- a/flake.lock +++ b/flake.lock @@ -18,16 +18,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1631098960, - "narHash": "sha256-6j6G/omHEFAnI2x7UvdrviGDNqiq1Fjd1A58Q6uc4sQ=", + "lastModified": 1633621759, + "narHash": "sha256-Mw29zuYjOozICTWsc9RvjuR7hW5D0H83onQh/WoVrMs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "12eb1d16ae3b6cbf0ea83e9228bca8ffd7cfe347", + "rev": "6755a884b24038a73dd4c8022dbb05375feef0a7", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-21.05", "repo": "nixpkgs", "type": "github" } @@ -38,18 +37,18 @@ "nixpkgs": "nixpkgs", "src-migen": "src-migen", "src-misoc": "src-misoc", - "src-pythonparser": "src-pythonparser", + "src-nac3": "src-nac3", "src-sipyco": "src-sipyco" } }, "src-migen": { "flake": false, "locked": { - "lastModified": 1628592379, - "narHash": "sha256-TQV6p0+Ri9HVge4qUKOCXe96cmcimNZ3R4sVwNY67DA=", + "lastModified": 1633615575, + "narHash": "sha256-frh+WVOkEKMpVEMhdE/GZKSj82XnSC8OF8SWNluk/Yg=", "owner": "m-labs", "repo": "migen", - "rev": "27dbf03edd75c32dc1706e2a1316950c3a8d452a", + "rev": "6e3f8e565704b4293174aedfb15b3470d233f528", "type": "github" }, "original": { @@ -76,30 +75,34 @@ "url": "https://github.com/m-labs/misoc.git" } }, - "src-pythonparser": { - "flake": false, + "src-nac3": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1628745371, - "narHash": "sha256-p6TgeeaK4NEmbhimEXp31W8hVRo4DgWmcCoqZ+UdN60=", - "owner": "m-labs", - "repo": "pythonparser", - "rev": "5413ee5c9f8760e95c6acd5d6e88dabb831ad201", - "type": "github" + "lastModified": 1633622662, + "narHash": "sha256-AEWqSxdl2aIibv7vtU9vDMu8ytdOY10fwBaM77+Il64=", + "ref": "master", + "rev": "79d3c5caae79fe15f41d2b2248cad2344389555f", + "revCount": 340, + "type": "git", + "url": "https://git.m-labs.hk/M-Labs/nac3.git" }, "original": { - "owner": "m-labs", - "repo": "pythonparser", - "type": "github" + "type": "git", + "url": "https://git.m-labs.hk/M-Labs/nac3.git" } }, "src-sipyco": { "flake": false, "locked": { - "lastModified": 1623807309, - "narHash": "sha256-FTRAS4RjqDOygu6+cP8mKbZHu/YZ7YKpEe2gSzJc9rk=", + "lastModified": 1632832039, + "narHash": "sha256-GYXXCCOxNZyy6j7qScB3/QWUUCEVX+4tM4bXXVGXty0=", "owner": "m-labs", "repo": "sipyco", - "rev": "20c946aad78872fe60b78d9b57a624d69f3eea47", + "rev": "b83d8e5d82b25dba9393f0c12bdc5253f8138545", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e4343766d..1abe0dc93 100644 --- a/flake.nix +++ b/flake.nix @@ -1,15 +1,15 @@ { description = "A leading-edge control system for quantum information experiments"; - inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.05; + inputs.nixpkgs.url = github:NixOS/nixpkgs; inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; }; inputs.src-sipyco = { url = github:m-labs/sipyco; flake = false; }; - inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; }; + inputs.src-nac3 = { type = "git"; url = "https://git.m-labs.hk/M-Labs/nac3.git"; inputs.nixpkgs.follows = "nixpkgs"; }; inputs.src-migen = { url = github:m-labs/migen; flake = false; }; inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; }; - outputs = { self, nixpkgs, mozilla-overlay, src-sipyco, src-pythonparser, src-migen, src-misoc }: + outputs = { self, nixpkgs, mozilla-overlay, src-sipyco, src-nac3, src-migen, src-misoc }: let pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; rustManifest = pkgs.fetchurl { @@ -50,13 +50,6 @@ propagatedBuildInputs = with pkgs.python3Packages; [ pybase64 numpy ]; }; - pythonparser = pkgs.python3Packages.buildPythonPackage { - name = "pythonparser"; - src = src-pythonparser; - doCheck = false; - propagatedBuildInputs = with pkgs.python3Packages; [ regex ]; - }; - qasync = pkgs.python3Packages.buildPythonPackage rec { pname = "qasync"; version = "0.10.0"; @@ -73,52 +66,6 @@ ''; }; - outputcheck = pkgs.python3Packages.buildPythonApplication rec { - pname = "outputcheck"; - version = "0.4.2"; - src = pkgs.fetchFromGitHub { - owner = "stp"; - repo = "OutputCheck"; - rev = "e0f533d3c5af2949349856c711bf4bca50022b48"; - sha256 = "1y27vz6jq6sywas07kz3v01sqjd0sga9yv9w2cksqac3v7wmf2a0"; - }; - prePatch = "echo ${version} > RELEASE-VERSION"; - }; - - libartiq-support = pkgs.stdenv.mkDerivation { - name = "libartiq-support"; - src = self; - buildInputs = [ rustPlatform.rust.rustc ]; - buildPhase = '' - rustc $src/artiq/test/libartiq_support/lib.rs -Cpanic=unwind -g - ''; - installPhase = '' - mkdir $out - cp libartiq_support.so $out - ''; - }; - - llvmlite-new = pkgs.python3Packages.buildPythonPackage rec { - pname = "llvmlite"; - version = "0.37.0rc2"; - src = pkgs.python3Packages.fetchPypi { - inherit pname version; - sha256 = "sha256-F1quz+76JOt1jaQPVzdKe7RfN6gWG2lyE82qTvgyY/c="; - }; - nativeBuildInputs = [ pkgs.llvm_11 ]; - # Disable static linking - # https://github.com/numba/llvmlite/issues/93 - postPatch = '' - substituteInPlace ffi/Makefile.linux --replace "-static-libstdc++" "" - substituteInPlace llvmlite/tests/test_binding.py --replace "test_linux" "nope" - ''; - # Set directory containing llvm-config binary - preConfigure = '' - export LLVM_CONFIG=${pkgs.llvm_11.dev}/bin/llvm-config - ''; - doCheck = false; # FIXME - }; - artiq = pkgs.python3Packages.buildPythonPackage rec { pname = "artiq"; version = "7.0-dev"; @@ -127,9 +74,9 @@ preBuild = "export VERSIONEER_OVERRIDE=${version}"; nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ]; - # keep llvm_x and lld_x in sync with llvmlite - propagatedBuildInputs = [ pkgs.llvm_11 pkgs.lld_11 llvmlite-new sipyco pythonparser ] - ++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial python-Levenshtein h5py pyqt5 qasync ]); + # keep llvm_x and lld_x in sync with nac3 + propagatedBuildInputs = [ pkgs.llvm_11 pkgs.lld_11 src-nac3.packages.x86_64-linux.nac3artiq sipyco ] + ++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt5 qasync ]); dontWrapQtApps = true; postFixup = '' @@ -146,14 +93,11 @@ ]; # FIXME: automatically propagate lld_11 llvm_11 dependencies - checkInputs = [ pkgs.lld_11 pkgs.llvm_11 pkgs.lit outputcheck ]; + checkInputs = [ pkgs.lld_11 pkgs.llvm_11 ]; checkPhase = '' python -m unittest discover -v artiq.test - - TESTDIR=`mktemp -d` - cp --no-preserve=mode,ownership -R $src/artiq/test/lit $TESTDIR - LIBARTIQ_SUPPORT=${libartiq-support}/libartiq_support.so lit -v $TESTDIR/lit ''; + doCheck = false; # TODO }; migen = pkgs.python3Packages.buildPythonPackage rec { @@ -350,57 +294,6 @@ hydraJobs = { inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi; - kc705-hitl = pkgs.stdenv.mkDerivation { - name = "kc705-hitl"; - - # requires patched Nix - __networked = true; - - buildInputs = [ - (pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ artiq artiq-board-kc705-nist_clock ps.paramiko ])) - pkgs.llvm_11 - pkgs.lld_11 - pkgs.openssh - packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams - ]; - phases = [ "buildPhase" ]; - buildPhase = - '' - export HOME=`mktemp -d` - mkdir $HOME/.ssh - cp /opt/hydra_id_rsa $HOME/.ssh/id_rsa - cp /opt/hydra_id_rsa.pub $HOME/.ssh/id_rsa.pub - echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPOBQVcsvk6WgRj18v4m0zkFeKrcN9gA+r6sxQxNwFpv" > $HOME/.ssh/known_hosts - chmod 600 $HOME/.ssh/id_rsa - LOCKCTL=$(mktemp -d) - mkfifo $LOCKCTL/lockctl - - cat $LOCKCTL/lockctl | ${pkgs.openssh}/bin/ssh \ - -i $HOME/.ssh/id_rsa \ - -o UserKnownHostsFile=$HOME/.ssh/known_hosts \ - rpi-1 \ - 'mkdir -p /tmp/board_lock && flock /tmp/board_lock/kc705-1 -c "echo Ok; cat"' \ - | ( - # End remote flock via FIFO - atexit_unlock() { - echo > $LOCKCTL/lockctl - } - trap atexit_unlock EXIT - - # Read "Ok" line when remote successfully locked - read LOCK_OK - - artiq_flash -t kc705 -H rpi-1 - sleep 15 - - export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock - export ARTIQ_LOW_LATENCY=1 - python -m unittest discover -v artiq.test.coredevice - ) - - touch $out - ''; - }; }; }; diff --git a/setup.py b/setup.py index d888e9402..e5752f47c 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ import sys import versioneer -if sys.version_info[:2] < (3, 7): - raise Exception("You need Python 3.7+") +if sys.version_info[:2] < (3, 9): + raise Exception("You need Python 3.9+") # Depends on PyQt5, but setuptools cannot check for it. @@ -15,7 +15,9 @@ requirements = [ "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "qasync", "pyqtgraph", "pygit2", - "llvmlite", "pythonparser", "python-Levenshtein", + # FIXME: figure out how to get the setuptools crap to find nac3artiq. Python imports it just fine. + # See: https://github.com/PyO3/setuptools-rust + # Alternatively, stop using setuptools, it sucks. ] console_scripts = [ @@ -58,7 +60,7 @@ Intended Audience :: Science/Research License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Operating System :: Microsoft :: Windows Operating System :: POSIX :: Linux -Programming Language :: Python :: 3.7 +Programming Language :: Python :: 3.9 Topic :: Scientific/Engineering :: Physics Topic :: System :: Hardware """.splitlines(),