From f2a6110cc49d8cf4f30069396be12601f86166d3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 18:34:52 +0300 Subject: [PATCH] Add integration tests for every language construct. --- artiq/compiler/testbench/inferencer.py | 11 +- artiq/compiler/testbench/{run.py => jit.py} | 0 artiq/compiler/testbench/llvmgen.py | 7 + .../compiler/transforms/artiq_ir_generator.py | 128 ++++++++++-------- .../compiler/transforms/asttyped_rewriter.py | 2 +- artiq/compiler/transforms/inferencer.py | 8 +- .../compiler/transforms/llvm_ir_generator.py | 79 ++++++++--- artiq/compiler/types.py | 4 +- artiq/compiler/validators/escape.py | 6 +- artiq/compiler/validators/local_access.py | 4 +- lit-test/compiler/integration/attribute.py | 6 + lit-test/compiler/integration/builtin.py | 24 ++++ lit-test/compiler/integration/compare.py | 18 +++ lit-test/compiler/integration/for.py | 28 ++++ lit-test/compiler/integration/if.py | 20 +++ lit-test/compiler/integration/list.py | 7 + lit-test/compiler/integration/locals.py | 6 + lit-test/compiler/integration/operator.py | 38 ++++++ lit-test/compiler/integration/print.py | 32 +++++ lit-test/compiler/integration/subscript.py | 12 ++ lit-test/compiler/integration/tuple.py | 6 + lit-test/compiler/integration/while.py | 30 ++++ 22 files changed, 384 insertions(+), 92 deletions(-) rename artiq/compiler/testbench/{run.py => jit.py} (100%) create mode 100644 lit-test/compiler/integration/attribute.py create mode 100644 lit-test/compiler/integration/builtin.py create mode 100644 lit-test/compiler/integration/compare.py create mode 100644 lit-test/compiler/integration/for.py create mode 100644 lit-test/compiler/integration/if.py create mode 100644 lit-test/compiler/integration/list.py create mode 100644 lit-test/compiler/integration/locals.py create mode 100644 lit-test/compiler/integration/operator.py create mode 100644 lit-test/compiler/integration/print.py create mode 100644 lit-test/compiler/integration/subscript.py create mode 100644 lit-test/compiler/integration/tuple.py create mode 100644 lit-test/compiler/integration/while.py diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index eda38c90e..64a24b39d 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -1,7 +1,7 @@ import sys, fileinput, os from pythonparser import source, diagnostic, algorithm, parse_buffer from .. import prelude, types -from ..transforms import ASTTypedRewriter, Inferencer +from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer class Printer(algorithm.Visitor): """ @@ -42,6 +42,12 @@ class Printer(algorithm.Visitor): ":{}".format(self.type_printer.name(node.type))) def main(): + if sys.argv[1] == "+mono": + del sys.argv[1] + monomorphize = True + else: + monomorphize = False + if len(sys.argv) > 1 and sys.argv[1] == "+diag": del sys.argv[1] def process_diagnostic(diag): @@ -62,6 +68,9 @@ def main(): parsed, comments = parse_buffer(buf, engine=engine) typed = ASTTypedRewriter(engine=engine).visit(parsed) Inferencer(engine=engine).visit(typed) + if monomorphize: + IntMonomorphizer(engine=engine).visit(typed) + Inferencer(engine=engine).visit(typed) printer = Printer(buf) printer.visit(typed) diff --git a/artiq/compiler/testbench/run.py b/artiq/compiler/testbench/jit.py similarity index 100% rename from artiq/compiler/testbench/run.py rename to artiq/compiler/testbench/jit.py diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index ecb396080..c32ceb98e 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -13,6 +13,13 @@ def main(): engine.process = process_diagnostic llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir + + # 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__"), []) + llbuilder.ret_void() + print(llmod) if __name__ == "__main__": diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index ab74a2374..09cea92ad 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -275,7 +275,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_assign = None def visit_AugAssign(self, node): - lhs = self.visit(target) + lhs = self.visit(node.target) rhs = self.visit(node.value) value = self.append(ir.Arith(node.op, lhs, rhs)) try: @@ -291,20 +291,22 @@ class ARTIQIRGenerator(algorithm.Visitor): if_true = self.add_block() self.current_block = if_true self.visit(node.body) + post_if_true = self.current_block if any(node.orelse): if_false = self.add_block() self.current_block = if_false self.visit(node.orelse) + post_if_false = self.current_block tail = self.add_block() self.current_block = tail - if not if_true.is_terminated(): - if_true.append(ir.Branch(tail)) + if not post_if_true.is_terminated(): + post_if_true.append(ir.Branch(tail)) if any(node.orelse): - if not if_false.is_terminated(): - if_false.append(ir.Branch(tail)) + 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) @@ -323,38 +325,42 @@ class ARTIQIRGenerator(algorithm.Visitor): body = self.add_block("while.body") self.current_block = body self.visit(node.body) + post_body = self.current_block 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 else_tail.is_terminated(): - else_tail.append(ir.Branch(tail)) + if not post_else_tail.is_terminated(): + post_else_tail.append(ir.Branch(tail)) else: else_tail = tail head.append(ir.BranchIf(cond, body, else_tail)) - if not body.is_terminated(): - body.append(ir.Branch(head)) + if not post_body.is_terminated(): + post_body.append(ir.Branch(head)) break_block.append(ir.Branch(tail)) finally: self.break_target = old_break self.continue_target = old_continue - def iterable_len(self, value, typ=builtins.TInt(types.TValue(32))): + def iterable_len(self, value, typ=_size_type): if builtins.is_list(value.type): - return self.append(ir.Builtin("len", [value], typ)) + return self.append(ir.Builtin("len", [value], typ, + name="{}.len".format(value.name))) 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)) + return self.append(ir.Arith(ast.FloorDiv(loc=None), spread, step, + name="{}.len".format(value.name))) else: assert False @@ -403,24 +409,26 @@ class ARTIQIRGenerator(algorithm.Visitor): finally: self.current_assign = None self.visit(node.body) + post_body = self.current_block 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 else_tail.is_terminated(): - else_tail.append(ir.Branch(tail)) + if not post_else_tail.is_terminated(): + post_else_tail.append(ir.Branch(tail)) else: else_tail = tail head.append(ir.BranchIf(cond, body, else_tail)) - if not body.is_terminated(): - body.append(ir.Branch(continue_block)) + if not post_body.is_terminated(): + post_body.append(ir.Branch(continue_block)) break_block.append(ir.Branch(tail)) finally: self.break_target = old_break @@ -611,15 +619,15 @@ class ARTIQIRGenerator(algorithm.Visitor): else: self.append(ir.SetAttr(obj, node.attr, self.current_assign)) - def _map_index(self, length, index): + def _map_index(self, length, index, one_past_the_end=False): 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))) - mapped_lt_len = self.append(ir.Compare(ast.Lt(loc=None), - mapped_index, length)) + 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()))) @@ -699,7 +707,7 @@ class ARTIQIRGenerator(algorithm.Visitor): max_index = self.visit(node.slice.upper) else: max_index = length - mapped_max_index = self._map_index(length, max_index) + mapped_max_index = self._map_index(length, max_index, one_past_the_end=True) if node.slice.step is not None: step = self.visit(node.slice.step) @@ -708,9 +716,10 @@ class ARTIQIRGenerator(algorithm.Visitor): unstepped_size = self.append(ir.Arith(ast.Sub(loc=None), mapped_max_index, mapped_min_index)) - slice_size = self.append(ir.Arith(ast.FloorDiv(loc=None), unstepped_size, step)) + slice_size = self.append(ir.Arith(ast.FloorDiv(loc=None), unstepped_size, step, + name="slice.size")) - self._make_check(self.append(ir.Compare(ast.Eq(loc=None), slice_size, length)), + self._make_check(self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), lambda: self.append(ir.Alloc([], builtins.TValueError()))) if self.current_assign is None: @@ -735,6 +744,9 @@ class ARTIQIRGenerator(algorithm.Visitor): lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, slice_size)), body_gen) + 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)) @@ -759,7 +771,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.SetElem(lst, ir.Constant(index, self._size_type), elt_node)) return lst else: - length = self.append(ir.Builtin("len", [self.current_assign], self._size_type)) + 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: self.append(ir.Alloc([], builtins.TValueError()))) @@ -793,7 +805,6 @@ class ARTIQIRGenerator(algorithm.Visitor): elt = self.iterable_get(iterable, index) try: old_assign, self.current_assign = self.current_assign, elt - print(comprehension.target, self.current_assign) self.visit(comprehension.target) finally: self.current_assign = old_assign @@ -837,7 +848,7 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_UnaryOpT(self, node): if isinstance(node.op, ast.Not): - return self.append(ir.Select(node.operand, + return self.append(ir.Select(self.visit(node.operand), ir.Constant(False, builtins.TBool()), ir.Constant(True, builtins.TBool()))) elif isinstance(node.op, ast.USub): @@ -866,7 +877,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Arith(node.op, self.visit(node.left), self.visit(node.right))) elif isinstance(node.op, ast.Add): # list + list, tuple + tuple lhs, rhs = self.visit(node.left), self.visit(node.right) - if types.is_tuple(node.left.type) and builtins.is_tuple(node.right.type): + 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))) @@ -874,8 +885,8 @@ class ARTIQIRGenerator(algorithm.Visitor): elts.append(self.append(ir.GetAttr(rhs, index))) return self.append(ir.Alloc(elts, node.type)) elif builtins.is_list(node.left.type) and builtins.is_list(node.right.type): - lhs_length = self.append(ir.Builtin("len", [lhs], self._size_type)) - rhs_length = self.append(ir.Builtin("len", [rhs], self._size_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)) @@ -913,7 +924,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False - lst_length = self.append(ir.Builtin("len", [lst], self._size_type)) + 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)) @@ -934,17 +945,21 @@ class ARTIQIRGenerator(algorithm.Visitor): lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, lst_length)), body_gen) - return self.append(ir.Arith(ast.Add(loc=None), lst_length, + 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_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)): @@ -959,8 +974,8 @@ class ARTIQIRGenerator(algorithm.Visitor): return result elif builtins.is_list(lhs.type) and builtins.is_list(rhs.type): head = self.current_block - lhs_length = self.append(ir.Builtin("len", [lhs], self._size_type)) - rhs_length = self.append(ir.Builtin("len", [rhs], self._size_type)) + 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)) @@ -1056,24 +1071,10 @@ class ARTIQIRGenerator(algorithm.Visitor): return result - def polymorphic_compare_pair_identity(self, op, lhs, rhs): - if builtins.is_allocated(lhs) and builtins.is_allocated(rhs): - # These are actually pointers, compare directly. - return self.append(ir.Compare(op, lhs, rhs)) - else: - # Compare by value instead, our backend cannot handle - # equality of aggregates. - if isinstance(op, ast.Is): - op = ast.Eq(loc=None) - elif isinstance(op, ast.IsNot): - op = ast.NotEq(loc=None) - else: - assert False - return self.polymorphic_compare_pair_order(op, lhs, rhs) - def polymorphic_compare_pair(self, op, lhs, rhs): if isinstance(op, (ast.Is, ast.IsNot)): - return self.polymorphic_compare_pair_identity(op, lhs, rhs) + # The backend will handle equality of aggregates. + return self.append(ir.Compare(op, lhs, rhs)) elif isinstance(op, (ast.In, ast.NotIn)): return self.polymorphic_compare_pair_inclusion(op, lhs, rhs) else: # Eq, NotEq, Lt, LtE, Gt, GtE @@ -1086,21 +1087,25 @@ class ARTIQIRGenerator(algorithm.Visitor): lhs = self.visit(node.left) self.instrument_assert(node.left, lhs) for op, rhs_node in zip(node.ops, node.comparators): + result_head = self.current_block rhs = self.visit(rhs_node) self.instrument_assert(rhs_node, rhs) result = self.polymorphic_compare_pair(op, lhs, rhs) - blocks.append((result, self.current_block)) + result_tail = self.current_block + + blocks.append((result, result_head, result_tail)) self.current_block = self.add_block() lhs = rhs tail = self.current_block phi = self.append(ir.Phi(node.type)) - for ((value, block), next_block) in zip(blocks, [b for (v,b) in blocks[1:]] + [tail]): - phi.add_incoming(value, block) - if next_block != tail: - block.append(ir.BranchIf(value, next_block, tail)) + 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: - block.append(ir.Branch(tail)) + result_tail.append(ir.Branch(tail)) return phi def visit_builtin_call(self, node): @@ -1138,7 +1143,7 @@ class ARTIQIRGenerator(algorithm.Visitor): elif types.is_builtin(typ, "list"): if len(node.args) == 0 and len(node.keywords) == 0: length = ir.Constant(0, builtins.TInt(types.TValue(32))) - return self.append(ir.Alloc(node.type, length)) + 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) @@ -1157,7 +1162,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False elif types.is_builtin(typ, "range"): - elt_typ = builtins.getiterable_elt(node.type) + 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([ @@ -1193,7 +1198,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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])) + return self.append(ir.Builtin("round", [arg], node.type)) else: assert False elif types.is_builtin(typ, "print"): @@ -1241,7 +1246,7 @@ class ARTIQIRGenerator(algorithm.Visitor): for (subexpr, name) in self.current_assert_subexprs]): return # don't display the same subexpression twice - name = self.current_assert_env.type.add("subexpr", ir.TOption(node.type)) + name = self.current_assert_env.type.add(".subexpr", ir.TOption(node.type)) value_opt = self.append(ir.Alloc([value], ir.TOption(node.type)), loc=node.loc) self.append(ir.SetLocal(self.current_assert_env, name, value_opt), @@ -1325,7 +1330,10 @@ class ARTIQIRGenerator(algorithm.Visitor): self.polymorphic_print([self.append(ir.GetAttr(value, index)) for index in range(len(value.type.elts))], separator=", ") - format_string += ")" + if len(value.type.elts) == 1: + format_string += ",)" + else: + format_string += ")" elif types.is_function(value.type): format_string += "" # We're relying on the internal layout of the closure here. @@ -1341,7 +1349,7 @@ class ARTIQIRGenerator(algorithm.Visitor): elif builtins.is_int(value.type): width = builtins.get_int_width(value.type) if width <= 32: - format_string += "%ld" + format_string += "%d" elif width <= 64: format_string += "%lld" else: diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 2b8d03f73..3adfde595 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -300,7 +300,7 @@ class ASTTypedRewriter(algorithm.Transformer): node = self.generic_visit(node) node = asttyped.SubscriptT(type=types.TVar(), value=node.value, slice=node.slice, ctx=node.ctx, - loc=node.loc) + begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) return self.visit(node) def visit_BoolOp(self, node): diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 21e803e9b..f6a4cee7f 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -109,6 +109,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) def visit_Index(self, node): + self.generic_visit(node) value = node.value if types.is_tuple(value.type): diag = diagnostic.Diagnostic("error", @@ -342,7 +343,8 @@ class Inferencer(algorithm.Visitor): 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(), typ, typ)) + return self._coerce_numeric((left, right), + lambda typ: (builtins.TFloat(), builtins.TFloat(), builtins.TFloat())) else: # MatMult diag = diagnostic.Diagnostic("error", "operator '{op}' is not supported", {"op": op.loc.source()}, @@ -377,7 +379,7 @@ class Inferencer(algorithm.Visitor): for left, right in pairs: self._unify(left.type, right.type, left.loc, right.loc) - else: + elif any(map(builtins.is_numeric, operand_types)): typ = self._coerce_numeric(operands) if typ: try: @@ -393,6 +395,8 @@ class Inferencer(algorithm.Visitor): 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) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index cf9deea1d..61b29abee 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -99,12 +99,16 @@ class LLVMIRGenerator: llty = ll.FunctionType(ll.VoidType(), []) elif name in "llvm.trap": llty = ll.FunctionType(ll.VoidType(), []) + elif name == "llvm.floor.f64": + llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) elif name == "llvm.round.f64": llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) elif name == "llvm.pow.f64": llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) elif name == "llvm.powi.f64": llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)]) + elif name == "llvm.copysign.f64": + llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) elif name == "printf": llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) else: @@ -331,27 +335,36 @@ class LLVMIRGenerator: elif isinstance(insn.op, ast.FloorDiv): if builtins.is_float(insn.type): llvalue = self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs())) - return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [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): + # Python only has the modulo operator, LLVM only has the remainder if builtins.is_float(insn.type): - return self.llbuilder.frem(self.map(insn.lhs()), self.map(insn.rhs()), + llvalue = self.llbuilder.frem(self.map(insn.lhs()), self.map(insn.rhs())) + return self.llbuilder.call(self.llbuiltin("llvm.copysign.f64"), + [llvalue, self.map(insn.rhs())], name=insn.name) else: - return self.llbuilder.srem(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) + lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs())) + llxorsign = self.llbuilder.and_(self.llbuilder.xor(lllhs, llrhs), + ll.Constant(lllhs.type, 1 << lllhs.type.width - 1)) + llnegate = self.llbuilder.icmp_unsigned('!=', + llxorsign, ll.Constant(llxorsign.type, 0)) + llvalue = self.llbuilder.srem(lllhs, llrhs) + llnegvalue = self.llbuilder.sub(ll.Constant(llvalue.type, 0), llvalue) + return self.llbuilder.select(llnegate, llnegvalue, llvalue) 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()), ll.DoubleType()) llrhs = self.llbuilder.trunc(self.map(insn.rhs()), ll.IntType(32)) - llvalue = self.llbuilder.call(self.llbuiltin("llvm.powi.f64"), - [self.map(insn.lhs()), llrhs]) + 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): @@ -373,9 +386,9 @@ class LLVMIRGenerator: assert False def process_Compare(self, insn): - if isinstance(insn.op, ast.Eq): + if isinstance(insn.op, (ast.Eq, ast.Is)): op = '==' - elif isinstance(insn.op, ast.NotEq): + elif isinstance(insn.op, (ast.NotEq, ast.IsNot)): op = '!=' elif isinstance(insn.op, ast.Gt): op = '>' @@ -388,12 +401,32 @@ class LLVMIRGenerator: else: assert False - if builtins.is_float(insn.lhs().type): - return self.llbuilder.fcmp_ordered(op, self.map(insn.lhs()), self.map(insn.rhs()), + 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.FloatType, ll.DoubleType)): + return self.llbuilder.fcmp_ordered(op, lllhs, llrhs, name=insn.name) + elif isinstance(lllhs.type, ll.LiteralStructType): + # Compare aggregates (such as lists or ranges) element-by-element. + llvalue = ll.Constant(ll.IntType(1), 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(ll.IntType(1), False)) + return self.llbuilder.icmp_unsigned(op, llvalue, ll.Constant(ll.IntType(1), True), + name=insn.name) else: - return self.llbuilder.icmp_signed(op, self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) + print(lllhs, llrhs) + assert False def process_Builtin(self, insn): if insn.op == "nop": @@ -401,22 +434,24 @@ class LLVMIRGenerator: if insn.op == "abort": return self.llbuilder.call(self.llbuiltin("llvm.trap"), []) elif insn.op == "is_some": - optarg = self.map(insn.operands[0]) - return self.llbuilder.extract_value(optarg, 0, + lloptarg = self.map(insn.operands[0]) + return self.llbuilder.extract_value(lloptarg, 0, name=insn.name) elif insn.op == "unwrap": - optarg = self.map(insn.operands[0]) - return self.llbuilder.extract_value(optarg, 1, + lloptarg = self.map(insn.operands[0]) + return self.llbuilder.extract_value(lloptarg, 1, name=insn.name) elif insn.op == "unwrap_or": - optarg, default = map(self.map, insn.operands) - has_arg = self.llbuilder.extract_value(optarg, 0) - arg = self.llbuilder.extract_value(optarg, 1) - return self.llbuilder.select(has_arg, arg, default, + lloptarg, lldefault = map(self.map, insn.operands) + llhas_arg = self.llbuilder.extract_value(lloptarg, 0) + llarg = self.llbuilder.extract_value(lloptarg, 1) + return self.llbuilder.select(llhas_arg, llarg, lldefault, name=insn.name) elif insn.op == "round": - return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llvalue], - name=insn.name) + 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: diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index b29eb513e..a84c1777e 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -13,14 +13,14 @@ def genalnum(): pos = len(ident) - 1 while pos >= 0: cur_n = string.ascii_lowercase.index(ident[pos]) - if cur_n < 26: + 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 + ident = ["a"] + ident class UnificationError(Exception): def __init__(self, typea, typeb): diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 603b97d7d..9f73b936e 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -77,12 +77,15 @@ class RegionOf(algorithm.Visitor): # Value lives as long as the current scope, if it's mutable, # or else forever - def visit_BinOpT(self, node): + def visit_sometimes_allocating(self, node): if builtins.is_allocated(node.type): return self.youngest_region else: return None + visit_BinOpT = visit_sometimes_allocating + visit_CallT = visit_sometimes_allocating + # Value lives as long as the object/container, if it's mutable, # or else forever def visit_accessor(self, node): @@ -136,7 +139,6 @@ class RegionOf(algorithm.Visitor): visit_EllipsisT = visit_immutable visit_UnaryOpT = visit_immutable visit_CompareT = visit_immutable - visit_CallT = visit_immutable # Value is mutable, but still lives forever def visit_StrT(self, node): diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index c86f3e46b..57fd8486c 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -55,8 +55,8 @@ class LocalAccessValidator: # 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(lambda a, b: merge_state(a[env], b[env]), - pred_states) + 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. diff --git a/lit-test/compiler/integration/attribute.py b/lit-test/compiler/integration/attribute.py new file mode 100644 index 000000000..672d02ef3 --- /dev/null +++ b/lit-test/compiler/integration/attribute.py @@ -0,0 +1,6 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +r = range(10) +assert r.start == 0 +assert r.stop == 10 +assert r.step == 1 diff --git a/lit-test/compiler/integration/builtin.py b/lit-test/compiler/integration/builtin.py new file mode 100644 index 000000000..20296fc8a --- /dev/null +++ b/lit-test/compiler/integration/builtin.py @@ -0,0 +1,24 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +assert bool() is False +# bool(x) is tested in bool.py + +assert int() is 0 +assert int(1.0) is 1 +assert int(1, width=64) << 40 is 1099511627776 + +assert float() is 0.0 +assert float(1) is 1.0 + +x = list() +if False: x = [1] +assert x == [] + +assert range(10) is range(0, 10, 1) +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 + +assert round(1.4) is 1 and round(1.6) is 2 diff --git a/lit-test/compiler/integration/compare.py b/lit-test/compiler/integration/compare.py new file mode 100644 index 000000000..000c9d8bf --- /dev/null +++ b/lit-test/compiler/integration/compare.py @@ -0,0 +1,18 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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 +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/lit-test/compiler/integration/for.py b/lit-test/compiler/integration/for.py new file mode 100644 index 000000000..9597c4de6 --- /dev/null +++ b/lit-test/compiler/integration/for.py @@ -0,0 +1,28 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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 diff --git a/lit-test/compiler/integration/if.py b/lit-test/compiler/integration/if.py new file mode 100644 index 000000000..8a282a594 --- /dev/null +++ b/lit-test/compiler/integration/if.py @@ -0,0 +1,20 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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 diff --git a/lit-test/compiler/integration/list.py b/lit-test/compiler/integration/list.py new file mode 100644 index 000000000..d843b93ae --- /dev/null +++ b/lit-test/compiler/integration/list.py @@ -0,0 +1,7 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +[x, y] = [1, 2] +assert (x, y) == (1, 2) + +lst = [1, 2, 3] +assert [x*x for x in lst] == [1, 4, 9] diff --git a/lit-test/compiler/integration/locals.py b/lit-test/compiler/integration/locals.py new file mode 100644 index 000000000..cc2c1f1be --- /dev/null +++ b/lit-test/compiler/integration/locals.py @@ -0,0 +1,6 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +x = 1 +assert x == 1 +x += 1 +assert x == 2 diff --git a/lit-test/compiler/integration/operator.py b/lit-test/compiler/integration/operator.py new file mode 100644 index 000000000..4bf0ab7c1 --- /dev/null +++ b/lit-test/compiler/integration/operator.py @@ -0,0 +1,38 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +assert (not True) == False +assert (not False) == True +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 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 +assert 0x18 & 0x0f == 0x08 +assert 0x18 | 0x0f == 0x1f +assert 0x18 ^ 0x0f == 0x17 + +assert [1] + [2] == [1, 2] +assert [1] * 3 == [1, 1, 1] diff --git a/lit-test/compiler/integration/print.py b/lit-test/compiler/integration/print.py new file mode 100644 index 000000000..06653d43b --- /dev/null +++ b/lit-test/compiler/integration/print.py @@ -0,0 +1,32 @@ +# 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)) diff --git a/lit-test/compiler/integration/subscript.py b/lit-test/compiler/integration/subscript.py new file mode 100644 index 000000000..2d80b1472 --- /dev/null +++ b/lit-test/compiler/integration/subscript.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +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] diff --git a/lit-test/compiler/integration/tuple.py b/lit-test/compiler/integration/tuple.py new file mode 100644 index 000000000..862154c9f --- /dev/null +++ b/lit-test/compiler/integration/tuple.py @@ -0,0 +1,6 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +x, y = 2, 1 +x, y = y, x +assert x == 1 and y == 2 +assert (1, 2) + (3.0,) == (1, 2, 3.0) diff --git a/lit-test/compiler/integration/while.py b/lit-test/compiler/integration/while.py new file mode 100644 index 000000000..fea81fadc --- /dev/null +++ b/lit-test/compiler/integration/while.py @@ -0,0 +1,30 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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